486 lines
16 KiB
C++
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
|