Files
video-v1/vav2/platforms/windows/applications/vav2player/Vav2Player/VideoPlayerControl2.xaml.cpp

486 lines
16 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()
{
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Constructor called");
// Create core components
m_playbackController = std::make_unique<::Vav2Player::PlaybackController>();
m_frameProcessor = std::make_unique<::Vav2Player::FrameProcessor>();
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Core components created");
}
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();
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()) {
LoadVideo(value);
}
}
}
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 winrt::Vav2Player::VideoDecoderType::Auto;
}
VavCoreDecoderType coreType = m_playbackController->GetDecoderType();
// Convert VavCoreDecoderType to VideoDecoderType
switch (coreType) {
case VAVCORE_DECODER_NVDEC:
return winrt::Vav2Player::VideoDecoderType::NVDEC;
case VAVCORE_DECODER_VPL:
return winrt::Vav2Player::VideoDecoderType::VPL;
case VAVCORE_DECODER_AMF:
return winrt::Vav2Player::VideoDecoderType::AMF;
case VAVCORE_DECODER_DAV1D:
return winrt::Vav2Player::VideoDecoderType::DAV1D;
case VAVCORE_DECODER_MEDIA_FOUNDATION:
return winrt::Vav2Player::VideoDecoderType::MediaFoundation;
default:
return winrt::Vav2Player::VideoDecoderType::Auto;
}
}
void VideoPlayerControl2::DecoderType(Vav2Player::VideoDecoderType value)
{
if (!m_playbackController) {
return;
}
// Convert VideoDecoderType to VavCoreDecoderType
VavCoreDecoderType coreType = VAVCORE_DECODER_AUTO;
if (value == winrt::Vav2Player::VideoDecoderType::NVDEC) {
coreType = VAVCORE_DECODER_NVDEC;
} else if (value == winrt::Vav2Player::VideoDecoderType::VPL) {
coreType = VAVCORE_DECODER_VPL;
} else if (value == winrt::Vav2Player::VideoDecoderType::AMF) {
coreType = VAVCORE_DECODER_AMF;
} else if (value == winrt::Vav2Player::VideoDecoderType::DAV1D) {
coreType = VAVCORE_DECODER_DAV1D;
} else if (value == winrt::Vav2Player::VideoDecoderType::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();
}
// Load video file
std::wstring filePathStr(filePath.c_str());
bool success = m_playbackController->LoadVideo(filePathStr);
if (success) {
// Get video dimensions and create NV12 texture
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));
// Create NV12 texture for CUDA interop
if (m_gpuRenderer) {
HRESULT hr = m_gpuRenderer->CreateNV12TextureR8Layout(videoWidth, videoHeight);
if (SUCCEEDED(hr)) {
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"NV12 texture created");
// Pass D3D12 device to VavCore for CUDA-D3D12 interop
ID3D12Device* d3dDevice = m_gpuRenderer->GetD3D12Device();
if (d3dDevice) {
VavCorePlayer* player = m_playbackController->GetVavCorePlayer();
if (player) {
vavcore_set_d3d_device(player, d3dDevice, VAVCORE_SURFACE_D3D12_RESOURCE);
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"D3D12 device passed to VavCore");
}
}
} else {
LogMgr::GetInstance().LogError(L"VideoPlayerControl2", L"Failed to create NV12 texture");
}
}
// 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 || !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()
{
if (m_gpuRenderer && m_gpuRenderer->IsInitialized()) {
LogMgr::GetInstance().LogInfo(L"VideoPlayerControl2", L"Renderer already initialized");
return;
}
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<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)
if (!m_frameProcessor || !m_playbackController) {
return;
}
// Process frame (decode on background, render on UI thread)
VavCorePlayer* player = m_playbackController->GetVavCorePlayer();
if (!player) {
return;
}
m_frameProcessor->ProcessFrame(player, [](bool success) {
// Frame processing complete callback (optional)
if (!success) {
OutputDebugStringA("[VideoPlayerControl2] Frame processing failed\n");
}
});
}
} // namespace winrt::Vav2Player::implementation