#include "pch.h" #include "VideoPlayerControl.xaml.h" #if __has_include("VideoPlayerControl.g.cpp") #include "VideoPlayerControl.g.cpp" #endif #include using namespace winrt; using namespace winrt::Microsoft::UI::Xaml; using namespace winrt::Microsoft::UI::Xaml::Controls; using namespace winrt::Microsoft::UI::Dispatching; namespace winrt::Vav2Player::implementation { VideoPlayerControl::VideoPlayerControl() { InitializeComponent(); } // Event Handlers void VideoPlayerControl::UserControl_Loaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { try { m_isInitialized = true; UpdateStatus(L"Ready"); // Set initial UI state if (!m_showControls) { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } // Auto load video if source is set if (!m_videoSource.empty()) { LoadVideo(m_videoSource); } OutputDebugStringA("VideoPlayerControl loaded successfully\n"); } catch (...) { UpdateStatus(L"Error during initialization"); } } void VideoPlayerControl::UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { try { Stop(); StopPlaybackTimer(); StopControlsHideTimer(); // Cleanup resources m_decoder.reset(); m_fileReader.reset(); m_renderBitmap = nullptr; m_isInitialized = false; OutputDebugStringA("VideoPlayerControl unloaded\n"); } catch (...) { // Ignore cleanup errors } } void VideoPlayerControl::PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { if (m_isPlaying) { Pause(); } else { Play(); } } void VideoPlayerControl::StopButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { Stop(); } void VideoPlayerControl::ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e) { if (!m_isLoaded || !m_fileReader) return; // Avoid feedback loop during programmatic updates if (e.OldValue() == e.NewValue()) return; try { double percentage = e.NewValue(); double targetTime = (percentage / 100.0) * m_duration; Seek(targetTime); } catch (...) { // Ignore seek errors } } void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&) { if (m_showControls && m_isLoaded) { ShowControlsInternal(); StopControlsHideTimer(); } } void VideoPlayerControl::HoverDetector_PointerExited(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&) { if (m_showControls && m_isLoaded && m_isPlaying) { StartControlsHideTimer(); } } // Public Properties winrt::hstring VideoPlayerControl::VideoSource() { return m_videoSource; } void VideoPlayerControl::VideoSource(winrt::hstring const& value) { if (m_videoSource != value) { m_videoSource = value; if (m_isInitialized && !value.empty()) { LoadVideo(value); } } } bool VideoPlayerControl::ShowControls() { return m_showControls; } void VideoPlayerControl::ShowControls(bool value) { m_showControls = value; if (m_isInitialized) { if (value && m_isLoaded) { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } else { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } } } bool VideoPlayerControl::AutoPlay() { return m_autoPlay; } void VideoPlayerControl::AutoPlay(bool value) { m_autoPlay = value; } Vav2Player::VideoDecoderType VideoPlayerControl::DecoderType() { switch (m_decoderType) { case VideoDecoderFactory::DecoderType::AUTO: return Vav2Player::VideoDecoderType::Auto; case VideoDecoderFactory::DecoderType::SOFTWARE: return Vav2Player::VideoDecoderType::Software; case VideoDecoderFactory::DecoderType::HARDWARE_MF: return Vav2Player::VideoDecoderType::HardwareMF; default: return Vav2Player::VideoDecoderType::Auto; } } void VideoPlayerControl::DecoderType(Vav2Player::VideoDecoderType value) { VideoDecoderFactory::DecoderType newType; switch (value) { case Vav2Player::VideoDecoderType::Auto: newType = VideoDecoderFactory::DecoderType::AUTO; break; case Vav2Player::VideoDecoderType::Software: newType = VideoDecoderFactory::DecoderType::SOFTWARE; break; case Vav2Player::VideoDecoderType::HardwareMF: newType = VideoDecoderFactory::DecoderType::HARDWARE_MF; break; default: newType = VideoDecoderFactory::DecoderType::AUTO; break; } SetInternalDecoderType(newType); } VideoDecoderFactory::DecoderType VideoPlayerControl::GetInternalDecoderType() { return m_decoderType; } void VideoPlayerControl::SetInternalDecoderType(VideoDecoderFactory::DecoderType value) { if (m_decoderType != value) { m_decoderType = value; // Reset decoder if currently loaded if (m_isLoaded) { m_decoder.reset(); CreateDecoder(); } } } // Public Methods void VideoPlayerControl::LoadVideo(winrt::hstring const& filePath) { try { std::string filePathStr = winrt::to_string(filePath); UpdateStatus(L"Loading video..."); LoadingRing().IsActive(true); // Reset previous state ResetVideoState(); // Create file reader if (!m_fileReader) { m_fileReader = std::make_unique(); } // Open file if (!m_fileReader->OpenFile(filePathStr)) { UpdateStatus(L"Failed to open video file"); LoadingRing().IsActive(false); return; } // Get video tracks auto tracks = m_fileReader->GetVideoTracks(); if (tracks.empty()) { UpdateStatus(L"No video tracks found"); LoadingRing().IsActive(false); return; } // Select first video track if (!m_fileReader->SelectVideoTrack(tracks[0].track_number)) { UpdateStatus(L"Failed to select video track"); LoadingRing().IsActive(false); return; } // Get metadata auto metadata = m_fileReader->GetVideoMetadata(); m_totalFrames = metadata.total_frames; m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0; m_duration = m_totalFrames / m_frameRate; // Create and initialize decoder if (!CreateDecoder() || !InitializeDecoder()) { UpdateStatus(L"Failed to initialize decoder"); LoadingRing().IsActive(false); return; } // Initialize video renderer InitializeVideoRenderer(); // Update UI state m_isLoaded = true; LoadingRing().IsActive(false); PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); if (m_showControls) { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } // Update duration display DurationText().Text(winrt::to_hstring(FormatTime(m_duration))); UpdateStatus(L"Video loaded successfully"); OutputDebugStringA(("Video loaded: " + filePathStr + "\n").c_str()); // Auto play if enabled if (m_autoPlay) { Play(); } } catch (...) { UpdateStatus(L"Error loading video"); LoadingRing().IsActive(false); } } void VideoPlayerControl::Play() { if (!m_isLoaded || m_isPlaying) return; try { m_isPlaying = true; UpdatePlaybackUI(); StartPlaybackTimer(); UpdateStatus(L"Playing"); if (m_showControls && m_isPlaying) { StartControlsHideTimer(); } OutputDebugStringA("Playback started\n"); } catch (...) { UpdateStatus(L"Error starting playback"); } } void VideoPlayerControl::Pause() { if (!m_isPlaying) return; try { m_isPlaying = false; StopPlaybackTimer(); UpdatePlaybackUI(); UpdateStatus(L"Paused"); if (m_showControls) { ShowControlsInternal(); StopControlsHideTimer(); } OutputDebugStringA("Playback paused\n"); } catch (...) { UpdateStatus(L"Error pausing playback"); } } void VideoPlayerControl::Stop() { if (!m_isLoaded) return; try { m_isPlaying = false; StopPlaybackTimer(); StopControlsHideTimer(); // Reset to beginning m_currentFrame = 0; m_currentTime = 0.0; if (m_fileReader) { m_fileReader->Reset(); } if (m_decoder) { m_decoder->Reset(); } UpdatePlaybackUI(); UpdateProgress(); UpdateStatus(L"Stopped"); if (m_showControls) { ShowControlsInternal(); } OutputDebugStringA("Playback stopped\n"); } catch (...) { UpdateStatus(L"Error stopping playback"); } } void VideoPlayerControl::Seek(double timeSeconds) { if (!m_isLoaded || !m_fileReader) return; try { bool wasPlaying = m_isPlaying; if (wasPlaying) { StopPlaybackTimer(); } // Calculate target frame uint64_t targetFrame = static_cast(timeSeconds * m_frameRate); targetFrame = std::min(targetFrame, m_totalFrames - 1); // Seek in file reader if (m_fileReader->SeekToFrame(targetFrame)) { m_currentFrame = targetFrame; m_currentTime = targetFrame / m_frameRate; if (m_decoder) { m_decoder->Reset(); } UpdateProgress(); ProcessSingleFrame(); // Render frame at seek position } if (wasPlaying) { StartPlaybackTimer(); } OutputDebugStringA(("Seeked to time: " + std::to_string(timeSeconds) + "s\n").c_str()); } catch (...) { UpdateStatus(L"Error during seek"); } } // Status Properties bool VideoPlayerControl::IsVideoPlaying() { return m_isPlaying; } bool VideoPlayerControl::IsVideoLoaded() { return m_isLoaded; } double VideoPlayerControl::CurrentTime() { return m_currentTime; } double VideoPlayerControl::Duration() { return m_duration; } winrt::hstring VideoPlayerControl::Status() { return m_status; } // Helper Methods void VideoPlayerControl::InitializeVideoRenderer() { try { if (!m_fileReader || !m_fileReader->IsFileOpen()) return; auto metadata = m_fileReader->GetVideoMetadata(); int width = metadata.width; int height = metadata.height; if (width <= 0 || height <= 0) return; // Create bitmap for rendering m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(width, height); m_bgraBuffer.resize(width * height * 4); // Set as image source VideoImage().Source(m_renderBitmap); OutputDebugStringA(("Video renderer initialized: " + std::to_string(width) + "x" + std::to_string(height) + "\n").c_str()); } catch (...) { UpdateStatus(L"Error initializing video renderer"); } } void VideoPlayerControl::ProcessSingleFrame() { if (!m_fileReader || !m_decoder || !m_fileReader->IsFileOpen()) return; try { VideoPacket packet; if (!m_fileReader->ReadNextPacket(packet)) { // End of file if (m_isPlaying) { Stop(); UpdateStatus(L"Playback completed"); } return; } VideoFrame frame; bool decodeSuccess = m_decoder->DecodeFrame(packet, frame); if (decodeSuccess) { m_currentFrame++; m_currentTime = m_currentFrame / m_frameRate; RenderFrameToScreen(frame); UpdateProgress(); } } catch (...) { // Continue playback on frame errors } } void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame) { if (!frame.is_valid || !m_renderBitmap) return; try { // Convert YUV to BGRA ConvertYUVToBGRA(frame, m_bgraBuffer.data(), frame.width, frame.height); // Update bitmap auto buffer = m_renderBitmap.PixelBuffer(); auto byteAccess = buffer.as<::IBufferByteAccess>(); uint8_t* pixels = nullptr; byteAccess->Buffer(&pixels); if (pixels) { memcpy(pixels, m_bgraBuffer.data(), m_bgraBuffer.size()); m_renderBitmap.Invalidate(); } } catch (...) { // Ignore rendering errors } } void VideoPlayerControl::ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height) { const uint8_t* y_plane = yuv_frame.y_plane.get(); const uint8_t* u_plane = yuv_frame.u_plane.get(); const uint8_t* v_plane = yuv_frame.v_plane.get(); const int32_t y_stride = yuv_frame.y_stride; const int32_t u_stride = yuv_frame.u_stride; const int32_t v_stride = yuv_frame.v_stride; for (uint32_t row = 0; row < height; ++row) { for (uint32_t col = 0; col < width; ++col) { int32_t y = y_plane[row * y_stride + col]; int32_t u = u_plane[(row / 2) * u_stride + (col / 2)]; int32_t v = v_plane[(row / 2) * v_stride + (col / 2)]; int c = y - 16; int d = u - 128; int e = v - 128; int r = std::clamp((298 * c + 409 * e + 128) >> 8, 0, 255); int g = std::clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255); int b = std::clamp((298 * c + 516 * d + 128) >> 8, 0, 255); uint32_t pixel_offset = (row * width + col) * 4; bgra_buffer[pixel_offset + 0] = static_cast(b); bgra_buffer[pixel_offset + 1] = static_cast(g); bgra_buffer[pixel_offset + 2] = static_cast(r); bgra_buffer[pixel_offset + 3] = 255; } } } void VideoPlayerControl::UpdateStatus(winrt::hstring const& message) { m_status = message; StatusText().Text(message); // Show status overlay briefly StatusOverlay().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); // Auto-hide after 2 seconds auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); auto timer = dispatcherQueue.CreateTimer(); timer.Interval(std::chrono::seconds(2)); timer.Tick([this](auto&&, auto&&) { StatusOverlay().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); }); timer.Start(); } void VideoPlayerControl::UpdatePlaybackUI() { // Update play/pause button if (m_isPlaying) { PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Pause); } else { PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Play); } // Update current time display CurrentTimeText().Text(winrt::to_hstring(FormatTime(m_currentTime))); } void VideoPlayerControl::UpdateProgress() { if (m_duration > 0) { double progress = (m_currentTime / m_duration) * 100.0; ProgressSlider().Value(progress); } } void VideoPlayerControl::StartPlaybackTimer() { if (m_playbackTimer) { m_playbackTimer.Stop(); } auto intervalMs = std::chrono::milliseconds(static_cast(1000.0 / m_frameRate)); auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); m_playbackTimer = dispatcherQueue.CreateTimer(); m_playbackTimer.Tick([this](auto&&, auto&&) { if (m_isPlaying) { ProcessSingleFrame(); UpdatePlaybackUI(); } }); m_playbackTimer.Interval(intervalMs); m_playbackTimer.Start(); } void VideoPlayerControl::StopPlaybackTimer() { if (m_playbackTimer) { m_playbackTimer.Stop(); } } void VideoPlayerControl::ShowControlsInternal() { if (m_showControls && m_isLoaded) { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } } void VideoPlayerControl::HideControls() { if (m_showControls && m_isPlaying) { ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } } void VideoPlayerControl::StartControlsHideTimer() { StopControlsHideTimer(); auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); m_controlsHideTimer = dispatcherQueue.CreateTimer(); m_controlsHideTimer.Interval(std::chrono::seconds(3)); m_controlsHideTimer.Tick([this](auto&&, auto&&) { HideControls(); }); m_controlsHideTimer.Start(); } void VideoPlayerControl::StopControlsHideTimer() { if (m_controlsHideTimer) { m_controlsHideTimer.Stop(); } } std::string VideoPlayerControl::FormatTime(double seconds) { int mins = static_cast(seconds) / 60; int secs = static_cast(seconds) % 60; char buffer[16]; sprintf_s(buffer, "%02d:%02d", mins, secs); return std::string(buffer); } void VideoPlayerControl::ResetVideoState() { m_isLoaded = false; m_isPlaying = false; m_currentFrame = 0; m_totalFrames = 0; m_currentTime = 0.0; m_duration = 0.0; StopPlaybackTimer(); StopControlsHideTimer(); if (m_decoder) { m_decoder.reset(); } if (m_fileReader) { m_fileReader.reset(); } m_renderBitmap = nullptr; VideoImage().Source(nullptr); PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } bool VideoPlayerControl::CreateDecoder() { try { if (!m_fileReader) return false; auto metadata = m_fileReader->GetVideoMetadata(); m_decoder = VideoDecoderFactory::CreateDecoder(metadata.codec_type, m_decoderType); return m_decoder != nullptr; } catch (...) { return false; } } bool VideoPlayerControl::InitializeDecoder() { try { if (!m_decoder || !m_fileReader) return false; auto metadata = m_fileReader->GetVideoMetadata(); return m_decoder->Initialize(metadata); } catch (...) { return false; } } }