├── .gitattributes ├── .gitignore ├── HttpWebcamLiveStream.sln ├── HttpWebcamLiveStream ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── 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 ├── Configuration │ ├── ConfigurationFile.cs │ └── ConfigurationFileHelper.cs ├── Devices │ └── Camera.cs ├── Helper │ ├── DispatcherHelper.cs │ └── TaskHelper.cs ├── HttpWebcamLiveStream.csproj ├── HttpWebcamLiveStream.pfx ├── MainPage.xaml ├── MainPage.xaml.cs ├── Package.appxmanifest ├── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml └── Web │ ├── Html │ └── Index.html │ ├── HttpContentType.cs │ ├── HttpServer.cs │ ├── HttpServerRequest.cs │ ├── HttpServerResponse.cs │ ├── HttpStatusCode.cs │ ├── JavaScript │ ├── Camera.js │ ├── Controls.js │ ├── Fullscreen.js │ ├── VideoSetting.js │ └── WebSocketHelper.js │ ├── Styles │ └── Index.css │ └── WebSocket.cs ├── LICENSE └── README.md /.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 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.publishsettings 192 | node_modules/ 193 | orleans.codegen.cs 194 | 195 | # Since there are multiple workflows, uncomment next line to ignore bower_components 196 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 197 | #bower_components/ 198 | 199 | # RIA/Silverlight projects 200 | Generated_Code/ 201 | 202 | # Backup & report files from converting an old project file 203 | # to a newer Visual Studio version. Backup files are not needed, 204 | # because we have git ;-) 205 | _UpgradeReport_Files/ 206 | Backup*/ 207 | UpgradeLog*.XML 208 | UpgradeLog*.htm 209 | 210 | # SQL Server files 211 | *.mdf 212 | *.ldf 213 | 214 | # Business Intelligence projects 215 | *.rdl.data 216 | *.bim.layout 217 | *.bim_*.settings 218 | 219 | # Microsoft Fakes 220 | FakesAssemblies/ 221 | 222 | # GhostDoc plugin setting file 223 | *.GhostDoc.xml 224 | 225 | # Node.js Tools for Visual Studio 226 | .ntvs_analysis.dat 227 | 228 | # Visual Studio 6 build log 229 | *.plg 230 | 231 | # Visual Studio 6 workspace options file 232 | *.opt 233 | 234 | # Visual Studio LightSwitch build output 235 | **/*.HTMLClient/GeneratedArtifacts 236 | **/*.DesktopClient/GeneratedArtifacts 237 | **/*.DesktopClient/ModelManifest.xml 238 | **/*.Server/GeneratedArtifacts 239 | **/*.Server/ModelManifest.xml 240 | _Pvt_Extensions 241 | 242 | # Paket dependency manager 243 | .paket/paket.exe 244 | paket-files/ 245 | 246 | # FAKE - F# Make 247 | .fake/ 248 | 249 | # JetBrains Rider 250 | .idea/ 251 | *.sln.iml -------------------------------------------------------------------------------- /HttpWebcamLiveStream.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpWebcamLiveStream", "HttpWebcamLiveStream\HttpWebcamLiveStream.csproj", "{DC8641B5-1EBD-48F3-B324-FF3E47045F3E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM = Debug|ARM 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM = Release|ARM 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|ARM.ActiveCfg = Debug|ARM 19 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|ARM.Build.0 = Debug|ARM 20 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|ARM.Deploy.0 = Debug|ARM 21 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x64.ActiveCfg = Debug|x64 22 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x64.Build.0 = Debug|x64 23 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x64.Deploy.0 = Debug|x64 24 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x86.ActiveCfg = Debug|x86 25 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x86.Build.0 = Debug|x86 26 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Debug|x86.Deploy.0 = Debug|x86 27 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|ARM.ActiveCfg = Release|ARM 28 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|ARM.Build.0 = Release|ARM 29 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|ARM.Deploy.0 = Release|ARM 30 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x64.ActiveCfg = Release|x64 31 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x64.Build.0 = Release|x64 32 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x64.Deploy.0 = Release|x64 33 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x86.ActiveCfg = Release|x86 34 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x86.Build.0 = Release|x86 35 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E}.Release|x86.Deploy.0 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices.WindowsRuntime; 6 | using Windows.ApplicationModel; 7 | using Windows.ApplicationModel.Activation; 8 | using Windows.Foundation; 9 | using Windows.Foundation.Collections; 10 | using Windows.UI.Xaml; 11 | using Windows.UI.Xaml.Controls; 12 | using Windows.UI.Xaml.Controls.Primitives; 13 | using Windows.UI.Xaml.Data; 14 | using Windows.UI.Xaml.Input; 15 | using Windows.UI.Xaml.Media; 16 | using Windows.UI.Xaml.Navigation; 17 | 18 | namespace HttpWebcamLiveStream 19 | { 20 | /// 21 | /// Provides application-specific behavior to supplement the default Application class. 22 | /// 23 | sealed partial class App : Application 24 | { 25 | /// 26 | /// Initializes the singleton application object. This is the first line of authored code 27 | /// executed, and as such is the logical equivalent of main() or WinMain(). 28 | /// 29 | public App() 30 | { 31 | this.InitializeComponent(); 32 | this.Suspending += OnSuspending; 33 | } 34 | 35 | /// 36 | /// Invoked when the application is launched normally by the end user. Other entry points 37 | /// will be used such as when the application is launched to open a specific file. 38 | /// 39 | /// Details about the launch request and process. 40 | protected override void OnLaunched(LaunchActivatedEventArgs e) 41 | { 42 | #if DEBUG 43 | if (System.Diagnostics.Debugger.IsAttached) 44 | { 45 | this.DebugSettings.EnableFrameRateCounter = true; 46 | } 47 | #endif 48 | Frame rootFrame = Window.Current.Content as Frame; 49 | 50 | // Do not repeat app initialization when the Window already has content, 51 | // just ensure that the window is active 52 | if (rootFrame == null) 53 | { 54 | // Create a Frame to act as the navigation context and navigate to the first page 55 | rootFrame = new Frame(); 56 | 57 | rootFrame.NavigationFailed += OnNavigationFailed; 58 | 59 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 60 | { 61 | //TODO: Load state from previously suspended application 62 | } 63 | 64 | // Place the frame in the current Window 65 | Window.Current.Content = rootFrame; 66 | } 67 | 68 | if (e.PrelaunchActivated == false) 69 | { 70 | if (rootFrame.Content == null) 71 | { 72 | // When the navigation stack isn't restored navigate to the first page, 73 | // configuring the new page by passing required information as a navigation 74 | // parameter 75 | rootFrame.Navigate(typeof(MainPage), e.Arguments); 76 | } 77 | // Ensure the current window is active 78 | Window.Current.Activate(); 79 | } 80 | } 81 | 82 | /// 83 | /// Invoked when Navigation to a certain page fails 84 | /// 85 | /// The Frame which failed navigation 86 | /// Details about the navigation failure 87 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e) 88 | { 89 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 90 | } 91 | 92 | /// 93 | /// Invoked when application execution is being suspended. Application state is saved 94 | /// without knowing whether the application will be terminated or resumed with the contents 95 | /// of memory still intact. 96 | /// 97 | /// The source of the suspend request. 98 | /// Details about the suspend request. 99 | private void OnSuspending(object sender, SuspendingEventArgs e) 100 | { 101 | var deferral = e.SuspendingOperation.GetDeferral(); 102 | //TODO: Save application state and stop any background activity 103 | deferral.Complete(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/StoreLogo.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Configuration/ConfigurationFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Windows.Data.Json; 6 | using Windows.Graphics.Imaging; 7 | using Windows.Media.Capture.Frames; 8 | using Windows.Storage; 9 | 10 | namespace HttpWebcamLiveStream.Configuration 11 | { 12 | public static class ConfigurationFile 13 | { 14 | const string CONFIGURATION_FILE_NAME = "ConfigurationFile.txt"; 15 | 16 | public static JsonArray VideoSettingsSupported { get; private set; } 17 | public static JsonObject VideoSetting { get; set; } 18 | 19 | public static void SetSupportedVideoFrameFormats(List mediaFrameFormats) 20 | { 21 | VideoSettingsSupported = new JsonArray(); 22 | 23 | var videoSettings = new List(); 24 | 25 | for (int videoSubTypeId = 0; videoSubTypeId <= 2; videoSubTypeId++) 26 | { 27 | var videoSubType = (VideoSubtype)videoSubTypeId; 28 | 29 | for (int videoResolutionId = 0; videoResolutionId <= 4; videoResolutionId++) 30 | { 31 | var videoResolution = (VideoResolution)videoResolutionId; 32 | var videoResolutionWidthHeight = VideoResolutionWidthHeight.Get(videoResolution); 33 | 34 | var mediaFrameFormat = mediaFrameFormats.FirstOrDefault(m => m.Subtype == VideoSubtypeHelper.Get(videoSubType) 35 | && m.VideoFormat.Width == videoResolutionWidthHeight.Width 36 | && m.VideoFormat.Height == videoResolutionWidthHeight.Height); 37 | 38 | if (mediaFrameFormat != null) 39 | { 40 | videoSettings.Add(new VideoSetting 41 | { 42 | VideoResolution = videoResolution, 43 | VideoSubtype = videoSubType 44 | }); 45 | } 46 | } 47 | } 48 | 49 | foreach (var videoSettingGrouped in videoSettings.GroupBy(v => v.VideoSubtype)) 50 | { 51 | var videoSubType = VideoSubtypeHelper.Get(videoSettingGrouped.Key); 52 | var videoSettingsSupported = new JsonArray(); 53 | 54 | foreach (var videoSetting in videoSettingGrouped) 55 | { 56 | videoSettingsSupported.Add(JsonValue.CreateNumberValue((int)videoSetting.VideoResolution)); 57 | } 58 | 59 | VideoSettingsSupported.Add(new JsonObject 60 | { 61 | { "VideoSubtype", JsonValue.CreateStringValue(videoSubType) }, 62 | { "VideoResolutions", videoSettingsSupported } 63 | }); 64 | } 65 | } 66 | 67 | public static async Task Write(VideoSetting videoSetting) 68 | { 69 | var localFolder = ApplicationData.Current.LocalFolder; 70 | var configurationFile = await localFolder.CreateFileAsync(CONFIGURATION_FILE_NAME, CreationCollisionOption.OpenIfExists); 71 | 72 | var configuration = new JsonObject 73 | { 74 | { "VideoResolution", JsonValue.CreateNumberValue((int)videoSetting.VideoResolution) }, 75 | { "VideoSubtype", JsonValue.CreateStringValue(VideoSubtypeHelper.Get(videoSetting.VideoSubtype)) }, 76 | { "VideoQuality", JsonValue.CreateNumberValue(videoSetting.VideoQuality) }, 77 | { "UsedThreads", JsonValue.CreateNumberValue(videoSetting.UsedThreads) }, 78 | { "Rotation", JsonValue.CreateNumberValue(RotationHelper.Get(videoSetting.Rotation)) } 79 | }; 80 | 81 | VideoSetting = configuration; 82 | 83 | await FileIO.WriteTextAsync(configurationFile, configuration.Stringify()); 84 | } 85 | 86 | public static async Task Read(List mediaFrameFormats) 87 | { 88 | VideoSetting videoSetting = null; 89 | JsonObject configuration = null; 90 | var configurationFileExists = await ApplicationData.Current.LocalFolder.TryGetItemAsync(CONFIGURATION_FILE_NAME); 91 | if(configurationFileExists != null) 92 | { 93 | var configurationFile = await ApplicationData.Current.LocalFolder.GetFileAsync(CONFIGURATION_FILE_NAME); 94 | configuration = JsonObject.Parse(await FileIO.ReadTextAsync(configurationFile)); 95 | } 96 | 97 | if (configuration != null) 98 | { 99 | var videoResolution = (VideoResolution)configuration["VideoResolution"].GetNumber(); 100 | var videoResolutionWidthHeight = VideoResolutionWidthHeight.Get(videoResolution); 101 | 102 | var videoSubType = configuration["VideoSubtype"].GetString(); 103 | var videoQuality = configuration["VideoQuality"].GetNumber(); 104 | var usedThreads = configuration["UsedThreads"].GetNumber(); 105 | var rotation = configuration["Rotation"].GetNumber(); 106 | 107 | var mediaFrameFormat = mediaFrameFormats.Where(m => m.Subtype == videoSubType 108 | && m.VideoFormat.Width == videoResolutionWidthHeight.Width 109 | && m.VideoFormat.Height == videoResolutionWidthHeight.Height) 110 | .OrderByDescending(m => m.FrameRate.Numerator / (decimal)m.FrameRate.Denominator) 111 | .FirstOrDefault(); 112 | if (mediaFrameFormat != null) 113 | { 114 | videoSetting = new VideoSetting 115 | { 116 | VideoResolution = videoResolution, 117 | VideoSubtype = VideoSubtypeHelper.Get(videoSubType), 118 | VideoQuality = videoQuality, 119 | UsedThreads = (int)usedThreads, 120 | Rotation = RotationHelper.Get((int)rotation) 121 | }; 122 | } 123 | } 124 | else 125 | { 126 | for (var videoSubType = 0; videoSubType <= 2; videoSubType++) 127 | { 128 | if (videoSetting != null) 129 | break; 130 | 131 | for (var videoResolutionId = 4; videoResolutionId >= 0; videoResolutionId--) 132 | { 133 | var videoResolutionLowWidthHeight = VideoResolutionWidthHeight.Get((VideoResolution)videoResolutionId); 134 | 135 | var mediaFrameFormat = mediaFrameFormats.Where(m => m.Subtype == VideoSubtypeHelper.Get((VideoSubtype)videoSubType) 136 | && m.VideoFormat.Width == videoResolutionLowWidthHeight.Width 137 | && m.VideoFormat.Height == videoResolutionLowWidthHeight.Height) 138 | .OrderByDescending(m => m.FrameRate.Numerator / m.FrameRate.Denominator) 139 | .FirstOrDefault(); 140 | 141 | if (mediaFrameFormat != null) 142 | { 143 | videoSetting = new VideoSetting 144 | { 145 | VideoResolution = VideoResolution.SD640_480, 146 | VideoSubtype = (VideoSubtype)videoSubType, 147 | VideoQuality = 0.6, 148 | UsedThreads = 0, 149 | Rotation = BitmapRotation.None 150 | }; 151 | 152 | break; 153 | } 154 | } 155 | } 156 | } 157 | 158 | if(videoSetting != null) 159 | { 160 | VideoSetting = new JsonObject 161 | { 162 | { "VideoResolution", JsonValue.CreateNumberValue((int)videoSetting.VideoResolution) }, 163 | { "VideoSubtype", JsonValue.CreateStringValue(VideoSubtypeHelper.Get(videoSetting.VideoSubtype)) }, 164 | { "VideoQuality", JsonValue.CreateNumberValue(videoSetting.VideoQuality) }, 165 | { "UsedThreads", JsonValue.CreateNumberValue(videoSetting.UsedThreads) }, 166 | { "Rotation", JsonValue.CreateNumberValue(RotationHelper.Get(videoSetting.Rotation)) } 167 | }; 168 | 169 | return videoSetting; 170 | } 171 | else 172 | { 173 | throw new Exception("Webcam not supported. Could not found correct video resolution and subtype. Please change code."); 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Configuration/ConfigurationFileHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Windows.Graphics.Imaging; 4 | 5 | namespace HttpWebcamLiveStream.Configuration 6 | { 7 | public enum VideoResolution 8 | { 9 | HD1080p = 0, 10 | HD720p = 1, 11 | SD1024_768 = 2, 12 | SD800_600 = 3, 13 | SD640_480 = 4 14 | } 15 | 16 | public enum VideoSubtype 17 | { 18 | YUY2 = 0, 19 | MJPG = 1, 20 | NV12 = 2 21 | } 22 | 23 | public class VideoSubtypeHelper 24 | { 25 | public static string Get(VideoSubtype videoSubType) 26 | { 27 | if (videoSubType == VideoSubtype.YUY2) 28 | { 29 | return "YUY2"; 30 | } 31 | else if (videoSubType == VideoSubtype.MJPG) 32 | { 33 | return "MJPG"; 34 | } 35 | else if (videoSubType == VideoSubtype.NV12) 36 | { 37 | return "NV12"; 38 | } 39 | 40 | throw new Exception("Video subtype not exists."); 41 | } 42 | 43 | public static VideoSubtype Get(string videoSubType) 44 | { 45 | if (videoSubType == "YUY2") 46 | { 47 | return VideoSubtype.YUY2; 48 | } 49 | else if (videoSubType == "MJPG") 50 | { 51 | return VideoSubtype.MJPG; 52 | } 53 | else if (videoSubType == "NV12") 54 | { 55 | return VideoSubtype.NV12; 56 | } 57 | 58 | throw new Exception("Video subtype not exists."); 59 | } 60 | } 61 | 62 | public class VideoResolutionWidthHeight 63 | { 64 | public int Width { get; set; } 65 | public int Height { get; set; } 66 | 67 | public static VideoResolutionWidthHeight Get(VideoResolution videoResolution) 68 | { 69 | var videoResolutionWidthHeight = new VideoResolutionWidthHeight(); 70 | 71 | if (videoResolution == VideoResolution.HD1080p) 72 | { 73 | videoResolutionWidthHeight.Width = 1920; 74 | videoResolutionWidthHeight.Height = 1080; 75 | } 76 | else if (videoResolution == VideoResolution.HD720p) 77 | { 78 | videoResolutionWidthHeight.Width = 1280; 79 | videoResolutionWidthHeight.Height = 720; 80 | } 81 | else if (videoResolution == VideoResolution.SD1024_768) 82 | { 83 | videoResolutionWidthHeight.Width = 1024; 84 | videoResolutionWidthHeight.Height = 768; 85 | } 86 | else if (videoResolution == VideoResolution.SD800_600) 87 | { 88 | videoResolutionWidthHeight.Width = 800; 89 | videoResolutionWidthHeight.Height = 600; 90 | } 91 | else if (videoResolution == VideoResolution.SD640_480) 92 | { 93 | videoResolutionWidthHeight.Width = 640; 94 | videoResolutionWidthHeight.Height = 480; 95 | } 96 | 97 | return videoResolutionWidthHeight; 98 | } 99 | } 100 | 101 | public static class RotationHelper 102 | { 103 | private const int NONE = 1; 104 | private const int CLOCKWISE_90_DEGREES = 2; 105 | private const int CLOCKWISE_180_DEGREES = 3; 106 | private const int CLOCKWISE_270_DEGREES = 4; 107 | 108 | public static int Get(BitmapRotation rotation) 109 | { 110 | if (rotation == BitmapRotation.None) 111 | { 112 | return NONE; 113 | } 114 | else if (rotation == BitmapRotation.Clockwise90Degrees) 115 | { 116 | return CLOCKWISE_90_DEGREES; 117 | } 118 | else if (rotation == BitmapRotation.Clockwise180Degrees) 119 | { 120 | return CLOCKWISE_180_DEGREES; 121 | } 122 | else if (rotation == BitmapRotation.Clockwise270Degrees) 123 | { 124 | return CLOCKWISE_270_DEGREES; 125 | } 126 | 127 | return NONE; 128 | } 129 | 130 | public static BitmapRotation Get(int rotation) 131 | { 132 | if (rotation == NONE) 133 | { 134 | return BitmapRotation.None; 135 | } 136 | else if (rotation == CLOCKWISE_90_DEGREES) 137 | { 138 | return BitmapRotation.Clockwise90Degrees; 139 | } 140 | else if (rotation == CLOCKWISE_180_DEGREES) 141 | { 142 | return BitmapRotation.Clockwise180Degrees; 143 | } 144 | else if (rotation == CLOCKWISE_270_DEGREES) 145 | { 146 | return BitmapRotation.Clockwise270Degrees; 147 | } 148 | 149 | return BitmapRotation.None; 150 | } 151 | } 152 | 153 | public class SupportetVideoResolution 154 | { 155 | public List VideoResolutions { get; set; } 156 | public string VideoSubtype { get; set; } 157 | } 158 | 159 | public class VideoSetting 160 | { 161 | public VideoResolution VideoResolution { get; set; } 162 | public VideoSubtype VideoSubtype { get; set; } 163 | public double VideoQuality { get; set; } 164 | public int UsedThreads { get; set; } 165 | public BitmapRotation Rotation { get; set; } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Devices/Camera.cs: -------------------------------------------------------------------------------- 1 | using HttpWebcamLiveStream.Configuration; 2 | using HttpWebcamLiveStream.Helper; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Runtime.InteropServices.WindowsRuntime; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Windows.ApplicationModel.Core; 12 | using Windows.Graphics.Imaging; 13 | using Windows.Media.Capture; 14 | using Windows.Media.Capture.Frames; 15 | using Windows.Storage.Streams; 16 | using Windows.UI.Core; 17 | 18 | namespace HttpWebcamLiveStream.Devices 19 | { 20 | /// 21 | /// Camera: ELP 2.8mm wide angle lens 1080p HD USB Camera Module (ELP-USBFHD01M-L28) 22 | /// 23 | public class Camera 24 | { 25 | public byte[] Frame { get; set; } 26 | 27 | private MediaCapture _mediaCapture; 28 | private MediaFrameReader _mediaFrameReader; 29 | private BitmapRotation _rotation = BitmapRotation.None; 30 | 31 | private BitmapPropertySet _imageQuality; 32 | 33 | private int _threadsCount = 0; 34 | private int _stoppedThreads = 0; 35 | private bool _stopThreads = false; 36 | 37 | private volatile Stopwatch _lastFrameAdded = new Stopwatch(); 38 | private volatile object _lastFrameAddedLock = new object(); 39 | 40 | public async Task Initialize(VideoSetting videoSetting) 41 | { 42 | await CoreApplication.MainView.CoreWindow.Dispatcher.RunAndAwaitAsync(CoreDispatcherPriority.Normal, async () => 43 | { 44 | _rotation = videoSetting.Rotation; 45 | _threadsCount = videoSetting.UsedThreads; 46 | _stoppedThreads = videoSetting.UsedThreads; 47 | 48 | _lastFrameAdded.Start(); 49 | 50 | _imageQuality = new BitmapPropertySet(); 51 | var imageQualityValue = new BitmapTypedValue(videoSetting.VideoQuality, Windows.Foundation.PropertyType.Single); 52 | _imageQuality.Add("ImageQuality", imageQualityValue); 53 | 54 | _mediaCapture = new MediaCapture(); 55 | 56 | var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync(); 57 | 58 | var settings = new MediaCaptureInitializationSettings() 59 | { 60 | //With CPU the results contain always SoftwareBitmaps, otherwise with GPU 61 | //they preferring D3DSurface 62 | MemoryPreference = MediaCaptureMemoryPreference.Auto, 63 | 64 | //Capture only video, no audio 65 | StreamingCaptureMode = StreamingCaptureMode.Video 66 | }; 67 | 68 | await _mediaCapture.InitializeAsync(settings); 69 | 70 | var mediaFrameSource = _mediaCapture.FrameSources.First().Value; 71 | var videoDeviceController = mediaFrameSource.Controller.VideoDeviceController; 72 | 73 | videoDeviceController.DesiredOptimization = Windows.Media.Devices.MediaCaptureOptimization.Quality; 74 | videoDeviceController.PrimaryUse = Windows.Media.Devices.CaptureUse.Video; 75 | 76 | //Set exposure (auto light adjustment) 77 | if (_mediaCapture.VideoDeviceController.Exposure.Capabilities.Supported 78 | && _mediaCapture.VideoDeviceController.Exposure.Capabilities.AutoModeSupported) 79 | { 80 | _mediaCapture.VideoDeviceController.Exposure.TrySetAuto(true); 81 | } 82 | 83 | var videoResolutionWidthHeight = VideoResolutionWidthHeight.Get(videoSetting.VideoResolution); 84 | var videoSubType = VideoSubtypeHelper.Get(videoSetting.VideoSubtype); 85 | 86 | //Set resolution, frame rate and video subtyp 87 | var videoFormat = mediaFrameSource.SupportedFormats.Where(sf => sf.VideoFormat.Width == videoResolutionWidthHeight.Width 88 | && sf.VideoFormat.Height == videoResolutionWidthHeight.Height 89 | && sf.Subtype == videoSubType) 90 | .OrderByDescending(m => m.FrameRate.Numerator / (decimal)m.FrameRate.Denominator) 91 | .First(); 92 | 93 | await mediaFrameSource.SetFormatAsync(videoFormat); 94 | 95 | _mediaFrameReader = await _mediaCapture.CreateFrameReaderAsync(mediaFrameSource); 96 | 97 | if (videoSetting.UsedThreads == 0) 98 | { 99 | _mediaFrameReader.FrameArrived += FrameArrived; 100 | } 101 | 102 | await _mediaFrameReader.StartAsync(); 103 | }); 104 | } 105 | 106 | private void ProcessFrame() 107 | { 108 | try 109 | { 110 | var frame = _mediaFrameReader.TryAcquireLatestFrame(); 111 | 112 | var frameDuration = new Stopwatch(); 113 | frameDuration.Start(); 114 | 115 | SoftwareBitmap frameBitmapTry = null; 116 | if (frame?.VideoMediaFrame?.Direct3DSurface != null) 117 | { 118 | frameBitmapTry = SoftwareBitmap.CreateCopyFromSurfaceAsync(frame.VideoMediaFrame.Direct3DSurface).AsTask().Result; 119 | } 120 | else if (frame?.VideoMediaFrame?.SoftwareBitmap != null) 121 | { 122 | frameBitmapTry = frame.VideoMediaFrame.SoftwareBitmap; 123 | } 124 | 125 | if (frameBitmapTry == null) 126 | return; 127 | 128 | using (var frameBitmap = frameBitmapTry) 129 | { 130 | using (var stream = new InMemoryRandomAccessStream()) 131 | { 132 | using (var bitmap = SoftwareBitmap.Convert(frameBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore)) 133 | { 134 | var imageTask = BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream, _imageQuality).AsTask(); 135 | imageTask.Wait(); 136 | var encoder = imageTask.Result; 137 | encoder.SetSoftwareBitmap(bitmap); 138 | 139 | if (_rotation != BitmapRotation.None) 140 | { 141 | var transform = encoder.BitmapTransform; 142 | transform.Rotation = _rotation; 143 | } 144 | 145 | var flushTask = encoder.FlushAsync().AsTask(); 146 | flushTask.Wait(); 147 | 148 | using (var asStream = stream.AsStream()) 149 | { 150 | asStream.Position = 0; 151 | 152 | var image = new byte[asStream.Length]; 153 | asStream.Read(image, 0, image.Length); 154 | 155 | lock (_lastFrameAddedLock) 156 | { 157 | if (_lastFrameAdded.Elapsed.Subtract(frameDuration.Elapsed) > TimeSpan.Zero) 158 | { 159 | Frame = image; 160 | 161 | _lastFrameAdded = frameDuration; 162 | } 163 | } 164 | 165 | encoder = null; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | catch (ObjectDisposedException) { } 172 | } 173 | 174 | private void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args) 175 | { 176 | ProcessFrame(); 177 | } 178 | 179 | private void ProcessFrames() 180 | { 181 | _stoppedThreads--; 182 | 183 | while (_stopThreads == false) 184 | { 185 | ProcessFrame(); 186 | } 187 | 188 | _stoppedThreads++; 189 | } 190 | 191 | public void Start() 192 | { 193 | if (_threadsCount > 0) 194 | { 195 | for (int workerNumber = 0; workerNumber < _threadsCount; workerNumber++) 196 | { 197 | var thread = new Thread(() => 198 | { 199 | ProcessFrames(); 200 | }); 201 | thread.Priority = ThreadPriority.Normal; 202 | thread.Start(); 203 | } 204 | } 205 | } 206 | 207 | public async Task StopAsync() 208 | { 209 | if (_threadsCount > 0) 210 | { 211 | _stopThreads = true; 212 | 213 | SpinWait.SpinUntil(() => { return _threadsCount == _stoppedThreads; }); 214 | 215 | _stopThreads = false; 216 | } 217 | else 218 | { 219 | _mediaFrameReader.FrameArrived -= FrameArrived; 220 | } 221 | 222 | await _mediaFrameReader.StopAsync(); 223 | } 224 | 225 | public async Task> GetMediaFrameFormatsAsync() 226 | { 227 | var mediaFrameFormats = new List(); 228 | 229 | await CoreApplication.MainView.CoreWindow.Dispatcher.RunAndAwaitAsync(CoreDispatcherPriority.Normal, async () => 230 | { 231 | var mediaCapture = new MediaCapture(); 232 | 233 | var settings = new MediaCaptureInitializationSettings() 234 | { 235 | MemoryPreference = MediaCaptureMemoryPreference.Auto, 236 | StreamingCaptureMode = StreamingCaptureMode.Video 237 | }; 238 | 239 | await mediaCapture.InitializeAsync(settings); 240 | 241 | var mediaFrameSource = mediaCapture.FrameSources.First().Value; 242 | 243 | mediaFrameFormats = mediaFrameSource.SupportedFormats.ToList(); 244 | 245 | mediaCapture.Dispose(); 246 | }); 247 | 248 | return mediaFrameFormats; 249 | } 250 | } 251 | } -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Helper/DispatcherHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Windows.UI.Core; 4 | 5 | namespace HttpWebcamLiveStream.Helper 6 | { 7 | public static class DispatcherHelper 8 | { 9 | public static async Task RunAndAwaitAsync(this CoreDispatcher dispatcher, CoreDispatcherPriority priority, Func asyncAction) 10 | { 11 | var taskCompletionSource = new TaskCompletionSource(); 12 | 13 | await dispatcher.RunAsync(priority, async () => 14 | { 15 | try 16 | { 17 | await asyncAction().ConfigureAwait(false); 18 | 19 | taskCompletionSource.TrySetResult(true); 20 | } 21 | catch (Exception ex) 22 | { 23 | taskCompletionSource.TrySetException(ex); 24 | } 25 | }); 26 | 27 | await taskCompletionSource.Task.ConfigureAwait(false); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Helper/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace HttpWebcamLiveStream.Helper 6 | { 7 | public static class TaskHelper 8 | { 9 | public static async Task WithTimeoutAfterStart(Func operation, TimeSpan timeout) 10 | { 11 | var source = new CancellationTokenSource(); 12 | var task = operation(source.Token); 13 | source.CancelAfter(timeout); 14 | await task; 15 | } 16 | 17 | public static async Task CancelTaskAfterTimeout(Func operation, TimeSpan timeout) 18 | { 19 | var source = new CancellationTokenSource(); 20 | var task = operation(source.Token); 21 | source.CancelAfter(timeout); 22 | await task; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/HttpWebcamLiveStream.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | {DC8641B5-1EBD-48F3-B324-FF3E47045F3E} 8 | AppContainerExe 9 | Properties 10 | HttpWebcamLiveStream 11 | HttpWebcamLiveStream 12 | en-US 13 | UAP 14 | 10.0.17763.0 15 | 10.0.17763.0 16 | 14 17 | 512 18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | C:\Entwicklung\HttpWebcamLiveStream\HttpWebcamLiveStream\HttpWebcamLiveStream.pfx 20 | win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot 21 | False 22 | True 23 | C38101D54C1DF70FB4AA4AF30272270EBBC5777B 24 | SHA256 25 | False 26 | True 27 | Always 28 | x86|x64|arm 29 | 0 30 | False 31 | 32 | 33 | true 34 | bin\x86\Debug\ 35 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 36 | ;2008 37 | full 38 | x86 39 | false 40 | prompt 41 | true 42 | 43 | 44 | bin\x86\Release\ 45 | TRACE;NETFX_CORE;WINDOWS_UWP 46 | true 47 | ;2008 48 | pdbonly 49 | x86 50 | false 51 | prompt 52 | true 53 | false 54 | 55 | 56 | true 57 | bin\ARM\Debug\ 58 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 59 | ;2008 60 | full 61 | ARM 62 | false 63 | prompt 64 | true 65 | 66 | 67 | bin\ARM\Release\ 68 | TRACE;NETFX_CORE;WINDOWS_UWP 69 | true 70 | ;2008 71 | pdbonly 72 | ARM 73 | false 74 | prompt 75 | true 76 | false 77 | 0 78 | 79 | 80 | true 81 | bin\x64\Debug\ 82 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 83 | ;2008 84 | full 85 | x64 86 | false 87 | prompt 88 | true 89 | 90 | 91 | bin\x64\Release\ 92 | TRACE;NETFX_CORE;WINDOWS_UWP 93 | true 94 | ;2008 95 | pdbonly 96 | x64 97 | false 98 | prompt 99 | true 100 | false 101 | 102 | 103 | 104 | App.xaml 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | MainPage.xaml 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Designer 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | MSBuild:Compile 147 | Designer 148 | 149 | 150 | MSBuild:Compile 151 | Designer 152 | 153 | 154 | 155 | 156 | 6.2.9 157 | 158 | 159 | 160 | 161 | 162 | 163 | 14.0 164 | 165 | 166 | 173 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/HttpWebcamLiveStream.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaschaIoT/HttpWebcamLiveStream/67ecb55d754f80b5b9a6170b9730d61397cebc99/HttpWebcamLiveStream/HttpWebcamLiveStream.pfx -------------------------------------------------------------------------------- /HttpWebcamLiveStream/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using HttpWebcamLiveStream.Configuration; 2 | using HttpWebcamLiveStream.Devices; 3 | using HttpWebcamLiveStream.Web; 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Controls; 6 | 7 | namespace HttpWebcamLiveStream 8 | { 9 | public sealed partial class MainPage : Page 10 | { 11 | public MainPage() 12 | { 13 | InitializeComponent(); 14 | 15 | Loaded += MainPage_Loaded; 16 | } 17 | 18 | private async void MainPage_Loaded(object sender, RoutedEventArgs eventArgs) 19 | { 20 | var camera = new Camera(); 21 | var mediaFrameFormats = await camera.GetMediaFrameFormatsAsync(); 22 | ConfigurationFile.SetSupportedVideoFrameFormats(mediaFrameFormats); 23 | var videoSetting = await ConfigurationFile.Read(mediaFrameFormats); 24 | 25 | await camera.Initialize(videoSetting); 26 | camera.Start(); 27 | 28 | var httpServer = new HttpServer(camera); 29 | httpServer.Start(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HttpWebcamLiveStream 7 | slehmann 8 | Assets\StoreLogo.png 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HttpWebcamLiveStream")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HttpWebcamLiveStream")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("1.0.0.0")] 28 | [assembly: AssemblyFileVersion("1.0.0.0")] 29 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Properties/Default.rd.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/Html/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HttpWebcamLiveStream 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Video resolution: 15 | 16 | 17 | 18 | 19 | 20 | Video subtype: 21 | 22 | 23 | 24 | 25 | 26 | Video quality: 27 | 28 | 29 | 10% 30 | 20% 31 | 30% 32 | 40% 33 | 50% 34 | 60% 35 | 70% 36 | 80% 37 | 90% 38 | 100% 39 | 40 | 41 | 42 | 43 | Event based/used threads: 44 | 45 | 46 | Event based (recommended) 47 | 1 thread 48 | 2 threads 49 | 3 threads 50 | 4 threads 51 | 52 | 53 | 54 | 55 | Rotation: 56 | 57 | 58 | None 59 | Clockwise 90 degrees 60 | Clockwise 180 degrees 61 | Clockwise 270 degrees 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/HttpContentType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HttpWebcamLiveStream.Web 4 | { 5 | public enum HttpContentType 6 | { 7 | Html, 8 | JavaScript, 9 | Css, 10 | Text, 11 | Json, 12 | Jpeg, 13 | Png 14 | } 15 | 16 | public static class MimeTypeHelper 17 | { 18 | public static string GetHttpContentType(HttpContentType httpContentType) 19 | { 20 | switch (httpContentType) 21 | { 22 | case HttpContentType.Html: 23 | return "text/html; charset=UTF-8"; 24 | case HttpContentType.JavaScript: 25 | return "text/javascript; charset=UTF-8"; 26 | case HttpContentType.Css: 27 | return "text/css; charset=UTF-8"; 28 | case HttpContentType.Text: 29 | return "text/plain; charset=UTF-8"; 30 | case HttpContentType.Json: 31 | return "application/json; charset=UTF-8"; 32 | case HttpContentType.Jpeg: 33 | return "image/jpeg"; 34 | case HttpContentType.Png: 35 | return "image/png"; 36 | default: 37 | throw new Exception($"Could not get mime type for http header for {httpContentType}"); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/HttpServer.cs: -------------------------------------------------------------------------------- 1 | using HttpWebcamLiveStream.Configuration; 2 | using HttpWebcamLiveStream.Devices; 3 | using HttpWebcamLiveStream.Helper; 4 | using System; 5 | using System.Runtime.InteropServices.WindowsRuntime; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using Windows.Networking.Sockets; 10 | using Windows.Storage.Streams; 11 | 12 | namespace HttpWebcamLiveStream.Web 13 | { 14 | public sealed class HttpServer 15 | { 16 | private const uint BUFFER_SIZE = 3024; 17 | private readonly StreamSocketListener _listener; 18 | 19 | //Dependency objects 20 | private Camera _camera; 21 | 22 | public HttpServer(Camera camera) 23 | { 24 | _camera = camera; 25 | 26 | _listener = new StreamSocketListener(); 27 | _listener.ConnectionReceived += ProcessRequest; 28 | _listener.Control.KeepAlive = false; 29 | _listener.Control.NoDelay = false; 30 | _listener.Control.QualityOfService = SocketQualityOfService.LowLatency; 31 | } 32 | 33 | public async void Start() 34 | { 35 | await _listener.BindServiceNameAsync(80.ToString()); 36 | } 37 | 38 | private async void ProcessRequest(StreamSocketListener streamSocktetListener, StreamSocketListenerConnectionReceivedEventArgs eventArgs) 39 | { 40 | try 41 | { 42 | var socket = eventArgs.Socket; 43 | 44 | //Read request 45 | var request = await ReadRequest(socket); 46 | 47 | //Write Response 48 | await WriteResponse(request, socket); 49 | 50 | socket.InputStream.Dispose(); 51 | socket.OutputStream.Dispose(); 52 | socket.Dispose(); 53 | } 54 | catch (Exception) { } 55 | } 56 | 57 | private async Task ReadRequest(StreamSocket socket) 58 | { 59 | var request = string.Empty; 60 | var error = false; 61 | 62 | var inputStream = socket.InputStream; 63 | 64 | var data = new byte[BUFFER_SIZE]; 65 | var buffer = data.AsBuffer(); 66 | 67 | var startReadRequest = DateTime.Now; 68 | while (!HttpGetRequestHasUrl(request)) 69 | { 70 | if (DateTime.Now.Subtract(startReadRequest) >= TimeSpan.FromMilliseconds(5000)) 71 | { 72 | error = true; 73 | return new HttpServerRequest(null, true); 74 | } 75 | 76 | var inputStreamReadTask = inputStream.ReadAsync(buffer, BUFFER_SIZE, InputStreamOptions.Partial); 77 | var timeout = TimeSpan.FromMilliseconds(1000); 78 | await TaskHelper.WithTimeoutAfterStart(ct => inputStreamReadTask.AsTask(ct), timeout); 79 | 80 | request += Encoding.UTF8.GetString(data, 0, (int)inputStreamReadTask.AsTask().Result.Length); 81 | } 82 | 83 | return new HttpServerRequest(request, error); 84 | } 85 | 86 | private async Task WriteResponse(HttpServerRequest request, StreamSocket socket) 87 | { 88 | var relativeUrlLower = request.Url.ToLowerInvariant(); 89 | var outputStream = socket.OutputStream; 90 | 91 | //Get javascript files 92 | if (relativeUrlLower.StartsWith("/javascript")) 93 | { 94 | await HttpServerResponse.WriteResponseFile(ToFolderPath(request.Url), HttpContentType.JavaScript, outputStream); 95 | } 96 | //Get css style files 97 | else if (relativeUrlLower.StartsWith("/styles")) 98 | { 99 | await HttpServerResponse.WriteResponseFile(ToFolderPath(request.Url), HttpContentType.Css, outputStream); 100 | } 101 | //Get video setting 102 | else if (relativeUrlLower.StartsWith("/videosetting")) 103 | { 104 | HttpServerResponse.WriteResponseJson(ConfigurationFile.VideoSetting.Stringify(), outputStream); 105 | } 106 | //Get supported video settings 107 | else if (relativeUrlLower.StartsWith("/supportedvideosettings")) 108 | { 109 | HttpServerResponse.WriteResponseJson(ConfigurationFile.VideoSettingsSupported.Stringify(), outputStream); 110 | } 111 | //Set video settings 112 | else if (relativeUrlLower.StartsWith("/savevideosetting")) 113 | { 114 | await _camera.StopAsync(); 115 | 116 | var videoSetting = new VideoSetting 117 | { 118 | VideoSubtype = VideoSubtypeHelper.Get(request.Body["VideoSubtype"].GetString()), 119 | VideoResolution = (VideoResolution)request.Body["VideoResolution"].GetNumber(), 120 | VideoQuality = request.Body["VideoQuality"].GetNumber(), 121 | UsedThreads = (int)request.Body["UsedThreads"].GetNumber(), 122 | Rotation = RotationHelper.Get((int)request.Body["Rotation"].GetNumber()) 123 | }; 124 | 125 | await ConfigurationFile.Write(videoSetting); 126 | await _camera.Initialize(videoSetting); 127 | _camera.Start(); 128 | 129 | HttpServerResponse.WriteResponseOk(outputStream); 130 | } 131 | //Get current camera frame 132 | else if (relativeUrlLower.StartsWith("/videoframe")) 133 | { 134 | if (_camera.Frame != null) 135 | { 136 | var webSocket = new WebSocket(socket, request, _camera); 137 | await webSocket.Start(); 138 | } 139 | else 140 | { 141 | HttpServerResponse.WriteResponseError("Not camera fram available. Maybe there is an error or camera is not started.", outputStream); 142 | } 143 | } 144 | //Get index.html page 145 | else 146 | { 147 | await HttpServerResponse.WriteResponseFile(@"\Html\Index.html", HttpContentType.Html, outputStream); 148 | } 149 | } 150 | 151 | private bool HttpGetRequestHasUrl(string httpRequest) 152 | { 153 | var regex = new Regex("GET.*HTTP.*\r\n", RegexOptions.IgnoreCase); 154 | return regex.IsMatch(httpRequest.ToUpper()); 155 | } 156 | 157 | private string ToFolderPath(string relativeUrl) 158 | { 159 | var folderPath = relativeUrl.Replace('/', '\\'); 160 | return folderPath; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/HttpServerRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using Windows.Data.Json; 4 | 5 | namespace HttpWebcamLiveStream.Web 6 | { 7 | public class HttpServerRequest 8 | { 9 | public string Request { get; private set; } 10 | public JsonObject Body { get; private set; } 11 | public string Url { get; private set; } 12 | public bool Error { get; private set; } 13 | 14 | public HttpServerRequest(string request, bool error) 15 | { 16 | request = request ?? string.Empty; 17 | 18 | Request = request; 19 | Error = error; 20 | 21 | var urlRegex = new Regex(".*GET(.*)HTTP.*", RegexOptions.IgnoreCase); 22 | var urlGroups = urlRegex.Match(request).Groups; 23 | Url = urlGroups.Count >= 2 ? urlGroups[1].Value.Trim() : string.Empty; 24 | 25 | var bodyRegex = new Regex("(.*)", RegexOptions.IgnoreCase); 26 | var bodyGroups = bodyRegex.Match(Uri.UnescapeDataString(Url)).Groups; 27 | var body = bodyGroups.Count >= 2 ? bodyGroups[1].Value.Trim() : null; 28 | if (body != null) 29 | { 30 | JsonObject bodyJson = null; 31 | if (JsonObject.TryParse(body, out bodyJson)) 32 | { 33 | Body = bodyJson; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/HttpServerResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices.WindowsRuntime; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Windows.ApplicationModel; 7 | using Windows.Storage; 8 | using Windows.Storage.Streams; 9 | 10 | namespace HttpWebcamLiveStream.Web 11 | { 12 | public static class HttpServerResponse 13 | { 14 | public static void WriteResponseError(string text, 15 | IOutputStream outputStream) 16 | { 17 | var textBytes = Encoding.UTF8.GetBytes(text); 18 | WriteResponse(HttpContentType.Text, textBytes, HttpStatusCode.HttpCode500, outputStream); 19 | } 20 | 21 | public static void WriteResponseOk(IOutputStream outputStream) 22 | { 23 | WriteResponse(null, null, HttpStatusCode.HttpCode204, outputStream); 24 | } 25 | 26 | public static void WriteResponseText(string text, 27 | IOutputStream outputStream) 28 | { 29 | var textBytes = Encoding.UTF8.GetBytes(text); 30 | WriteResponse(HttpContentType.Text, textBytes, HttpStatusCode.HttpCode200, outputStream); 31 | } 32 | 33 | public static void WriteResponseJson(string json, 34 | IOutputStream outputStream) 35 | { 36 | var jsonBytes = Encoding.UTF8.GetBytes(json); 37 | WriteResponse(HttpContentType.Json, jsonBytes, HttpStatusCode.HttpCode200, outputStream); 38 | } 39 | 40 | public static async Task WriteResponseFile(string pathFileName, 41 | HttpContentType mimeType, 42 | IOutputStream outputStream) 43 | { 44 | var file = await Package.Current.InstalledLocation.GetFileAsync(@"Web" + pathFileName); 45 | 46 | byte[] fileBytes = null; 47 | switch (mimeType) 48 | { 49 | case HttpContentType.Html: 50 | case HttpContentType.Text: 51 | case HttpContentType.Json: 52 | case HttpContentType.JavaScript: 53 | case HttpContentType.Css: 54 | var fileString = await FileIO.ReadTextAsync(file); 55 | var textBytes = Encoding.UTF8.GetBytes(fileString); 56 | fileBytes = textBytes; 57 | break; 58 | case HttpContentType.Jpeg: 59 | case HttpContentType.Png: 60 | var fileImage = await FileIO.ReadBufferAsync(file); 61 | fileBytes = new byte[fileImage.Length]; 62 | fileImage.CopyTo(fileBytes); 63 | break; 64 | } 65 | 66 | WriteResponse(mimeType, fileBytes, HttpStatusCode.HttpCode200, outputStream); 67 | } 68 | 69 | public static void WriteResponseFile(byte[] content, 70 | HttpContentType mimeType, 71 | IOutputStream outputStream) 72 | { 73 | WriteResponse(mimeType, content, HttpStatusCode.HttpCode200, outputStream); 74 | } 75 | 76 | private static void WriteResponse(HttpContentType? mimeType, 77 | byte[] content, 78 | HttpStatusCode httpStatusCode, 79 | IOutputStream outputStream) 80 | { 81 | var stream = outputStream.AsStreamForWrite(); 82 | 83 | var responseHeader = new StringBuilder(); 84 | responseHeader.Append($"{HttpStatusCodeHelper.GetHttpStatusCodeForHttpHeader(httpStatusCode)}\r\n"); 85 | 86 | if (httpStatusCode != HttpStatusCode.HttpCode204) 87 | { 88 | responseHeader.Append($"Content-Type: {MimeTypeHelper.GetHttpContentType(mimeType.Value)}\r\n"); 89 | responseHeader.Append($"Content-Length: {content.Length}\r\n"); 90 | } 91 | 92 | responseHeader.Append("Connection: Close\r\n\r\n"); 93 | 94 | var responsHeaderBytes = Encoding.UTF8.GetBytes(responseHeader.ToString()); 95 | stream.Write(responsHeaderBytes, 0, responsHeaderBytes.Length); 96 | 97 | if (content != null) 98 | { 99 | stream.Write(content, 0, content.Length); 100 | } 101 | 102 | stream.Flush(); 103 | stream.Dispose(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/HttpStatusCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HttpWebcamLiveStream.Web 4 | { 5 | public enum HttpStatusCode 6 | { 7 | HttpCode200, 8 | HttpCode204, 9 | HttpCode500 10 | } 11 | 12 | public static class HttpStatusCodeHelper 13 | { 14 | public static string GetHttpStatusCodeForHttpHeader(HttpStatusCode httpStatusCode) 15 | { 16 | switch (httpStatusCode) 17 | { 18 | case HttpStatusCode.HttpCode200: 19 | return "HTTP/1.1 200 OK"; 20 | case HttpStatusCode.HttpCode204: 21 | return "HTTP/1.1 204 No Content"; 22 | case HttpStatusCode.HttpCode500: 23 | return "HTTP/1.1 500 Internal Server Error"; 24 | default: 25 | throw new Exception($"Could not get http status code for http header for {httpStatusCode}"); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/JavaScript/Camera.js: -------------------------------------------------------------------------------- 1 | var webSocketVideoFrame; 2 | var frameTime; 3 | var videoFrameElement = document.querySelector("#videoFrame"); 4 | var lastImageUrl; 5 | 6 | function GetVideoFrames() { 7 | 8 | webSocketVideoFrame = new WebSocket('ws://' + location.host + "/VideoFrame"); 9 | webSocketVideoFrame.binaryType = "arraybuffer"; 10 | 11 | webSocketVideoFrame.onopen = function () { 12 | webSocketHelper.waitUntilWebsocketReady(function () { 13 | webSocketVideoFrame.send(JSON.stringify({ command: "VideoFrame" })); 14 | }, webSocketVideoFrame, 0); 15 | }; 16 | 17 | webSocketVideoFrame.onmessage = function () { 18 | 19 | var blob = new Blob([event.data], { type: "image/jpeg" }); 20 | lastImageUrl = createObjectURL(blob); 21 | videoFrameElement.src = lastImageUrl; 22 | 23 | frameTime = new Date().getTime(); 24 | 25 | webSocketHelper.waitUntilWebsocketReady(function () { 26 | webSocketVideoFrame.send(JSON.stringify({ command: "VideoFrame" })); 27 | }, webSocketVideoFrame, 0); 28 | }; 29 | } 30 | 31 | videoFrameElement.addEventListener("load", function (e) { 32 | URL.revokeObjectURL(lastImageUrl); 33 | }); 34 | 35 | function createObjectURL(blob) { 36 | var URL = window.URL || window.webkitURL; 37 | if (URL && URL.createObjectURL) { 38 | return URL.createObjectURL(blob); 39 | } else { 40 | return null; 41 | } 42 | } 43 | 44 | function KeepAliveGetVideoFrames() { 45 | 46 | var duration = 0; 47 | if (frameTime !== undefined) { 48 | duration = new Date().getTime() - frameTime 49 | } 50 | 51 | if (frameTime !== undefined 52 | && duration <= 1000) { 53 | 54 | setTimeout(function () { 55 | KeepAliveGetVideoFrames(); 56 | }, 100); 57 | } else { 58 | 59 | if (webSocketVideoFrame !== undefined) { 60 | try { 61 | webSocketVideoFrame.close(); 62 | } catch (e) { } 63 | } 64 | 65 | GetVideoFrames(); 66 | 67 | setTimeout(function () { 68 | KeepAliveGetVideoFrames(); 69 | }, 4000); 70 | } 71 | } 72 | 73 | KeepAliveGetVideoFrames(); -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/JavaScript/Controls.js: -------------------------------------------------------------------------------- 1 | var showControls = document.location.pathname !== undefined ? document.location.pathname.toLowerCase().startsWith("/control") : false; 2 | 3 | if (showControls === true) { 4 | document.getElementById("video-settings").classList.remove("video-settings-hide"); 5 | } -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/JavaScript/Fullscreen.js: -------------------------------------------------------------------------------- 1 | var videoFrameElement = document.getElementById("videoFrame"); 2 | var bodyElement = document.getElementsByTagName("body")[0]; 3 | 4 | videoFrameElement.addEventListener("dblclick", function (e) { 5 | toggleFullScreen(); 6 | }); 7 | 8 | videoFrameElement.addEventListener("touchstart", function (e) { 9 | toggleFullScreen(); 10 | }, { passive: true }); 11 | 12 | function toggleFullScreen() { 13 | if (videoFrameElement.classList.contains("video-frame")) { 14 | videoFrameElement.classList.add("video-frame-full-screen"); 15 | videoFrameElement.classList.remove("video-frame"); 16 | bodyElement.classList.add("body-full-screen"); 17 | 18 | if (showControls === true) { 19 | document.getElementById("video-settings").classList.add("video-settings-hide"); 20 | } 21 | } else { 22 | videoFrameElement.classList.add("video-frame"); 23 | videoFrameElement.classList.remove("video-frame-full-screen"); 24 | bodyElement.classList.remove("body-full-screen"); 25 | 26 | if (showControls === true) { 27 | document.getElementById("video-settings").classList.remove("video-settings-hide"); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/JavaScript/VideoSetting.js: -------------------------------------------------------------------------------- 1 | var supportedVideoSettings; 2 | 3 | var videoResolutionElement = document.getElementById("videoResolution"); 4 | var videoSubtypeElement = document.getElementById("videoSubtype"); 5 | var videoQualityElement = document.getElementById("videoQuality"); 6 | var usedThreadsElement = document.getElementById("usedThreads"); 7 | var rotationElement = document.getElementById("rotation"); 8 | var saveElement = document.getElementById("save"); 9 | 10 | var videoSettingRequest = new XMLHttpRequest(); 11 | videoSettingRequest.open("GET", "VideoSetting", true); 12 | videoSettingRequest.responseType = "json"; 13 | videoSettingRequest.onload = function () { 14 | var status = videoSettingRequest.status; 15 | if (status === 200) { 16 | var videoSetting = videoSettingRequest.response; 17 | 18 | document.querySelector('#videoQuality > option[value="' + videoSetting.VideoQuality.toString() + '"]').selected = true; 19 | document.querySelector('#usedThreads > option[value="' + videoSetting.UsedThreads.toString() + '"]').selected = true; 20 | document.querySelector('#rotation > option[value="' + videoSetting.Rotation.toString() + '"]').selected = true; 21 | 22 | var supportedVideoSettingRequest = new XMLHttpRequest(); 23 | supportedVideoSettingRequest.open("GET", "SupportedVideoSettings", true); 24 | supportedVideoSettingRequest.responseType = "json"; 25 | supportedVideoSettingRequest.onload = function () { 26 | var status = supportedVideoSettingRequest.status; 27 | if (status == 200) { 28 | supportedVideoSettings = supportedVideoSettingRequest.response; 29 | 30 | //Set video subtypes 31 | supportedVideoSettings.forEach(function (videoSetting) { 32 | 33 | var videoSubtypeOption = document.createElement('option'); 34 | videoSubtypeOption.value = videoSetting.VideoSubtype; 35 | videoSubtypeOption.innerHTML = videoSetting.VideoSubtype; 36 | videoSubtypeElement.appendChild(videoSubtypeOption); 37 | }); 38 | 39 | document.querySelector('#videoSubtype > option[value="' + videoSetting.VideoSubtype.toString() + '"]').selected = true; 40 | 41 | //Set video resolutions 42 | SetVideoResolutions(videoSetting.VideoSubtype); 43 | 44 | document.querySelector('#videoResolution > option[value="' + videoSetting.VideoResolution.toString() + '"]').selected = true; 45 | } 46 | }; 47 | supportedVideoSettingRequest.send(); 48 | } 49 | }; 50 | 51 | function SetVideoResolutions(videoSubtype) { 52 | 53 | var videoResolution = null; 54 | 55 | if (videoResolutionElement.options.length > 0) { 56 | videoResolution = videoResolutionElement.options[videoResolutionElement.options.selectedIndex].value; 57 | } 58 | 59 | while (videoResolutionElement.options.length > 0) { 60 | videoResolutionElement.remove(0); 61 | } 62 | 63 | var videoResolutions = supportedVideoSettings.filter(function (svs) { return svs.VideoSubtype === videoSubtype; })[0].VideoResolutions; 64 | 65 | var containOldVideoResolution = videoResolution === null ? false : videoResolutions.filter(function (svs) { return svs.toString() === videoResolution; }).length == 1; 66 | 67 | videoResolutions.forEach(function(videoResolution) { 68 | 69 | var videoSubtypeVideoResolution = document.createElement('option'); 70 | videoSubtypeVideoResolution.value = videoResolution; 71 | videoSubtypeVideoResolution.innerHTML = GetVideoResolutionName(videoResolution); 72 | videoResolutionElement.appendChild(videoSubtypeVideoResolution); 73 | }); 74 | 75 | if(containOldVideoResolution === true) { 76 | document.querySelector('#videoResolution > option[value="' + videoResolution.toString() + '"]').selected = true; 77 | } 78 | } 79 | 80 | function GetVideoResolutionName(videoResolution) { 81 | 82 | if (videoResolution === 0) { 83 | return "HD1080p"; 84 | } 85 | else if (videoResolution === 1) { 86 | return "HD720p"; 87 | } 88 | else if (videoResolution === 2) { 89 | return "SD1024_768"; 90 | } 91 | else if (videoResolution === 3) { 92 | return "SD800_600"; 93 | } 94 | else if (videoResolution === 4) { 95 | return "SD640_480"; 96 | } 97 | } 98 | 99 | videoSubtypeElement.addEventListener("change", function () { 100 | SetVideoResolutions(videoSubtypeElement.value); 101 | }); 102 | 103 | saveElement.addEventListener("click", function () { 104 | SetVideoSetting(); 105 | }); 106 | 107 | saveElement.addEventListener("touchstart", function () { 108 | SetVideoSetting(); 109 | }, { passive: true }); 110 | 111 | function SetVideoSetting() { 112 | var videoSubtype = videoSubtypeElement.options[videoSubtypeElement.options.selectedIndex].value; 113 | var videoResolution = videoResolutionElement.options[videoResolutionElement.options.selectedIndex].value; 114 | var videoQuality = videoQualityElement.options[videoQualityElement.options.selectedIndex].value; 115 | var usedThreads = usedThreadsElement.options[usedThreadsElement.options.selectedIndex].value; 116 | var rotation = rotationElement.options[rotationElement.options.selectedIndex].value; 117 | 118 | var saveRequest = new XMLHttpRequest(); 119 | var videoSetting = { "VideoSubtype": videoSubtype, "VideoResolution": parseInt(videoResolution, 10), "VideoQuality": parseFloat(videoQuality), "UsedThreads": parseInt(usedThreads, 10), "Rotation": parseInt(rotation, 10) }; 120 | saveRequest.open("GET", "SaveVideoSetting/" + JSON.stringify(videoSetting) + "", true); 121 | saveRequest.responseType = "json"; 122 | saveRequest.send(); 123 | } 124 | 125 | videoSettingRequest.send(); -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/JavaScript/WebSocketHelper.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var webSocketHelper = { 3 | waitUntilWebsocketReady: function (callback, webSocket, retries) { 4 | 5 | retries++; 6 | 7 | if (retries === 20) { 8 | webSocket.close(); 9 | return; 10 | } 11 | 12 | if (webSocket.readyState === 1) { 13 | callback(); 14 | } else { 15 | setTimeout(function () { 16 | webSocketHelper.waitUntilWebsocketReady(callback, webSocket, retries); 17 | }, 1); 18 | } 19 | } 20 | }; 21 | 22 | window.webSocketHelper = webSocketHelper; 23 | })(); -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/Styles/Index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | width: 100%; 4 | height: 100%; 5 | font-family: Verdana, Geneva, Tahoma, sans-serif; 6 | color: white; 7 | } 8 | 9 | .body-full-screen { 10 | background-color: #1a1a1a; 11 | } 12 | 13 | .video-frame-full-screen { 14 | position: fixed; 15 | left: 0; 16 | right: 0; 17 | top: 0; 18 | bottom: 0; 19 | margin: auto; 20 | } 21 | 22 | @media (orientation:landscape) { 23 | 24 | .video-frame-full-screen { 25 | width: auto; 26 | height: 100%; 27 | } 28 | 29 | @supports (object-fit:contain) { 30 | .video-frame-full-screen { 31 | width: 100%; 32 | height: 100%; 33 | object-fit: contain; 34 | } 35 | } 36 | } 37 | 38 | @media (orientation:portrait) { 39 | 40 | .video-frame-full-screen { 41 | width: 100%; 42 | height: auto; 43 | } 44 | 45 | @supports (object-fit:contain) { 46 | .video-frame-full-screen { 47 | width: 100%; 48 | height: 100%; 49 | object-fit: contain; 50 | } 51 | } 52 | } 53 | 54 | .video-frame { 55 | display: block; 56 | margin-left: auto; 57 | margin-right: auto; 58 | } 59 | 60 | .video-settings-hide { 61 | display: none; 62 | } 63 | 64 | .video-settings { 65 | margin: 0px auto; 66 | width: 310px; 67 | color: black; 68 | font-size: 14px; 69 | } 70 | 71 | .video-settings select { 72 | width: 180px; 73 | border-radius: 4px; 74 | border: 1px solid #AAAAAA; 75 | } 76 | 77 | .video-settings tr:last-child td:last-child { 78 | text-align: right; 79 | padding-top: 15px; 80 | } 81 | 82 | #save { 83 | border-radius: 4px; 84 | border: 1px solid #AAAAAA; 85 | } 86 | -------------------------------------------------------------------------------- /HttpWebcamLiveStream/Web/WebSocket.cs: -------------------------------------------------------------------------------- 1 | using HttpWebcamLiveStream.Devices; 2 | using HttpWebcamLiveStream.Helper; 3 | using System; 4 | using System.Linq; 5 | using System.Runtime.InteropServices.WindowsRuntime; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Windows.Data.Json; 12 | using Windows.Networking.Sockets; 13 | using Windows.Storage.Streams; 14 | 15 | namespace HttpWebcamLiveStream.Web 16 | { 17 | public enum OpCode 18 | { 19 | Undefined = -1, 20 | Text = 129, 21 | Binary = 130 22 | } 23 | 24 | public class WebSocket 25 | { 26 | private IInputStream _inputStream; 27 | private IOutputStream _outputStream; 28 | private HttpServerRequest _httpServerRequest; 29 | private byte[] _alreadySendFrame = null; 30 | private const uint BUFFER_SIZE = 3024; 31 | private const int NEW_FRAME_AVAILABLE_CHECK_DURATION_MS = 5; 32 | 33 | //Dependencies 34 | private Camera _camera; 35 | 36 | public WebSocket(StreamSocket socket, 37 | HttpServerRequest httpServerRequest, 38 | Camera camera) 39 | { 40 | _inputStream = socket.InputStream; 41 | _outputStream = socket.OutputStream; 42 | _httpServerRequest = httpServerRequest; 43 | _camera = camera; 44 | } 45 | 46 | public async Task Start() 47 | { 48 | if (await CheckWebSocketVersionSupport()) 49 | { 50 | await ReadFrames(); 51 | } 52 | } 53 | 54 | private async Task CheckWebSocketVersionSupport() 55 | { 56 | var webSocketVersion = new Regex("Sec-WebSocket-Version:(.*)", RegexOptions.IgnoreCase).Match(_httpServerRequest.Request).Groups[1].Value.Trim(); 57 | if (webSocketVersion != "13") 58 | { 59 | await WriteUpgradeRequired(); 60 | 61 | return false; 62 | } 63 | else 64 | { 65 | await WriteHandshake(); 66 | 67 | return true; 68 | } 69 | } 70 | 71 | private async Task WriteUpgradeRequired() 72 | { 73 | var response = Encoding.UTF8.GetBytes("HTTP/1.1 426 Upgrade Required" + Environment.NewLine 74 | + "Connection: Upgrade" + Environment.NewLine 75 | + "Upgrade: websocket" + Environment.NewLine 76 | + "Sec-WebSocket-Version: 13" + Environment.NewLine 77 | + Environment.NewLine); 78 | 79 | await _outputStream.WriteAsync(response.AsBuffer()); 80 | await _outputStream.FlushAsync(); 81 | } 82 | 83 | private async Task WriteHandshake() 84 | { 85 | var response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine 86 | + "Connection: Upgrade" + Environment.NewLine 87 | + "Upgrade: websocket" + Environment.NewLine 88 | + "Sec-WebSocket-Accept: " + Convert.ToBase64String( 89 | SHA1.Create().ComputeHash( 90 | Encoding.UTF8.GetBytes( 91 | new Regex("Sec-WebSocket-Key:(.*)", RegexOptions.IgnoreCase).Match(_httpServerRequest.Request).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 92 | ) 93 | ) 94 | ) + Environment.NewLine 95 | + Environment.NewLine); 96 | 97 | await _outputStream.WriteAsync(response.AsBuffer()); 98 | await _outputStream.FlushAsync(); 99 | } 100 | 101 | private async Task ProcessFrame(string frameContent) 102 | { 103 | var content = JsonObject.Parse(frameContent); 104 | var command = content["command"].GetString(); 105 | 106 | switch (command) 107 | { 108 | case "VideoFrame": 109 | 110 | // Wait frames available 111 | SpinWait.SpinUntil(() => { return _camera != null && _camera.Frame != null; }); 112 | 113 | // Check new frame available 114 | while (_alreadySendFrame == _camera.Frame) 115 | { 116 | await Task.Delay(NEW_FRAME_AVAILABLE_CHECK_DURATION_MS); 117 | } 118 | 119 | _alreadySendFrame = _camera.Frame; 120 | 121 | var cameraFrame = _alreadySendFrame.ToArray(); 122 | await WriteFrame(cameraFrame, OpCode.Binary); 123 | 124 | break; 125 | } 126 | } 127 | 128 | private async Task WriteFrame(string data) 129 | { 130 | var dataBytes = Encoding.UTF8.GetBytes(data); 131 | await WriteFrame(dataBytes, OpCode.Text); 132 | } 133 | 134 | private async Task WriteFrame(byte[] data, OpCode opCode) 135 | { 136 | byte[] header = new byte[2]; 137 | 138 | if (opCode == OpCode.Text) 139 | { 140 | header[0] = 129; 141 | } 142 | else if (opCode == OpCode.Binary) 143 | { 144 | header[0] = 130; 145 | } 146 | 147 | if (data.Length <= 125) 148 | { 149 | header[1] = (byte)data.Length; 150 | } 151 | else if (data.Length >= 126 && data.Length <= 65535) 152 | { 153 | header[1] = 126; 154 | 155 | var length = Convert.ToUInt16(data.Length); 156 | var lengthBytes = BitConverter.GetBytes(length); 157 | Array.Reverse(lengthBytes, 0, lengthBytes.Length); 158 | 159 | header = header.Concat(lengthBytes).ToArray(); 160 | } 161 | else 162 | { 163 | header[1] = 127; 164 | 165 | var length = Convert.ToUInt64(data.Length); 166 | var lengthBytes = BitConverter.GetBytes(length); 167 | Array.Reverse(lengthBytes, 0, lengthBytes.Length); 168 | 169 | header = header.Concat(lengthBytes).ToArray(); 170 | } 171 | 172 | var headerData = header.Concat(data).ToArray(); 173 | 174 | await _outputStream.WriteAsync(headerData.AsBuffer()); 175 | await _outputStream.FlushAsync(); 176 | } 177 | 178 | private async Task ReadFrames() 179 | { 180 | var data = new byte[BUFFER_SIZE]; 181 | var buffer = data.AsBuffer(); 182 | var frameData = Array.Empty(); 183 | 184 | while (true) 185 | { 186 | var readBytesTask = _inputStream.ReadAsync(buffer, BUFFER_SIZE, InputStreamOptions.Partial); 187 | var timeout = TimeSpan.FromMilliseconds(5000); 188 | await TaskHelper.WithTimeoutAfterStart(ct => readBytesTask.AsTask(ct), timeout); 189 | 190 | var readBytes = readBytesTask.AsTask().Result; 191 | 192 | var readBytesLength = (int)readBytes.Length; 193 | 194 | if (readBytesLength >= 2) 195 | { 196 | var newData = data.Take(readBytesLength); 197 | frameData = frameData.Concat(newData).ToArray(); 198 | 199 | readBytesLength = frameData.Length; 200 | 201 | var opCode = OpCode.Undefined; 202 | 203 | if (frameData[0] == 136) //Close frame was send 204 | { 205 | var closeFrame = new byte[] { 136, 0 }; 206 | await _outputStream.WriteAsync(closeFrame.AsBuffer()); 207 | await _outputStream.FlushAsync(); 208 | return; 209 | } 210 | else if (frameData[0] == 129) 211 | { 212 | opCode = OpCode.Text; 213 | } 214 | else if (frameData[0] == 130) 215 | { 216 | opCode = OpCode.Binary; 217 | } 218 | 219 | var contentLength = (long)(frameData[1] & 127); 220 | 221 | var indexFirstMask = 2; 222 | 223 | if (contentLength == 126) 224 | { 225 | if (readBytesLength < 4) 226 | continue; 227 | 228 | Array.Reverse(frameData, 2, 2); 229 | 230 | contentLength = BitConverter.ToInt16(frameData, 2); 231 | indexFirstMask = 4; 232 | } 233 | else if (contentLength == 127) 234 | { 235 | if (readBytesLength < 10) 236 | continue; 237 | 238 | Array.Reverse(frameData, 2, 8); 239 | 240 | contentLength = BitConverter.ToInt64(frameData, 2); 241 | 242 | indexFirstMask = 10; 243 | } 244 | 245 | var maskLength = 4; 246 | var indexFirstDataByte = indexFirstMask + maskLength; 247 | 248 | var frameLength = contentLength + indexFirstDataByte; 249 | 250 | if (readBytesLength < frameLength) //Is complete frame read? 251 | continue; 252 | 253 | var masks = frameData.Skip(indexFirstMask).Take(maskLength).ToArray(); 254 | 255 | byte[] decoded = new byte[contentLength]; 256 | 257 | for (int i = indexFirstDataByte, j = 0; i < frameLength; i++, j++) 258 | { 259 | decoded[j] = (byte)(frameData[i] ^ masks.ElementAt(j % 4)); 260 | } 261 | 262 | if (frameData.Length > frameLength) 263 | { 264 | frameData = frameData.Skip(frameData.Length).ToArray(); 265 | } 266 | else 267 | { 268 | frameData = Array.Empty(); 269 | } 270 | 271 | data = new byte[BUFFER_SIZE]; 272 | buffer = data.AsBuffer(); 273 | 274 | if (opCode == OpCode.Text) 275 | { 276 | await ProcessFrame(Encoding.UTF8.GetString(decoded, 0, decoded.Length)); 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SaschaIoT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpWebcamLiveStream 2 | Simple browser webcam live stream with Windows IoT Core 17763 and Raspberry Pi 3 3 | 4 | See project at hackster.io: https://www.hackster.io/sascha/browser-webcam-live-stream-with-windows-iot-core-raspberry-3-1dc38b 5 | --------------------------------------------------------------------------------