diff --git a/README.md b/README.md index a6c0ed3f..ef8102f9 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,51 @@ Under `.addPackage(new MainReactPackage())`: .addPackage(new ReactVideoPackage()) ``` +#### Windows + +Make the following additions to the given files manually: + +**windows/myapp.sln** + +Add the `ReactNativeVideo` project to your solution. + +1. Open the solution in Visual Studio 2015 +2. Right-click Solution icon in Solution Explorer > Add > Existing Project... +3. Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj` + +**windows/myapp/myapp.csproj** + +Add a reference to `ReactNativeVideo` to your main application project. From Visual Studio 2015: + +1. Right-click main application project > Add > Reference... +2. Check `ReactNativeVideo` from Solution Projects. + +**MainPage.cs** + +Add the `ReactVideoPackage` class to your list of exported packages. +```cs +using ReactNative; +using ReactNative.Modules.Core; +using ReactNative.Shell; +using ReactNativeVideo; // <-- Add this +using System.Collections.Generic; +... + + public override List Packages + { + get + { + return new List + { + new MainReactPackage(), + new ReactVideoPackage(), // <-- Add this + }; + } + } + +... +``` + ## Usage ```javascript diff --git a/Video.js b/Video.js index 3c032f74..d5281523 100644 --- a/Video.js +++ b/Video.js @@ -125,7 +125,7 @@ export default class Video extends Component { } const isNetwork = !!(uri && uri.match(/^https?:/)); - const isAsset = !!(uri && uri.match(/^(assets-library|file|content):/)); + const isAsset = !!(uri && uri.match(/^(assets-library|file|content|ms-appx|ms-appdata):/)); let nativeResizeMode; if (resizeMode === VideoResizeMode.stretch) { diff --git a/example/index.windows.js b/example/index.windows.js new file mode 100644 index 00000000..a3d21c6f --- /dev/null +++ b/example/index.windows.js @@ -0,0 +1,210 @@ +'use strict'; + +import React, { + Component +} from 'react'; + +import { + AppRegistry, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; + +import Video from 'react-native-video'; + +class VideoPlayer extends Component { + constructor(props) { + super(props); + this.onLoad = this.onLoad.bind(this); + this.onProgress = this.onProgress.bind(this); + } + + state = { + rate: 1, + volume: 1, + muted: false, + resizeMode: 'contain', + duration: 0.0, + currentTime: 0.0, + }; + + onLoad(data) { + this.setState({duration: data.duration}); + } + + onProgress(data) { + this.setState({currentTime: data.currentTime}); + } + + getCurrentTimePercentage() { + if (this.state.currentTime > 0) { + return parseFloat(this.state.currentTime) / parseFloat(this.state.duration); + } else { + return 0; + } + } + + renderRateControl(rate) { + const isSelected = (this.state.rate == rate); + + return ( + { this.setState({rate: rate}) }}> + + {rate}x + + + ) + } + + renderResizeModeControl(resizeMode) { + const isSelected = (this.state.resizeMode == resizeMode); + + return ( + { this.setState({resizeMode: resizeMode}) }}> + + {resizeMode} + + + ) + } + + renderVolumeControl(volume) { + const isSelected = (this.state.volume == volume); + + return ( + { this.setState({volume: volume}) }}> + + {volume * 100}% + + + ) + } + + render() { + const flexCompleted = this.getCurrentTimePercentage() * 100; + const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100; + + return ( + + {this.setState({paused: !this.state.paused})}}> + + + + + + {this.renderRateControl(0.25)} + {this.renderRateControl(0.5)} + {this.renderRateControl(1.0)} + {this.renderRateControl(1.5)} + {this.renderRateControl(2.0)} + + + + {this.renderVolumeControl(0.5)} + {this.renderVolumeControl(1)} + {this.renderVolumeControl(1.5)} + + + + {this.renderResizeModeControl('cover')} + {this.renderResizeModeControl('contain')} + {this.renderResizeModeControl('stretch')} + + + + + + + + + + + + ); + } +} + + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'black', + }, + fullScreen: { + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, + }, + controls: { + backgroundColor: "transparent", + borderRadius: 5, + position: 'absolute', + bottom: 20, + left: 20, + right: 20, + }, + progress: { + flex: 1, + flexDirection: 'row', + borderRadius: 3, + overflow: 'hidden', + }, + innerProgressCompleted: { + height: 20, + backgroundColor: '#cccccc', + }, + innerProgressRemaining: { + height: 20, + backgroundColor: '#2C2C2C', + }, + generalControls: { + flex: 1, + flexDirection: 'row', + borderRadius: 4, + overflow: 'hidden', + paddingBottom: 10, + }, + rateControl: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + }, + volumeControl: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + }, + resizeModeControl: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + controlOption: { + alignSelf: 'center', + fontSize: 11, + color: "white", + paddingLeft: 2, + paddingRight: 2, + lineHeight: 12, + }, +}); + +AppRegistry.registerComponent('VideoPlayer', () => VideoPlayer); diff --git a/example/package.json b/example/package.json index 0dcf72d8..a620fb4e 100644 --- a/example/package.json +++ b/example/package.json @@ -8,6 +8,10 @@ "dependencies": { "react": "15.3.1", "react-native": "^0.33.0", - "react-native-video": "file:../" + "react-native-video": "file:../", + "react-native-windows": "^0.33.4" + }, + "devDependencies": { + "rnpm-plugin-windows": "^0.2.3" } } diff --git a/example/windows/.gitignore b/example/windows/.gitignore new file mode 100644 index 00000000..33d3fde2 --- /dev/null +++ b/example/windows/.gitignore @@ -0,0 +1,89 @@ +*AppPackages* +*BundleArtifacts* +*ReactAssets* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +*project.lock.json diff --git a/example/windows/VideoPlayer.sln b/example/windows/VideoPlayer.sln new file mode 100644 index 00000000..3d10db9e --- /dev/null +++ b/example/windows/VideoPlayer.sln @@ -0,0 +1,141 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VideoPlayer", "VideoPlayer\VideoPlayer.csproj", "{A027BE54-D118-49A4-8D84-0666A2A93E64}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "..\node_modules\react-native-windows\ReactWindows\ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ChakraBridge", "..\node_modules\react-native-windows\ReactWindows\ChakraBridge\ChakraBridge.vcxproj", "{4B72C796-16D5-4E3A-81C0-3E36F531E578}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNativeVideo", "..\node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj", "{E8F5F57F-757E-4237-AD23-F7A8755427CD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + DebugBundle|ARM = DebugBundle|ARM + DebugBundle|x64 = DebugBundle|x64 + DebugBundle|x86 = DebugBundle|x86 + Release|ARM = Release|ARM + Release|x64 = Release|x64 + Release|x86 = Release|x86 + ReleaseBundle|ARM = ReleaseBundle|ARM + ReleaseBundle|x64 = ReleaseBundle|x64 + ReleaseBundle|x86 = ReleaseBundle|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|ARM.ActiveCfg = Debug|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|ARM.Build.0 = Debug|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|ARM.Deploy.0 = Debug|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x64.ActiveCfg = Debug|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x64.Build.0 = Debug|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x64.Deploy.0 = Debug|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x86.ActiveCfg = Debug|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x86.Build.0 = Debug|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Debug|x86.Deploy.0 = Debug|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|ARM.ActiveCfg = DebugBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|ARM.Build.0 = DebugBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|ARM.Deploy.0 = DebugBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x64.ActiveCfg = DebugBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x64.Build.0 = DebugBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x64.Deploy.0 = DebugBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x86.ActiveCfg = DebugBundle|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x86.Build.0 = DebugBundle|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.DebugBundle|x86.Deploy.0 = DebugBundle|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|ARM.ActiveCfg = Release|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|ARM.Build.0 = Release|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|ARM.Deploy.0 = Release|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x64.ActiveCfg = Release|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x64.Build.0 = Release|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x64.Deploy.0 = Release|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x86.ActiveCfg = Release|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x86.Build.0 = Release|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.Release|x86.Deploy.0 = Release|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|ARM.ActiveCfg = ReleaseBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|ARM.Build.0 = ReleaseBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|ARM.Deploy.0 = ReleaseBundle|ARM + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x64.ActiveCfg = ReleaseBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x64.Build.0 = ReleaseBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x64.Deploy.0 = ReleaseBundle|x64 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x86.ActiveCfg = ReleaseBundle|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x86.Build.0 = ReleaseBundle|x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64}.ReleaseBundle|x86.Deploy.0 = ReleaseBundle|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.ActiveCfg = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.Build.0 = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.ActiveCfg = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.Build.0 = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.ActiveCfg = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.Build.0 = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|ARM.ActiveCfg = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|ARM.Build.0 = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|x64.ActiveCfg = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|x64.Build.0 = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|x86.ActiveCfg = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.DebugBundle|x86.Build.0 = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.ActiveCfg = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.Build.0 = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.ActiveCfg = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.Build.0 = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.ActiveCfg = Release|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.Build.0 = Release|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|ARM.ActiveCfg = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|ARM.Build.0 = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|x64.ActiveCfg = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|x64.Build.0 = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|x86.ActiveCfg = Release|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.ReleaseBundle|x86.Build.0 = Release|x86 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.ActiveCfg = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.Build.0 = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.ActiveCfg = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.Build.0 = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.ActiveCfg = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.Build.0 = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|ARM.ActiveCfg = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|ARM.Build.0 = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x64.ActiveCfg = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x64.Build.0 = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x86.ActiveCfg = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x86.Build.0 = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.ActiveCfg = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.Build.0 = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.ActiveCfg = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.Build.0 = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|ARM.ActiveCfg = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|ARM.Build.0 = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x64.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x64.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x86.ActiveCfg = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x86.Build.0 = Release|Win32 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|ARM.ActiveCfg = Debug|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|ARM.Build.0 = Debug|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|x64.ActiveCfg = Debug|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|x64.Build.0 = Debug|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|x86.ActiveCfg = Debug|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Debug|x86.Build.0 = Debug|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|ARM.ActiveCfg = Debug|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|ARM.Build.0 = Debug|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|x64.ActiveCfg = Debug|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|x64.Build.0 = Debug|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|x86.ActiveCfg = Debug|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.DebugBundle|x86.Build.0 = Debug|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|ARM.ActiveCfg = Release|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|ARM.Build.0 = Release|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|x64.ActiveCfg = Release|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|x64.Build.0 = Release|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|x86.ActiveCfg = Release|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.Release|x86.Build.0 = Release|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|ARM.ActiveCfg = Release|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|ARM.Build.0 = Release|ARM + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|x64.ActiveCfg = Release|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|x64.Build.0 = Release|x64 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|x86.ActiveCfg = Release|x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD}.ReleaseBundle|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/example/windows/VideoPlayer/App.xaml b/example/windows/VideoPlayer/App.xaml new file mode 100644 index 00000000..02a3efbb --- /dev/null +++ b/example/windows/VideoPlayer/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/example/windows/VideoPlayer/App.xaml.cs b/example/windows/VideoPlayer/App.xaml.cs new file mode 100644 index 00000000..491a7ce5 --- /dev/null +++ b/example/windows/VideoPlayer/App.xaml.cs @@ -0,0 +1,112 @@ +using ReactNative; +using System; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace VideoPlayer +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + private readonly ReactPage _reactPage; + + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + this.Suspending += OnSuspending; + this.Resuming += OnResuming; + + _reactPage = new MainPage(); + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + _reactPage.OnResume(Exit); + +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + this.DebugSettings.EnableFrameRateCounter = true; + } + + SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = + AppViewBackButtonVisibility.Visible; +#endif + + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + _reactPage.OnCreate(e.Arguments); + + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Content = _reactPage; + } + + // Ensure the current window is active + Window.Current.Activate(); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + _reactPage.OnSuspend(); + } + + /// + /// Invoked when application execution is being resumed. + /// + /// The source of the resume request. + /// Details about the resume request. + private void OnResuming(object sender, object e) + { + _reactPage.OnResume(Exit); + } + } +} diff --git a/example/windows/VideoPlayer/Assets/LockScreenLogo.scale-200.png b/example/windows/VideoPlayer/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 00000000..735f57ad Binary files /dev/null and b/example/windows/VideoPlayer/Assets/LockScreenLogo.scale-200.png differ diff --git a/example/windows/VideoPlayer/Assets/SplashScreen.scale-200.png b/example/windows/VideoPlayer/Assets/SplashScreen.scale-200.png new file mode 100644 index 00000000..023e7f1f Binary files /dev/null and b/example/windows/VideoPlayer/Assets/SplashScreen.scale-200.png differ diff --git a/example/windows/VideoPlayer/Assets/Square150x150Logo.scale-200.png b/example/windows/VideoPlayer/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 00000000..af49fec1 Binary files /dev/null and b/example/windows/VideoPlayer/Assets/Square150x150Logo.scale-200.png differ diff --git a/example/windows/VideoPlayer/Assets/Square44x44Logo.scale-200.png b/example/windows/VideoPlayer/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000..ce342a2e Binary files /dev/null and b/example/windows/VideoPlayer/Assets/Square44x44Logo.scale-200.png differ diff --git a/example/windows/VideoPlayer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/example/windows/VideoPlayer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000..f6c02ce9 Binary files /dev/null and b/example/windows/VideoPlayer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/example/windows/VideoPlayer/Assets/StoreLogo.png b/example/windows/VideoPlayer/Assets/StoreLogo.png new file mode 100644 index 00000000..7385b56c Binary files /dev/null and b/example/windows/VideoPlayer/Assets/StoreLogo.png differ diff --git a/example/windows/VideoPlayer/Assets/Wide310x150Logo.scale-200.png b/example/windows/VideoPlayer/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000..288995b3 Binary files /dev/null and b/example/windows/VideoPlayer/Assets/Wide310x150Logo.scale-200.png differ diff --git a/example/windows/VideoPlayer/MainPage.cs b/example/windows/VideoPlayer/MainPage.cs new file mode 100644 index 00000000..4db71186 --- /dev/null +++ b/example/windows/VideoPlayer/MainPage.cs @@ -0,0 +1,55 @@ +using ReactNative; +using ReactNative.Modules.Core; +using ReactNative.Shell; +using ReactNativeVideo; +using System.Collections.Generic; +using Windows.UI.Xaml.Media.Imaging; + +namespace VideoPlayer +{ + class MainPage : ReactPage + { + public override string MainComponentName + { + get + { + return "VideoPlayer"; + } + } + +#if BUNDLE + public override string JavaScriptBundleFile + { + get + { + return "ms-appx:///ReactAssets/index.windows.bundle"; + } + } +#endif + + public override List Packages + { + get + { + return new List + { + new MainReactPackage(), + new ReactVideoPackage(), + }; + } + } + + public override bool UseDeveloperSupport + { + get + { +#if !BUNDLE || DEBUG + return true; +#else + return false; +#endif + } + } + } + +} diff --git a/example/windows/VideoPlayer/Package.appxmanifest b/example/windows/VideoPlayer/Package.appxmanifest new file mode 100644 index 00000000..be303ca5 --- /dev/null +++ b/example/windows/VideoPlayer/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + VideoPlayer + React Native for UWP + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/windows/VideoPlayer/Properties/AssemblyInfo.cs b/example/windows/VideoPlayer/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..cb6ff6b7 --- /dev/null +++ b/example/windows/VideoPlayer/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("VideoPlayer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VideoPlayer")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/example/windows/VideoPlayer/Properties/Default.rd.xml b/example/windows/VideoPlayer/Properties/Default.rd.xml new file mode 100644 index 00000000..86952373 --- /dev/null +++ b/example/windows/VideoPlayer/Properties/Default.rd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/windows/VideoPlayer/VideoPlayer.csproj b/example/windows/VideoPlayer/VideoPlayer.csproj new file mode 100644 index 00000000..96611032 --- /dev/null +++ b/example/windows/VideoPlayer/VideoPlayer.csproj @@ -0,0 +1,230 @@ + + + + + Debug + x86 + {A027BE54-D118-49A4-8D84-0666A2A93E64} + AppContainerExe + Properties + VideoPlayer + VideoPlayer + en-US + UAP + 10.0.14393.0 + 10.0.10586.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + VideoPlayer_TemporaryKey.pfx + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + true + bin\x86\DebugBundle\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + ;2008 + true + full + x86 + false + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + bin\x86\ReleaseBundle\ + TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + true + ;2008 + true + pdbonly + x86 + false + prompt + MinimumRecommendedRules.ruleset + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + true + bin\ARM\DebugBundle\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + ;2008 + true + full + ARM + false + prompt + MinimumRecommendedRules.ruleset + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + bin\ARM\ReleaseBundle\ + TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + true + ;2008 + true + pdbonly + ARM + false + prompt + MinimumRecommendedRules.ruleset + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + true + bin\x64\DebugBundle\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + ;2008 + true + full + x64 + false + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + bin\x64\ReleaseBundle\ + TRACE;NETFX_CORE;WINDOWS_UWP;CODE_ANALYSIS;BUNDLE + true + ;2008 + true + pdbonly + x64 + false + prompt + MinimumRecommendedRules.ruleset + true + true + + + + + + + + App.xaml + + + + + + + Designer + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + + + {e8f5f57f-757e-4237-ad23-f7a8755427cd} + ReactNativeVideo + + + {c7673ad5-e3aa-468c-a5fd-fa38154e205c} + ReactNative + + + + + PreserveNewest + + + + 14.0 + + + + \ No newline at end of file diff --git a/example/windows/VideoPlayer/VideoPlayer_TemporaryKey.pfx b/example/windows/VideoPlayer/VideoPlayer_TemporaryKey.pfx new file mode 100644 index 00000000..08328fc1 Binary files /dev/null and b/example/windows/VideoPlayer/VideoPlayer_TemporaryKey.pfx differ diff --git a/example/windows/VideoPlayer/project.json b/example/windows/VideoPlayer/project.json new file mode 100644 index 00000000..bf00b26b --- /dev/null +++ b/example/windows/VideoPlayer/project.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "Facebook.CSSLayout": "2.0.1-pre", + "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.2" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..74236826 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,97 @@ +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.tlog +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +*.lastbuildstate +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +*.tlog/ +Ankh.NoLoad +UpgradeLog.htm + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +.vscode/ +*project.lock.json + +#JavaScript files +*.jsbundle + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Build results +[Dd]ebugPublic/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Ll]og/ diff --git a/windows/ReactNativeVideo/Properties/AssemblyInfo.cs b/windows/ReactNativeVideo/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..1d4241b5 --- /dev/null +++ b/windows/ReactNativeVideo/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ReactNativeVideo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ReactNativeVideo")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/windows/ReactNativeVideo/Properties/ReactNativeVideo.rd.xml b/windows/ReactNativeVideo/Properties/ReactNativeVideo.rd.xml new file mode 100644 index 00000000..19ad73b1 --- /dev/null +++ b/windows/ReactNativeVideo/Properties/ReactNativeVideo.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/windows/ReactNativeVideo/ReactNativeVideo.csproj b/windows/ReactNativeVideo/ReactNativeVideo.csproj new file mode 100644 index 00000000..3fd0e59d --- /dev/null +++ b/windows/ReactNativeVideo/ReactNativeVideo.csproj @@ -0,0 +1,116 @@ + + + + + Debug + x86 + {E8F5F57F-757E-4237-AD23-F7A8755427CD} + Library + Properties + ReactNativeVideo + ReactNativeVideo + en-US + UAP + 10.0.14393.0 + 10.0.10586.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + + + + + + + + + + + + + + + + + {c7673ad5-e3aa-468c-a5fd-fa38154e205c} + ReactNative + + + + 14.0 + + + + diff --git a/windows/ReactNativeVideo/ReactVideoEventType.cs b/windows/ReactNativeVideo/ReactVideoEventType.cs new file mode 100644 index 00000000..dd3f3feb --- /dev/null +++ b/windows/ReactNativeVideo/ReactVideoEventType.cs @@ -0,0 +1,15 @@ +namespace ReactNativeVideo +{ + enum ReactVideoEventType + { + LoadStart, + Load, + Error, + Progress, + Seek, + End, + Stalled, + Resume, + ReadyForDisplay, + } +} diff --git a/windows/ReactNativeVideo/ReactVideoEventTypeExtensions.cs b/windows/ReactNativeVideo/ReactVideoEventTypeExtensions.cs new file mode 100644 index 00000000..5f2b63db --- /dev/null +++ b/windows/ReactNativeVideo/ReactVideoEventTypeExtensions.cs @@ -0,0 +1,36 @@ +using System; +using static System.FormattableString; + +namespace ReactNativeVideo +{ + static class ReactVideoEventTypeExtensions + { + public static string GetEventName(this ReactVideoEventType eventType) + { + switch (eventType) + { + case ReactVideoEventType.LoadStart: + return "onVideoLoadStart"; + case ReactVideoEventType.Load: + return "onVideoLoad"; + case ReactVideoEventType.Error: + return "onVideoError"; + case ReactVideoEventType.Progress: + return "onVideoProgress"; + case ReactVideoEventType.Seek: + return "onVideoSeek"; + case ReactVideoEventType.End: + return "onVideoEnd"; + case ReactVideoEventType.Stalled: + return "onPlaybackStalled"; + case ReactVideoEventType.Resume: + return "onPlaybackResume"; + case ReactVideoEventType.ReadyForDisplay: + return "onReadyForDisplay"; + default: + throw new NotSupportedException( + Invariant($"No event name added for event type '{eventType}'.")); + } + } + } +} diff --git a/windows/ReactNativeVideo/ReactVideoPackage.cs b/windows/ReactNativeVideo/ReactVideoPackage.cs new file mode 100644 index 00000000..b8cc654b --- /dev/null +++ b/windows/ReactNativeVideo/ReactVideoPackage.cs @@ -0,0 +1,30 @@ +using ReactNative.Bridge; +using ReactNative.Modules.Core; +using ReactNative.UIManager; +using System; +using System.Collections.Generic; + +namespace ReactNativeVideo +{ + public class ReactVideoPackage : IReactPackage + { + public IReadOnlyList CreateJavaScriptModulesConfig() + { + return Array.Empty(); + } + + public IReadOnlyList CreateNativeModules(ReactContext reactContext) + { + return Array.Empty(); + + } + + public IReadOnlyList CreateViewManagers(ReactContext reactContext) + { + return new List + { + new ReactVideoViewManager(), + }; + } + } +} diff --git a/windows/ReactNativeVideo/ReactVideoView.cs b/windows/ReactNativeVideo/ReactVideoView.cs new file mode 100644 index 00000000..22063aff --- /dev/null +++ b/windows/ReactNativeVideo/ReactVideoView.cs @@ -0,0 +1,365 @@ +using Newtonsoft.Json.Linq; +using ReactNative.UIManager; +using ReactNative.UIManager.Events; +using System; +using Windows.ApplicationModel.Core; +using Windows.Media.Core; +using Windows.Media.Playback; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace ReactNativeVideo +{ + class ReactVideoView : MediaPlayerElement, IDisposable + { + public const string EVENT_PROP_SEEK_TIME = "seekTime"; + + private readonly DispatcherTimer _timer; + + private bool _isLoopingEnabled; + private bool _isPaused; + private bool _isMuted; + private bool _isUserControlEnabled; + private bool _isCompleted; + private double _volume; + private double _rate; + + public ReactVideoView() + { + _timer = new DispatcherTimer(); + _timer.Interval = TimeSpan.FromMilliseconds(250.0); + _timer.Start(); + } + + public new string Source + { + set + { + var uri = value; + + base.Source = MediaSource.CreateFromUri(new Uri(uri)); + + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.LoadStart.GetEventName(), + this.GetTag(), + new JObject + { + { "src", uri } + })); + + ApplyModifiers(); + SubscribeEvents(); + } + } + + public bool IsLoopingEnabled + { + set + { + _isLoopingEnabled = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.IsLoopingEnabled = _isLoopingEnabled; + } + } + } + + public bool IsMuted + { + set + { + _isMuted = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.IsMuted = _isMuted; + } + } + } + + public bool IsPaused + { + set + { + _isPaused = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + if (_isPaused) + { + mediaPlayer.Pause(); + } + else + { + mediaPlayer.Play(); + } + } + } + } + + public bool IsUserControlEnabled + { + set + { + _isUserControlEnabled = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.SystemMediaTransportControls.IsEnabled = _isUserControlEnabled; + } + } + } + + public double Volume + { + set + { + _volume = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.Volume = _volume; + } + } + } + + public double Rate + { + set + { + _rate = value; + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.PlaybackSession.PlaybackRate = _rate; + } + } + } + + public double ProgressUpdateInterval + { + set + { + _timer.Interval = TimeSpan.FromSeconds(value); + } + } + + public void Seek(double seek) + { + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + mediaPlayer.PlaybackSession.Position = TimeSpan.FromSeconds(seek); + } + } + + public void Dispose() + { + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + _timer.Tick -= OnTick; + mediaPlayer.MediaOpened -= OnMediaOpened; + mediaPlayer.MediaFailed -= OnMediaFailed; + mediaPlayer.MediaEnded -= OnMediaEnded; + mediaPlayer.PlaybackSession.BufferingStarted -= OnBufferingStarted; + mediaPlayer.PlaybackSession.BufferingEnded -= OnBufferingEnded; + MediaPlayer.PlaybackSession.SeekCompleted -= OnSeekCompleted; + } + + _timer.Stop(); + } + + private void ApplyModifiers() + { + IsLoopingEnabled = _isLoopingEnabled; + IsMuted = _isMuted; + IsPaused = _isPaused; + IsUserControlEnabled = _isUserControlEnabled; + Volume = _volume; + Rate = _rate; + } + + private void SubscribeEvents() + { + _timer.Tick += OnTick; + var mediaPlayer = MediaPlayer; + mediaPlayer.MediaOpened += OnMediaOpened; + mediaPlayer.MediaFailed += OnMediaFailed; + mediaPlayer.MediaEnded += OnMediaEnded; + mediaPlayer.PlaybackSession.BufferingStarted += OnBufferingStarted; + mediaPlayer.PlaybackSession.BufferingEnded += OnBufferingEnded; + mediaPlayer.PlaybackSession.SeekCompleted += OnSeekCompleted; + } + + private void OnTick(object sender, object e) + { + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null && !_isCompleted && !_isPaused) + { + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Progress.GetEventName(), + this.GetTag(), + new JObject + { + { "currentTime", mediaPlayer.PlaybackSession.Position.TotalSeconds }, + { "playableDuration", 0.0 /* TODO */ } + })); + } + } + + private void OnMediaOpened(MediaPlayer sender, object args) + { + RunOnDispatcher(delegate + { + var width = sender.PlaybackSession.NaturalVideoWidth; + var height = sender.PlaybackSession.NaturalVideoHeight; + var orientation = (width > height) ? "landscape" : "portrait"; + var size = new JObject + { + { "width", width }, + { "height", height }, + { "orientation", orientation } + }; + + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Load.GetEventName(), + this.GetTag(), + new JObject + { + { "duration", sender.PlaybackSession.NaturalDuration.TotalSeconds }, + { "currentTime", sender.PlaybackSession.Position.TotalSeconds }, + { "naturalSize", size }, + { "canPlayFastForward", false }, + { "canPlaySlowForward", false }, + { "canPlaySlow", false }, + { "canPlayReverse", false }, + { "canStepBackward", false }, + { "canStepForward", false } + })); + }); + } + + private void OnMediaFailed(MediaPlayer sender, MediaPlayerFailedEventArgs args) + { + var errorData = new JObject + { + { "what", args.Error.ToString() }, + { "extra", args.ErrorMessage } + }; + + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Error.GetEventName(), + this.GetTag(), + new JObject + { + { "error", errorData } + })); + } + + private void OnMediaEnded(MediaPlayer sender, object args) + { + _isCompleted = true; + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.End.GetEventName(), + this.GetTag(), + null)); + } + + private void OnBufferingStarted(MediaPlaybackSession sender, object args) + { + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Stalled.GetEventName(), + this.GetTag(), + new JObject())); + } + + private void OnBufferingEnded(MediaPlaybackSession sender, object args) + { + this.GetReactContext() + .GetNativeModule() + .EventDispatcher + .DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Resume.GetEventName(), + this.GetTag(), + new JObject())); + } + + private void OnSeekCompleted(MediaPlaybackSession sender, object args) + { + this.GetReactContext() + .GetNativeModule() + .EventDispatcher.DispatchEvent( + new ReactVideoEvent( + ReactVideoEventType.Seek.GetEventName(), + this.GetTag(), + new JObject())); + } + + private static async void RunOnDispatcher(DispatchedHandler action) + { + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, action).AsTask().ConfigureAwait(false); + } + + class ReactVideoEvent : Event + { + private readonly string _eventName; + private readonly JObject _eventData; + + public ReactVideoEvent(string eventName, int viewTag, JObject eventData) + : base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) + { + _eventName = eventName; + _eventData = eventData; + } + + public override string EventName + { + get + { + return _eventName; + } + } + + public override bool CanCoalesce + { + get + { + return false; + } + } + + public override void Dispatch(RCTEventEmitter eventEmitter) + { + eventEmitter.receiveEvent(ViewTag, EventName, _eventData); + } + } + } +} diff --git a/windows/ReactNativeVideo/ReactVideoViewManager.cs b/windows/ReactNativeVideo/ReactVideoViewManager.cs new file mode 100644 index 00000000..e93456c3 --- /dev/null +++ b/windows/ReactNativeVideo/ReactVideoViewManager.cs @@ -0,0 +1,137 @@ +using Newtonsoft.Json.Linq; +using ReactNative.UIManager; +using ReactNative.UIManager.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace ReactNativeVideo +{ + class ReactVideoViewManager : SimpleViewManager + { + public override string Name + { + get + { + return "RCTVideo"; + } + } + + public override IReadOnlyDictionary ExportedViewConstants + { + get + { + return new Dictionary + { + { "ScaleNone", ((int)Stretch.None).ToString() }, + { "ScaleToFill", ((int)Stretch.UniformToFill).ToString() }, + { "ScaleAspectFit", ((int)Stretch.Uniform).ToString() }, + { "ScaleAspectFill", ((int)Stretch.Fill).ToString() }, + }; + } + } + + public override IReadOnlyDictionary ExportedCustomDirectEventTypeConstants + { + get + { + var events = new Dictionary(); + var eventTypes = Enum.GetValues(typeof(ReactVideoEventType)).OfType(); + foreach (var eventType in eventTypes) + { + events.Add(eventType.GetEventName(), new Dictionary + { + { "registrationName", eventType.GetEventName() }, + }); + } + + return events; + } + } + + [ReactProp("src")] + public void SetSource(ReactVideoView view, JObject source) + { + view.Source = source.Value("uri"); + } + + [ReactProp("resizeMode")] + public void SetResizeMode(ReactVideoView view, string resizeMode) + { + view.Stretch = (Stretch)int.Parse(resizeMode); + } + + [ReactProp("repeat")] + public void SetRepeat(ReactVideoView view, bool repeat) + { + view.IsLoopingEnabled = repeat; + } + + [ReactProp("paused")] + public void SetPaused(ReactVideoView view, bool paused) + { + view.IsPaused = paused; + } + + [ReactProp("muted")] + public void SetMuted(ReactVideoView view, bool muted) + { + view.IsMuted = muted; + } + + [ReactProp("volume", DefaultDouble = 1.0)] + public void SetVolume(ReactVideoView view, double volume) + { + view.Volume = volume; + } + + [ReactProp("seek")] + public void SetSeek(ReactVideoView view, double? seek) + { + if (seek.HasValue) + { + view.Seek(seek.Value); + } + } + + [ReactProp("rate", DefaultDouble = 1.0)] + public void SetPlaybackRate(ReactVideoView view, double rate) + { + view.Rate = rate; + } + + [ReactProp("playInBackground")] + public void SetPlayInBackground(ReactVideoView view, bool playInBackground) + { + throw new NotImplementedException("Play in background has not been implemented on Windows."); + } + + [ReactProp("controls")] + public void SetControls(ReactVideoView view, bool controls) + { + view.IsUserControlEnabled = controls; + } + + [ReactProp("progressUpdateInterval")] + public void SetProgressUpdateInterval(ReactVideoView view, double progressUpdateInterval) + { + view.ProgressUpdateInterval = progressUpdateInterval; + } + + public override void OnDropViewInstance(ThemedReactContext reactContext, ReactVideoView view) + { + base.OnDropViewInstance(reactContext, view); + view.Dispose(); + } + + protected override ReactVideoView CreateViewInstance(ThemedReactContext reactContext) + { + return new ReactVideoView + { + HorizontalAlignment = HorizontalAlignment.Stretch, + }; + } + } +} diff --git a/windows/ReactNativeVideo/project.json b/windows/ReactNativeVideo/project.json new file mode 100644 index 00000000..21494af7 --- /dev/null +++ b/windows/ReactNativeVideo/project.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.2", + "Newtonsoft.Json": "9.0.1" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file