#include "pch.h" #include "VideoPlayerControl2.xaml.h" #if __has_include("VideoPlayerControl2.g.cpp") #include "VideoPlayerControl2.g.cpp" #endif #include #include #include // D3D12 for GPU surface decoding #include #include using Microsoft::WRL::ComPtr; // Include log manager for logging #include "src/Logger/LogManager.h" // Using alias to avoid namespace conflicts using LogMgr = Vav2Player::LogManager; using namespace winrt; using namespace winrt::Microsoft::UI::Xaml; using namespace winrt::Microsoft::UI::Xaml::Controls; namespace winrt::Vav2Player::implementation { VideoPlayerControl2::VideoPlayerControl2() { try { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Constructor START - creating core components"); // Create core components m_playbackController = std::make_unique<::Vav2Player::PlaybackController>(); m_frameProcessor = std::make_unique<::Vav2Player::FrameProcessor>(); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Constructor END - core components created successfully"); } catch (const std::exception& e) { std::wstring error = L"Constructor exception: " + std::wstring(e.what(), e.what() + strlen(e.what())); LogMgr::GetInstance().LogError(L"VideoPlayerControl2", error); throw; } catch (...) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Constructor unknown exception"); throw; } } VideoPlayerControl2::~VideoPlayerControl2() { // DO NOT LOG in destructor - DispatcherQueue may already be shut down // This causes crashes when LogMessagePage tries to access UI thread // Stop playback and cleanup if (m_playbackController) { m_playbackController->Stop(); } CleanupRenderer(); } // ======================================== // XAML Event Handlers // ======================================== void VideoPlayerControl2::UserControl_Loaded(IInspectable const&, RoutedEventArgs const&) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"UserControl_Loaded"); try { // Initialize renderer with SwapChainPanel InitializeRenderer(); // Connect FrameProcessor to renderer and dispatcher if (m_frameProcessor && m_gpuRenderer) { m_frameProcessor->SetRenderer(m_gpuRenderer.get()); m_frameProcessor->SetDispatcherQueue(DispatcherQueue()); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"FrameProcessor configured"); } m_initialized = true; UpdateStatus(L"Ready"); } catch (const std::exception& e) { std::wstring error = L"Initialization failed: " + std::wstring(e.what(), e.what() + strlen(e.what())); LogMgr::GetInstance().LogError(L"VideoPlayerControl2", error); UpdateStatus(L"Initialization failed"); m_initialized = false; // Ensure we stay in uninitialized state on failure } catch (...) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Initialization failed: unknown exception"); UpdateStatus(L"Initialization failed"); m_initialized = false; } } void VideoPlayerControl2::UserControl_Unloaded(IInspectable const&, RoutedEventArgs const&) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"UserControl_Unloaded"); // Stop playback if (m_playbackController) { m_playbackController->Stop(); m_playbackController->Unload(); } // Cleanup renderer CleanupRenderer(); m_initialized = false; } void VideoPlayerControl2::UserControl_SizeChanged(IInspectable const&, SizeChangedEventArgs const& e) { auto newSize = e.NewSize(); auto oldSize = e.PreviousSize(); // Ignore trivial size changes (< 1 pixel) to avoid unnecessary work if (std::abs(newSize.Width - oldSize.Width) < 1.0 && std::abs(newSize.Height - oldSize.Height) < 1.0) { return; } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"SizeChanged: " + std::to_wstring((int)newSize.Width) + L"x" + std::to_wstring((int)newSize.Height)); // Update renderer size if (m_gpuRenderer && m_gpuRenderer->IsInitialized()) { HRESULT hr = m_gpuRenderer->Resize( static_cast(newSize.Width), static_cast(newSize.Height) ); if (FAILED(hr)) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to resize renderer"); } } // Update AspectFit if video is loaded if (m_playbackController && m_playbackController->IsLoaded()) { UpdateVideoImageAspectFit( m_playbackController->GetVideoWidth(), m_playbackController->GetVideoHeight() ); } } void VideoPlayerControl2::HoverDetector_PointerEntered(IInspectable const&, Input::PointerRoutedEventArgs const&) { // Future: Show video controls on hover } void VideoPlayerControl2::HoverDetector_PointerExited(IInspectable const&, Input::PointerRoutedEventArgs const&) { // Future: Hide video controls } // ======================================== // Public Properties // ======================================== winrt::hstring VideoPlayerControl2::VideoSource() { return m_videoSource; } void VideoPlayerControl2::VideoSource(winrt::hstring const& value) { if (m_videoSource != value) { m_videoSource = value; if (!value.empty()) { if (m_initialized) { LoadVideo(value); } else { LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"VideoSource set before initialization, will load on Loaded event"); } } else { // Empty source - unload current video if (m_playbackController && m_playbackController->IsLoaded()) { Stop(); m_playbackController->Unload(); UpdateStatus(L"No video"); } } } } bool VideoPlayerControl2::ShowControls() { return m_showControls; } void VideoPlayerControl2::ShowControls(bool value) { m_showControls = value; } bool VideoPlayerControl2::AutoPlay() { return m_autoPlay; } void VideoPlayerControl2::AutoPlay(bool value) { m_autoPlay = value; } Vav2Player::VideoDecoderType VideoPlayerControl2::DecoderType() { if (!m_playbackController) { return static_cast(0); // Auto } VavCoreDecoderType coreType = m_playbackController->GetDecoderType(); // Convert VavCoreDecoderType to VideoDecoderType // Use switch on C enum (compile-time constant), return WinRT enum as integer // WinRT enum values: Auto=0, NVDEC=1, VPL=2, AMF=3, DAV1D=4, MediaFoundation=5 switch (coreType) { case VAVCORE_DECODER_NVDEC: return static_cast(1); // NVDEC case VAVCORE_DECODER_VPL: return static_cast(2); // VPL case VAVCORE_DECODER_AMF: return static_cast(3); // AMF case VAVCORE_DECODER_DAV1D: return static_cast(4); // DAV1D case VAVCORE_DECODER_MEDIA_FOUNDATION: return static_cast(5); // MediaFoundation default: return static_cast(0); // Auto } } void VideoPlayerControl2::DecoderType(Vav2Player::VideoDecoderType value) { if (!m_playbackController) { return; } // Convert VideoDecoderType to VavCoreDecoderType // WinRT enums are projected as integers: Auto=0, NVDEC=1, VPL=2, AMF=3, DAV1D=4, MediaFoundation=5 VavCoreDecoderType coreType = VAVCORE_DECODER_AUTO; int32_t enumValue = static_cast(value); if (enumValue == 1) { // NVDEC coreType = VAVCORE_DECODER_NVDEC; } else if (enumValue == 2) { // VPL coreType = VAVCORE_DECODER_VPL; } else if (enumValue == 3) { // AMF coreType = VAVCORE_DECODER_AMF; } else if (enumValue == 4) { // DAV1D coreType = VAVCORE_DECODER_DAV1D; } else if (enumValue == 5) { // MediaFoundation coreType = VAVCORE_DECODER_MEDIA_FOUNDATION; } m_playbackController->SetDecoderType(coreType); } // ======================================== // Public Methods // ======================================== void VideoPlayerControl2::LoadVideo(winrt::hstring const& filePath) { if (!m_initialized) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Cannot load video: not initialized"); return; } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Loading video: " + std::wstring(filePath)); UpdateStatus(L"Loading..."); // Stop current playback if (m_playbackController->IsPlaying()) { m_playbackController->Stop(); } // --- Start of Corrected Initialization Order --- // 1. Pass D3D12 device to the PlaybackController (with null check) if (m_gpuRenderer) { ID3D12Device* d3dDevice = m_gpuRenderer->GetD3D12Device(); if (d3dDevice) { m_playbackController->SetD3DDevice(d3dDevice, VAVCORE_SURFACE_D3D12_RESOURCE); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"D3D12 device will be set by PlaybackController."); } else { LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"D3D12 device is null"); } } else { LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"Cannot set D3D device: renderer not initialized"); } // 2. Load the video file. VavCore will now see the pending D3D device // and initialize the decoder in the correct GPU-accelerated mode. std::wstring filePathStr(filePath.c_str()); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"About to call LoadVideo with path: " + std::wstring(filePath)); // Safety check: Verify m_playbackController is valid if (!m_playbackController) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"CRITICAL: m_playbackController is nullptr!"); UpdateStatus(L"Load failed - internal error"); return; } bool success = m_playbackController->LoadVideo(filePathStr); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", success ? L"LoadVideo returned TRUE" : L"LoadVideo returned FALSE"); if (success) { // 3. Get video dimensions (which are now available). uint32_t videoWidth = m_playbackController->GetVideoWidth(); uint32_t videoHeight = m_playbackController->GetVideoHeight(); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Video loaded: " + std::to_wstring(videoWidth) + L"x" + std::to_wstring(videoHeight)); // Link FrameProcessor to PlaybackController for timing synchronization if (m_frameProcessor) { m_playbackController->SetFrameProcessor(m_frameProcessor.get()); m_frameProcessor->SetDecoderType(m_playbackController->GetDecoderType()); m_frameProcessor->PrepareVideoTexture(videoWidth, videoHeight); } // Update AspectFit UpdateVideoImageAspectFit(videoWidth, videoHeight); UpdateStatus(L"Loaded"); // Auto-play if enabled if (m_autoPlay) { Play(); } } else { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to load video"); UpdateStatus(L"Load failed"); } } void VideoPlayerControl2::Play() { if (!m_initialized || !m_playbackController->IsLoaded()) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Cannot play: video not loaded"); return; } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Starting playback"); // Start playback with frame-ready callback auto weakThis = get_weak(); m_playbackController->Play([weakThis]() { if (auto strongThis = weakThis.get()) { strongThis->OnFrameReady(); } }); UpdateStatus(L"Playing"); } void VideoPlayerControl2::Pause() { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Pausing playback"); m_playbackController->Pause(); UpdateStatus(L"Paused"); } void VideoPlayerControl2::Stop() { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Stopping playback"); m_playbackController->Stop(); UpdateStatus(L"Stopped"); } void VideoPlayerControl2::Seek(double timeSeconds) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Seeking to " + std::to_wstring(timeSeconds) + L"s"); m_playbackController->Seek(timeSeconds); } // ======================================== // Status Queries // ======================================== bool VideoPlayerControl2::IsVideoPlaying() { return m_playbackController->IsPlaying(); } bool VideoPlayerControl2::IsVideoLoaded() { return m_playbackController->IsLoaded(); } double VideoPlayerControl2::CurrentTime() { return m_playbackController->GetCurrentTime(); } double VideoPlayerControl2::Duration() { return m_playbackController->GetDuration(); } winrt::hstring VideoPlayerControl2::Status() { return m_status; } double VideoPlayerControl2::PlaybackSpeed() { return m_playbackController->GetPlaybackSpeed(); } void VideoPlayerControl2::SetPlaybackSpeed(double speed) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", std::wstring(L"SetPlaybackSpeed: ") + std::to_wstring(speed) + L"x"); m_playbackController->SetPlaybackSpeed(speed); } // ======================================== // Private Helper Methods // ======================================== void VideoPlayerControl2::InitializeRenderer() { // Cleanup existing renderer if not properly initialized if (m_gpuRenderer) { if (m_gpuRenderer->IsInitialized()) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Renderer already initialized"); return; } else { LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"Cleaning up partially initialized renderer"); CleanupRenderer(); } } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Initializing renderer"); m_gpuRenderer = std::make_unique<::Vav2Player::D3D12VideoRenderer>(); // Get SwapChainPanel size auto panelSize = VideoSwapChainPanel().ActualSize(); uint32_t width = static_cast(panelSize.x); uint32_t height = static_cast(panelSize.y); // Initialize with SwapChainPanel HRESULT hr = m_gpuRenderer->InitializeWithSwapChain( VideoSwapChainPanel(), width > 0 ? width : 1920, height > 0 ? height : 1080 ); if (SUCCEEDED(hr)) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Renderer initialized successfully"); } else { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to initialize renderer"); m_gpuRenderer.reset(); } } void VideoPlayerControl2::CleanupRenderer() { if (m_gpuRenderer) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Cleaning up renderer"); m_gpuRenderer->Shutdown(); m_gpuRenderer.reset(); } } void VideoPlayerControl2::UpdateStatus(winrt::hstring const& message) { m_status = message; // Update UI on UI thread DispatcherQueue().TryEnqueue([weakThis = get_weak(), message]() { if (auto strongThis = weakThis.get()) { strongThis->StatusText().Text(message); strongThis->StatusOverlay().Visibility(Visibility::Visible); } }); } void VideoPlayerControl2::UpdateVideoImageAspectFit(int videoWidth, int videoHeight) { if (videoWidth == 0 || videoHeight == 0) { return; } // Get container size auto containerSize = VideoSwapChainPanel().ActualSize(); double containerWidth = containerSize.x; double containerHeight = containerSize.y; if (containerWidth == 0 || containerHeight == 0) { return; } // Calculate AspectFit dimensions double videoAspectRatio = static_cast(videoWidth) / videoHeight; double containerAspectRatio = containerWidth / containerHeight; double displayWidth, displayHeight; if (videoAspectRatio > containerAspectRatio) { // Video is wider - fit to container width displayWidth = containerWidth; displayHeight = containerWidth / videoAspectRatio; } else { // Video is taller - fit to container height displayHeight = containerHeight; displayWidth = containerHeight * videoAspectRatio; } // Update SwapChainPanel size VideoSwapChainPanel().Width(displayWidth); VideoSwapChainPanel().Height(displayHeight); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"AspectFit: " + std::to_wstring((int)displayWidth) + L"x" + std::to_wstring((int)displayHeight)); } void VideoPlayerControl2::OnFrameReady() { // Called from PlaybackController timing thread (30fps) // Track consecutive decode errors for auto-recovery (function-scope static) static int consecutiveErrors = 0; // Thread-safe: Check if still initialized to prevent race conditions during cleanup if (!m_initialized) { return; } // Defensive: Get raw pointers atomically to avoid race with destruction auto processor = m_frameProcessor.get(); auto controller = m_playbackController.get(); if (!processor || !controller) { return; } // Skip if previous frame is still being processed if (processor->IsProcessing()) { // Frame is still being decoded/rendered, skip this timing tick return; } // Process frame (decode on background, render on UI thread) VavCorePlayer* player = controller->GetVavCorePlayer(); if (!player) { return; } bool shouldContinue = processor->ProcessFrame(player, [weakThis = get_weak()](bool success) { // Static variable is already in function scope, no need to capture if (auto strongThis = weakThis.get()) { if (!success) { consecutiveErrors++; LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"Frame processing failed (consecutive errors: " + std::to_wstring(consecutiveErrors) + L")"); // Auto-recovery after too many consecutive errors if (consecutiveErrors > 10) { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Too many consecutive errors, stopping playback"); // Stop playback on UI thread strongThis->DispatcherQueue().TryEnqueue([weakThis]() { if (auto strongThis2 = weakThis.get()) { strongThis2->Stop(); strongThis2->UpdateStatus(L"Playback error - stopped"); } }); consecutiveErrors = 0; } } else { // Reset error counter on success consecutiveErrors = 0; } } }); // If ProcessFrame returns false (end of stream), stop playback if (!shouldContinue) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"End of stream detected, stopping playback"); DispatcherQueue().TryEnqueue([weakThis = get_weak()]() { if (auto strongThis = weakThis.get()) { strongThis->Stop(); strongThis->UpdateStatus(L"Playback completed"); } }); } } } // namespace winrt::Vav2Player::implementation