#include "pch.h" #include "ReactVideoView.h" #include "ReactVideoView.g.cpp" #include "NativeModules.h" using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Core; using namespace Windows::Media::Core; using namespace Windows::Media::Playback; namespace winrt::ReactNativeVideoCPP::implementation { ReactVideoView::ReactVideoView(winrt::Microsoft::ReactNative::IReactContext const& reactContext) : m_reactContext(reactContext){ // always create and set the player here instead of depending on auto-create logic // in the MediaPlayerElement (only when auto play is on or URI is set) m_player = winrt::Windows::Media::Playback::MediaPlayer(); SetMediaPlayer(m_player); m_uiDispatcher = CoreWindow::GetForCurrentThread().Dispatcher(); m_mediaOpenedToken = m_player.MediaOpened(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnMediaOpened(sender, args); } }); m_mediaFailedToken = m_player.MediaFailed(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnMediaFailed(sender, args); } }); m_mediaEndedToken = m_player.MediaEnded(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnMediaEnded(sender, args); } }); m_bufferingStartedToken = m_player.PlaybackSession().BufferingStarted(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnBufferingStarted(sender, args); } }); m_bufferingEndedToken = m_player.PlaybackSession().BufferingEnded(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnBufferingEnded(sender, args); } }); m_seekCompletedToken = m_player.PlaybackSession().SeekCompleted(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) { if (auto self = ref.get()) { self->OnSeekCompleted(sender, args); } }); m_timer = Windows::UI::Xaml::DispatcherTimer(); m_timer.Interval(std::chrono::milliseconds{ 250 }); m_timer.Start(); auto token = m_timer.Tick([ref = get_weak()](auto const&, auto const&) { if (auto self = ref.get()){ if (auto mediaPlayer = self->m_player) { if (mediaPlayer.PlaybackSession().PlaybackState() == winrt::Windows::Media::Playback::MediaPlaybackState::Playing) { auto currentTimeInSeconds = mediaPlayer.PlaybackSession().Position().count() / 10000000; self->m_reactContext.DispatchEvent( *self, L"topProgress", [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { eventDataWriter.WriteObjectBegin(); { WriteProperty(eventDataWriter, L"currentTime", currentTimeInSeconds); WriteProperty(eventDataWriter, L"playableDuration", 0.0); } eventDataWriter.WriteObjectEnd(); }); } } } }); } void ReactVideoView::OnMediaOpened(IInspectable const&, IInspectable const&) { runOnQueue([weak_this{ get_weak() }]() { if (auto strong_this{ weak_this.get() }) { if (auto mediaPlayer = strong_this->m_player) { auto width = mediaPlayer.PlaybackSession().NaturalVideoWidth(); auto height = mediaPlayer.PlaybackSession().NaturalVideoHeight(); auto orientation = (width > height) ? L"landscape" : L"portrait"; auto durationInSeconds = mediaPlayer.PlaybackSession().NaturalDuration().count() / 10000000; auto currentTimeInSeconds = mediaPlayer.PlaybackSession().Position().count() / 10000000; strong_this->m_reactContext.DispatchEvent( *strong_this, L"topLoad", [&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept { eventDataWriter.WriteObjectBegin(); { WriteProperty(eventDataWriter, L"duration", durationInSeconds); WriteProperty(eventDataWriter, L"currentTime", currentTimeInSeconds); eventDataWriter.WritePropertyName(L"naturalSize"); { eventDataWriter.WriteObjectBegin(); WriteProperty(eventDataWriter, L"width", width); WriteProperty(eventDataWriter, L"height", height); WriteProperty(eventDataWriter, L"orientation", orientation); WriteProperty(eventDataWriter, L"orientation", orientation); eventDataWriter.WriteObjectEnd(); } WriteProperty(eventDataWriter, L"canPlayFastForward", false); WriteProperty(eventDataWriter, L"canPlaySlowForward", false); WriteProperty(eventDataWriter, L"canPlaySlow", false); WriteProperty(eventDataWriter, L"canStepBackward", false); WriteProperty(eventDataWriter, L"canStepForward", false); } eventDataWriter.WriteObjectEnd(); }); } } }); } void ReactVideoView::OnMediaFailed(IInspectable const&, IInspectable const&) {} void ReactVideoView::OnMediaEnded(IInspectable const&, IInspectable const&) { runOnQueue([weak_this{ get_weak() }]() { if (auto strong_this{ weak_this.get() }) { strong_this->m_reactContext.DispatchEvent(*strong_this, L"topEnd", nullptr); } }); } void ReactVideoView::OnBufferingStarted(IInspectable const&, IInspectable const&) {} void ReactVideoView::OnBufferingEnded(IInspectable const&, IInspectable const&) {} void ReactVideoView::OnSeekCompleted(IInspectable const&, IInspectable const&) { runOnQueue([weak_this{ get_weak() }]() { if (auto strong_this{ weak_this.get() }) { strong_this->m_reactContext.DispatchEvent(*strong_this, L"topSeek", nullptr); } }); } void ReactVideoView::Set_ProgressUpdateInterval(int64_t interval) { m_timer.Interval(std::chrono::milliseconds{ interval }); } void ReactVideoView::Set_IsLoopingEnabled(bool value) { m_isLoopingEnabled = value; if (m_player != nullptr) { m_player.IsLoopingEnabled(m_isLoopingEnabled); } } void ReactVideoView::Set_UriString(hstring const& value) { m_uriString = value; if (m_player != nullptr) { auto uri = Uri(m_uriString); m_player.Source(MediaSource::CreateFromUri(uri)); } } void ReactVideoView::Set_Paused(bool value) { m_isPaused = value; if (m_player != nullptr) { if (m_isPaused) { if (IsPlaying(m_player.PlaybackSession().PlaybackState())) { m_player.Pause(); } } else { if (!IsPlaying(m_player.PlaybackSession().PlaybackState())) { m_player.Play(); } } } } void ReactVideoView::Set_AutoPlay(bool autoPlay) { m_player.AutoPlay(autoPlay); } void ReactVideoView::Set_Muted(bool isMuted) { m_isMuted = isMuted; if (m_player != nullptr) { m_player.IsMuted(m_isMuted); } } void ReactVideoView::Set_Controls(bool useControls) { m_useControls = useControls; AreTransportControlsEnabled(m_useControls); } void ReactVideoView::Set_FullScreen(bool fullScreen) { m_fullScreen = fullScreen; IsFullWindow(m_fullScreen); if (m_fullScreen) { Set_Controls(true); // full window will always have transport control enabled } } void ReactVideoView::Set_Volume(double volume) { m_volume = volume; if (m_player != nullptr) { m_player.Volume(m_volume); } } void ReactVideoView::Set_Position(double position) { m_position = position; if (m_player != nullptr) { std::chrono::seconds duration(static_cast(m_position)); m_player.PlaybackSession().Position(duration); } } bool ReactVideoView::IsPlaying(MediaPlaybackState currentState) { return ( currentState == MediaPlaybackState::Buffering || currentState == MediaPlaybackState::Opening || currentState == MediaPlaybackState::Playing ); } void ReactVideoView::runOnQueue(std::function&& func) { m_uiDispatcher.RunAsync( winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [func = std::move(func)]() { func(); }); } } // namespace winrt::ReactNativeVideoCPP::implementation