├── .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 execute; 13 | private readonly Func canExecute; 14 | 15 | public event EventHandler CanExecuteChanged 16 | { 17 | add { CommandManager.RequerySuggested += value; } 18 | remove { CommandManager.RequerySuggested -= value; } 19 | } 20 | 21 | public RelayCommand(Action execute, Func canExecute = null) 22 | { 23 | this.execute = execute; 24 | this.canExecute = canExecute; 25 | } 26 | 27 | public bool CanExecute(object parameter) 28 | { 29 | return this.canExecute == null || this.canExecute(parameter); 30 | } 31 | 32 | public void Execute(object parameter) 33 | { 34 | this.execute(parameter); 35 | } 36 | } 37 | public class RelayCommand : ICommand 38 | { 39 | #region Fields 40 | 41 | readonly Action _execute = null; 42 | readonly Predicate _canExecute = null; 43 | 44 | #endregion 45 | 46 | #region Constructors 47 | 48 | /// 49 | /// Initializes a new instance of . 50 | /// 51 | /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate. 52 | /// will always return true. 53 | public RelayCommand(Action execute) 54 | : this(execute, null) 55 | { 56 | } 57 | 58 | /// 59 | /// Creates a new command. 60 | /// 61 | /// The execution logic. 62 | /// The execution status logic. 63 | public RelayCommand(Action execute, Predicate canExecute) 64 | { 65 | if (execute == null) 66 | throw new ArgumentNullException("execute"); 67 | 68 | _execute = execute; 69 | _canExecute = canExecute; 70 | } 71 | 72 | #endregion 73 | 74 | #region ICommand Members 75 | 76 | /// 77 | ///Defines the method that determines whether the command can execute in its current state. 78 | /// 79 | ///Data used by the command. If the command does not require data to be passed, this object can be set to null. 80 | /// 81 | ///true if this command can be executed; otherwise, false. 82 | /// 83 | public bool CanExecute(object parameter) 84 | { 85 | return _canExecute == null || _canExecute((T)parameter); 86 | } 87 | 88 | /// 89 | ///Occurs when changes occur that affect whether or not the command should execute. 90 | /// 91 | public event EventHandler CanExecuteChanged 92 | { 93 | add { CommandManager.RequerySuggested += value; } 94 | remove { CommandManager.RequerySuggested -= value; } 95 | } 96 | 97 | /// 98 | ///Defines the method to be called when the command is invoked. 99 | /// 100 | ///Data used by the command. If the command does not require data to be passed, this object can be set to . 101 | public void Execute(object parameter) 102 | { 103 | _execute((T)parameter); 104 | } 105 | 106 | #endregion 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /Videocall/UserControls/SoundVisualizer.xaml.cs: -------------------------------------------------------------------------------- 1 | using ServiceProvider.Services.Audio; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Navigation; 16 | using System.Windows.Shapes; 17 | 18 | namespace Videocall.UserControls 19 | { 20 | 21 | //public class SoundSliceData 22 | //{ 23 | 24 | // public SoundSliceData() { } 25 | // public SoundSliceData(float[] sums) 26 | // { 27 | // V1 = (int)sums[0]; 28 | // V2 = (int)sums[1]; 29 | // V3 = (int)sums[2]; 30 | // V4 = (int)sums[3]; 31 | // V5 = (int)sums[4]; 32 | // V6 = (int)sums[5]; 33 | // V7 = (int)sums[6]; 34 | // V8 = (int)sums[7]; 35 | // V9 = (int)sums[8]; 36 | // V10 = (int)sums[9]; 37 | // V11 = (int)sums[10]; 38 | // V12 = (int)sums[11]; 39 | // V13 = (int)sums[12]; 40 | // V14 = (int)sums[13]; 41 | // V15 = (int)sums[14]; 42 | // V16 = (int)sums[15]; 43 | // V17 = (int)sums[16]; 44 | // V18 = (int)sums[17]; 45 | // V19 = (int)sums[18]; 46 | // V20 = (int)sums[19]; 47 | // } 48 | 49 | // public int V1 { get; } 50 | // public int V2 { get; } 51 | // public int V3 { get; } 52 | // public int V4 { get; } 53 | // public int V5 { get; } 54 | // public int V6 { get; } 55 | // public int V7 { get; } 56 | // public int V8 { get; } 57 | // public int V9 { get; } 58 | // public int V10 { get; } 59 | // public int V11 { get; } 60 | // public int V12 { get; } 61 | // public int V13 { get; } 62 | // public int V14 { get; } 63 | // public int V15 { get; } 64 | // public int V16 { get; } 65 | // public int V17 { get; } 66 | // public int V18 { get; } 67 | // public int V19 { get; } 68 | // public int V20 { get; } 69 | //} 70 | 71 | // Itemsource binding is too darn slow.. 72 | 73 | //public struct IntBox 74 | //{ 75 | // public int Val 76 | // { 77 | // get; set; 78 | // } 79 | //} 80 | //public class SoundSliceData : PropertyNotifyBase 81 | //{ 82 | // List soundValues = new List() { new IntBox { Val = 55 } }; 83 | // public List SoundValues 84 | // { 85 | // get => soundValues; set { soundValues = value; OnPropertyChanged(); } 86 | // } 87 | // public SoundSliceData() 88 | // { 89 | // } 90 | 91 | // public SoundSliceData(double[] sums) 92 | // { 93 | // var sv = new List(); 94 | // foreach (var item in sums) 95 | // { 96 | // sv.Add(new IntBox { Val = (int)item }); 97 | // } 98 | // SoundValues = sv; 99 | 100 | // } 101 | 102 | //} 103 | 104 | public partial class SoundVisualizer : UserControl 105 | { 106 | public static readonly DependencyProperty SlideDataProp = DependencyProperty.Register("Data", typeof(SoundSliceData), typeof(SoundVisualizer), new FrameworkPropertyMetadata(new SoundSliceData())); 107 | 108 | public SoundSliceData Data 109 | { 110 | get { return (SoundSliceData)GetValue(SlideDataProp); } 111 | set { SetValue(SlideDataProp, value); } 112 | } 113 | 114 | 115 | public SoundVisualizer() 116 | { 117 | DataContext = this; 118 | InitializeComponent(); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /Videocall/Videocall.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows10.0.17763.0 4 | WinExe 5 | false 6 | C:\Users\dcano\Desktop\x64\ 7 | true 8 | Disk 9 | false 10 | Foreground 11 | 7 12 | Days 13 | false 14 | false 15 | true 16 | true 17 | 0 18 | 1.0.0.0 19 | false 20 | true 21 | true 22 | false 23 | False 24 | true 25 | true 26 | 27 | 28 | true 29 | 30 | 31 | x64 32 | true 33 | 34 | 35 | bin\x86\Debug\ 36 | true 37 | 38 | 39 | bin\x86\Release\ 40 | true 41 | 42 | 43 | Resources\favicon2.ico 44 | 45 | 46 | FB75839D94FE0B2591767C511B0A7D3FA3CEFB07 47 | 48 | 49 | Videocall_TemporaryKey.pfx 50 | 51 | 52 | true 53 | 54 | 55 | false 56 | 10.0.17763.0 57 | 58 | 59 | 60 | PreserveNewest 61 | 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 2.2.1 79 | 80 | 81 | 2.2.1 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 1.9.0 90 | 91 | 92 | 3.2.30 93 | 94 | 95 | 4.2.0 96 | 97 | 98 | 4.2.0 99 | 100 | 101 | 4.2.0 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | False 110 | Microsoft .NET Framework 4.7.2 %28x86 and x64%29 111 | true 112 | 113 | 114 | False 115 | .NET Framework 3.5 SP1 116 | false 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | Always 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | ..\..\NetworkExperiments\Protobuff\bin\Release\net6.0\NetworkLibrary.dll 148 | 149 | 150 | ..\..\NetworkExperiments\Protobuff\bin\Release\net6.0\Protobuff.dll 151 | 152 | 153 | -------------------------------------------------------------------------------- /Videocall/Windows/AlertWindow.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 |