├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── ServiceProvider
├── ServiceProvider.csproj
└── Services
│ ├── Audio
│ ├── AudioHandler.cs
│ ├── Dependency
│ │ ├── WasapiInAdapter.cs
│ │ ├── WasapiOutAdapter.cs
│ │ ├── WaveInAdapter.cs
│ │ └── WaveOutAdapter.cs
│ ├── G722Codec.cs
│ ├── IAudioIn.cs
│ ├── IAudioOut.cs
│ ├── JitterBuffer.cs
│ ├── SoundSliceData.cs
│ └── WaveBuffer.cs
│ ├── File Transfer
│ ├── FileTransferHelper.cs
│ ├── FileTransferStateManager.cs
│ ├── ReceiveState.cs
│ └── SendState.cs
│ ├── Latency
│ └── LatencyPublisher.cs
│ ├── Network
│ ├── MessageHandler.cs
│ └── VCPeerInfo.cs
│ ├── Notifications
│ └── ToastNotificationHandler.cs
│ ├── ScreenShare
│ ├── DXScreenCapture.cs
│ ├── DxCaptureProvider.cs
│ ├── IScreenCapture.cs
│ ├── ScreenHelper.cs
│ └── ScreenShareHandlerH264.cs
│ ├── ServiceHub.cs
│ ├── SingleThreadDispatcher.cs
│ └── Video
│ ├── Camera
│ ├── ICameraProvider.cs
│ └── WindowsCameraProvider.cs
│ ├── H264
│ ├── H264Transcoder.cs
│ ├── H264TranscoderProvider.cs
│ └── JitterBufffer.cs
│ ├── ImageReference.cs
│ └── VideoHandler2.cs
├── Videocall.sln
├── Videocall
├── App.config
├── App.xaml
├── App.xaml.cs
├── CallStateManager.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Models
│ ├── MainWindow
│ │ ├── ChatDataModel.cs
│ │ ├── ChatSerializer.cs
│ │ ├── MainWindowModel.cs
│ │ ├── MainWindowViewModel.cs
│ │ └── PeerInfo.cs
│ ├── MainWindowEventAggregator.cs
│ └── Settings
│ │ ├── PersistentSettingConfig.cs
│ │ ├── Settings.json
│ │ └── SettingsViewModel.cs
├── Properties
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources
│ ├── 9-95099_camera-icon-png-image-camera-apple-icon-transparent.png
│ ├── cam off.png
│ ├── camon.png
│ ├── favicon2.ico
│ ├── m2.png
│ ├── micoff_111072.png
│ └── micon.png
├── Services
│ ├── Audio
│ │ ├── AudioHandler.cs
│ │ └── JitterBuffer.cs
│ ├── File Transfer
│ │ ├── FileShare.cs
│ │ ├── FileTransferStateManager.cs
│ │ ├── ReceiveState.cs
│ │ └── SendState.cs
│ ├── Latency
│ │ └── LatencyPublisher.cs
│ ├── Network
│ │ └── MessageHandler.cs
│ ├── Notifications
│ │ └── ToastNotificationHandler.cs
│ ├── ScreenShare
│ │ ├── DXScreenCapture.cs
│ │ ├── ScreenHelper.cs
│ │ ├── ScreenShareHandlerH264.cs
│ │ └── SimpleScreenShareHandler.cs
│ ├── ServiceHub.cs
│ └── Video
│ │ └── H264
│ │ ├── H264Transcoder.cs
│ │ ├── H264TranscoderProvider.cs
│ │ ├── JitterBufffer.cs
│ │ └── VideoHandler2.cs
├── UIHelpers
│ ├── PropertyNotifyBase.cs
│ └── RelayCommand.cs
├── UserControls
│ ├── SoundVisualizer.xaml
│ └── SoundVisualizer.xaml.cs
├── Videocall.csproj
├── Windows
│ ├── AlertWindow.xaml
│ ├── AlertWindow.xaml.cs
│ ├── CameraWindow.xaml
│ ├── CameraWindow.xaml.cs
│ ├── DebugLogWindow.xaml
│ └── DebugLogWindow.xaml.cs
└── client.pfx
└── WapProjTemplate1
├── Images
├── LockScreenLogo.scale-200.png
├── SplashScreen.scale-200.png
├── Square150x150Logo.scale-200.png
├── Square44x44Logo.scale-200.png
├── Square44x44Logo.targetsize-24_altform-unplated.png
├── StoreLogo.png
└── Wide310x150Logo.scale-200.png
├── Package.appxmanifest
├── Settings.json
└── VideocallRelease.wapproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # P2PVideocall
2 | Voice-Video Call Wpf Application based on P2PNetwork. Allows users to have Videocall, Chat and Filetransfer securely over local network or Internet.
3 |
Tested and established stable calls between 1.204,5 km distance clients
4 |
5 |
Release available https://github.com/ReferenceType/P2PVideocall/releases/
6 | ## Features
7 | - Secure Videocall among connected peers
8 | - H264 Encoding
9 | - Secure Persisitent Chat
10 | - Secure File-Directory Transfer with hash file integrity verification(TCP and UDP)
11 | - High Performance Secure Screen Share based on DirectX 11 API (up to 60 fps @1080p single core).
12 | ## Technical features
13 | - Secure Tcp & Udp Hole punching
14 | - Reliable Udp channels and Jumbo Udp Message support
15 | - Congestion Avoidance and Control
16 | - Dynamic Jitter Buffer for Audio and Video
17 | - Toast notifications
18 | - Statistics data and cool settings for nerds
19 |
20 | Application has powerfull and optimised backend which is a hobby for me. However, Front end is incomplete,and its quite boring to develop. Slowly, i intend to complete it.
21 | ### Note:
22 | Application runs on background when closed with X button, to shut it down fully you have to shutdown from hidden icons on your taskbar (like skype)
23 | ## How It works
24 |
25 |
26 | Each application is a Peer client to a Relay Server. This server is used as a randezvous point for Udp Holepunching.
27 | If holepunch is sucessfull, all Tcp & Udp traffic is send directly among peers.Otherwise it will be relayed over the server.
28 | Tcp holepunch succes rate is lower than udp. All reliable communication is through Reliable Udp channel by default.
29 | Relay server is included on releases and can be found on repository: https://github.com/ReferenceType/RelayServer
30 |
31 | The back-end network core libraries are located on : https://github.com/ReferenceType/StandardNetworkLibrary
32 | ## How to setup
33 | Launch the Relay Server console application. Launch the Videocall Application and write your Relay Servers Ip and Port and hit comnnect.
34 | If any other application connects to same relay server it will apper on your list on main window. You can select and call that peer or transfer files or chat.
35 |
36 | If you intend to use this application over Internet, You need to enable port forwarding on your router for your relay server.
37 | If you dont wanna bother with your ISPs dynamic IPs, you can use a DDNS service like NoIP.
38 | or you can deploy relay server on a cloud etc.
39 |
40 | ## Images
41 | ### Call interface
42 | - Call can be initiated by selecting a peer from left menu anc clicking call button. Other end will receive notification for confirmation.
43 |
44 |
45 |
46 |
47 | ### Share Screen
48 | - You can share a screen during a call.
49 |
50 |
51 | ### Settings
52 | - Provided varoious settings and displays for visualising.
53 |
54 |
55 |
56 |
57 |
58 | ### Background mode mini-window
59 | - When application is on background, a mini camera window will appear. It is resizeable and it automaticaally disappears when main app is active.
60 |
61 |
62 | ### File transfer
63 | - You can Drag and drop a single file or full directory tree of folders, and application will send them through secure channel.
64 | - xxHash hashing is also applied here where receiver verifies the file integrity.
65 | - Both TCP and UDP is supported for transport.
66 |
67 |
68 | ### Toast Notification
69 | - Various windows toast notifications are implemented to notify user. They are similar to skype
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/ServiceProvider/ServiceProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | True
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ..\..\NetworkExperiments\Protobuff\bin\Release\net6.0\NetworkLibrary.dll
33 |
34 |
35 | ..\..\NetworkExperiments\Protobuff\bin\Release\net6.0\Protobuff.dll
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/Dependency/WasapiInAdapter.cs:
--------------------------------------------------------------------------------
1 | using NAudio.CoreAudioApi;
2 | using NAudio.Wave;
3 | using NAudio.Wave.SampleProviders;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Channels;
9 | using System.Threading.Tasks;
10 | using Videocall;
11 |
12 | namespace ServiceProvider.Services.Audio.Dependency
13 | {
14 | public class Waveformat
15 | {
16 | public int Rate, Bits, Channel;
17 | public readonly int AverageBytesPerSecond;
18 |
19 | public Waveformat(int rate, int bits, int channel)
20 | {
21 | Rate = rate;
22 | Bits = bits;
23 | Channel = channel;
24 | var blockAlign = (short)(Channel * (bits / 8));
25 | AverageBytesPerSecond = Rate * blockAlign;
26 | }
27 | }
28 | public class DeviceInfo
29 | {
30 | public string Name { get; set; }
31 | }
32 | public class WasapiInAdapter : WaveInAdapter
33 | {
34 |
35 | public override List EnumerateDevices()
36 | {
37 | InputDevices.Clear();
38 | var enumerator = new MMDeviceEnumerator();
39 | foreach (MMDevice wasapi in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active))
40 | {
41 | InputDevices.Add(new DeviceInfo() { Name = wasapi.FriendlyName });
42 | }
43 | var inp = enumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications);
44 | enumerator.Dispose();
45 |
46 | if (inp == null)
47 | return InputDevices;
48 |
49 | SelectedDevice = InputDevices.Where(x => x.Name == inp.FriendlyName).FirstOrDefault()!;
50 | return InputDevices;
51 | }
52 |
53 | public override void Init(Waveformat Format, int captureInterval)
54 | {
55 | format = new WaveFormat(Format.Rate, Format.Bits, Format.Channel);
56 | CaptureInterval = captureInterval;
57 |
58 |
59 | MMDevice inputDevice = null;
60 |
61 | var enumerator = new MMDeviceEnumerator();
62 | List devices = new List();
63 | foreach (MMDevice wasapi in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active))
64 | {
65 | devices.Add(wasapi);
66 | }
67 |
68 | if (SelectedDevice != null)
69 | {
70 | inputDevice = devices.Where(x => x.FriendlyName == SelectedDevice.Name).FirstOrDefault()!;
71 |
72 | }
73 |
74 | if (inputDevice == null)
75 | {
76 | inputDevice = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications);
77 | }
78 |
79 | if (inputDevice == null)
80 | {
81 | return;
82 | }
83 |
84 |
85 | var waveIn_ = new WasapiCapture(inputDevice, true, CaptureInterval);
86 | waveIn_.WaveFormat = format;
87 | waveIn_.DataAvailable += MicrophoneSampleAvailable;
88 | var old = Interlocked.Exchange(ref waveIn, waveIn_);
89 |
90 | }
91 |
92 |
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/Dependency/WasapiOutAdapter.cs:
--------------------------------------------------------------------------------
1 | using NAudio.CoreAudioApi;
2 | using NAudio.Wave.SampleProviders;
3 | using NAudio.Wave;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace ServiceProvider.Services.Audio.Dependency
11 | {
12 | public class WasapiOutAdapter : WaveOutAdapter
13 | {
14 | public override void Init(Waveformat format)
15 | {
16 | soundListenBuffer = new BufferedWaveProvider(new WaveFormat(format.Rate, format.Bits, format.Channel));
17 | soundListenBuffer.BufferLength = 320 * format.AverageBytesPerSecond / 1000;
18 | soundListenBuffer.DiscardOnBufferOverflow = true;
19 |
20 | volumeSampleProvider = new VolumeSampleProvider(soundListenBuffer.ToSampleProvider());
21 | volumeSampleProvider.Volume = Gain;
22 |
23 | var outputDevice = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Communications);
24 | if (outputDevice == null)
25 | return;
26 |
27 | var player_ = new WasapiOut(outputDevice, AudioClientShareMode.Shared, true, 60);
28 | player_.Init(volumeSampleProvider);
29 | Interlocked.Exchange(ref player, player_)?.Dispose();
30 | }
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/Dependency/WaveInAdapter.cs:
--------------------------------------------------------------------------------
1 | using NAudio.CoreAudioApi;
2 | using NAudio.Wave;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ServiceProvider.Services.Audio.Dependency
10 | {
11 | public class WaveInAdapter : IAudioIn
12 | {
13 | public Waveformat Format { get; }
14 | public event Action SampleAvailable;
15 | public DeviceInfo SelectedDevice { get; set; } = null;
16 | public List InputDevices { get; set; } = new List();
17 | public int CaptureInterval { get; protected set; }
18 |
19 | protected WaveFormat format;
20 | protected IWaveIn waveIn;
21 |
22 | public WaveInAdapter()
23 | {
24 |
25 | }
26 |
27 | public virtual List EnumerateDevices()
28 | {
29 | return InputDevices;
30 | }
31 |
32 | public virtual void Init(Waveformat Format, int captureInterval)
33 | {
34 | format = new WaveFormat(Format.Rate, Format.Bits, Format.Channel);
35 | CaptureInterval = captureInterval;
36 |
37 |
38 | var waveIn_ = new WaveInEvent();
39 | waveIn_.BufferMilliseconds = captureInterval;
40 | waveIn_.WaveFormat = format;
41 | waveIn_.DataAvailable += MicrophoneSampleAvailable;
42 |
43 | var old = Interlocked.Exchange(ref waveIn, waveIn_);
44 | if (old != null)
45 | {
46 | old.StopRecording();
47 | old.Dispose();
48 | waveIn.StartRecording();
49 | }
50 |
51 | }
52 |
53 | protected void MicrophoneSampleAvailable(object? sender, WaveInEventArgs e)
54 | {
55 | SampleAvailable?.Invoke(e.Buffer, 0, e.BytesRecorded);
56 | }
57 |
58 | public void Dispose()
59 | {
60 | if (waveIn != null)
61 | {
62 | waveIn.DataAvailable -= MicrophoneSampleAvailable;
63 | waveIn.Dispose();
64 | }
65 | SampleAvailable = null;
66 |
67 |
68 | }
69 |
70 | public void StartRecording()
71 | {
72 | waveIn?.StartRecording();
73 | }
74 |
75 | public void StopRecording()
76 | {
77 | waveIn?.StopRecording();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/Dependency/WaveOutAdapter.cs:
--------------------------------------------------------------------------------
1 | using NAudio.CoreAudioApi;
2 | using NAudio.Wave.SampleProviders;
3 | using NAudio.Wave;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace ServiceProvider.Services.Audio.Dependency
11 | {
12 | public class WaveOutAdapter : IAudioOut
13 | {
14 | protected BufferedWaveProvider soundListenBuffer;
15 | protected VolumeSampleProvider volumeSampleProvider;
16 | protected IWavePlayer player;
17 | protected float volume;
18 |
19 | public float Gain { get; set; } = 1;
20 | public float Volume { get => volume; set { volume = value; volumeSampleProvider.Volume = value; } }
21 | public TimeSpan BufferDuration => soundListenBuffer?.BufferDuration ?? new TimeSpan(10000);
22 | public TimeSpan BufferedDuration => soundListenBuffer?.BufferedDuration ?? new TimeSpan(10000);
23 |
24 | public virtual void Init(Waveformat format)
25 | {
26 | soundListenBuffer = new BufferedWaveProvider(new WaveFormat(format.Rate, format.Bits, format.Channel));
27 | soundListenBuffer.BufferLength = 320 * format.AverageBytesPerSecond / 1000;
28 | soundListenBuffer.DiscardOnBufferOverflow = true;
29 |
30 | volumeSampleProvider = new VolumeSampleProvider(soundListenBuffer.ToSampleProvider());
31 | volumeSampleProvider.Volume = Gain;
32 |
33 | var player_ = new WaveOutEvent();
34 | player_.DesiredLatency = 60;
35 | player_.Init(volumeSampleProvider);
36 | Interlocked.Exchange(ref player, player_)?.Dispose();
37 | }
38 | public void Dispose()
39 | {
40 | player?.Dispose();
41 | }
42 |
43 | public void Stop()
44 | {
45 | player?.Stop();
46 | }
47 |
48 | public void AddSamples(byte[] buffer, int offset_, int pos)
49 | {
50 | soundListenBuffer.AddSamples(buffer, offset_, pos);
51 | }
52 |
53 | public void Play()
54 | {
55 | player?.Play();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/IAudioIn.cs:
--------------------------------------------------------------------------------
1 |
2 | using ServiceProvider.Services.Audio.Dependency;
3 |
4 | namespace ServiceProvider.Services.Audio
5 | {
6 | public interface IAudioIn
7 | {
8 | int CaptureInterval { get; }
9 |
10 | Waveformat Format { get; }
11 | List InputDevices { get; set; }
12 | DeviceInfo SelectedDevice { get; set; }
13 |
14 | event Action SampleAvailable;
15 |
16 | void Dispose();
17 | List EnumerateDevices();
18 | void Init(Waveformat Format, int captureInterval);
19 | void StartRecording();
20 | void StopRecording();
21 | }
22 | }
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/IAudioOut.cs:
--------------------------------------------------------------------------------
1 |
2 | using ServiceProvider.Services.Audio.Dependency;
3 |
4 | namespace ServiceProvider.Services.Audio
5 | {
6 | public interface IAudioOut
7 | {
8 | TimeSpan BufferDuration { get; }
9 | TimeSpan BufferedDuration { get; }
10 | float Gain { get; set; }
11 | float Volume { get; set; }
12 |
13 | void AddSamples(byte[] buffer, int offset_, int pos);
14 | void Dispose();
15 | void Init(Waveformat format);
16 | void Play();
17 | void Stop();
18 | }
19 | }
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/JitterBuffer.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.Collections.Immutable;
6 | using System.Data;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Threading;
10 |
11 | namespace Videocall
12 | {
13 | class JitterBuffer
14 | {
15 | public Action OnSamplesCollected;
16 | public int NumLostPackages = 0;
17 | public int BufferLatency;
18 | public int MinBufferLatency = 60;
19 | public int CaptureInterval = 20;
20 | public int Duration => numSeqBuffered * CaptureInterval;
21 | private ConcurrentDictionary samples = new ConcurrentDictionary();
22 | private readonly object locker = new object();
23 | private MemoryStream sampleStream = new MemoryStream();
24 | private DateTime lastBatchTimeStamp = DateTime.Now;
25 | private int numSeqBuffered = 0;
26 | private AutoResetEvent bufferFullEvent = new AutoResetEvent(false);
27 | private DateTime lastIn = DateTime.Now;
28 | private int lastSeq;
29 |
30 | public JitterBuffer(int bufferLatency)
31 | {
32 | this.BufferLatency = bufferLatency;
33 | StartPublushing2();
34 |
35 | }
36 | // publish if anything is in buffer.
37 | public void StartPublushing2()
38 | {
39 | Thread t = new Thread(() =>
40 | {
41 | lastSeq = 0;
42 | while (true)
43 | {
44 | bufferFullEvent.WaitOne();
45 | KeyValuePair[] samplesOrdered_;
46 | lock (locker)
47 | {
48 | samplesOrdered_ = samples.OrderByDescending(x => x.Key).Reverse().ToArray();
49 | }
50 | // iterate and count all consecutive sequences.
51 | int consequtiveSeqLenght = 0;
52 | for (int i = 0; i < samplesOrdered_.Length - 1; i++)
53 | {
54 | if (samplesOrdered_[i].Value.SquenceNumber + 1 == samplesOrdered_[i + 1].Value.SquenceNumber)
55 | consequtiveSeqLenght++;
56 | else break;
57 | }
58 | //int toTake = Math.Min(2, numSeqBuffered) + (samplesOrdered_.Count() - (BufferLatency / 20));
59 | IEnumerable> samplesOrdered;
60 | // jittr buffer reached max duration, we have to take even if seq is broken
61 | if (numSeqBuffered >= BufferLatency / CaptureInterval)
62 | {
63 | int toTake = Math.Max(2, consequtiveSeqLenght+1);
64 | samplesOrdered = samplesOrdered_.Take(Math.Min(toTake, samplesOrdered_.Count()));
65 | }
66 | // keep buffering seq isnt complete
67 | else if (consequtiveSeqLenght == 0)
68 | {
69 | continue;
70 | }
71 | //key point is here, if its continous sequence we take else we buffer
72 | else if (lastSeq == samplesOrdered_.First().Value.SquenceNumber - 1)
73 | {
74 | int toTake = Math.Max(2, consequtiveSeqLenght+1); // this was without max
75 | samplesOrdered = samplesOrdered_.Take(Math.Min(toTake, samplesOrdered_.Count()));
76 | }
77 | else continue;
78 |
79 | lastBatchTimeStamp = samplesOrdered.Last().Key;
80 |
81 | var sampArry = samplesOrdered.ToImmutableArray();
82 | for (int i = 0; i < sampArry.Length - 1; i++)
83 | {
84 |
85 | if (sampArry[i].Value.SquenceNumber + 1 == sampArry[i + 1].Value.SquenceNumber)
86 | {
87 | sampleStream.Write(sampArry[i].Value.Data, 0, sampArry[i].Value.DataLenght);
88 | lastSeq = sampArry[i].Value.SquenceNumber;
89 | }
90 | // lost packet we conceal them here
91 | else
92 | {
93 | //delta is at least 2 here
94 | int delta = sampArry[i + 1].Value.SquenceNumber - sampArry[i].Value.SquenceNumber;
95 | for (int j = 0; j < delta - 1; j++)
96 | {
97 | sampleStream.Write(sampArry[i].Value.Data, 0, sampArry[i].Value.DataLenght);
98 | NumLostPackages++;
99 | }
100 | }
101 |
102 | lock (locker)
103 | {
104 | samples.TryRemove(sampArry[i].Key, out _);
105 | numSeqBuffered--;
106 | }
107 |
108 | }
109 |
110 | try
111 | {
112 | OnSamplesCollected?.Invoke(sampleStream.GetBuffer(), 0, (int)sampleStream.Position);
113 | }
114 | catch (Exception e)
115 | {
116 | Console.WriteLine(e.Message);
117 | }
118 |
119 | sampleStream.Position = 0;
120 |
121 |
122 | }
123 | });
124 | t.Priority = ThreadPriority.AboveNormal;
125 | t.Start();
126 | }
127 | public void AddSample(AudioSample sample)
128 | {
129 | // special debug case, it cant be with gui slider.
130 | if (BufferLatency < 60)
131 | {
132 | OnSamplesCollected?.Invoke(sample.Data, 0, sample.DataLenght);
133 | return;
134 | }
135 |
136 | lock (locker)
137 | {
138 | if (!samples.ContainsKey(sample.Timestamp) && sample.Timestamp >= lastBatchTimeStamp)
139 | {
140 | samples.TryAdd(sample.Timestamp, sample);
141 | numSeqBuffered++;
142 | var now = DateTime.Now;
143 | if ((now - lastIn).TotalMilliseconds < 10 && numSeqBuffered>4)
144 | {
145 | Console.WriteLine("Audio Buff Forced");
146 | lastIn = now;
147 | return;
148 |
149 | }
150 | lastIn = now;
151 | if (numSeqBuffered >= 2)
152 | {
153 | bufferFullEvent.Set();
154 | }
155 | }
156 | }
157 | }
158 |
159 | public void DiscardSamples(int num)
160 | {
161 | lock (locker)
162 | {
163 | var samplesOrdered = samples.OrderByDescending(x => x.Key).Reverse();
164 | //samplesOrdered = samplesOrdered.Take(samplesOrdered.Count() - 10);
165 | samplesOrdered = samplesOrdered.Take(Math.Min(num, samplesOrdered.Count()));
166 |
167 | foreach (var item in samplesOrdered)
168 | {
169 | samples.TryRemove(item.Key, out _);
170 | numSeqBuffered--;
171 |
172 | }
173 | lastSeq = 0;
174 | }
175 |
176 | }
177 |
178 |
179 | }
180 | }
181 |
182 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Audio/SoundSliceData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ServiceProvider.Services.Audio
8 | {
9 | public class SoundSliceData
10 | {
11 | public SoundSliceData() { }
12 | public SoundSliceData(float[] sums)
13 | {
14 | V1 = (int)sums[0];
15 | V2 = (int)sums[1];
16 | V3 = (int)sums[2];
17 | V4 = (int)sums[3];
18 | V5 = (int)sums[4];
19 | V6 = (int)sums[5];
20 | V7 = (int)sums[6];
21 | V8 = (int)sums[7];
22 | V9 = (int)sums[8];
23 | V10 = (int)sums[9];
24 | V11 = (int)sums[10];
25 | V12 = (int)sums[11];
26 | V13 = (int)sums[12];
27 | V14 = (int)sums[13];
28 | V15 = (int)sums[14];
29 | V16 = (int)sums[15];
30 | V17 = (int)sums[16];
31 | V18 = (int)sums[17];
32 | V19 = (int)sums[18];
33 | V20 = (int)sums[19];
34 | }
35 |
36 | public int V1 { get; }
37 | public int V2 { get; }
38 | public int V3 { get; }
39 | public int V4 { get; }
40 | public int V5 { get; }
41 | public int V6 { get; }
42 | public int V7 { get; }
43 | public int V8 { get; }
44 | public int V9 { get; }
45 | public int V10 { get; }
46 | public int V11 { get; }
47 | public int V12 { get; }
48 | public int V13 { get; }
49 | public int V14 { get; }
50 | public int V15 { get; }
51 | public int V16 { get; }
52 | public int V17 { get; }
53 | public int V18 { get; }
54 | public int V19 { get; }
55 | public int V20 { get; }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/File Transfer/FileTransferStateManager.cs:
--------------------------------------------------------------------------------
1 | using NetworkLibrary;
2 | using System.Collections.Concurrent;
3 |
4 | namespace Videocall.Services.File_Transfer
5 | {
6 | public interface IFileTransferState
7 | {
8 | Guid AssociatedPeer { get; }
9 | Guid StateId { get; }
10 | void Cancel(string why);
11 | void CancelExplicit(string why);
12 | void HandleMessage(MessageEnvelope envelope);
13 |
14 | int Progress { get; }
15 | long TotalSize { get; }
16 | }
17 | public class TransferStatus
18 | {
19 | public string FileName;
20 | public float Percentage;
21 |
22 | }
23 | public class Completion
24 | {
25 | public string Directory;
26 | public string AdditionalInfo;
27 | public TimeSpan elapsed;
28 | }
29 |
30 | public class FileTransferStateManager
31 | {
32 | public Action OnTransferStatus;
33 | public Action OnReceiveStatus;
34 | public Action OnTransferComplete;
35 | public Action OnReceiveComplete;
36 | public Action OnTransferCancelled;
37 | public Action OnReceiveCancelled;
38 | // private ServiceHub services => ServiceHub.Instance;
39 | private ConcurrentDictionary activeStates = new ConcurrentDictionary();
40 |
41 | public static int WindowsSize = 12800000;
42 | public static int ChunkSize = 128000;
43 | private FileTransferHelper fs;
44 | public FileTransferStateManager(FileTransferHelper fs)
45 | {
46 | this.fs = fs;
47 | }
48 |
49 | public void CancelExplicit(Guid stateId)
50 | {
51 | if(activeStates.TryGetValue(stateId, out var state))
52 | {
53 | state.CancelExplicit("User Cancelled");
54 | }
55 | }
56 |
57 | public void CancelAssociatedState(Guid peerId)
58 | {
59 | foreach (var state in activeStates.Values)
60 | {
61 | if(state.AssociatedPeer == peerId)
62 | {
63 | state.Cancel("Cancelled due to Disconnect");
64 | activeStates.TryRemove(state.StateId, out _);
65 | }
66 | }
67 | }
68 |
69 | public void SendFile(string[] files, Guid selectedPeer)
70 | {
71 | var sendState = new SendState(files, selectedPeer, fs);
72 | sendState.OnProgress += (sts) => OnTransferStatus?.Invoke(sendState,sts);
73 | sendState.Completed =(c) => HandleCompletedSend(sendState,c);
74 | sendState.Cancelled =(c) => HandleCancelledSend(sendState,c);
75 | activeStates.TryAdd(sendState.StateId, sendState);
76 | }
77 |
78 | public void HandleReceiveFile(MessageEnvelope message)
79 | {
80 | var receiveState = new ReceiveState(message,fs);
81 | receiveState.OnProgress += (sts) => OnReceiveStatus?.Invoke(receiveState,sts);
82 | receiveState.OnCompleted = (c) => HandleCompletionReceive(receiveState, c);
83 | receiveState.Cancelled = (c) => HandleCancelledReceive(receiveState, c);
84 | activeStates.TryAdd(receiveState.StateId, receiveState);
85 | }
86 |
87 | private void HandleCancelledSend(SendState sendState, Completion c)
88 | {
89 | OnTransferCancelled?.Invoke(sendState, c);
90 | activeStates.TryRemove(sendState.StateId, out _);
91 | fs.CleanUp(sendState.StateId);
92 | sendState.Cleanup();
93 | }
94 |
95 | private void HandleCompletedSend(SendState sendState, Completion completion)
96 | {
97 | OnTransferComplete?.Invoke(sendState, completion);
98 | activeStates.TryRemove(sendState.StateId, out _);
99 | fs.CleanUp(sendState.StateId);
100 | sendState.Cleanup();
101 |
102 | }
103 |
104 | private void HandleCancelledReceive(ReceiveState receiveState, Completion c)
105 | {
106 | OnReceiveCancelled?.Invoke(receiveState,c);
107 | activeStates.TryRemove(receiveState.StateId, out _);
108 | fs.CleanUp(receiveState.StateId);
109 | }
110 |
111 | private void HandleCompletionReceive(ReceiveState receiveState,Completion completion)
112 | {
113 | OnReceiveComplete?.Invoke(receiveState,completion);
114 | activeStates.TryRemove(receiveState.StateId, out _);
115 | fs.CleanUp(receiveState.StateId);
116 | }
117 |
118 | public bool HandleMessage(MessageEnvelope message)
119 | {
120 | if (activeStates.TryGetValue(message.MessageId, out var state))
121 | {
122 | state.HandleMessage(message);
123 | return true;
124 | }
125 | return false;
126 | }
127 |
128 | public void CancelAllStates()
129 | {
130 | foreach (var state in activeStates.Values)
131 | {
132 | state.CancelExplicit("User Cancelled");
133 | }
134 |
135 | fs.ReleaseAll();
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/File Transfer/ReceiveState.cs:
--------------------------------------------------------------------------------
1 | using NetworkLibrary;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Data;
8 |
9 | namespace Videocall.Services.File_Transfer
10 | {
11 | class ReceiveState : IFileTransferState
12 | {
13 | public int Progress => (int)(100 * ((double)TotalReceived / (double)TotalSize));
14 |
15 | public Guid AssociatedPeer { get; private set; }
16 | public Guid StateId { get; private set; }
17 | public Action OnProgress;
18 | public Action OnCompleted;
19 | public Action Cancelled;
20 | private Stopwatch sw;
21 | private FileDirectoryStructure fileTree;
22 | private FileTransferHelper fileShare;
23 | private long TotalReceived;
24 | public long TotalSize { get; private set; } = 1;
25 |
26 | private bool forceTCP => services.MessageHandler.FTTransportLayer == "Tcp";
27 |
28 | private ServiceHub services => ServiceHub.Instance;
29 |
30 | public ReceiveState(MessageEnvelope fileDirectoryMessage, FileTransferHelper fs)
31 | {
32 | this.fileShare = fs;
33 | AssociatedPeer = fileDirectoryMessage.From;
34 | StateId = fileDirectoryMessage.MessageId;
35 | HandleDirectoryMessage(fileDirectoryMessage);
36 | }
37 |
38 | private void HandleDirectoryMessage(MessageEnvelope fileDirectoryMessage)
39 | {
40 | sw = Stopwatch.StartNew();
41 | fileTree = fileShare.HandleDirectoryStructure(fileDirectoryMessage);
42 | TotalSize = fileTree.TotalSize;
43 | int howManyFiles = fileTree.FileStructure.Values.Select(x => x.Count).Sum();
44 | GetNext(0);
45 | }
46 |
47 | public void HandleMessage(MessageEnvelope message)
48 | {
49 | if(message.Header == MessageHeaders.FileTransfer)
50 | {
51 | HandleFileChunk(message);
52 | }
53 | else if(message.Header == "FTComplete")
54 | {
55 | var msg = new MessageEnvelope()
56 | {
57 | MessageId = StateId,
58 | Header = "AllGood",
59 | };
60 |
61 | services.MessageHandler.SendAsyncMessage(AssociatedPeer, msg, forceTCP);
62 | OnCompleted?.Invoke(new Completion() { Directory = message.KeyValuePairs.Keys.First(), elapsed=sw.Elapsed });
63 | }
64 | else if (message.Header == "Cancel")
65 | {
66 | Cancel("Remote " + message.KeyValuePairs["Why"]);
67 | }
68 | }
69 | public void Cancel(string why )
70 | {
71 | OnProgress = null;
72 | Cancelled?.Invoke(new Completion() {AdditionalInfo = why, Directory = fileTree.seed});
73 | }
74 | private void HandleFileChunk(MessageEnvelope message)
75 | {
76 | try
77 | {
78 | var fileMsg = fileShare.HandleFileTransferMessage(message, out string error);
79 | if (!string.IsNullOrEmpty(error))
80 | {
81 | CancelExplicit("File Integrity Corrupted");
82 | return;
83 | }
84 | Interlocked.Add(ref TotalReceived, message.PayloadCount);
85 | GetNext(message.PayloadCount);
86 | UpdateStatus(fileMsg);
87 | }
88 | catch(Exception e)
89 | {
90 | CancelExplicit("Exception Occurred : " + e.ToString());
91 | }
92 | }
93 |
94 | public void CancelExplicit(string why)
95 | {
96 | Cancel(why);
97 | var msg = new MessageEnvelope()
98 | {
99 | MessageId = StateId,
100 | Header = "Cancel",
101 | KeyValuePairs = new Dictionary() { { "Why", why } }
102 |
103 | };
104 |
105 | services.MessageHandler.SendAsyncMessage(AssociatedPeer, msg, forceTCP);
106 | }
107 |
108 | private void UpdateStatus(FileChunk fileMsg)
109 | {
110 | float percentage = (100 * ((float)fileMsg.SequenceNumber / (float)(fileMsg.TotalSequences)));
111 | OnProgress?.Invoke(new TransferStatus() {Percentage= percentage,FileName=fileMsg.FilePath });
112 | }
113 |
114 | private void GetNext(int prevPayloadCount)
115 | {
116 | var response = new MessageEnvelope()
117 | {
118 | MessageId = StateId,
119 | Header = "Next",
120 | Payload = BitConverter.GetBytes(prevPayloadCount)
121 | };
122 |
123 | services.MessageHandler.SendAsyncMessage(AssociatedPeer, response, forceTCP);
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Latency/LatencyPublisher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Videocall.Services.Latency
8 | {public class LatencyPublisher
9 | {
10 | public event EventHandler Latency;
11 | private MessageHandler MessageHandler;
12 |
13 | public LatencyPublisher(MessageHandler messageHandler)
14 | {
15 | MessageHandler = messageHandler;
16 | Publish();
17 | }
18 |
19 | private void Publish()
20 | {
21 | Task.Run(async () =>
22 | {
23 | while (true)
24 | {
25 |
26 | await Task.Delay(900);
27 | Dictionary sts = MessageHandler.GetUdpPingStatus();
28 | Dictionary sts2 = MessageHandler.GetTcpPingStatus();
29 | if (sts == null || sts2 == null)
30 | return;
31 | LatencyEventArgs args = new LatencyEventArgs(sts, sts2);
32 | Latency?.Invoke(this, args);
33 | }
34 |
35 | });
36 | }
37 | }
38 |
39 | public class LatencyEventArgs:EventArgs
40 | {
41 | public Dictionary UdpLatency;
42 | public Dictionary TcpLatency;
43 |
44 | public LatencyEventArgs(Dictionary udpLatency, Dictionary tcpLatency)
45 | {
46 | UdpLatency = udpLatency;
47 | TcpLatency = tcpLatency;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Network/VCPeerInfo.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Linq;
6 | using System.Runtime.CompilerServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Input;
10 |
11 | namespace Videocall
12 | {
13 | [ProtoContract]
14 | public class VCPeerInfo : INotifyPropertyChanged, IProtoMessage
15 | {
16 | private double tcpLatency;
17 | private double udpLatency;
18 |
19 | public VCPeerInfo(string name, string ip, int port, Guid guid)
20 | {
21 | Name = name;
22 | Ip = ip;
23 | Port = port;
24 | Guid = guid;
25 | }
26 | public VCPeerInfo()
27 | {
28 | }
29 |
30 | [ProtoMember(1)]
31 | public string Name { get; }
32 | [ProtoMember(2)]
33 | public string Ip { get; }
34 | [ProtoMember(3)]
35 | public int Port { get; }
36 |
37 | [ProtoMember(4)]
38 | public Guid Guid { get; }
39 |
40 | public double TcpLatency { get => tcpLatency; set { tcpLatency = value; OnPropertyChanged(); } }
41 | public double UdpLatency { get => udpLatency; set { udpLatency = value; OnPropertyChanged(); } }
42 |
43 | public event PropertyChangedEventHandler PropertyChanged;
44 | protected void OnPropertyChanged([CallerMemberName] string name = null)
45 | {
46 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Notifications/ToastNotificationHandler.cs:
--------------------------------------------------------------------------------
1 | //using Microsoft.Toolkit.Uwp.Notifications;
2 | //using System;
3 | //using System.Collections.Concurrent;
4 | //using System.Threading.Tasks;
5 | //using Windows.UI.Notifications;
6 |
7 | //namespace Videocall
8 | //{
9 | // public class AsyncToastNotificationHandler
10 | // {
11 | // public const string CallAccepted = "accept";
12 | // public const string CallRejected = "reject";
13 | // public const string UserDoubleClicked = "doubleClicked";
14 | // public const string NotificationTimeout = "timeout";
15 |
16 | // private static ConcurrentDictionary> awaitingTasks
17 | // = new ConcurrentDictionary>();
18 | // static AsyncToastNotificationHandler()
19 | // {
20 | // ToastNotificationManagerCompat.OnActivated += toastArgs =>
21 | // {
22 | // HandleUserInput(toastArgs);
23 | // };
24 | // }
25 |
26 | // private static async Task RegisterWait(string notificationId, int timeoutMs)
27 | // {
28 | // awaitingTasks.TryAdd(notificationId, new TaskCompletionSource());
29 | // var pending = awaitingTasks[notificationId].Task;
30 | // //var result = Task.WhenAny(pending, Task.Delay(timeout));
31 |
32 | // string result = "";
33 | // if (await Task.WhenAny(pending, Task.Delay(timeoutMs)) == pending)
34 | // {
35 | // // Task completed within timeout.
36 | // result = pending.Result;
37 | // }
38 | // else
39 | // {
40 | // // timeout/cancellation logic
41 | // result = "timeout";
42 | // try
43 | // {
44 | // // only on uwp so far..
45 | // // ToastNotificationManager.History.Remove(notificationId);
46 |
47 | // }
48 | // catch (Exception e)
49 | // {
50 |
51 | // }
52 | // }
53 |
54 | // awaitingTasks.TryRemove(notificationId, out _);
55 | // return result;
56 | // }
57 | // //action=accept;notificationId=60cc3e4f-500c-42fc-8e88-ca8279c7d3cf
58 | // private static void HandleUserInput(ToastNotificationActivatedEventArgsCompat toastArgs)
59 | // {
60 | // App.ShowMainWindow();
61 | // Console.WriteLine("input");
62 | // //w.WtireTextOnChatWindow("got input");
63 |
64 | // var action = toastArgs.Argument.Split(';')[0];
65 | // var id = toastArgs.Argument.Split(';')[1];
66 |
67 | // var actionName = action.Split('=')[1];
68 | // var actionId = id.Split('=')[1];
69 |
70 | // ActionComplete(actionId, actionName);
71 | // }
72 | // private static void ActionComplete(string notificationId, string result)
73 | // {
74 | // if (awaitingTasks.TryGetValue(notificationId, out var completionSource))
75 | // completionSource.SetResult(result);
76 | // }
77 |
78 | // public static async Task ShowCallNotification(string callerName = "Someone", int timeout = 10000)
79 | // {
80 | // var notificationId = Guid.NewGuid().ToString();
81 | // new ToastContentBuilder()
82 | // .AddArgument("action", "doubleClicked")
83 | // .AddArgument("notificationId", notificationId)
84 | // .AddText(callerName + " Wants to call you")
85 | // .AddText("Would you like to accept the call?")
86 | // .AddButton(new ToastButton().SetContent("Accept Call").AddArgument("action", "accept"))
87 | // .AddButton(new ToastButton().SetContent("Reject Call").AddArgument("action", "reject"))
88 | // .Show(toast =>
89 | // {
90 | // toast.Tag = notificationId;
91 | // });
92 | // return await RegisterWait(notificationId, timeout);
93 | // //return await Task.FromResult("");
94 | // }
95 |
96 | // public static async Task ShowInfoNotification(string notificationString, int timeout = 5000)
97 | // {
98 | // var notificationId = Guid.NewGuid().ToString();
99 | // try
100 | // {
101 | // new ToastContentBuilder()
102 | // .AddArgument("action", "doubleClicked")
103 | // .AddArgument("notificationId", notificationId)
104 | // .AddText(notificationString)
105 | // .Show(toast =>
106 | // {
107 | // toast.Tag = notificationId;
108 | // toast.Group = "1";
109 | // //toast.ExpirationTime = DateTimeOffset.Now + TimeSpan.FromMilliseconds(3000);
110 |
111 | // });
112 | // }
113 | // catch (Exception e)
114 | // {
115 | // }
116 |
117 | // return await RegisterWait(notificationId, timeout);
118 | // }
119 | // }
120 | //}
121 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/ScreenShare/DxCaptureProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Videocall.Services.ScreenShare;
7 |
8 | namespace ServiceProvider.Services.ScreenShare
9 | {
10 | public class DxCaptureProvider : IScreenCapture
11 | {
12 | DXScreenCapture screenCapture;
13 | public void Init(double scale = 0.5, int screen = 0, int device = 0)
14 | {
15 | screenCapture = new DXScreenCapture();
16 | screenCapture.Init(scale, screen, device);
17 | }
18 | public void CaptureAuto(int targetFrameRate, Action onCaptured)
19 | {
20 | screenCapture.CaptureAuto(targetFrameRate, onCaptured);
21 | }
22 |
23 | public void Dispose()
24 | {
25 | screenCapture?.Dispose();
26 | }
27 |
28 |
29 |
30 | public void StopCapture()
31 | {
32 | screenCapture?.StopCapture();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/ScreenShare/IScreenCapture.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Videocall.Services.ScreenShare
3 | {
4 | public interface IScreenCapture
5 | {
6 | void CaptureAuto(int targetFrameRate, Action onCaptured);
7 | void Dispose();
8 | void Init(double scale = 0.5, int screen = 0, int device = 0);
9 | void StopCapture();
10 | }
11 | }
--------------------------------------------------------------------------------
/ServiceProvider/Services/ScreenShare/ScreenHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows.Forms;
9 |
10 | namespace Videocall.Services.ScreenShare
11 | {
12 | public class ScreenInformations
13 | {
14 | public static uint RawDpi { get; private set; }
15 | public static uint RawDpiY { get; private set; }
16 |
17 | static ScreenInformations()
18 | {
19 | uint dpiX;
20 | uint dpiY;
21 | GetDpi(DpiType.EFFECTIVE, out dpiX, out dpiY);
22 | RawDpi = dpiX;
23 | RawDpiY = dpiY;
24 | }
25 |
26 | ///
27 | /// Returns the scaling of the given screen.
28 | ///
29 | /// The type of dpi that should be given back..
30 | /// Gives the horizontal scaling back (in dpi).
31 | /// Gives the vertical scaling back (in dpi).
32 | private static void GetDpi(DpiType dpiType, out uint dpiX, out uint dpiY)
33 | {
34 | var point = new System.Drawing.Point(1, 1);
35 | var hmonitor = MonitorFromPoint(point, _MONITOR_DEFAULTTONEAREST);
36 |
37 | switch (GetDpiForMonitor(hmonitor, dpiType, out dpiX, out dpiY).ToInt32())
38 | {
39 | case _S_OK: return;
40 | case _E_INVALIDARG:
41 | throw new ArgumentException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
42 | default:
43 | throw new COMException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
44 | }
45 | }
46 |
47 | //https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062.aspx
48 | [DllImport("User32.dll")]
49 | private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);
50 |
51 | //https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx
52 | [DllImport("Shcore.dll")]
53 | private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);
54 |
55 | const int _S_OK = 0;
56 | const int _MONITOR_DEFAULTTONEAREST = 2;
57 | const int _E_INVALIDARG = -2147024809;
58 | [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
59 | public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
60 |
61 | public enum DeviceCap
62 | {
63 | VERTRES = 10,
64 | DESKTOPVERTRES = 117
65 | }
66 |
67 |
68 | public static double GetWindowsScreenScalingFactor(bool percentage = true)
69 | {
70 | //Create Graphics object from the current windows handle
71 | Graphics GraphicsObject = Graphics.FromHwnd(IntPtr.Zero);
72 | //Get Handle to the device context associated with this Graphics object
73 | IntPtr DeviceContextHandle = GraphicsObject.GetHdc();
74 | //Call GetDeviceCaps with the Handle to retrieve the Screen Height
75 | int LogicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.VERTRES);
76 | int PhysicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.DESKTOPVERTRES);
77 | //Divide the Screen Heights to get the scaling factor and round it to two decimals
78 | double ScreenScalingFactor = Math.Round(PhysicalScreenHeight / (double)LogicalScreenHeight, 2);
79 | //If requested as percentage - convert it
80 | if (percentage)
81 | {
82 | ScreenScalingFactor *= 100.0;
83 | }
84 | //Release the Handle and Dispose of the GraphicsObject object
85 | GraphicsObject.ReleaseHdc(DeviceContextHandle);
86 | GraphicsObject.Dispose();
87 | //Return the Scaling Factor
88 | return ScreenScalingFactor;
89 | }
90 |
91 | public static Size GetDisplayResolution()
92 | {
93 | var sf = GetWindowsScreenScalingFactor(false);
94 | var screenWidth = Screen.PrimaryScreen.Bounds.Width * sf;
95 | var screenHeight = Screen.PrimaryScreen.Bounds.Height * sf;
96 | return new Size((int)screenWidth, (int)screenHeight);
97 | }
98 |
99 | }
100 |
101 | ///
102 | /// Represents the different types of scaling.
103 | ///
104 | ///
105 | public enum DpiType
106 | {
107 | EFFECTIVE = 0,
108 | ANGULAR = 1,
109 | RAW = 2,
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/ServiceHub.cs:
--------------------------------------------------------------------------------
1 | using NetworkLibrary;
2 | using Protobuff;
3 | using ServiceProvider.Services.Audio;
4 | using ServiceProvider.Services.Audio.Dependency;
5 | using ServiceProvider.Services.ScreenShare;
6 | using ServiceProvider.Services.Video.Camera;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using Videocall.Services.File_Transfer;
14 | using Videocall.Services.Latency;
15 | using Videocall.Services.ScreenShare;
16 | using Videocall.Services.Video;
17 |
18 | namespace Videocall
19 | {
20 | public class ServiceHub
21 | {
22 |
23 | private static ServiceHub instance;
24 | public static ServiceHub Instance
25 | {
26 | get
27 | {
28 | if(instance == null)
29 | instance = new ServiceHub();
30 | return instance;
31 | }
32 | }
33 | public AudioHandler AudioHandler { get; private set; }
34 | public VideoHandler2 VideoHandler { get; private set; }
35 |
36 | public MessageHandler MessageHandler { get; private set; }
37 |
38 | public FileTransferStateManager FileTransfer { get; private set; }
39 |
40 | public LatencyPublisher LatencyPublisher { get; private set; }
41 | public ScreenShareHandlerH264 ScreenShareHandler { get; private set; }
42 |
43 | public Action VideoStatisticsAvailable;
44 | public Action CamSizeFeedbackAvailable;
45 | public event Action LogAvailable;
46 | private VCStatistics stats;
47 | private VCStatistics statsPrev;
48 |
49 | private ServiceHub()
50 | {
51 |
52 | }
53 | public void Initialize(IAudioIn audioIn, IAudioOut audioOut,ICameraProvider camProvider,IScreenCapture screenCapture)
54 | {
55 | AudioHandler = new AudioHandler(audioIn,audioOut);
56 | VideoHandler = new VideoHandler2(camProvider);
57 | ScreenShareHandler = new ScreenShareHandlerH264(screenCapture);
58 |
59 | FileTransfer = new FileTransferStateManager(new FileTransferHelper());
60 | MessageHandler = new MessageHandler();
61 | LatencyPublisher = new LatencyPublisher(MessageHandler);
62 |
63 | MessageHandler.OnMessageAvailable += HandleMessage;
64 | AudioHandler.OnStatisticsAvailable += OnAudioStatsAvailable;
65 | VideoHandler.CamSizeFeedbackAvailable = (w, h) => CamSizeFeedbackAvailable?.Invoke(w, h);
66 | PublishStatistics();
67 | }
68 |
69 | private void PublishStatistics()
70 | {
71 | Task.Run(async () =>
72 | {
73 | while (true)
74 | {
75 | await Task.Delay(1000);// dont change time
76 | var vs = VideoHandler.GetStatistics();
77 | var scs = ScreenShareHandler.GetStatistics();
78 |
79 | stats.OutgoingFrameRate = vs.OutgoingFrameRate + scs.OutgoingFrameRate;
80 | stats.IncomingFrameRate = vs.IncomingFrameRate + scs.IncomingFrameRate;
81 | stats.TransferRate = scs.TransferRate + vs.TransferRate;
82 | stats.AverageLatency = vs.AverageLatency;
83 | stats.ReceiveRate = vs.ReceiveRate + scs.ReceiveRate;
84 | stats.CurrentMaxBitRate = vs.CurrentMaxBitRate;
85 |
86 | if(statsPrev != stats)
87 | {
88 | statsPrev = stats;
89 | VideoStatisticsAvailable?.Invoke(stats);
90 | }
91 | }
92 |
93 | });
94 | }
95 |
96 | private void HandleMessage(MessageEnvelope message)
97 | {
98 | if(message.Header == MessageHeaders.MicClosed)
99 | {
100 | AudioHandler.FlushBuffers();
101 | }
102 | else if (message.Header == MessageHeaders.RemoteClosedCam)
103 | {
104 | VideoHandler.FlushBuffers();
105 | }
106 | }
107 |
108 | private void OnAudioStatsAvailable(AudioStatistics stats)
109 | {
110 | // you can mode it as prop
111 | VideoHandler.AudioBufferLatency = AudioHandler.BufferedDurationAvg;
112 | }
113 |
114 |
115 | public void ResetBuffers()
116 | {
117 | AudioHandler.ResetStatistics();
118 | AudioHandler.FlushBuffers();
119 | VideoHandler.FlushBuffers();
120 | }
121 |
122 | public void Log(string logType,string log)
123 | {
124 | LogAvailable?.Invoke(logType, log);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/SingleThreadDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ServiceProvider.Services
9 | {
10 | ///
11 | /// COM objects, especially audio and MIDI devices requires this.
12 | /// Thread that makes them must be the the one who destroys.
13 | ///
14 | internal class SingleThreadDispatcher
15 | {
16 | Thread executor;
17 | AutoResetEvent execute = new AutoResetEvent(false);
18 | ConcurrentQueue actions = new ConcurrentQueue();
19 | int disposed = 0;
20 | bool noThrow;
21 | public SingleThreadDispatcher(bool noThrow = true)
22 | {
23 | this.noThrow = noThrow;
24 | executor = new Thread(ExecutionLoop);
25 | executor.Name = "CustomDispatcher";
26 | executor.Start();
27 | }
28 |
29 | private void ExecutionLoop()
30 | {
31 | while (true)
32 | {
33 | try
34 | {
35 | execute.WaitOne();
36 | }
37 | catch { }
38 |
39 | if (Interlocked.CompareExchange(ref disposed, 0, 0) == 1)
40 | return;
41 |
42 | while (actions.TryDequeue(out var todo))
43 | {
44 | try
45 | {
46 | todo?.Invoke();
47 |
48 | }
49 | catch
50 | {
51 | if (!noThrow)
52 | throw;
53 | }
54 | }
55 | }
56 | }
57 |
58 | public void Enqueue(Action todo)
59 | {
60 | actions.Enqueue(todo);
61 | execute.Set();
62 |
63 | }
64 | public void EnqueueBlocking(Action todo, int timeout=0)
65 | {
66 | ManualResetEvent mre = new ManualResetEvent(false);
67 | actions.Enqueue(() =>
68 | {
69 | try
70 | {
71 | todo?.Invoke();
72 |
73 | }
74 | finally
75 | {
76 | try
77 | {
78 | mre.Set();
79 | }
80 | catch{ }
81 | }
82 | });
83 | execute.Set();
84 | if(Thread.CurrentThread.ManagedThreadId == executor.ManagedThreadId)
85 | {
86 | return;
87 | }
88 | _=timeout == 0 ? mre.WaitOne(): mre.WaitOne(timeout);
89 |
90 |
91 | }
92 |
93 | internal void Dispose()
94 | {
95 | Interlocked.Exchange(ref disposed, 1);
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Video/Camera/ICameraProvider.cs:
--------------------------------------------------------------------------------
1 | namespace ServiceProvider.Services.Video.Camera
2 | {
3 | public interface ICameraProvider
4 | {
5 | int FrameHeight { get; set; }
6 | int FrameWidth { get; set; }
7 |
8 | void Dispose();
9 | bool Grab();
10 | void Init(int camIdx);
11 | bool IsOpened();
12 | void Open(int camIdx);
13 | void Release();
14 | bool Retrieve(out ImageReference im);
15 | }
16 | }
--------------------------------------------------------------------------------
/ServiceProvider/Services/Video/Camera/WindowsCameraProvider.cs:
--------------------------------------------------------------------------------
1 | using OpenCvSharp;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ServiceProvider.Services.Video.Camera
10 | {
11 | public class WindowsCameraProvider : ICameraProvider
12 | {
13 | private ConcurrentBag mats = new ConcurrentBag();
14 | private ConcurrentBag imrefs = new ConcurrentBag();
15 | private VideoCapture capture;
16 | private int frameWidth => capture.FrameWidth;
17 | private int frameHeight => capture.FrameHeight;
18 |
19 | public WindowsCameraProvider()
20 | {
21 | }
22 |
23 | public int FrameWidth { get => frameWidth; set { capture.FrameWidth = value; } }
24 | public int FrameHeight { get => frameHeight; set => capture.FrameHeight = value; }
25 |
26 | public void Init(int camIdx)
27 | {
28 | capture = new VideoCapture(camIdx, VideoCaptureAPIs.MSMF);
29 |
30 | }
31 | public void Open(int camIdx)
32 | {
33 | capture.Open(camIdx);
34 | }
35 |
36 | public bool IsOpened()
37 | {
38 | return capture.IsOpened();
39 | }
40 |
41 | public void Release()
42 | {
43 | capture?.Release();
44 | }
45 |
46 | public void Dispose()
47 | {
48 | capture?.Dispose();
49 | }
50 |
51 | public bool Grab()
52 | {
53 | return capture.Grab();
54 | }
55 |
56 | public bool Retrieve(out ImageReference im)
57 | {
58 | mats.TryTake(out var frame);
59 | imrefs.TryTake(out im);
60 |
61 | if (frame == null)
62 | {
63 | frame= new Mat();
64 | }
65 | if (frame.Width != capture.FrameWidth || frame.Height != capture.FrameHeight)
66 | {
67 | frame.Dispose();
68 | frame = new Mat();
69 | }
70 |
71 | var res = capture.Retrieve(frame);
72 | if (res == false)
73 | {
74 | return false;
75 | }
76 | if (im == null)
77 | {
78 | im = ImageReference.FromMat(frame,ReturnMat);
79 | }
80 | else
81 | {
82 | im.Update(frame);
83 |
84 | }
85 | return res;
86 |
87 | }
88 |
89 | private void ReturnMat(ImageReference reference)
90 | {
91 | imrefs.Add(reference);
92 | mats.Add((Mat)reference.underlyingData);
93 | }
94 |
95 |
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Video/H264/H264TranscoderProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using H264Sharp;
10 | namespace Videocall.Services.Video.H264
11 | {
12 | internal class H264TranscoderProvider
13 | {
14 | static H264TranscoderProvider()
15 | {
16 | //H264Sharp.Defines.CiscoDllName32bit = "openh264-2.4.0-win32.dll";
17 | //H264Sharp.Defines.CiscoDllName64bit = "openh264-2.4.0-win64.dll";
18 | }
19 |
20 | public static H264Encoder CreateEncoder(int width, int height,
21 | int fps = 30, int bps = 3_000_000, ConfigType configNo = ConfigType.CameraBasic)
22 | {
23 |
24 | H264Encoder encoder = new H264Encoder();
25 | encoder.Initialize(width, height, bps, fps, configNo );
26 | encoder.SetOption(ENCODER_OPTION.ENCODER_OPTION_RC_FRAME_SKIP, (byte)1);
27 |
28 | if (configNo == ConfigType.CameraCaptureAdvanced)
29 | {
30 | encoder.SetOption(ENCODER_OPTION.ENCODER_OPTION_IDR_INTERVAL, 600);
31 | encoder.ConverterNumberOfThreads = 1;
32 | }
33 | else if(configNo == ConfigType.ScreenCaptureAdvanced)
34 | encoder.SetOption(ENCODER_OPTION.ENCODER_OPTION_IDR_INTERVAL, 1800);
35 |
36 | //encoder.SetMaxBitrate(bps);
37 | //encoder.SetTargetFps(fps);
38 | return encoder;
39 | }
40 |
41 | public static H264Decoder CreateDecoder()
42 | {
43 | H264Decoder decoder = new H264Decoder();
44 | decoder.Initialize();
45 | decoder.ConverterNumberOfThreads = 1;
46 | // decoder.EnableParallelImageConversion = true;
47 | return decoder;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Video/H264/JitterBufffer.cs:
--------------------------------------------------------------------------------
1 | using NetworkLibrary;
2 | using NetworkLibrary.Utils;
3 | using ProtoBuf.WellKnownTypes;
4 | using System;
5 | using System.Collections.Concurrent;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace Videocall.Services.Video.H264
13 | {
14 | class Frame
15 | {
16 | public DateTime TimeStamp;
17 | public byte[] Data;
18 | public int Offset;
19 | public int Count;
20 | public int w;
21 | public int h;
22 | }
23 | internal class JitterBufffer
24 | {
25 | public Action FrameAvailable;
26 | public double Duration => ((latestTs - oldestTs).TotalMilliseconds);
27 | public int MaxNumberOfFramesBuffered = 5;
28 |
29 | private ConcurrentDictionary reorderBuffer = new ConcurrentDictionary();
30 | private DateTime lastStamp = DateTime.Now;
31 | private DateTime latestTs = DateTime.Now;
32 | private DateTime oldestTs = DateTime.Now;
33 | private DateTime lastIn = DateTime.Now;
34 |
35 | private ushort prevSqn;
36 | private readonly object locker = new object();
37 | private Stopwatch sw = new Stopwatch();
38 | private int incomingFrameCount = 0;
39 |
40 | public void HandleFrame(DateTime timeStamp, ushort currentSqn, ushort w, ushort h, byte[] payload, int payloadOffset, int payloadCount)
41 | {
42 | if (latestTs < timeStamp)
43 | {
44 | latestTs = timeStamp;
45 | }
46 | lock (locker)
47 | {
48 | if (!sw.IsRunning)
49 | {
50 | sw.Start();
51 | }
52 | incomingFrameCount++;
53 | if (sw.ElapsedMilliseconds > 1000)
54 | {
55 | MaxNumberOfFramesBuffered = Math.Max(2, (incomingFrameCount / 4));//250ms
56 | incomingFrameCount = 0;
57 | sw.Restart();
58 | }
59 |
60 |
61 | var buffer = BufferPool.RentBuffer(payloadCount);
62 | ByteCopy.BlockCopy(payload, payloadOffset, buffer, 0, payloadCount);
63 |
64 | Frame f = new Frame { Data = buffer, Count = payloadCount, TimeStamp = timeStamp, w=w,h=h };
65 | reorderBuffer.TryAdd(currentSqn, f);
66 | var now = DateTime.Now;
67 |
68 | if((now-lastIn).TotalMilliseconds<15 && currentSqn != prevSqn + 1 && reorderBuffer.Count > MaxNumberOfFramesBuffered)
69 | {
70 | //Console.WriteLine("-- Video Buff Forced");
71 |
72 | lastIn = now;
73 | return;
74 | }
75 | lastIn = now;
76 | while (currentSqn == prevSqn + 1 || reorderBuffer.Count > MaxNumberOfFramesBuffered)
77 | {
78 | var key = reorderBuffer.Keys.Min();
79 |
80 | if (reorderBuffer.TryRemove(key, out Frame ff))
81 | {
82 | oldestTs = ff.TimeStamp;
83 | FrameAvailable?.Invoke(ff);
84 | BufferPool.ReturnBuffer(ff.Data);
85 | }
86 | lastStamp = timeStamp;
87 | prevSqn = key;
88 |
89 | var next = (ushort)(prevSqn + 1);
90 | if (reorderBuffer.ContainsKey(next))
91 | {
92 | currentSqn= next;
93 | }
94 |
95 | }
96 | //Console.WriteLine("Dur " + Duration);
97 | //Console.WriteLine("CCC " + reorderBuffer.Count);
98 | }
99 | }
100 |
101 | public void Discard()
102 | {
103 | reorderBuffer.Clear();
104 | oldestTs = lastStamp;
105 | }
106 | public void Reset()
107 | {
108 | prevSqn = 0;
109 | reorderBuffer.Clear();
110 | oldestTs = lastStamp;
111 |
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/ServiceProvider/Services/Video/ImageReference.cs:
--------------------------------------------------------------------------------
1 | using H264Sharp;
2 | using OpenCvSharp;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Videocall.Services.Video.H264;
9 |
10 | namespace ServiceProvider.Services.Video
11 | {
12 | public class ImageReference
13 | {
14 | public object underlyingData;
15 | public bool isManaged;
16 | private IntPtr dataStart;
17 | public byte[] Data;
18 | public int Offset;
19 | public int Length;
20 | public int Width, Height, Stride;
21 | public Action ReturnImage;
22 | public IntPtr DataStart {
23 | get
24 | {
25 |
26 | if (!isManaged)
27 | return dataStart;
28 | else
29 | {
30 | unsafe
31 | {
32 | fixed (byte* p = &Data[Offset])
33 | {
34 | return (IntPtr)(p);
35 | }
36 | }
37 |
38 | }
39 | }
40 | set => dataStart = value; }
41 |
42 | public ImageReference(object underlyingData, IntPtr dataPtr, int width, int height, int stride, Action whenReturning)
43 | {
44 | Create(underlyingData, dataPtr, width, height, stride, () => whenReturning.Invoke(this));
45 | }
46 | private void Create(object underlyingData, IntPtr dataPtr, int width, int height, int stride, Action whenReturning)
47 | {
48 | this.underlyingData = underlyingData;
49 | this.DataStart = dataPtr;
50 | Width = width;
51 | Height = height;
52 | Stride = stride;
53 | ReturnImage = whenReturning;
54 | }
55 |
56 | public ImageReference( byte[] data, int offset, int length, int width, int height, int stride)
57 | {
58 | Data = data;
59 | Offset = offset;
60 | Length = length;
61 | Width = width;
62 | Height = height;
63 | Stride = stride;
64 | isManaged = true;
65 | }
66 |
67 | public void Update(Mat mat)
68 | {
69 |
70 | Create(mat, mat.DataStart, mat.Width, mat.Height, (int)mat.Step(), ReturnImage);
71 | }
72 | internal void Update(RgbImage mat)
73 | {
74 | Create(mat, mat.ImageBytes, mat.Width, mat.Height, mat.Stride, ReturnImage);
75 | }
76 | public static ImageReference FromMat(Mat mat,Action whenReturning)
77 | {
78 | return new ImageReference(mat, mat.DataStart, mat.Width, mat.Height,(int)mat.Step(), whenReturning);
79 | }
80 |
81 | public static ImageReference FromRgbImage(RgbImage rgb, Action whenReturning)
82 | {
83 | return new ImageReference(rgb, rgb.ImageBytes, rgb.Width, rgb.Height, rgb.Stride, whenReturning);
84 | }
85 |
86 | public void Release()
87 | {
88 | if(underlyingData!=null && underlyingData is IDisposable)
89 | ((IDisposable)underlyingData).Dispose();
90 | }
91 |
92 |
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Videocall.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.0.32014.148
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Videocall", "Videocall\Videocall.csproj", "{01361BE3-409B-4B8C-9FDD-875827605743}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceProvider", "ServiceProvider\ServiceProvider.csproj", "{F9621290-0B11-42A7-A028-07872DE874DB}"
8 | EndProject
9 | Global
10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
11 | Debug|Any CPU = Debug|Any CPU
12 | Debug|ARM = Debug|ARM
13 | Debug|ARM64 = Debug|ARM64
14 | Debug|x64 = Debug|x64
15 | Debug|x86 = Debug|x86
16 | Release|Any CPU = Release|Any CPU
17 | Release|ARM = Release|ARM
18 | Release|ARM64 = Release|ARM64
19 | Release|x64 = Release|x64
20 | Release|x86 = Release|x86
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|ARM.ActiveCfg = Debug|Any CPU
26 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|ARM.Build.0 = Debug|Any CPU
27 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|ARM64.ActiveCfg = Debug|Any CPU
28 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|ARM64.Build.0 = Debug|Any CPU
29 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|x64.ActiveCfg = Debug|Any CPU
30 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|x64.Build.0 = Debug|Any CPU
31 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|x86.ActiveCfg = Debug|x86
32 | {01361BE3-409B-4B8C-9FDD-875827605743}.Debug|x86.Build.0 = Debug|x86
33 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|ARM.ActiveCfg = Release|Any CPU
36 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|ARM.Build.0 = Release|Any CPU
37 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|ARM64.ActiveCfg = Release|Any CPU
38 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|ARM64.Build.0 = Release|Any CPU
39 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|x64.ActiveCfg = Release|Any CPU
40 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|x64.Build.0 = Release|Any CPU
41 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|x86.ActiveCfg = Debug|Any CPU
42 | {01361BE3-409B-4B8C-9FDD-875827605743}.Release|x86.Build.0 = Debug|Any CPU
43 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|ARM.ActiveCfg = Debug|Any CPU
46 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|ARM.Build.0 = Debug|Any CPU
47 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|ARM64.ActiveCfg = Debug|Any CPU
48 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|ARM64.Build.0 = Debug|Any CPU
49 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|x64.ActiveCfg = Debug|Any CPU
50 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|x64.Build.0 = Debug|Any CPU
51 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|x86.ActiveCfg = Debug|Any CPU
52 | {F9621290-0B11-42A7-A028-07872DE874DB}.Debug|x86.Build.0 = Debug|Any CPU
53 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|ARM.ActiveCfg = Release|Any CPU
56 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|ARM.Build.0 = Release|Any CPU
57 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|ARM64.ActiveCfg = Release|Any CPU
58 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|ARM64.Build.0 = Release|Any CPU
59 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|x64.ActiveCfg = Release|Any CPU
60 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|x64.Build.0 = Release|Any CPU
61 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|x86.ActiveCfg = Release|Any CPU
62 | {F9621290-0B11-42A7-A028-07872DE874DB}.Release|x86.Build.0 = Release|Any CPU
63 | EndGlobalSection
64 | GlobalSection(SolutionProperties) = preSolution
65 | HideSolutionNode = FALSE
66 | EndGlobalSection
67 | GlobalSection(ExtensibilityGlobals) = postSolution
68 | SolutionGuid = {1B328380-F20E-4375-B143-C086E4F3677E}
69 | EndGlobalSection
70 | EndGlobal
71 |
--------------------------------------------------------------------------------
/Videocall/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Videocall/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Configuration;
5 | using System.Data;
6 | using System.Diagnostics;
7 | using System.Drawing;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Xml.Linq;
13 |
14 | namespace Videocall
15 | {
16 | ///
17 | /// Interaction logic for App.xaml
18 | ///
19 | public partial class App : Application
20 | {
21 | public static App Instance { get; private set; }
22 | private void Application_Activated(object sender, EventArgs e)
23 | {
24 |
25 | }
26 |
27 | private System.Windows.Forms.NotifyIcon _notifyIcon;
28 | private bool _isExit;
29 |
30 | protected override void OnStartup(StartupEventArgs e)
31 | {
32 | Instance = this;
33 | base.OnStartup(e);
34 |
35 | MainWindow = new MainWindow();
36 |
37 | MainWindow.Closing += MainWindow_Closing;
38 | _notifyIcon = new System.Windows.Forms.NotifyIcon();
39 | _notifyIcon.DoubleClick += (s, args) => ShowMainWindow_();
40 | var iconStream=Application.GetResourceStream
41 | (new Uri("pack://application:,,,/Videocall;component/Resources/favicon2.ico")).Stream;
42 |
43 | _notifyIcon.Icon = new System.Drawing.Icon(iconStream);//new Icon("Resources/favicon2.ico");//new System.Drawing.Icon("favicon2.ico");
44 | _notifyIcon.Visible = true;
45 |
46 | CreateContextMenu();
47 | ShowMainWindow_();
48 | }
49 |
50 | private void CreateContextMenu()
51 | {
52 | _notifyIcon.ContextMenuStrip =
53 | new System.Windows.Forms.ContextMenuStrip();
54 | _notifyIcon.ContextMenuStrip.Items.Add("MainWindow...").Click += (s, e) => ShowMainWindow_();
55 | _notifyIcon.ContextMenuStrip.Items.Add("Exit").Click += (s, e) => ExitApplication();
56 | }
57 |
58 | private void ExitApplication()
59 | {
60 | _isExit = true;
61 | MainWindow.Close();
62 | _notifyIcon.Dispose();
63 | _notifyIcon = null;
64 | }
65 |
66 | public static void ShowMainWindow()
67 | {
68 | DispatcherRun(() => Instance.ShowMainWindow_());
69 | }
70 | private static void DispatcherRun(Action todo)
71 | {
72 | try
73 | {
74 | Application.Current?.Dispatcher?.BeginInvoke(todo);
75 |
76 | }
77 | catch
78 | {
79 | }
80 | }
81 | private void ShowMainWindow_()
82 | {
83 | if (MainWindow.IsVisible)
84 | {
85 | if (MainWindow.WindowState == WindowState.Minimized)
86 | {
87 | MainWindow.WindowState = WindowState.Normal;
88 | }
89 | MainWindow.Activate();
90 | }
91 | else
92 | {
93 | MainWindow.Show();
94 | }
95 | }
96 |
97 | private void MainWindow_Closing(object sender, CancelEventArgs e)
98 | {
99 | if (!_isExit)
100 | {
101 | e.Cancel = true;
102 | MainWindow.Hide();
103 | }
104 |
105 | }
106 | }
107 |
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/Videocall/CallStateManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 |
5 |
6 | namespace Videocall
7 | {
8 | internal class CallStateManager
9 | {
10 | private static CallStateManager instance;
11 | internal static CallStateManager Instance {
12 | get
13 | {
14 | if(instance == null)
15 | {
16 | instance = new CallStateManager();
17 | }
18 | return instance;
19 | }
20 | }
21 |
22 | public event EventHandler StaticPropertyChanged;
23 | protected void OnPropertyChanged([CallerMemberName] string name = null)
24 | {
25 | StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(name));
26 | }
27 |
28 | public enum CallState
29 | {
30 | Available,
31 | Calling,
32 | ReceivingCall,
33 | OnCall,
34 | }
35 | CallState cs = CallState.Available;
36 | CallState currentState { get => Instance.cs; set
37 | {
38 | cs= value;
39 | CurrentState = Enum.GetName(typeof(CallState), cs);
40 | } }
41 |
42 | public string CurrentState {
43 | get => currentState1;
44 | set
45 | {
46 | if (currentState1 == value)
47 | return;
48 | currentState1 = value;
49 | OnPropertyChanged();
50 | }
51 | }
52 |
53 | private static string currentState1 = "Available";
54 | Guid CallingWith;
55 |
56 |
57 | public static void RegisterCall(Guid callingWith)
58 | {
59 | Instance.CallingWith = callingWith;
60 | Instance.currentState = CallState.OnCall;
61 | //AsyncToastNotificationHandler.ShowInfoNotification("You are on call",3000);
62 |
63 |
64 | }
65 |
66 | public static void UnregisterCall(Guid callingWith)
67 | {
68 | if(callingWith == Instance.CallingWith)
69 | {
70 | Instance.currentState = CallState.Available;
71 | // AsyncToastNotificationHandler.ShowInfoNotification("Call Ended", 3000);
72 | }
73 |
74 |
75 | }
76 |
77 | public static void EndCall()
78 | {
79 | if (Instance.currentState != CallState.Available)
80 | {
81 | Instance.currentState = CallState.Available;
82 | //AsyncToastNotificationHandler.ShowInfoNotification("Call Ended");
83 | }
84 |
85 |
86 | }
87 |
88 | public static void Calling()
89 | {
90 | if (Instance.currentState == CallState.Available)
91 | {
92 | Instance.currentState = CallState.Calling;
93 |
94 | }
95 | }
96 |
97 | public static void ReceivingCall()
98 | {
99 | if (Instance.currentState == CallState.Available)
100 | {
101 | Instance.currentState = CallState.ReceivingCall;
102 |
103 | }
104 | }
105 | public static bool CanReceiveCall() => Instance.currentState == CallState.Available;
106 | public static bool CanSendCall() => Instance.currentState == CallState.Available;
107 | public static CallState GetState() => CallStateManager.Instance.currentState;
108 | public static Guid GetCallerId() => Instance.CallingWith;
109 | public static bool IsOnACall =>Instance.currentState == CallState.OnCall;
110 |
111 |
112 | internal static void CallRejected() => Instance.currentState = CallState.Available;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Videocall/Models/MainWindow/ChatDataModel.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Forms;
10 |
11 | namespace Videocall.Models.MainWindow
12 | {
13 | public class ChatDataModel
14 | {
15 |
16 | public string Sender { get; set; }
17 | public string Message { get; set; }
18 | public string Time { get; set; }
19 | public string Allignment { get; set; } = "Left";
20 | public string URI { get; set; } = "";
21 |
22 | public double URIHeight { get => string.IsNullOrEmpty(URI) ? 0 : -1; }
23 |
24 | public System.Windows.Media.Color BackgroundColor { get; set; } = System.Windows.Media.Color.FromRgb(30, 30, 30);
25 | public Thickness RectMargin { get => string.IsNullOrEmpty(Sender) ? new Thickness(0, 0, 0, 0) : new Thickness(-3, 20, -3, 0); }
26 |
27 | public double SenderTextHeight { get => string.IsNullOrEmpty(Sender) ? 0 : 20; }
28 |
29 | public void CreateRemoteChatEntry(string sender, string message)
30 | {
31 | if (IsValidURL(message))
32 | {
33 | if (!message.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
34 | && !message.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
35 | message = "http://" + message;
36 | URI = message;
37 | }
38 | else
39 | {
40 | Message = message;
41 |
42 | }
43 | Sender = sender;
44 | Time = DateTime.Now.ToShortTimeString();
45 | Allignment = "Left";
46 | BackgroundColor = System.Windows.Media.Color.FromRgb(30, 30, 30);
47 |
48 | }
49 |
50 | public void CreateLocalChatEntry(string message, DateTime timestamp)
51 | {
52 | if (IsValidURL(message))
53 | {
54 | if (!message.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
55 | && !message.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
56 | message = "http://" + message;
57 | URI = message;
58 | }
59 | else
60 | {
61 | Message = message;
62 |
63 | }
64 | Sender = "You";
65 | Time = timestamp.ToShortTimeString();
66 | Allignment = "Right";
67 | BackgroundColor = System.Windows.Media.Color.FromRgb(35, 35, 42);
68 | }
69 |
70 | public void CreateInfoChatEntry(string info,string url = null)
71 | {
72 | URI = url;
73 |
74 | Message = info;
75 | Time = DateTime.Now.ToShortTimeString();
76 | Allignment = "Center";
77 | BackgroundColor = System.Windows.Media.Color.FromRgb(50, 50, 50);
78 | }
79 |
80 | const string Pattern = @"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$";
81 | readonly Regex Rgx = new Regex(Pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
82 | bool IsValidURL(string URL)
83 | {
84 | if (string.IsNullOrEmpty(URL)) return false;
85 | return Rgx.IsMatch(URL);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Videocall/Models/MainWindow/ChatSerializer.cs:
--------------------------------------------------------------------------------
1 | using NetworkLibrary.Utils;
2 | using ProtoBuf;
3 | using Protobuff;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Collections.ObjectModel;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows.Markup;
12 |
13 | namespace Videocall.Models.MainWindow
14 | {
15 | [ProtoContract]
16 | class ChatSerializationData:IProtoMessage
17 | {
18 | [ProtoContract]
19 | public enum MsgType
20 | {
21 | Local,
22 | Remote,
23 | Info
24 | }
25 |
26 | [ProtoMember(1)]
27 | public MsgType MessageType { get; set; }
28 | [ProtoMember(2)]
29 | public string Sender { get; set; }
30 | [ProtoMember(3)]
31 | public string Message { get; set; }
32 |
33 | [ProtoMember(4)]
34 | public DateTime TimeStamp { get; set; }
35 |
36 | }
37 | internal class ChatSerializer
38 | {
39 | ConcurrentProtoSerialiser serializer = new ConcurrentProtoSerialiser();
40 | private int lastOffset=0;
41 | private bool allMessagesLoaded;
42 |
43 | public string PathIndex { get; private set; }
44 | public string PathData { get; private set; }
45 |
46 | private readonly object StreamLocker = new object();
47 | public ChatSerializer(string path)
48 | {
49 | PathData = path+@"\data.bin";
50 | new FileStream(PathData, FileMode.OpenOrCreate).Dispose();
51 |
52 | }
53 |
54 | public void SerializeRemoteEntry(string sender, string message, DateTime timestamp)
55 | {
56 | ChatSerializationData data = new ChatSerializationData
57 | {
58 | MessageType = ChatSerializationData.MsgType.Remote,
59 | Sender = sender,
60 | Message = message,
61 | TimeStamp = timestamp
62 | };
63 | SerializeIntoStream(data);
64 | }
65 | public void SerializeLocalEntry(string message, DateTime timestamp, string sender)
66 | {
67 | ChatSerializationData data = new ChatSerializationData
68 | {
69 | MessageType = ChatSerializationData.MsgType.Local,
70 | Sender= sender,
71 | Message = message,
72 | TimeStamp = timestamp
73 |
74 | };
75 | SerializeIntoStream(data);
76 | }
77 | public void SerializeInfoEntry(string message, DateTime timestamp)
78 | {
79 | ChatSerializationData data = new ChatSerializationData
80 | {
81 | MessageType = ChatSerializationData.MsgType.Info,
82 | Message = message,
83 | TimeStamp = timestamp
84 | };
85 |
86 | SerializeIntoStream(data);
87 |
88 | }
89 |
90 | private void SerializeIntoStream(ChatSerializationData data)
91 | {
92 | lock (StreamLocker)
93 | {
94 | using (var streamData = new FileStream(PathData, FileMode.Append))
95 | {
96 | // prefix + postfix in case ending is corrupted
97 | var bytes = serializer.Serialize(data);
98 | int lenght = bytes.Length;
99 | var msgByteLength = BitConverter.GetBytes(lenght);
100 |
101 | streamData.Write(msgByteLength, 0, 4);
102 | streamData.Write(bytes, 0, bytes.Length);
103 | streamData.Write(msgByteLength, 0, 4);
104 |
105 | streamData.Flush();
106 | lastOffset += bytes.Length + 8;
107 | };
108 | }
109 |
110 | }
111 |
112 |
113 | public bool LoadFromEnd(int maxAmount, out List messages)
114 | {
115 | lock (StreamLocker)
116 | {
117 | messages = null;
118 | if (allMessagesLoaded)
119 | {
120 | return false;
121 | }
122 |
123 | using (var streamData = new FileStream(PathData, FileMode.Open))
124 | {
125 | // seek start + 4, -> get len
126 | // seek start + len + 4, -> get msg
127 | // seek start + len + 12, -> get len2
128 | // seek start + len + len2 + 12, ...
129 | // seek start + len + len2 + 20
130 | // seek start + len + len2 + len3 + 20
131 |
132 | bool retval = false;
133 | try
134 | {
135 | int offset = lastOffset;
136 | byte[] suffix = new byte[4];
137 | messages = new List();
138 | int numMessages = 0;
139 | while (numMessages < maxAmount && (streamData.Length >= offset + 8))
140 | {
141 | var pos = streamData.Seek(-(offset + 4), SeekOrigin.End);
142 | streamData.Read(suffix, 0, 4);
143 | var lenght = BitConverter.ToInt32(suffix, 0);
144 |
145 | streamData.Seek(-(lenght + offset + 4), SeekOrigin.End);
146 | byte[] message = new byte[lenght];
147 | streamData.Read(message, 0, message.Length);
148 |
149 | var msg = serializer.Deserialize(message,0,message.Length);
150 | messages.Add(msg);
151 |
152 | numMessages++;
153 | offset += lenght + 8;
154 | lastOffset = offset;
155 | retval = true;
156 | }
157 | if((streamData.Length < offset + 8)) allMessagesLoaded=true;
158 | return retval;
159 | }
160 | catch(Exception ex)
161 | {
162 |
163 | return false;
164 | }
165 |
166 |
167 | }
168 |
169 | }
170 |
171 | }
172 |
173 | public void ClearAllHistory()
174 | {
175 | lock (StreamLocker)
176 | {
177 | allMessagesLoaded = true;
178 | System.IO.File.WriteAllText(PathData, string.Empty);
179 | }
180 |
181 |
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/Videocall/Models/MainWindow/PeerInfo.cs:
--------------------------------------------------------------------------------
1 | //using ProtoBuf;
2 | //using System;
3 | //using System.Collections.Generic;
4 | //using System.ComponentModel;
5 | //using System.Linq;
6 | //using System.Runtime.CompilerServices;
7 | //using System.Text;
8 | //using System.Threading.Tasks;
9 | //using System.Windows.Input;
10 |
11 | //namespace Videocall
12 | //{
13 | // [ProtoContract]
14 | // public class PeerInfo : INotifyPropertyChanged, IProtoMessage
15 | // {
16 | // private double tcpLatency;
17 | // private double udpLatency;
18 |
19 | // public PeerInfo(string name, string ip, int port, Guid guid)
20 | // {
21 | // Name = name;
22 | // Ip = ip;
23 | // Port = port;
24 | // Guid = guid;
25 | // }
26 | // public PeerInfo()
27 | // {
28 | // }
29 |
30 | // [ProtoMember(1)]
31 | // public string Name { get; }
32 | // [ProtoMember(2)]
33 | // public string Ip { get; }
34 | // [ProtoMember(3)]
35 | // public int Port { get; }
36 |
37 | // [ProtoMember(4)]
38 | // public Guid Guid { get; }
39 |
40 | // public double TcpLatency { get => tcpLatency; set {tcpLatency = value; OnPropertyChanged(); } }
41 | // public double UdpLatency { get => udpLatency; set { udpLatency = value; OnPropertyChanged(); } }
42 |
43 | // public event PropertyChangedEventHandler PropertyChanged;
44 | // protected void OnPropertyChanged([CallerMemberName] string name = null)
45 | // {
46 | // PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
47 | // }
48 | // }
49 | //}
50 |
--------------------------------------------------------------------------------
/Videocall/Models/MainWindowEventAggregator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Videocall.Settings;
7 |
8 | namespace Videocall.Models
9 | {
10 | internal class MainWindowEventAggregator
11 | {
12 | private static MainWindowEventAggregator instance;
13 |
14 | public static MainWindowEventAggregator Instance
15 | {
16 | get
17 | {
18 | if (instance == null)
19 | instance = new MainWindowEventAggregator();
20 | return instance;
21 | }
22 | }
23 |
24 | public event Action ClearChatHistoryRequested;
25 | public event Action PeerRegistered;
26 |
27 | public void InvokeClearChatEvent()
28 | {
29 | ClearChatHistoryRequested?.Invoke();
30 | }
31 | public void InvokePeerRegisteredEvent(Videocall.VCPeerInfo info)
32 | {
33 | PeerRegistered?.Invoke(info);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Videocall/Models/Settings/PersistentSettingConfig.cs:
--------------------------------------------------------------------------------
1 |
2 | using ProtoBuf.Meta;
3 | using System.ComponentModel;
4 | using System.IO;
5 | using System.Reflection;
6 | using System.Runtime.CompilerServices;
7 | using System.Text.Json;
8 | using Videocall.Services.File_Transfer;
9 |
10 | namespace Videocall
11 | {
12 | //{"Ip":"82.61.88.82","Port":"20011","Name":"CinnamonBun"}
13 | // all props goes to disk as json
14 | public class PersistentSettingConfig : INotifyPropertyChanged
15 | {
16 | public static PersistentSettingConfig instance;
17 | private string ip;
18 | private int port;
19 | private string name;
20 | private int fTWindowSize = 2560000;
21 |
22 | private int chunkSize = 127000;
23 | private bool autoReconnect = true;
24 | private bool autoHolepunch = true;
25 | private int targetBps = 1500;
26 | private int minBps = 300;
27 | private int sctargetBps = 2000;
28 | private static bool dontInvoke = true;
29 | private int screenId =0;
30 | private int gpuId = 0;
31 | private int idrInterval = -1;
32 | private int sCTargetFps = 15;
33 | private bool multiThreadedScreenShare = false;
34 | private int cameraIndex = 0;
35 | private int camFrameWidth = 640;
36 | private int camFrameHeight = 480;
37 |
38 | public event PropertyChangedEventHandler PropertyChanged;
39 |
40 | public static PersistentSettingConfig Instance
41 | {
42 | get
43 | {
44 | if (instance == null)
45 | {
46 | instance = DeserializeToJsonAndLoad();
47 | dontInvoke = false;
48 | }
49 | return instance;
50 | }
51 | }
52 |
53 | public string Ip { get => ip; set { ip = value; OnPropertyChanged(); } }
54 | public int Port { get => port; set { port = value; OnPropertyChanged(); } }
55 |
56 | public string Name { get => name; set { name = value; OnPropertyChanged(); } }
57 | public int FTWindowSize { get => fTWindowSize;
58 | set
59 | {
60 | fTWindowSize = value;
61 | FileTransferStateManager.WindowsSize = fTWindowSize;
62 | }
63 | }
64 |
65 | public int ChunkSize { get => chunkSize;
66 | set
67 | {
68 | chunkSize = value; OnPropertyChanged();
69 | FileTransferStateManager.ChunkSize = chunkSize;
70 | }
71 | }
72 | public bool AutoReconnect { get => autoReconnect; set { autoReconnect = value; OnPropertyChanged(); } }
73 | public bool AutoHolepunch { get => autoHolepunch; set { autoHolepunch = value; OnPropertyChanged(); } }
74 | public int CameraIndex { get => cameraIndex; set {
75 | cameraIndex = value;
76 | OnPropertyChanged();
77 | } }
78 | public int CamFrameWidth { get => camFrameWidth; set { camFrameWidth = value; OnPropertyChanged(); } }
79 | public int CamFrameHeight { get => camFrameHeight; set { camFrameHeight = value; OnPropertyChanged(); } }
80 | public int ScreenId { get => screenId; set { screenId = value; OnPropertyChanged(); } }
81 | public int GpuId { get => gpuId; set { gpuId = value; OnPropertyChanged(); } }
82 | public int TargetBps { get => targetBps; set { targetBps = value; OnPropertyChanged(); } }
83 | public int MinBps { get => minBps; set { minBps = value; OnPropertyChanged(); } }
84 | public int IdrInterval { get => idrInterval; set {idrInterval = value; OnPropertyChanged(); } }
85 | public int SCTargetBps { get => sctargetBps; set { sctargetBps = value; OnPropertyChanged(); } }
86 | public int SCTargetFps { get => sCTargetFps; set { sCTargetFps = value; OnPropertyChanged(); } }
87 | public bool MultiThreadedScreenShare { get => multiThreadedScreenShare; set {
88 | multiThreadedScreenShare = value;
89 | ServiceHub.Instance.ScreenShareHandler.EnableParalelisation = multiThreadedScreenShare;
90 | OnPropertyChanged();
91 | } }
92 | public bool UseWasapi
93 | {
94 | get => useWasapi;
95 | set
96 | {
97 | useWasapi = value;
98 | //ServiceHub.Instance.AudioHandler.useWasapi = value;
99 | OnPropertyChanged();
100 | }
101 | }
102 | public bool EnableCongestionAvoidance { get => enableCongestionAvoidance;
103 | set { enableCongestionAvoidance = value;
104 | ServiceHub.Instance.VideoHandler.EnableCongestionAvoidance = enableCongestionAvoidance;
105 | OnPropertyChanged();
106 | } }
107 |
108 | public bool ReliableIDR { get => reliableIDR; set
109 | {
110 | reliableIDR = value;
111 | OnPropertyChanged();
112 | }
113 | }
114 | private int transportLayer = 0;
115 |
116 | public int TransportLayer
117 | {
118 | get => transportLayer; set
119 | {
120 | transportLayer = value;
121 | string layer = "";
122 | switch (transportLayer)
123 | {
124 | case 0:
125 | layer = "Udp";
126 | break;
127 | case 1:
128 | layer = "Tcp";
129 | break;
130 | default:
131 | layer = "Udp";
132 | break;
133 | }
134 | HandleTransportLayerChanged(layer);
135 | OnPropertyChanged();
136 | }
137 | }
138 | public bool AutoAcceptCalls { get => autoAcceptCalls; set { autoAcceptCalls = value; OnPropertyChanged(); } }
139 |
140 |
141 | private bool autoAcceptCalls = false;
142 | private bool reliableIDR = true;
143 | private bool enableCongestionAvoidance = true;
144 | private bool useWasapi = false;
145 |
146 | public static void SerializeToJsonAndSave()
147 | {
148 | string jsonString = JsonSerializer.Serialize(Instance);
149 | string workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
150 | string path = workingDir+ "/Settings/Settings.json";
151 | File.WriteAllText(path, jsonString);
152 | }
153 |
154 | public static PersistentSettingConfig DeserializeToJsonAndLoad()
155 | {
156 | string workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
157 | string path = workingDir+ "/Settings/Settings.json";
158 | if (!File.Exists(path))
159 | {
160 | PersistentSettingConfig cnf = new PersistentSettingConfig();
161 | string jsonString = JsonSerializer.Serialize(cnf);
162 | var dir = Path.GetDirectoryName(path);
163 | Directory.CreateDirectory(dir);
164 | File.WriteAllText(path, jsonString);
165 | }
166 | string jsonText = File.ReadAllText(path);
167 | return JsonSerializer.Deserialize(jsonText);
168 | }
169 | private void HandleTransportLayerChanged(string value)
170 | {
171 | ServiceHub.Instance.MessageHandler.TransportLayer = value;
172 | }
173 | protected void OnPropertyChanged([CallerMemberName] string name = null)
174 | {
175 | if (dontInvoke) return;
176 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
177 | SerializeToJsonAndSave();
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/Videocall/Models/Settings/Settings.json:
--------------------------------------------------------------------------------
1 | {"Ip":"82.61.88.82","Port":"20011","Name":"CinnamonBun"}
2 |
--------------------------------------------------------------------------------
/Videocall/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Videocall.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Videocall.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Videocall/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/Videocall/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Videocall.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Videocall/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Videocall/Resources/9-95099_camera-icon-png-image-camera-apple-icon-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/9-95099_camera-icon-png-image-camera-apple-icon-transparent.png
--------------------------------------------------------------------------------
/Videocall/Resources/cam off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/cam off.png
--------------------------------------------------------------------------------
/Videocall/Resources/camon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/camon.png
--------------------------------------------------------------------------------
/Videocall/Resources/favicon2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/favicon2.ico
--------------------------------------------------------------------------------
/Videocall/Resources/m2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/m2.png
--------------------------------------------------------------------------------
/Videocall/Resources/micoff_111072.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/micoff_111072.png
--------------------------------------------------------------------------------
/Videocall/Resources/micon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReferenceType/P2PVideocall/1ae219fc855b53afdef50b8937ffa191d8b9eaed/Videocall/Resources/micon.png
--------------------------------------------------------------------------------
/Videocall/Services/Audio/JitterBuffer.cs:
--------------------------------------------------------------------------------
1 | //using NAudio.Wave;
2 | //using System;
3 | //using System.Collections.Concurrent;
4 | //using System.Collections.Generic;
5 | //using System.Collections.Immutable;
6 | //using System.Data;
7 | //using System.IO;
8 | //using System.Linq;
9 | //using System.Threading;
10 |
11 | //namespace Videocall
12 | //{
13 | // class JitterBuffer
14 | // {
15 | // public Action OnSamplesCollected;
16 | // public int NumLostPackages = 0;
17 | // public int BufferLatency;
18 | // public int MinBufferLatency = 60;
19 | // public int Duration => numSeqBuffered * 20;
20 | // private ConcurrentDictionary samples = new ConcurrentDictionary();
21 | // private readonly object locker = new object();
22 | // private MemoryStream sampleStream = new MemoryStream();
23 | // private DateTime lastBatchTimeStamp = DateTime.Now;
24 | // private int numSeqBuffered = 0;
25 | // private AutoResetEvent bufferFullEvent = new AutoResetEvent(false);
26 | // private DateTime lastIn = DateTime.Now;
27 | // private int lastSeq;
28 |
29 | // public JitterBuffer(int bufferLatency)
30 | // {
31 | // this.BufferLatency = bufferLatency;
32 | // StartPublushing2();
33 |
34 | // }
35 | // // publish if anything is in buffer.
36 | // public void StartPublushing2()
37 | // {
38 | // Thread t = new Thread(() =>
39 | // {
40 | // lastSeq = 0;
41 | // while (true)
42 | // {
43 | // bufferFullEvent.WaitOne();
44 | // KeyValuePair[] samplesOrdered_;
45 | // lock (locker)
46 | // {
47 | // samplesOrdered_ = samples.OrderByDescending(x => x.Key).Reverse().ToArray();
48 | // }
49 | // // iterate and count all consecutive sequences.
50 | // int consequtiveSeqLenght = 0;
51 | // for (int i = 0; i < samplesOrdered_.Length - 1; i++)
52 | // {
53 | // if (samplesOrdered_[i].Value.SquenceNumber + 1 == samplesOrdered_[i + 1].Value.SquenceNumber)
54 | // consequtiveSeqLenght++;
55 | // else break;
56 | // }
57 | // //int toTake = Math.Min(2, numSeqBuffered) + (samplesOrdered_.Count() - (BufferLatency / 20));
58 | // IEnumerable> samplesOrdered;
59 | // // jittr buffer reached max duration, we have to take even if seq is broken
60 | // if (numSeqBuffered >= BufferLatency / 20)
61 | // {
62 | // int toTake = Math.Max(2, consequtiveSeqLenght+1);
63 | // samplesOrdered = samplesOrdered_.Take(Math.Min(toTake, samplesOrdered_.Count()));
64 | // }
65 | // // keep buffering seq isnt complete
66 | // else if (consequtiveSeqLenght == 0)
67 | // {
68 | // continue;
69 | // }
70 | // //key point is here, if its continous sequence we take else we buffer
71 | // else if (lastSeq == samplesOrdered_.First().Value.SquenceNumber - 1)
72 | // {
73 | // int toTake = Math.Max(2, consequtiveSeqLenght+1); // this was without max
74 | // samplesOrdered = samplesOrdered_.Take(Math.Min(toTake, samplesOrdered_.Count()));
75 | // }
76 | // else continue;
77 |
78 | // lastBatchTimeStamp = samplesOrdered.Last().Key;
79 |
80 | // var sampArry = samplesOrdered.ToImmutableArray();
81 | // for (int i = 0; i < sampArry.Length - 1; i++)
82 | // {
83 |
84 | // if (sampArry[i].Value.SquenceNumber + 1 == sampArry[i + 1].Value.SquenceNumber)
85 | // {
86 | // sampleStream.Write(sampArry[i].Value.Data, 0, sampArry[i].Value.DataLenght);
87 | // lastSeq = sampArry[i].Value.SquenceNumber;
88 | // }
89 | // // lost packet we conceal them here
90 | // else
91 | // {
92 | // //delta is at least 2 here
93 | // int delta = sampArry[i + 1].Value.SquenceNumber - sampArry[i].Value.SquenceNumber;
94 | // for (int j = 0; j < delta - 1; j++)
95 | // {
96 | // sampleStream.Write(sampArry[i].Value.Data, 0, sampArry[i].Value.DataLenght);
97 | // NumLostPackages++;
98 | // }
99 | // }
100 |
101 | // lock (locker)
102 | // {
103 | // samples.TryRemove(sampArry[i].Key, out _);
104 | // numSeqBuffered--;
105 | // }
106 |
107 | // }
108 |
109 | // try
110 | // {
111 | // OnSamplesCollected?.Invoke(sampleStream.GetBuffer(), 0, (int)sampleStream.Position);
112 | // }
113 | // catch (Exception e)
114 | // {
115 | // Console.WriteLine(e.Message);
116 | // }
117 |
118 | // sampleStream.Position = 0;
119 |
120 |
121 | // }
122 | // });
123 | // t.Priority = ThreadPriority.AboveNormal;
124 | // t.Start();
125 | // }
126 | // public void AddSample(AudioSample sample)
127 | // {
128 | // // special debug case, it cant be with gui slider.
129 | // if (BufferLatency < 60)
130 | // {
131 | // OnSamplesCollected?.Invoke(sample.Data, 0, sample.DataLenght);
132 | // return;
133 | // }
134 |
135 | // lock (locker)
136 | // {
137 | // if (!samples.ContainsKey(sample.Timestamp) && sample.Timestamp >= lastBatchTimeStamp)
138 | // {
139 | // samples.TryAdd(sample.Timestamp, sample);
140 | // numSeqBuffered++;
141 | // var now = DateTime.Now;
142 | // if ((now - lastIn).TotalMilliseconds < 10 && numSeqBuffered>4)
143 | // {
144 | // Console.WriteLine("Audio Buff Forced");
145 | // lastIn = now;
146 | // return;
147 |
148 | // }
149 | // lastIn = now;
150 | // if (numSeqBuffered >= 2)
151 | // {
152 | // bufferFullEvent.Set();
153 | // }
154 | // }
155 | // }
156 | // }
157 |
158 | // public void DiscardSamples(int num)
159 | // {
160 | // lock (locker)
161 | // {
162 | // var samplesOrdered = samples.OrderByDescending(x => x.Key).Reverse();
163 | // //samplesOrdered = samplesOrdered.Take(samplesOrdered.Count() - 10);
164 | // samplesOrdered = samplesOrdered.Take(Math.Min(num, samplesOrdered.Count()));
165 |
166 | // foreach (var item in samplesOrdered)
167 | // {
168 | // samples.TryRemove(item.Key, out _);
169 | // numSeqBuffered--;
170 |
171 | // }
172 | // lastSeq = 0;
173 | // }
174 |
175 | // }
176 |
177 |
178 | // }
179 | //}
180 |
181 |
--------------------------------------------------------------------------------
/Videocall/Services/File Transfer/FileTransferStateManager.cs:
--------------------------------------------------------------------------------
1 | //using NetworkLibrary.P2P.Generic;
2 | //using NetworkLibrary;
3 | //using ProtoBuf.Meta;
4 | //using System;
5 | //using System.Security.Cryptography;
6 | //using System.Text;
7 | //using System.Threading.Tasks;
8 | //using System.Security.Policy;
9 | //using System.Collections.Concurrent;
10 | //using System.Xml.Linq;
11 | //using System.Xml;
12 |
13 | //namespace Videocall.Services.File_Transfer
14 | //{
15 | // interface IFileTransferState
16 | // {
17 | // Guid AssociatedPeer { get; }
18 | // Guid StateId { get; }
19 | // void Cancel(string why);
20 | // void CancelExplicit(string why);
21 | // void HandleMessage(MessageEnvelope envelope);
22 |
23 | // int Progress { get; }
24 | // long TotalSize { get; }
25 | // }
26 | // public class TransferStatus
27 | // {
28 | // public string FileName;
29 | // public float Percentage;
30 |
31 | // }
32 | // public class Completion
33 | // {
34 | // public string Directory;
35 | // public string AdditionalInfo;
36 | // public TimeSpan elapsed;
37 | // }
38 |
39 | // internal class FileTransferStateManager
40 | // {
41 | // public Action OnTransferStatus;
42 | // public Action OnReceiveStatus;
43 | // public Action OnTransferComplete;
44 | // public Action OnReceiveComplete;
45 | // public Action OnTransferCancelled;
46 | // public Action OnReceiveCancelled;
47 | // private ServiceHub services => ServiceHub.Instance;
48 | // private ConcurrentDictionary activeStates = new ConcurrentDictionary();
49 |
50 | // public void CancelExplicit(Guid stateId)
51 | // {
52 | // if(activeStates.TryGetValue(stateId, out var state))
53 | // {
54 | // state.CancelExplicit("User Cancelled");
55 | // }
56 | // }
57 |
58 | // public void CancelAssociatedState(Guid peerId)
59 | // {
60 | // foreach (var state in activeStates.Values)
61 | // {
62 | // if(state.AssociatedPeer == peerId)
63 | // {
64 | // state.Cancel("Cancelled due to Disconnect");
65 | // activeStates.TryRemove(state.StateId, out _);
66 | // }
67 | // }
68 | // }
69 |
70 | // public void SendFile(string[] files, Guid selectedPeer)
71 | // {
72 | // var sendState = new SendState(files, selectedPeer);
73 | // sendState.OnProgress += (sts) => OnTransferStatus?.Invoke(sendState,sts);
74 | // sendState.Completed =(c) => HandleCompletedSend(sendState,c);
75 | // sendState.Cancelled =(c) => HandleCancelledSend(sendState,c);
76 | // activeStates.TryAdd(sendState.StateId, sendState);
77 | // }
78 |
79 | // public void HandleReceiveFile(MessageEnvelope message)
80 | // {
81 | // var receiveState = new ReceiveState(message);
82 | // receiveState.OnProgress += (sts) => OnReceiveStatus?.Invoke(receiveState,sts);
83 | // receiveState.OnCompleted = (c) => HandleCompletionReceive(receiveState, c);
84 | // receiveState.Cancelled = (c) => HandleCancelledReceive(receiveState, c);
85 | // activeStates.TryAdd(receiveState.StateId, receiveState);
86 | // }
87 |
88 | // private void HandleCancelledSend(SendState sendState, Completion c)
89 | // {
90 | // OnTransferCancelled?.Invoke(sendState, c);
91 | // activeStates.TryRemove(sendState.StateId, out _);
92 | // services.FileShare.CleanUp(sendState.StateId);
93 | // sendState.Cleanup();
94 | // }
95 |
96 | // private void HandleCompletedSend(SendState sendState, Completion completion)
97 | // {
98 | // OnTransferComplete?.Invoke(sendState, completion);
99 | // activeStates.TryRemove(sendState.StateId, out _);
100 | // services.FileShare.CleanUp(sendState.StateId);
101 | // sendState.Cleanup();
102 |
103 | // }
104 |
105 | // private void HandleCancelledReceive(ReceiveState receiveState, Completion c)
106 | // {
107 | // OnReceiveCancelled?.Invoke(receiveState,c);
108 | // activeStates.TryRemove(receiveState.StateId, out _);
109 | // services.FileShare.CleanUp(receiveState.StateId);
110 | // }
111 |
112 | // private void HandleCompletionReceive(ReceiveState receiveState,Completion completion)
113 | // {
114 | // OnReceiveComplete?.Invoke(receiveState,completion);
115 | // activeStates.TryRemove(receiveState.StateId, out _);
116 | // services.FileShare.CleanUp(receiveState.StateId);
117 | // }
118 |
119 | // public bool HandleMessage(MessageEnvelope message)
120 | // {
121 | // if (activeStates.TryGetValue(message.MessageId, out var state))
122 | // {
123 | // state.HandleMessage(message);
124 | // return true;
125 | // }
126 | // return false;
127 | // }
128 |
129 | // internal void CancelAllStates()
130 | // {
131 | // foreach (var state in activeStates.Values)
132 | // {
133 | // state.CancelExplicit("User Cancelled");
134 | // }
135 |
136 | // services.FileShare.ReleaseAll();
137 | // }
138 | // }
139 | //}
140 |
--------------------------------------------------------------------------------
/Videocall/Services/File Transfer/ReceiveState.cs:
--------------------------------------------------------------------------------
1 | //using NetworkLibrary;
2 | //using System;
3 | //using System.Collections.Generic;
4 | //using System.Diagnostics;
5 | //using System.Linq;
6 | //using System.Threading;
7 | //using System.Data;
8 |
9 | //namespace Videocall.Services.File_Transfer
10 | //{
11 | // class ReceiveState : IFileTransferState
12 | // {
13 | // public int Progress => (int)(100 * ((double)TotalReceived / (double)TotalSize));
14 |
15 | // public Guid AssociatedPeer { get; private set; }
16 | // public Guid StateId { get; private set; }
17 | // public Action OnProgress;
18 | // public Action OnCompleted;
19 | // public Action Cancelled;
20 | // private Stopwatch sw;
21 | // private FileDirectoryStructure fileTree;
22 | // private long TotalReceived;
23 | // public long TotalSize { get; private set; } = 1;
24 |
25 | // private bool forceTCP => services.MessageHandler.FTTransportLayer == "Tcp";
26 |
27 | // private ServiceHub services => ServiceHub.Instance;
28 |
29 | // public ReceiveState(MessageEnvelope fileDirectoryMessage)
30 | // {
31 | // AssociatedPeer = fileDirectoryMessage.From;
32 | // StateId = fileDirectoryMessage.MessageId;
33 | // HandleDirectoryMessage(fileDirectoryMessage);
34 | // }
35 |
36 | // private void HandleDirectoryMessage(MessageEnvelope fileDirectoryMessage)
37 | // {
38 | // sw = Stopwatch.StartNew();
39 | // fileTree = services.FileShare.HandleDirectoryStructure(fileDirectoryMessage);
40 | // TotalSize = fileTree.TotalSize;
41 | // int howManyFiles = fileTree.FileStructure.Values.Select(x => x.Count).Sum();
42 | // GetNext(0);
43 | // }
44 |
45 | // public void HandleMessage(MessageEnvelope message)
46 | // {
47 | // if(message.Header == MessageHeaders.FileTransfer)
48 | // {
49 | // HandleFileChunk(message);
50 | // }
51 | // else if(message.Header == "FTComplete")
52 | // {
53 | // var msg = new MessageEnvelope()
54 | // {
55 | // MessageId = StateId,
56 | // Header = "AllGood",
57 | // };
58 |
59 | // services.MessageHandler.SendAsyncMessage(AssociatedPeer, msg, forceTCP);
60 | // OnCompleted?.Invoke(new Completion() { Directory = message.KeyValuePairs.Keys.First(), elapsed=sw.Elapsed });
61 | // }
62 | // else if (message.Header == "Cancel")
63 | // {
64 | // Cancel("Remote " + message.KeyValuePairs["Why"]);
65 | // }
66 | // }
67 | // public void Cancel(string why )
68 | // {
69 | // OnProgress = null;
70 | // Cancelled?.Invoke(new Completion() {AdditionalInfo = why, Directory = fileTree.seed});
71 | // }
72 | // private void HandleFileChunk(MessageEnvelope message)
73 | // {
74 | // try
75 | // {
76 | // var fileMsg = services.FileShare.HandleFileTransferMessage(message, out string error);
77 | // if (!string.IsNullOrEmpty(error))
78 | // {
79 | // CancelExplicit("File Integrity Corrupted");
80 | // return;
81 | // }
82 | // Interlocked.Add(ref TotalReceived, message.PayloadCount);
83 | // GetNext(message.PayloadCount);
84 | // UpdateStatus(fileMsg);
85 | // }
86 | // catch(Exception e)
87 | // {
88 | // CancelExplicit("Exception Occurred : " + e.ToString());
89 | // }
90 | // }
91 |
92 | // public void CancelExplicit(string why)
93 | // {
94 | // Cancel(why);
95 | // var msg = new MessageEnvelope()
96 | // {
97 | // MessageId = StateId,
98 | // Header = "Cancel",
99 | // KeyValuePairs = new Dictionary() { { "Why", why } }
100 |
101 | // };
102 |
103 | // services.MessageHandler.SendAsyncMessage(AssociatedPeer, msg, forceTCP);
104 | // }
105 |
106 | // private void UpdateStatus(FileChunk fileMsg)
107 | // {
108 | // float percentage = (100 * ((float)fileMsg.SequenceNumber / (float)(fileMsg.TotalSequences)));
109 | // OnProgress?.Invoke(new TransferStatus() {Percentage= percentage,FileName=fileMsg.FilePath });
110 | // }
111 |
112 | // private void GetNext(int prevPayloadCount)
113 | // {
114 | // var response = new MessageEnvelope()
115 | // {
116 | // MessageId = StateId,
117 | // Header = "Next",
118 | // Payload = BitConverter.GetBytes(prevPayloadCount)
119 | // };
120 |
121 | // services.MessageHandler.SendAsyncMessage(AssociatedPeer, response, forceTCP);
122 | // }
123 | // }
124 | //}
125 |
--------------------------------------------------------------------------------
/Videocall/Services/Latency/LatencyPublisher.cs:
--------------------------------------------------------------------------------
1 | //using System;
2 | //using System.Collections.Generic;
3 | //using System.Linq;
4 | //using System.Text;
5 | //using System.Threading.Tasks;
6 |
7 | //namespace Videocall.Services.Latency
8 | //{
9 | // internal class LatencyPublisher
10 | // {
11 | // public event EventHandler Latency;
12 | // private MessageHandler MessageHandler;
13 |
14 | // public LatencyPublisher(MessageHandler messageHandler)
15 | // {
16 | // MessageHandler = messageHandler;
17 | // Publish();
18 | // }
19 |
20 | // private void Publish()
21 | // {
22 | // Task.Run(async () =>
23 | // {
24 | // while (true)
25 | // {
26 |
27 | // await Task.Delay(900);
28 | // Dictionary sts = MessageHandler.GetUdpPingStatus();
29 | // Dictionary sts2 = MessageHandler.GetTcpPingStatus();
30 | // if (sts == null || sts2 == null)
31 | // return;
32 | // LatencyEventArgs args = new LatencyEventArgs(sts, sts2);
33 | // Latency?.Invoke(this, args);
34 | // }
35 |
36 | // });
37 | // }
38 | // }
39 |
40 | // public class LatencyEventArgs:EventArgs
41 | // {
42 | // public Dictionary UdpLatency;
43 | // public Dictionary TcpLatency;
44 |
45 | // public LatencyEventArgs(Dictionary udpLatency, Dictionary tcpLatency)
46 | // {
47 | // UdpLatency = udpLatency;
48 | // TcpLatency = tcpLatency;
49 | // }
50 | // }
51 | //}
52 |
--------------------------------------------------------------------------------
/Videocall/Services/Notifications/ToastNotificationHandler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Uwp.Notifications;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Threading.Tasks;
5 | using Windows.UI.Notifications;
6 |
7 | namespace Videocall
8 | {
9 | internal class AsyncToastNotificationHandler
10 | {
11 | public const string CallAccepted = "accept";
12 | public const string CallRejected = "reject";
13 | public const string UserDoubleClicked = "doubleClicked";
14 | public const string NotificationTimeout = "timeout";
15 |
16 | private static ConcurrentDictionary> awaitingTasks
17 | = new ConcurrentDictionary>();
18 | static AsyncToastNotificationHandler()
19 | {
20 | ToastNotificationManagerCompat.OnActivated += toastArgs =>
21 | {
22 | HandleUserInput(toastArgs);
23 | };
24 | }
25 |
26 | private static async Task RegisterWait(string notificationId, int timeoutMs)
27 | {
28 | awaitingTasks.TryAdd(notificationId, new TaskCompletionSource());
29 | var pending = awaitingTasks[notificationId].Task;
30 | //var result = Task.WhenAny(pending, Task.Delay(timeout));
31 |
32 | string result = "";
33 | if (await Task.WhenAny(pending, Task.Delay(timeoutMs)) == pending)
34 | {
35 | // Task completed within timeout.
36 | result = pending.Result;
37 | }
38 | else
39 | {
40 | // timeout/cancellation logic
41 | result = "timeout";
42 | try
43 | {
44 | // only on uwp so far..
45 | // ToastNotificationManager.History.Remove(notificationId);
46 |
47 | }
48 | catch(Exception e)
49 | {
50 |
51 | }
52 | }
53 |
54 | awaitingTasks.TryRemove(notificationId, out _);
55 | return result;
56 | }
57 | //action=accept;notificationId=60cc3e4f-500c-42fc-8e88-ca8279c7d3cf
58 | private static void HandleUserInput(ToastNotificationActivatedEventArgsCompat toastArgs)
59 | {
60 | App.ShowMainWindow();
61 | Console.WriteLine("input");
62 | //w.WtireTextOnChatWindow("got input");
63 |
64 | var action = toastArgs.Argument.Split(';')[0];
65 | var id = toastArgs.Argument.Split(';')[1];
66 |
67 | var actionName = action.Split('=')[1];
68 | var actionId = id.Split('=')[1];
69 |
70 | ActionComplete(actionId, actionName);
71 | }
72 | private static void ActionComplete(string notificationId, string result)
73 | {
74 | if (awaitingTasks.TryGetValue(notificationId, out var completionSource))
75 | completionSource.SetResult(result);
76 | }
77 |
78 | public static async Task ShowCallNotification(string callerName="Someone", int timeout = 10000)
79 | {
80 | var notificationId = Guid.NewGuid().ToString();
81 | new ToastContentBuilder()
82 | .AddArgument("action", "doubleClicked")
83 | .AddArgument("notificationId", notificationId)
84 | .AddText(callerName + " Wants to call you")
85 | .AddText("Would you like to accept the call?")
86 | .AddButton(new ToastButton().SetContent("Accept Call").AddArgument("action", "accept"))
87 | .AddButton(new ToastButton().SetContent("Reject Call").AddArgument("action", "reject"))
88 | .Show(toast =>
89 | {
90 | toast.Tag = notificationId;
91 | });
92 | return await RegisterWait(notificationId, timeout);
93 | //return await Task.FromResult("");
94 | }
95 |
96 | public static async Task ShowInfoNotification(string notificationString, int timeout = 5000)
97 | {
98 | var notificationId = Guid.NewGuid().ToString();
99 | try
100 | {
101 | new ToastContentBuilder()
102 | .AddArgument("action", "doubleClicked")
103 | .AddArgument("notificationId", notificationId)
104 | .AddText(notificationString)
105 | .Show(toast =>
106 | {
107 | toast.Tag = notificationId;
108 | toast.Group = "1";
109 | //toast.ExpirationTime = DateTimeOffset.Now + TimeSpan.FromMilliseconds(3000);
110 |
111 | });
112 | }
113 | catch (Exception e)
114 | {
115 | }
116 |
117 | return await RegisterWait(notificationId, timeout);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Videocall/Services/ScreenShare/ScreenHelper.cs:
--------------------------------------------------------------------------------
1 | //using System;
2 | //using System.Collections.Generic;
3 | //using System.Drawing;
4 | //using System.Linq;
5 | //using System.Runtime.InteropServices;
6 | //using System.Text;
7 | //using System.Threading.Tasks;
8 | //using System.Windows.Forms;
9 |
10 | //namespace Videocall.Services.ScreenShare
11 | //{
12 | // public class ScreenInformations
13 | // {
14 | // public static uint RawDpi { get; private set; }
15 | // public static uint RawDpiY { get; private set; }
16 |
17 | // static ScreenInformations()
18 | // {
19 | // uint dpiX;
20 | // uint dpiY;
21 | // GetDpi(DpiType.EFFECTIVE, out dpiX, out dpiY);
22 | // RawDpi = dpiX;
23 | // RawDpiY = dpiY;
24 | // }
25 |
26 | // ///
27 | // /// Returns the scaling of the given screen.
28 | // ///
29 | // /// The type of dpi that should be given back..
30 | // /// Gives the horizontal scaling back (in dpi).
31 | // /// Gives the vertical scaling back (in dpi).
32 | // private static void GetDpi(DpiType dpiType, out uint dpiX, out uint dpiY)
33 | // {
34 | // var point = new System.Drawing.Point(1, 1);
35 | // var hmonitor = MonitorFromPoint(point, _MONITOR_DEFAULTTONEAREST);
36 |
37 | // switch (GetDpiForMonitor(hmonitor, dpiType, out dpiX, out dpiY).ToInt32())
38 | // {
39 | // case _S_OK: return;
40 | // case _E_INVALIDARG:
41 | // throw new ArgumentException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
42 | // default:
43 | // throw new COMException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
44 | // }
45 | // }
46 |
47 | // //https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062.aspx
48 | // [DllImport("User32.dll")]
49 | // private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);
50 |
51 | // //https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx
52 | // [DllImport("Shcore.dll")]
53 | // private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);
54 |
55 | // const int _S_OK = 0;
56 | // const int _MONITOR_DEFAULTTONEAREST = 2;
57 | // const int _E_INVALIDARG = -2147024809;
58 | // [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
59 | // public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
60 |
61 | // public enum DeviceCap
62 | // {
63 | // VERTRES = 10,
64 | // DESKTOPVERTRES = 117
65 | // }
66 |
67 |
68 | // public static double GetWindowsScreenScalingFactor(bool percentage = true)
69 | // {
70 | // //Create Graphics object from the current windows handle
71 | // Graphics GraphicsObject = Graphics.FromHwnd(IntPtr.Zero);
72 | // //Get Handle to the device context associated with this Graphics object
73 | // IntPtr DeviceContextHandle = GraphicsObject.GetHdc();
74 | // //Call GetDeviceCaps with the Handle to retrieve the Screen Height
75 | // int LogicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.VERTRES);
76 | // int PhysicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.DESKTOPVERTRES);
77 | // //Divide the Screen Heights to get the scaling factor and round it to two decimals
78 | // double ScreenScalingFactor = Math.Round(PhysicalScreenHeight / (double)LogicalScreenHeight, 2);
79 | // //If requested as percentage - convert it
80 | // if (percentage)
81 | // {
82 | // ScreenScalingFactor *= 100.0;
83 | // }
84 | // //Release the Handle and Dispose of the GraphicsObject object
85 | // GraphicsObject.ReleaseHdc(DeviceContextHandle);
86 | // GraphicsObject.Dispose();
87 | // //Return the Scaling Factor
88 | // return ScreenScalingFactor;
89 | // }
90 |
91 | // public static Size GetDisplayResolution()
92 | // {
93 | // var sf = GetWindowsScreenScalingFactor(false);
94 | // var screenWidth = Screen.PrimaryScreen.Bounds.Width * sf;
95 | // var screenHeight = Screen.PrimaryScreen.Bounds.Height * sf;
96 | // return new Size((int)screenWidth, (int)screenHeight);
97 | // }
98 |
99 | // }
100 |
101 | // ///
102 | // /// Represents the different types of scaling.
103 | // ///
104 | // ///
105 | // public enum DpiType
106 | // {
107 | // EFFECTIVE = 0,
108 | // ANGULAR = 1,
109 | // RAW = 2,
110 | // }
111 |
112 | //}
113 |
--------------------------------------------------------------------------------
/Videocall/Services/ScreenShare/SimpleScreenShareHandler.cs:
--------------------------------------------------------------------------------
1 | //using NetworkLibrary;
2 | //using OpenCvSharp;
3 | //using OpenCvSharp.Extensions;
4 | //using System;
5 | //using System.Collections.Generic;
6 | //using System.Drawing;
7 | //using System.Linq;
8 | //using System.Runtime.InteropServices;
9 | //using System.Text;
10 | //using System.Threading.Tasks;
11 | //using System.Windows;
12 | //using Windows.Storage.Compression;
13 |
14 | //namespace Videocall.Services.ScreenShare
15 | //{
16 | // internal class SimpleScreenShareHandler
17 | // {
18 | // [StructLayout(LayoutKind.Sequential)]
19 | // struct CursorInfo
20 | // {
21 | // public Int32 cbSize;
22 | // public Int32 flags;
23 | // public IntPtr hCursor;
24 | // public PointApi ptScreenPos;
25 | // }
26 |
27 | // [StructLayout(LayoutKind.Sequential)]
28 | // struct PointApi
29 | // {
30 | // public int x;
31 | // public int y;
32 | // }
33 |
34 | // [DllImport("user32.dll")]
35 | // static extern bool GetCursorInfo(out CursorInfo pci);
36 |
37 | // [DllImport("user32.dll")]
38 | // static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
39 |
40 | // const Int32 cursorVisible = 0x00000001;
41 |
42 |
43 | // public Action LocalImageAvailable;
44 | // public Action RemoteImageAvailable;
45 | // private bool captureActive = false;
46 | // public void StartCapture()
47 | // {
48 | // if (captureActive) return;
49 | // captureActive = true;
50 | // var res = ScreenInformations.GetDisplayResolution();
51 |
52 | // double screenLeft = SystemParameters.VirtualScreenLeft;
53 | // double screenTop = SystemParameters.VirtualScreenTop;
54 | // double screenWidth = res.Width;//3840; //SystemParameters.FullPrimaryScreenWidth;
55 | // double screenHeight = res.Height;//2400; // SystemParameters.FullPrimaryScreenHeight;
56 | // Bitmap bmp = new Bitmap((int)screenWidth, (int)screenHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
57 |
58 |
59 | // ImageEncodingParam[] par = new ImageEncodingParam[1];
60 | // par[0] = new ImageEncodingParam(ImwriteFlags.WebPQuality, 80);
61 | // //par[1] = new ImageEncodingParam(ImwriteFlags.JpegOptimize, 1);
62 | // //par[2] = new ImageEncodingParam(ImwriteFlags.JpegProgressive, 1);
63 | // Task secondary = null;
64 | // Task.Run(async () =>
65 | // {
66 | // while (captureActive)
67 | // {
68 | // // await Task.Delay(30);
69 | // Graphics g = Graphics.FromImage(bmp);
70 | // g.CopyFromScreen((int)screenLeft, (int)screenTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
71 |
72 | // CursorInfo pci;
73 | // pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CursorInfo));
74 |
75 | // if (GetCursorInfo(out pci))
76 | // {
77 | // if (pci.flags == cursorVisible)
78 | // {
79 | // DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
80 | // g.ReleaseHdc();
81 | // }
82 | // }
83 |
84 | // Mat mat = null;
85 | // if (screenWidth > 1920)
86 | // {
87 | // var resizedBmp = new Bitmap(bmp, (int)1440, (int)810);
88 | // mat = resizedBmp.ToMat();
89 | // }
90 | // else
91 | // mat=bmp.ToMat();
92 |
93 | // //var compressed = mat.ImEncode(ext: ".webp", par);
94 | // //Console.WriteLine(compressed.Length);
95 | // //LocalImageAvailable?.Invoke(compressed, mat);
96 |
97 | // if (secondary != null)
98 | // await secondary;
99 | // secondary = Task.Run(() =>
100 | // {
101 | // var compressed = mat.ImEncode(ext: ".webp", par);
102 | // Console.WriteLine(compressed.Length);
103 | // LocalImageAvailable?.Invoke(compressed, mat);
104 | // });
105 |
106 | // }
107 |
108 | // });
109 | // }
110 |
111 | // public void HandleNetworkImageBytes(byte[] payload, int payloadOffset, int payloadCount)
112 | // {
113 | // var buff = BufferPool.RentBuffer(payloadCount);
114 | // Buffer.BlockCopy(payload, payloadOffset, buff, 0, payloadCount);
115 | // Mat img = new Mat(NativeMethods.imgcodecs_imdecode_vector(buff, new IntPtr(payloadCount), (int)ImreadModes.Color));
116 | // RemoteImageAvailable?.Invoke(img);
117 | // BufferPool.ReturnBuffer(buff);
118 | // }
119 |
120 | // internal void StopCapture()
121 | // {
122 | // captureActive= false;
123 | // }
124 | // }
125 | //}
126 |
--------------------------------------------------------------------------------
/Videocall/Services/ServiceHub.cs:
--------------------------------------------------------------------------------
1 | //using NetworkLibrary;
2 | //using Protobuff;
3 | //using System;
4 | //using System.Collections.Generic;
5 | //using System.Linq;
6 | //using System.Text;
7 | //using System.Threading;
8 | //using System.Threading.Tasks;
9 | //using Videocall.Services.Latency;
10 | //using Videocall.Services.ScreenShare;
11 | //using Videocall.Services.Video.H264;
12 |
13 | //namespace Videocall
14 | //{
15 | // internal class ServiceHub
16 | // {
17 |
18 | // private static ServiceHub instance;
19 | // public static ServiceHub Instance
20 | // {
21 | // get
22 | // {
23 | // if(instance == null)
24 | // instance = new ServiceHub();
25 | // return instance;
26 | // }
27 | // }
28 | // public AudioHandler AudioHandler { get;}
29 | // public VideoHandler2 VideoHandler { get;}
30 |
31 | // public MessageHandler MessageHandler { get;}
32 |
33 | // public FileShare FileShare { get;}
34 |
35 | // public LatencyPublisher LatencyPublisher { get; }
36 | // public ScreenShareHandlerH264 ScreenShareHandler { get; }
37 |
38 | // public Action VideoStatisticsAvailable;
39 | // public Action CamSizeFeedbackAvailable;
40 | // private VCStatistics stats;
41 | // private VCStatistics statsPrev;
42 |
43 | // private ServiceHub()
44 | // {
45 | // AudioHandler = new AudioHandler();
46 | // VideoHandler = new VideoHandler2();
47 | // FileShare = new FileShare();
48 | // MessageHandler = new MessageHandler();
49 | // LatencyPublisher = new LatencyPublisher(MessageHandler);
50 | // ScreenShareHandler = new ScreenShareHandlerH264();
51 |
52 | // AudioHandler.StartSpeakers();
53 | // MessageHandler.OnMessageAvailable += HandleMessage;
54 | // CallStateManager.Instance.StaticPropertyChanged += CallStateChanged;
55 | // AudioHandler.OnStatisticsAvailable += OnAudioStatsAvailable;
56 | // VideoHandler.CamSizeFeedbackAvailable = (w, h) => CamSizeFeedbackAvailable?.Invoke(w, h);
57 | // PublishStatistics();
58 | // }
59 |
60 | // private void PublishStatistics()
61 | // {
62 | // Task.Run(async () =>
63 | // {
64 | // while (true)
65 | // {
66 | // await Task.Delay(1000);// dont change time
67 | // var vs = VideoHandler.GetStatistics();
68 | // var scs = ScreenShareHandler.GetStatistics();
69 |
70 | // stats.OutgoingFrameRate = vs.OutgoingFrameRate + scs.OutgoingFrameRate;
71 | // stats.IncomingFrameRate = vs.IncomingFrameRate + scs.IncomingFrameRate;
72 | // stats.TransferRate = scs.TransferRate + vs.TransferRate;
73 | // stats.AverageLatency = vs.AverageLatency;
74 | // stats.ReceiveRate = vs.ReceiveRate + scs.ReceiveRate;
75 | // stats.CurrentMaxBitRate = vs.CurrentMaxBitRate;
76 |
77 | // if(statsPrev != stats)
78 | // {
79 | // statsPrev = stats;
80 | // VideoStatisticsAvailable?.Invoke(stats);
81 | // }
82 | // }
83 |
84 | // });
85 | // }
86 |
87 | // private void HandleMessage(MessageEnvelope message)
88 | // {
89 | // if(message.Header == MessageHeaders.MicClosed)
90 | // {
91 | // AudioHandler.FlushBuffers();
92 | // }
93 | // else if (message.Header == MessageHeaders.RemoteClosedCam)
94 | // {
95 | // VideoHandler.FlushBuffers();
96 | // }
97 | // }
98 |
99 | // private void OnAudioStatsAvailable(AudioStatistics stats)
100 | // {
101 | // // you can mode it as prop
102 | // VideoHandler.AudioBufferLatency = AudioHandler.BufferedDurationAvg;
103 | // }
104 |
105 | // private void CallStateChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
106 | // {
107 | // var currState = CallStateManager.GetState();
108 | // if (currState == CallStateManager.CallState.OnCall ||
109 | // currState == CallStateManager.CallState.Available)
110 | // {
111 | // AudioHandler.ResetStatistics();
112 | // AudioHandler.FlushBuffers();
113 | // VideoHandler.FlushBuffers();
114 | // }
115 | // }
116 | // }
117 | //}
118 |
--------------------------------------------------------------------------------
/Videocall/Services/Video/H264/H264TranscoderProvider.cs:
--------------------------------------------------------------------------------
1 | //using System;
2 | //using System.Collections.Generic;
3 | //using System.Drawing;
4 | //using System.IO;
5 | //using System.Linq;
6 | //using System.Text;
7 | //using System.Threading;
8 | //using System.Threading.Tasks;
9 | //using static H264Sharp.Encoder;
10 | //using H264Sharp;
11 | //namespace Videocall.Services.Video.H264
12 | //{
13 | // internal class H264TranscoderProvider
14 | // {
15 | // private const string DllName64 = "openh264-2.3.1-win64.dll";
16 | // private const string DllName32 = "openh264-2.3.1-win32.dll";
17 |
18 | // public static H264Sharp.Encoder CreateEncoder(int width, int height,
19 | // int fps = 30, int bps = 3_000_000, ConfigType configNo=ConfigType.CameraBasic)
20 | // {
21 | // string ddlName = Environment.Is64BitProcess ? DllName64 : DllName32;
22 |
23 | // H264Sharp.Encoder encoder = new H264Sharp.Encoder(ddlName);
24 | // encoder.Initialize(width, height, bps, fps, configNo );
25 |
26 | // return encoder;
27 | // }
28 |
29 | // public static H264Sharp.Decoder CreateDecoder()
30 | // {
31 | // string ddlName = Environment.Is64BitProcess ? DllName64 : DllName32;
32 | // H264Sharp.Decoder decoder = new H264Sharp.Decoder(ddlName);
33 | // return decoder;
34 | // }
35 |
36 |
37 | // }
38 | //}
39 |
--------------------------------------------------------------------------------
/Videocall/Services/Video/H264/JitterBufffer.cs:
--------------------------------------------------------------------------------
1 | //using NetworkLibrary;
2 | //using NetworkLibrary.Utils;
3 | //using ProtoBuf.WellKnownTypes;
4 | //using System;
5 | //using System.Collections.Concurrent;
6 | //using System.Collections.Generic;
7 | //using System.Diagnostics;
8 | //using System.Linq;
9 | //using System.Text;
10 | //using System.Threading.Tasks;
11 |
12 | //namespace Videocall.Services.Video.H264
13 | //{
14 | // class Frame
15 | // {
16 | // public DateTime TimeStamp;
17 | // public byte[] Data;
18 | // public int Offset;
19 | // public int Count;
20 | // }
21 | // internal class JitterBufffer
22 | // {
23 | // public Action FrameAvailable;
24 | // public double Duration => ((latestTs - oldestTs).TotalMilliseconds);
25 | // public int MaxNumberOfFramesBuffered = 5;
26 |
27 | // private ConcurrentDictionary reorderBuffer = new ConcurrentDictionary();
28 | // private DateTime lastStamp = DateTime.Now;
29 | // private DateTime latestTs = DateTime.Now;
30 | // private DateTime oldestTs = DateTime.Now;
31 | // private DateTime lastIn = DateTime.Now;
32 |
33 | // private ushort prevSqn;
34 | // private readonly object locker = new object();
35 | // private Stopwatch sw = new Stopwatch();
36 | // private int incomingFrameCount = 0;
37 |
38 | // public void HandleFrame(DateTime timeStamp, ushort currentSqn, byte[] payload, int payloadOffset, int payloadCount)
39 | // {
40 | // if (latestTs < timeStamp)
41 | // {
42 | // latestTs = timeStamp;
43 | // }
44 | // lock (locker)
45 | // {
46 | // if (!sw.IsRunning)
47 | // {
48 | // sw.Start();
49 | // }
50 | // incomingFrameCount++;
51 | // if (sw.ElapsedMilliseconds > 1000)
52 | // {
53 | // MaxNumberOfFramesBuffered = Math.Max(2, (incomingFrameCount / 4));//250ms
54 | // incomingFrameCount = 0;
55 | // sw.Restart();
56 | // }
57 |
58 |
59 | // var buffer = BufferPool.RentBuffer(payloadCount);
60 | // ByteCopy.BlockCopy(payload, payloadOffset, buffer, 0, payloadCount);
61 |
62 | // Frame f = new Frame { Data = buffer, Count = payloadCount, TimeStamp = timeStamp };
63 | // reorderBuffer.TryAdd(currentSqn, f);
64 | // var now = DateTime.Now;
65 |
66 | // if((now-lastIn).TotalMilliseconds<15 && currentSqn != prevSqn + 1 && reorderBuffer.Count > MaxNumberOfFramesBuffered)
67 | // {
68 | // //Console.WriteLine("-- Video Buff Forced");
69 |
70 | // lastIn = now;
71 | // return;
72 | // }
73 | // lastIn = now;
74 | // while (currentSqn == prevSqn + 1 || reorderBuffer.Count > MaxNumberOfFramesBuffered)
75 | // {
76 | // var key = reorderBuffer.Keys.Min();
77 |
78 | // if (reorderBuffer.TryRemove(key, out Frame ff))
79 | // {
80 | // oldestTs = ff.TimeStamp;
81 | // FrameAvailable?.Invoke(ff);
82 | // BufferPool.ReturnBuffer(ff.Data);
83 | // }
84 | // lastStamp = timeStamp;
85 | // prevSqn = key;
86 |
87 | // var next = (ushort)(prevSqn + 1);
88 | // if (reorderBuffer.ContainsKey(next))
89 | // {
90 | // currentSqn= next;
91 | // }
92 |
93 | // }
94 | // //Console.WriteLine("Dur " + Duration);
95 | // //Console.WriteLine("CCC " + reorderBuffer.Count);
96 | // }
97 | // }
98 |
99 | // public void Discard()
100 | // {
101 | // reorderBuffer.Clear();
102 | // oldestTs = lastStamp;
103 | // }
104 | // public void Reset()
105 | // {
106 | // prevSqn = 0;
107 | // reorderBuffer.Clear();
108 | // oldestTs = lastStamp;
109 |
110 | // }
111 | // }
112 | //}
113 |
--------------------------------------------------------------------------------
/Videocall/UIHelpers/PropertyNotifyBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Videocall
10 | {
11 | public class PropertyNotifyBase : INotifyPropertyChanged
12 | {
13 | public event PropertyChangedEventHandler PropertyChanged;
14 | public virtual void OnPropertyChanged([CallerMemberName] string name = null)
15 | {
16 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Videocall/UIHelpers/RelayCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Input;
7 |
8 | namespace Videocall
9 | {
10 | public class RelayCommand : ICommand
11 | {
12 | private readonly Action