#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 { OutputDebugStringA("[VideoPlayerControl2] Constructor START\n"); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Constructor called"); // Create core components OutputDebugStringA("[VideoPlayerControl2] Creating PlaybackController...\n"); m_playbackController = std::make_unique<::Vav2Player::PlaybackController>(); OutputDebugStringA("[VideoPlayerControl2] PlaybackController created\n"); OutputDebugStringA("[VideoPlayerControl2] Creating FrameProcessor...\n"); m_frameProcessor = std::make_unique<::Vav2Player::FrameProcessor>(); OutputDebugStringA("[VideoPlayerControl2] FrameProcessor created\n"); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Core components created"); OutputDebugStringA("[VideoPlayerControl2] Constructor END (SUCCESS)\n"); } catch (const std::exception& e) { std::string error = "[VideoPlayerControl2] Constructor EXCEPTION: "; error += e.what(); OutputDebugStringA(error.c_str()); OutputDebugStringA("\n"); throw; } catch (...) { OutputDebugStringA("[VideoPlayerControl2] Constructor UNKNOWN EXCEPTION\n"); throw; } } VideoPlayerControl2::~VideoPlayerControl2() { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Destructor called"); // 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"Initialized"); } catch (const std::exception& e) { std::string error = "Failed to initialize: " + std::string(e.what()); LogMgr::GetInstance().LogError(L"VideoPlayerControl2", std::wstring(error.begin(), error.end())); UpdateStatus(L"Initialization failed"); } } 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 || !m_playbackController) { 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()); bool success = m_playbackController->LoadVideo(filePathStr); 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)); // 4. Create the NV12 texture for zero-copy, now that we have the dimensions. if (m_gpuRenderer) { HRESULT hr = m_gpuRenderer->CreateNV12TextureR8Layout(videoWidth, videoHeight); if (SUCCEEDED(hr)) { LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"NV12 texture created"); } else { LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to create NV12 texture"); } } // 5. Get D3D12 fence from VavCore and pass it to SimpleGPURenderer for GPU synchronization VavCorePlayer* player = m_playbackController->GetVavCorePlayer(); if (player && m_gpuRenderer) { void* syncFence = vavcore_get_sync_fence(player); if (syncFence) { m_gpuRenderer->SetSyncFence(syncFence); LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"D3D12 fence set for GPU synchronization"); } else { LogMgr::GetInstance().LogWarning(L"VideoPlayerControl2", L"No sync fence available from VavCore"); } } // --- End of Corrected Initialization Order --- // 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"); // Cleanup partial initialization on failure if (m_gpuRenderer) { m_gpuRenderer->ReleaseNV12Texture(); } UpdateStatus(L"Load failed"); } } void VideoPlayerControl2::Play() { if (!m_initialized || !m_playbackController || !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() { if (!m_playbackController) { return; } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Pausing playback"); m_playbackController->Pause(); UpdateStatus(L"Paused"); } void VideoPlayerControl2::Stop() { if (!m_playbackController) { return; } LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Stopping playback"); m_playbackController->Stop(); UpdateStatus(L"Stopped"); } void VideoPlayerControl2::Seek(double timeSeconds) { if (!m_playbackController) { return; } 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 ? m_playbackController->IsPlaying() : false; } bool VideoPlayerControl2::IsVideoLoaded() { return m_playbackController ? m_playbackController->IsLoaded() : false; } double VideoPlayerControl2::CurrentTime() { return m_playbackController ? m_playbackController->GetCurrentTime() : 0.0; } double VideoPlayerControl2::Duration() { return m_playbackController ? m_playbackController->GetDuration() : 0.0; } winrt::hstring VideoPlayerControl2::Status() { return m_status; } // ======================================== // 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::SimpleGPURenderer>(); // 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) // 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; } // Process frame (decode on background, render on UI thread) VavCorePlayer* player = controller->GetVavCorePlayer(); if (!player) { return; } processor->ProcessFrame(player, [weakThis = get_weak()](bool success) { if (auto strongThis = weakThis.get()) { if (!success) { OutputDebugStringA("[VideoPlayerControl2] Frame processing failed\n"); // Track consecutive errors for auto-recovery static int consecutiveErrors = 0; consecutiveErrors++; // Auto-recovery after too many consecutive errors if (consecutiveErrors > 10) { OutputDebugStringA("[VideoPlayerControl2] Too many consecutive errors, attempting recovery\n"); // 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 static int consecutiveErrors = 0; consecutiveErrors = 0; } } }); } } // namespace winrt::Vav2Player::implementation