Files
video-v1/vav2/platforms/windows/applications/vav2player/Vav2Player/VideoPlayerControl2.xaml.cpp
2025-10-06 14:47:55 +09:00

571 lines
20 KiB
C++

#include "pch.h"
#include "VideoPlayerControl2.xaml.h"
#if __has_include("VideoPlayerControl2.g.cpp")
#include "VideoPlayerControl2.g.cpp"
#endif
#include <winrt/Microsoft.UI.Dispatching.h>
#include <chrono>
#include <algorithm>
// D3D12 for GPU surface decoding
#include <d3d12.h>
#include <wrl/client.h>
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()
{
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"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<uint32_t>(newSize.Width),
static_cast<uint32_t>(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<Vav2Player::VideoDecoderType>(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<Vav2Player::VideoDecoderType>(1); // NVDEC
case VAVCORE_DECODER_VPL:
return static_cast<Vav2Player::VideoDecoderType>(2); // VPL
case VAVCORE_DECODER_AMF:
return static_cast<Vav2Player::VideoDecoderType>(3); // AMF
case VAVCORE_DECODER_DAV1D:
return static_cast<Vav2Player::VideoDecoderType>(4); // DAV1D
case VAVCORE_DECODER_MEDIA_FOUNDATION:
return static_cast<Vav2Player::VideoDecoderType>(5); // MediaFoundation
default:
return static_cast<Vav2Player::VideoDecoderType>(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<int32_t>(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));
// Prepare video texture before decoding (via FrameProcessor)
if (m_frameProcessor) {
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;
}
// ========================================
// 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<uint32_t>(panelSize.x);
uint32_t height = static_cast<uint32_t>(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<double>(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;
}
// Process frame (decode on background, render on UI thread)
VavCorePlayer* player = controller->GetVavCorePlayer();
if (!player) {
return;
}
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;
}
}
});
}
} // namespace winrt::Vav2Player::implementation