2025-09-20 15:40:39 +09:00
|
|
|
#include "pch.h"
|
|
|
|
|
#include "VideoPlayerControl.xaml.h"
|
|
|
|
|
#if __has_include("VideoPlayerControl.g.cpp")
|
|
|
|
|
#include "VideoPlayerControl.g.cpp"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Note: VideoTypes.h not included due to VavCore migration guard
|
2025-09-20 15:40:39 +09:00
|
|
|
#include <winrt/Microsoft.UI.Dispatching.h>
|
2025-09-22 22:01:53 +09:00
|
|
|
#include <winrt/Windows.Storage.Streams.h>
|
2025-09-26 23:46:21 +09:00
|
|
|
#include <winrt/Windows.Storage.h>
|
2025-09-22 22:01:53 +09:00
|
|
|
#include <windows.storage.streams.h>
|
|
|
|
|
#include <chrono>
|
2025-09-20 23:49:47 +09:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstring>
|
2025-09-21 14:52:33 +09:00
|
|
|
#include <cassert>
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
// Include log manager for logging
|
|
|
|
|
#include "src/Logger/LogManager.h"
|
|
|
|
|
|
|
|
|
|
// Using alias to avoid namespace conflicts
|
|
|
|
|
using LogMgr = Vav2Player::LogManager;
|
|
|
|
|
|
2025-09-20 15:40:39 +09:00
|
|
|
using namespace winrt;
|
|
|
|
|
using namespace winrt::Microsoft::UI::Xaml;
|
|
|
|
|
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
|
|
|
|
using namespace winrt::Microsoft::UI::Dispatching;
|
|
|
|
|
|
|
|
|
|
namespace winrt::Vav2Player::implementation
|
|
|
|
|
{
|
|
|
|
|
VideoPlayerControl::VideoPlayerControl()
|
2025-09-23 03:39:44 +09:00
|
|
|
: m_useHardwareRendering(true) // Default to GPU rendering
|
2025-09-25 21:54:50 +09:00
|
|
|
, m_vavCorePlayer(nullptr)
|
2025-09-20 15:40:39 +09:00
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
2025-09-25 21:54:50 +09:00
|
|
|
|
2025-09-26 23:46:21 +09:00
|
|
|
// Load decoder settings from Windows.Storage.ApplicationData
|
|
|
|
|
LoadDecoderSettings();
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Initialize VavCore library (only once)
|
|
|
|
|
static bool vavCoreInitialized = false;
|
|
|
|
|
if (!vavCoreInitialized) {
|
|
|
|
|
VavCoreResult result = vavcore_initialize();
|
|
|
|
|
vavCoreInitialized = (result == VAVCORE_SUCCESS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create VavCore player
|
|
|
|
|
m_vavCorePlayer = vavcore_create_player();
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-21 14:52:33 +09:00
|
|
|
VideoPlayerControl::~VideoPlayerControl()
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
// Stop all playback immediately
|
|
|
|
|
m_isPlaying = false;
|
|
|
|
|
m_shouldStopTiming = true;
|
|
|
|
|
|
|
|
|
|
// Clean up VavCore player
|
|
|
|
|
if (m_vavCorePlayer) {
|
|
|
|
|
vavcore_destroy_player(m_vavCorePlayer);
|
|
|
|
|
m_vavCorePlayer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GPU renderer cleanup re-enabled
|
|
|
|
|
if (m_gpuRenderer) {
|
|
|
|
|
m_gpuRenderer->Shutdown();
|
|
|
|
|
m_gpuRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up timing thread
|
|
|
|
|
if (m_timingThread && m_timingThread->joinable()) {
|
|
|
|
|
m_timingThread->join();
|
|
|
|
|
m_timingThread.reset();
|
|
|
|
|
}
|
2025-09-21 14:52:33 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-20 15:40:39 +09:00
|
|
|
// Event Handlers
|
|
|
|
|
void VideoPlayerControl::UserControl_Loaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
m_isInitialized = true;
|
|
|
|
|
UpdateStatus(L"Ready");
|
|
|
|
|
|
|
|
|
|
// Auto load video if source is set
|
|
|
|
|
if (!m_videoSource.empty())
|
|
|
|
|
{
|
|
|
|
|
LoadVideo(m_videoSource);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-21 01:22:28 +09:00
|
|
|
// Setup container size change handler for AspectFit updates
|
|
|
|
|
VideoDisplayArea().SizeChanged([this](auto&&, auto&&) {
|
2025-09-23 02:25:59 +09:00
|
|
|
ApplyAspectFitIfReady();
|
2025-09-21 01:22:28 +09:00
|
|
|
});
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-23 03:54:40 +09:00
|
|
|
// Ready for user interaction
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
UpdateStatus(L"Error during initialization");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-20 15:40:39 +09:00
|
|
|
void VideoPlayerControl::UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
// Stop all playback immediately (avoid seeking to prevent deadlock)
|
2025-09-23 02:25:59 +09:00
|
|
|
m_isPlaying = false;
|
|
|
|
|
m_isLoaded = false;
|
|
|
|
|
m_isInitialized = false;
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Stop timing thread safely
|
|
|
|
|
m_shouldStopTiming = true;
|
|
|
|
|
if (m_timingThread && m_timingThread->joinable()) {
|
|
|
|
|
m_timingThread->join();
|
|
|
|
|
m_timingThread.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop UI timer
|
2025-09-22 22:01:53 +09:00
|
|
|
if (m_playbackTimer)
|
|
|
|
|
{
|
|
|
|
|
m_playbackTimer.Stop();
|
2025-09-25 21:54:50 +09:00
|
|
|
m_playbackTimer = nullptr;
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// GPU renderer cleanup
|
2025-09-22 02:15:47 +09:00
|
|
|
if (m_gpuRenderer)
|
2025-09-21 01:22:28 +09:00
|
|
|
{
|
2025-09-22 02:15:47 +09:00
|
|
|
m_gpuRenderer->Shutdown();
|
|
|
|
|
m_gpuRenderer.reset();
|
2025-09-21 01:22:28 +09:00
|
|
|
}
|
2025-09-25 21:54:50 +09:00
|
|
|
|
|
|
|
|
// Clean up VavCore player (this will handle internal cleanup safely)
|
|
|
|
|
if (m_vavCorePlayer) {
|
|
|
|
|
vavcore_destroy_player(m_vavCorePlayer);
|
|
|
|
|
m_vavCorePlayer = nullptr;
|
2025-09-23 02:25:59 +09:00
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
m_renderBitmap = nullptr;
|
|
|
|
|
UpdateStatus(L"Unloaded");
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
// Ignore cleanup errors during unload
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 04:22:38 +09:00
|
|
|
void VideoPlayerControl::UserControl_SizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::SizeChangedEventArgs const& e)
|
|
|
|
|
{
|
|
|
|
|
// Recalculate AspectFit when container size changes
|
|
|
|
|
if (m_hasValidVideoSize && m_videoWidth > 0 && m_videoHeight > 0) {
|
|
|
|
|
UpdateVideoImageAspectFit(m_videoWidth, m_videoHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retry GPU rendering initialization if user prefers hardware rendering
|
|
|
|
|
// but we're currently using CPU rendering due to previous container size issues
|
|
|
|
|
if (m_useHardwareRendering && m_isLoaded) {
|
|
|
|
|
auto container = VideoDisplayArea();
|
|
|
|
|
if (container) {
|
|
|
|
|
double containerWidth = container.ActualWidth();
|
|
|
|
|
double containerHeight = container.ActualHeight();
|
|
|
|
|
|
|
|
|
|
// If container size is now valid and we're not showing GPU panel, retry GPU init
|
|
|
|
|
if (containerWidth > 0 && containerHeight > 0 &&
|
|
|
|
|
VideoSwapChainPanel().Visibility() == winrt::Microsoft::UI::Xaml::Visibility::Collapsed) {
|
|
|
|
|
InitializeVideoRenderer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 15:40:39 +09:00
|
|
|
void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&)
|
|
|
|
|
{
|
2025-09-23 03:54:40 +09:00
|
|
|
// Controls are disabled for now
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::HoverDetector_PointerExited(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&)
|
|
|
|
|
{
|
2025-09-23 03:54:40 +09:00
|
|
|
// Controls are disabled for now
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public Properties
|
|
|
|
|
winrt::hstring VideoPlayerControl::VideoSource()
|
|
|
|
|
{
|
|
|
|
|
return m_videoSource;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::VideoSource(winrt::hstring const& value)
|
|
|
|
|
{
|
|
|
|
|
if (m_videoSource != value)
|
|
|
|
|
{
|
|
|
|
|
m_videoSource = value;
|
|
|
|
|
if (m_isInitialized && !value.empty())
|
|
|
|
|
{
|
|
|
|
|
LoadVideo(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VideoPlayerControl::ShowControls()
|
|
|
|
|
{
|
|
|
|
|
return m_showControls;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::ShowControls(bool value)
|
|
|
|
|
{
|
|
|
|
|
m_showControls = value;
|
|
|
|
|
if (m_isInitialized)
|
|
|
|
|
{
|
2025-09-22 22:01:53 +09:00
|
|
|
// Update controls visibility based on value and loaded state
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VideoPlayerControl::AutoPlay()
|
|
|
|
|
{
|
|
|
|
|
return m_autoPlay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::AutoPlay(bool value)
|
|
|
|
|
{
|
|
|
|
|
m_autoPlay = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vav2Player::VideoDecoderType VideoPlayerControl::DecoderType()
|
|
|
|
|
{
|
|
|
|
|
switch (m_decoderType)
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
case VAVCORE_DECODER_AUTO:
|
2025-09-20 15:40:39 +09:00
|
|
|
return Vav2Player::VideoDecoderType::Auto;
|
2025-09-25 21:54:50 +09:00
|
|
|
case VAVCORE_DECODER_DAV1D:
|
2025-09-20 15:40:39 +09:00
|
|
|
return Vav2Player::VideoDecoderType::Software;
|
2025-09-25 21:54:50 +09:00
|
|
|
case VAVCORE_DECODER_NVDEC:
|
|
|
|
|
return Vav2Player::VideoDecoderType::Software; // Temporarily map to Software
|
|
|
|
|
case VAVCORE_DECODER_MEDIA_FOUNDATION:
|
2025-09-20 15:40:39 +09:00
|
|
|
return Vav2Player::VideoDecoderType::HardwareMF;
|
|
|
|
|
default:
|
|
|
|
|
return Vav2Player::VideoDecoderType::Auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::DecoderType(Vav2Player::VideoDecoderType value)
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
VavCoreDecoderType newType;
|
2025-09-20 15:40:39 +09:00
|
|
|
switch (value)
|
|
|
|
|
{
|
|
|
|
|
case Vav2Player::VideoDecoderType::Auto:
|
2025-09-25 21:54:50 +09:00
|
|
|
newType = VAVCORE_DECODER_AUTO;
|
2025-09-20 15:40:39 +09:00
|
|
|
break;
|
|
|
|
|
case Vav2Player::VideoDecoderType::Software:
|
2025-09-25 21:54:50 +09:00
|
|
|
newType = VAVCORE_DECODER_DAV1D;
|
2025-09-20 15:40:39 +09:00
|
|
|
break;
|
2025-09-25 21:54:50 +09:00
|
|
|
// case Vav2Player::VideoDecoderType::HardwareNV:
|
|
|
|
|
// newType = VAVCORE_DECODER_NVDEC;
|
|
|
|
|
// break;
|
2025-09-20 15:40:39 +09:00
|
|
|
case Vav2Player::VideoDecoderType::HardwareMF:
|
2025-09-25 21:54:50 +09:00
|
|
|
newType = VAVCORE_DECODER_MEDIA_FOUNDATION;
|
2025-09-20 15:40:39 +09:00
|
|
|
break;
|
|
|
|
|
default:
|
2025-09-25 21:54:50 +09:00
|
|
|
newType = VAVCORE_DECODER_AUTO;
|
2025-09-20 15:40:39 +09:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetInternalDecoderType(newType);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 23:49:47 +09:00
|
|
|
bool VideoPlayerControl::UseHardwareRendering()
|
|
|
|
|
{
|
|
|
|
|
return m_useHardwareRendering;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::UseHardwareRendering(bool value)
|
|
|
|
|
{
|
|
|
|
|
if (m_useHardwareRendering != value)
|
|
|
|
|
{
|
|
|
|
|
m_useHardwareRendering = value;
|
|
|
|
|
|
2025-09-21 01:22:28 +09:00
|
|
|
// Reinitialize renderer if video is already loaded
|
2025-09-25 21:54:50 +09:00
|
|
|
if (m_isLoaded && m_vavCorePlayer)
|
2025-09-20 23:49:47 +09:00
|
|
|
{
|
2025-09-21 01:22:28 +09:00
|
|
|
InitializeVideoRenderer();
|
2025-09-20 23:49:47 +09:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-21 01:22:28 +09:00
|
|
|
// Just switch visibility for now
|
|
|
|
|
if (value)
|
|
|
|
|
{
|
|
|
|
|
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
|
|
|
|
}
|
2025-09-20 23:49:47 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
VavCoreDecoderType VideoPlayerControl::GetInternalDecoderType()
|
2025-09-20 15:40:39 +09:00
|
|
|
{
|
|
|
|
|
return m_decoderType;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
void VideoPlayerControl::SetInternalDecoderType(VavCoreDecoderType value)
|
2025-09-20 15:40:39 +09:00
|
|
|
{
|
|
|
|
|
if (m_decoderType != value)
|
|
|
|
|
{
|
|
|
|
|
m_decoderType = value;
|
2025-09-25 21:54:50 +09:00
|
|
|
// Update VavCore decoder type if player is active
|
|
|
|
|
if (m_isLoaded && m_vavCorePlayer)
|
2025-09-20 15:40:39 +09:00
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
vavcore_set_decoder_type(m_vavCorePlayer, value);
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public Methods
|
|
|
|
|
void VideoPlayerControl::LoadVideo(winrt::hstring const& filePath)
|
|
|
|
|
{
|
2025-09-23 04:22:38 +09:00
|
|
|
std::string filePathStr = winrt::to_string(filePath);
|
|
|
|
|
UpdateStatus(L"Loading video...");
|
|
|
|
|
LoadingRing().IsActive(true);
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
// Log video load attempt
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Attempting to load video: " + std::wstring(filePath), L"VideoPlayerControl");
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Reset video state
|
2025-09-23 04:22:38 +09:00
|
|
|
ResetVideoState();
|
2025-09-25 21:54:50 +09:00
|
|
|
|
|
|
|
|
if (!m_vavCorePlayer) {
|
|
|
|
|
UpdateStatus(L"VavCore player not initialized");
|
|
|
|
|
LoadingRing().IsActive(false);
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogError(L"VavCore player not initialized", L"VideoPlayerControl");
|
2025-09-25 21:54:50 +09:00
|
|
|
return;
|
2025-09-23 04:22:38 +09:00
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Set decoder type before opening file
|
|
|
|
|
vavcore_set_decoder_type(m_vavCorePlayer, m_decoderType);
|
|
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
// Log decoder type selection
|
|
|
|
|
std::wstring decoderName = L"Unknown";
|
|
|
|
|
switch (m_decoderType) {
|
|
|
|
|
case VAVCORE_DECODER_AUTO: decoderName = L"Auto"; break;
|
|
|
|
|
case VAVCORE_DECODER_DAV1D: decoderName = L"Software (dav1d)"; break;
|
|
|
|
|
case VAVCORE_DECODER_MEDIA_FOUNDATION: decoderName = L"Hardware (Media Foundation)"; break;
|
|
|
|
|
case VAVCORE_DECODER_NVDEC: decoderName = L"Hardware (NVDEC)"; break;
|
2025-09-26 23:46:21 +09:00
|
|
|
case VAVCORE_DECODER_VPL: decoderName = L"Hardware (Intel VPL)"; break;
|
|
|
|
|
case VAVCORE_DECODER_AMF: decoderName = L"Hardware (AMD AMF)"; break;
|
2025-09-26 01:32:24 +09:00
|
|
|
}
|
|
|
|
|
LogMgr::GetInstance().LogDecoderInfo(decoderName, L"Decoder type selected");
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Open video file using VavCore API
|
|
|
|
|
VavCoreResult result = vavcore_open_file(m_vavCorePlayer, filePathStr.c_str());
|
|
|
|
|
if (result != VAVCORE_SUCCESS) {
|
2025-09-23 04:22:38 +09:00
|
|
|
UpdateStatus(L"Failed to open video file");
|
|
|
|
|
LoadingRing().IsActive(false);
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoError(L"Failed to open file", std::wstring(filePath));
|
2025-09-23 04:22:38 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Get video metadata from VavCore
|
|
|
|
|
VavCoreVideoMetadata metadata;
|
|
|
|
|
result = vavcore_get_metadata(m_vavCorePlayer, &metadata);
|
|
|
|
|
if (result != VAVCORE_SUCCESS) {
|
|
|
|
|
UpdateStatus(L"Failed to get video metadata");
|
2025-09-23 04:22:38 +09:00
|
|
|
LoadingRing().IsActive(false);
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoError(L"Failed to get metadata", std::wstring(filePath));
|
2025-09-23 04:22:38 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2025-09-23 02:25:59 +09:00
|
|
|
|
2025-09-23 04:22:38 +09:00
|
|
|
// Set up video properties
|
|
|
|
|
m_videoWidth = metadata.width;
|
|
|
|
|
m_videoHeight = metadata.height;
|
|
|
|
|
m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0;
|
2025-09-25 21:54:50 +09:00
|
|
|
m_totalFrames = metadata.total_frames;
|
2025-09-23 04:22:38 +09:00
|
|
|
m_duration = metadata.total_frames / m_frameRate;
|
|
|
|
|
|
2025-09-27 08:44:28 +09:00
|
|
|
// Initialize D3D surface support if hardware rendering is enabled
|
|
|
|
|
if (m_useHardwareRendering) {
|
|
|
|
|
InitializeD3DSurfaceSupport();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
// Log video info
|
|
|
|
|
std::wstring videoInfo = L"Resolution: " + std::to_wstring(m_videoWidth) + L"x" + std::to_wstring(m_videoHeight) +
|
|
|
|
|
L", FPS: " + std::to_wstring(static_cast<int>(m_frameRate)) +
|
|
|
|
|
L", Frames: " + std::to_wstring(m_totalFrames) +
|
|
|
|
|
L", Duration: " + std::to_wstring(static_cast<int>(m_duration)) + L"s";
|
|
|
|
|
LogMgr::GetInstance().LogInfo(videoInfo, L"VideoPlayerControl");
|
|
|
|
|
|
2025-09-23 04:22:38 +09:00
|
|
|
InitializeVideoRenderer();
|
|
|
|
|
m_hasValidVideoSize = true;
|
|
|
|
|
m_isLoaded = true;
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-23 04:22:38 +09:00
|
|
|
ApplyAspectFitIfReady();
|
|
|
|
|
LoadingRing().IsActive(false);
|
|
|
|
|
UpdateStatus(L"Video loaded");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoLoad(std::wstring(filePath), true);
|
2025-09-20 15:40:39 +09:00
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
if (m_autoPlay) {
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Auto-play enabled, starting playback", L"VideoPlayerControl");
|
|
|
|
|
Play();
|
|
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::Play()
|
|
|
|
|
{
|
2025-09-23 04:22:38 +09:00
|
|
|
if (!m_isLoaded || m_isPlaying) {
|
2025-09-26 01:32:24 +09:00
|
|
|
if (!m_isLoaded) {
|
|
|
|
|
LogMgr::GetInstance().LogWarning(L"Cannot play: Video not loaded", L"VideoPlayerControl");
|
|
|
|
|
}
|
2025-09-20 15:40:39 +09:00
|
|
|
return;
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-22 02:15:47 +09:00
|
|
|
m_isPlaying = true;
|
|
|
|
|
UpdateStatus(L"Playing");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoPlay(std::wstring(m_videoSource));
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
// Record playback start time for accurate speed measurement
|
|
|
|
|
m_playbackStartTime = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
// Stop any existing timer/thread
|
2025-09-23 04:22:38 +09:00
|
|
|
if (m_playbackTimer)
|
2025-09-22 22:01:53 +09:00
|
|
|
{
|
2025-09-23 04:22:38 +09:00
|
|
|
m_playbackTimer.Stop();
|
|
|
|
|
m_playbackTimer = nullptr;
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
if (m_timingThread && m_timingThread->joinable()) {
|
|
|
|
|
m_shouldStopTiming = true;
|
|
|
|
|
m_timingThread->join();
|
|
|
|
|
m_timingThread.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start high-resolution timing thread
|
|
|
|
|
m_shouldStopTiming = false;
|
2025-09-23 04:22:38 +09:00
|
|
|
auto weakThis = get_weak();
|
2025-09-23 23:35:57 +09:00
|
|
|
double targetIntervalMs = 1000.0 / m_frameRate;
|
|
|
|
|
|
|
|
|
|
m_timingThread = std::make_unique<std::thread>([weakThis, targetIntervalMs]() {
|
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (auto strongThis = weakThis.get()) {
|
|
|
|
|
if (strongThis->m_shouldStopTiming || !strongThis->m_isPlaying) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process frame on UI thread
|
|
|
|
|
strongThis->DispatcherQueue().TryEnqueue([strongThis]() {
|
|
|
|
|
if (strongThis->m_isPlaying && strongThis->m_isLoaded) {
|
|
|
|
|
strongThis->ProcessSingleFrame();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// High-precision sleep until next frame
|
|
|
|
|
auto nextFrame = start + std::chrono::microseconds(
|
|
|
|
|
static_cast<long long>(targetIntervalMs * 1000));
|
|
|
|
|
std::this_thread::sleep_until(nextFrame);
|
|
|
|
|
start = nextFrame;
|
|
|
|
|
} else {
|
|
|
|
|
break; // Object was destroyed
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
2025-09-23 04:22:38 +09:00
|
|
|
}
|
|
|
|
|
});
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
ProcessSingleFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::Pause()
|
|
|
|
|
{
|
|
|
|
|
m_isPlaying = false;
|
2025-09-23 23:35:57 +09:00
|
|
|
m_shouldStopTiming = true;
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
if (m_playbackTimer)
|
|
|
|
|
{
|
|
|
|
|
m_playbackTimer.Stop();
|
|
|
|
|
}
|
2025-09-23 23:35:57 +09:00
|
|
|
|
|
|
|
|
if (m_timingThread && m_timingThread->joinable()) {
|
|
|
|
|
m_timingThread->join();
|
|
|
|
|
m_timingThread.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
UpdateStatus(L"Paused");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoPause(std::wstring(m_videoSource));
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::Stop()
|
|
|
|
|
{
|
|
|
|
|
m_isPlaying = false;
|
2025-09-23 23:35:57 +09:00
|
|
|
m_shouldStopTiming = true;
|
2025-09-23 04:22:38 +09:00
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
// Properly cleanup timer and thread to prevent resource leaks
|
2025-09-22 22:01:53 +09:00
|
|
|
if (m_playbackTimer)
|
|
|
|
|
{
|
|
|
|
|
m_playbackTimer.Stop();
|
2025-09-23 04:22:38 +09:00
|
|
|
m_playbackTimer = nullptr; // Release timer completely
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
if (m_timingThread && m_timingThread->joinable()) {
|
|
|
|
|
m_timingThread->join();
|
|
|
|
|
m_timingThread.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
m_currentFrame = 0;
|
|
|
|
|
m_currentTime = 0.0;
|
|
|
|
|
|
2025-09-25 22:43:07 +09:00
|
|
|
// Reset VavCore player to beginning for next playback
|
|
|
|
|
if (m_vavCorePlayer && m_isLoaded) {
|
|
|
|
|
VavCoreResult result = vavcore_reset(m_vavCorePlayer);
|
|
|
|
|
if (result != VAVCORE_SUCCESS) {
|
|
|
|
|
UpdateStatus(L"Stop - Reset failed");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogError(L"Failed to reset VavCore player", L"VideoPlayerControl");
|
|
|
|
|
} else {
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"VavCore player reset to beginning", L"VideoPlayerControl");
|
2025-09-25 22:43:07 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-25 22:43:07 +09:00
|
|
|
UpdateStatus(L"Stopped - Ready to play from beginning");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogVideoStop(std::wstring(m_videoSource));
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::ProcessSingleFrame()
|
|
|
|
|
{
|
2025-09-23 03:54:40 +09:00
|
|
|
// Simple validation
|
2025-09-25 21:54:50 +09:00
|
|
|
if (!m_isPlaying || !m_vavCorePlayer) {
|
2025-09-23 04:22:38 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-27 08:44:28 +09:00
|
|
|
// Choose decode path based on D3D surface support
|
|
|
|
|
if (m_useD3DSurfaces) {
|
|
|
|
|
ProcessSingleFrameWithSurfaces();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simple decode-render flow using VavCore API (CPU path)
|
2025-09-23 23:35:57 +09:00
|
|
|
auto totalStart = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Decode next frame using VavCore
|
|
|
|
|
VavCoreVideoFrame vavFrame;
|
|
|
|
|
VavCoreResult result = vavcore_decode_next_frame(m_vavCorePlayer, &vavFrame);
|
|
|
|
|
|
|
|
|
|
if (result == VAVCORE_END_OF_STREAM) {
|
2025-09-23 00:31:16 +09:00
|
|
|
// End of video - stop playback
|
2025-09-22 22:01:53 +09:00
|
|
|
m_isPlaying = false;
|
2025-09-23 00:31:16 +09:00
|
|
|
if (m_playbackTimer) m_playbackTimer.Stop();
|
2025-09-22 22:01:53 +09:00
|
|
|
UpdateStatus(L"Playback completed");
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogInfo(L"Playback completed - End of stream reached", L"VideoPlayerControl");
|
2025-09-22 22:01:53 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
if (result != VAVCORE_SUCCESS) {
|
|
|
|
|
// Decode error - count but continue processing
|
2025-09-23 23:35:57 +09:00
|
|
|
m_framesDecodeErrors++;
|
|
|
|
|
m_currentFrame++;
|
|
|
|
|
m_currentTime = m_currentFrame / m_frameRate;
|
|
|
|
|
|
|
|
|
|
// Log decode error occasionally
|
|
|
|
|
if (m_framesDecodeErrors % 10 == 1) {
|
2025-09-26 01:32:24 +09:00
|
|
|
LogMgr::GetInstance().LogError(L"Decode error count: " + std::to_wstring(m_framesDecodeErrors), L"VideoPlayerControl");
|
2025-09-23 23:35:57 +09:00
|
|
|
wchar_t errorMsg[256];
|
2025-09-25 21:54:50 +09:00
|
|
|
swprintf_s(errorMsg, L"VavCore decode error #%llu at frame %llu", m_framesDecodeErrors, m_currentFrame);
|
2025-09-23 23:35:57 +09:00
|
|
|
OutputDebugStringW(errorMsg);
|
|
|
|
|
OutputDebugStringW(L"\n");
|
|
|
|
|
}
|
|
|
|
|
return;
|
2025-09-23 04:22:38 +09:00
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
// Render frame
|
2025-09-25 21:54:50 +09:00
|
|
|
RenderFrameToScreen(vavFrame);
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
// Update counters
|
2025-09-22 22:01:53 +09:00
|
|
|
m_currentFrame++;
|
|
|
|
|
m_currentTime = m_currentFrame / m_frameRate;
|
2025-09-23 23:35:57 +09:00
|
|
|
|
|
|
|
|
// Performance logging every 30 frames
|
|
|
|
|
if (m_currentFrame % 30 == 0) {
|
|
|
|
|
auto totalEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
|
double totalTime = std::chrono::duration<double, std::milli>(totalEnd - totalStart).count();
|
|
|
|
|
|
|
|
|
|
// Calculate realSpeed for performance monitoring
|
|
|
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
|
|
|
double realElapsedMs = std::chrono::duration<double, std::milli>(now - m_playbackStartTime).count();
|
|
|
|
|
double expectedElapsedMs = (m_currentFrame * 1000.0) / m_frameRate;
|
|
|
|
|
double realSpeed = expectedElapsedMs / realElapsedMs; // 1.0 = real-time, 0.5 = half speed
|
|
|
|
|
|
|
|
|
|
wchar_t perfMsg[256];
|
|
|
|
|
const wchar_t* bufferingMode = m_useHardwareRendering ? L"GPU" : L"CPU";
|
|
|
|
|
double actualFPS = 1000.0 / totalTime;
|
|
|
|
|
double errorRate = (m_framesDecodeErrors * 100.0) / m_currentFrame;
|
|
|
|
|
|
|
|
|
|
swprintf_s(perfMsg, L"Frame %llu [%s]: processing=%.1ffps, realSpeed=%.2fx, errors=%.1f%%",
|
|
|
|
|
m_currentFrame, bufferingMode, actualFPS, realSpeed, errorRate);
|
|
|
|
|
UpdateStatus(perfMsg);
|
|
|
|
|
|
2025-09-26 01:32:24 +09:00
|
|
|
// Log performance info
|
|
|
|
|
LogMgr::GetInstance().LogFrameInfo(static_cast<int>(m_currentFrame), static_cast<int>(m_totalFrames), actualFPS);
|
|
|
|
|
|
|
|
|
|
std::wstring performanceInfo = L"[" + std::wstring(bufferingMode) + L"] Processing: " +
|
|
|
|
|
std::to_wstring(static_cast<int>(actualFPS)) + L"fps, " +
|
|
|
|
|
L"Real Speed: " + std::to_wstring(realSpeed).substr(0, 4) + L"x, " +
|
|
|
|
|
L"Error Rate: " + std::to_wstring(errorRate).substr(0, 4) + L"%";
|
|
|
|
|
LogMgr::GetInstance().LogDebug(performanceInfo, L"VideoPlayerControl");
|
|
|
|
|
|
2025-09-23 23:35:57 +09:00
|
|
|
// Also output to debug console for analysis
|
|
|
|
|
OutputDebugStringW(perfMsg);
|
|
|
|
|
OutputDebugStringW(L"\n");
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::ProcessSingleFrameLegacy()
|
|
|
|
|
{
|
|
|
|
|
// Legacy method - calls ProcessSingleFrame for compatibility
|
|
|
|
|
ProcessSingleFrame();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
void VideoPlayerControl::RenderFrameToScreen(const VavCoreVideoFrame& frame)
|
2025-09-22 22:01:53 +09:00
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
// GPU rendering re-enabled for VavCore
|
|
|
|
|
// Try GPU rendering first if available and enabled
|
|
|
|
|
if (m_gpuRenderer && m_useHardwareRendering) {
|
|
|
|
|
// Direct VavCoreVideoFrame usage - no adapter needed
|
|
|
|
|
if (m_gpuRenderer->TryRenderFrame(frame)) {
|
|
|
|
|
return; // GPU rendering successful
|
2025-09-23 00:31:16 +09:00
|
|
|
}
|
2025-09-25 21:54:50 +09:00
|
|
|
// Fall through to CPU rendering if GPU fails
|
2025-09-23 00:31:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-23 03:39:44 +09:00
|
|
|
// CPU rendering (either by user choice or GPU fallback)
|
2025-09-23 23:35:57 +09:00
|
|
|
auto cpuStart = std::chrono::high_resolution_clock::now();
|
2025-09-22 22:01:53 +09:00
|
|
|
RenderFrameSoftware(frame);
|
2025-09-23 23:35:57 +09:00
|
|
|
auto cpuEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
|
double cpuTime = std::chrono::duration<double, std::milli>(cpuEnd - cpuStart).count();
|
|
|
|
|
|
|
|
|
|
// Log CPU rendering time occasionally for debugging
|
|
|
|
|
if (m_currentFrame % 60 == 0) { // Every 2 seconds
|
|
|
|
|
wchar_t cpuMsg[256];
|
|
|
|
|
swprintf_s(cpuMsg, L"CPU render time: %.2fms", cpuTime);
|
|
|
|
|
OutputDebugStringW(cpuMsg);
|
|
|
|
|
OutputDebugStringW(L"\n");
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
void VideoPlayerControl::RenderFrameSoftware(const VavCoreVideoFrame& frame)
|
2025-09-22 22:01:53 +09:00
|
|
|
{
|
2025-09-23 04:22:38 +09:00
|
|
|
if (!frame.y_plane || frame.width == 0 || frame.height == 0) return;
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
try {
|
2025-09-25 21:54:50 +09:00
|
|
|
// Check if bitmap needs recreation (avoid unnecessary allocations)
|
|
|
|
|
bool needNewBitmap = !m_renderBitmap ||
|
|
|
|
|
m_lastFrameWidth != static_cast<uint32_t>(frame.width) ||
|
|
|
|
|
m_lastFrameHeight != static_cast<uint32_t>(frame.height);
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
if (needNewBitmap) {
|
2025-09-22 22:01:53 +09:00
|
|
|
m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(
|
|
|
|
|
frame.width, frame.height);
|
|
|
|
|
VideoImage().Source(m_renderBitmap);
|
2025-09-23 02:25:59 +09:00
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Cache dimensions to avoid repeated checks
|
|
|
|
|
m_lastFrameWidth = static_cast<uint32_t>(frame.width);
|
|
|
|
|
m_lastFrameHeight = static_cast<uint32_t>(frame.height);
|
|
|
|
|
|
|
|
|
|
// Update video dimensions and apply AspectFit
|
|
|
|
|
if (m_videoWidth != static_cast<uint32_t>(frame.width) || m_videoHeight != static_cast<uint32_t>(frame.height)) {
|
|
|
|
|
m_videoWidth = static_cast<uint32_t>(frame.width);
|
|
|
|
|
m_videoHeight = static_cast<uint32_t>(frame.height);
|
2025-09-23 02:25:59 +09:00
|
|
|
m_hasValidVideoSize = true;
|
|
|
|
|
UpdateVideoImageAspectFit(frame.width, frame.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Fast path: direct conversion to bitmap buffer
|
2025-09-22 22:01:53 +09:00
|
|
|
auto buffer = m_renderBitmap.PixelBuffer();
|
2025-09-25 21:54:50 +09:00
|
|
|
auto bufferByteAccess = buffer.as<::Windows::Storage::Streams::IBufferByteAccess>();
|
|
|
|
|
uint8_t* bufferData = nullptr;
|
|
|
|
|
winrt::check_hresult(bufferByteAccess->Buffer(&bufferData));
|
|
|
|
|
|
|
|
|
|
// Optimized YUV to BGRA conversion (direct to target buffer)
|
|
|
|
|
ConvertYUVToBGRA(frame, bufferData, frame.width, frame.height);
|
|
|
|
|
buffer.Length(frame.width * frame.height * 4);
|
|
|
|
|
|
|
|
|
|
// Minimal UI update
|
|
|
|
|
m_renderBitmap.Invalidate();
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
} catch (...) {
|
2025-09-25 21:54:50 +09:00
|
|
|
// Ignore render errors to maintain playback
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
void VideoPlayerControl::ConvertYUVToBGRA(const VavCoreVideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height)
|
2025-09-22 22:01:53 +09:00
|
|
|
{
|
|
|
|
|
// YUV420P to BGRA conversion using BT.709 color space
|
2025-09-25 21:54:50 +09:00
|
|
|
const uint8_t* y_plane = yuv_frame.y_plane;
|
|
|
|
|
const uint8_t* u_plane = yuv_frame.u_plane;
|
|
|
|
|
const uint8_t* v_plane = yuv_frame.v_plane;
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
if (!y_plane || !u_plane || !v_plane) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint32_t y_stride = yuv_frame.y_stride;
|
|
|
|
|
const uint32_t u_stride = yuv_frame.u_stride;
|
|
|
|
|
const uint32_t v_stride = yuv_frame.v_stride;
|
|
|
|
|
|
|
|
|
|
for (uint32_t y = 0; y < height; y++) {
|
|
|
|
|
const uint8_t* y_row = y_plane + y * y_stride;
|
|
|
|
|
const uint8_t* u_row = u_plane + (y / 2) * u_stride;
|
|
|
|
|
const uint8_t* v_row = v_plane + (y / 2) * v_stride;
|
|
|
|
|
|
|
|
|
|
uint8_t* bgra_row = bgra_buffer + y * width * 4;
|
|
|
|
|
|
|
|
|
|
for (uint32_t x = 0; x < width; x++) {
|
|
|
|
|
const uint8_t Y = y_row[x];
|
|
|
|
|
const uint8_t U = u_row[x / 2];
|
|
|
|
|
const uint8_t V = v_row[x / 2];
|
|
|
|
|
|
|
|
|
|
// BT.709 YUV to RGB conversion
|
|
|
|
|
const int C = Y - 16;
|
|
|
|
|
const int D = U - 128;
|
|
|
|
|
const int E = V - 128;
|
|
|
|
|
|
|
|
|
|
int R = (298 * C + 409 * E + 128) >> 8;
|
|
|
|
|
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
|
|
|
|
|
int B = (298 * C + 516 * D + 128) >> 8;
|
|
|
|
|
|
|
|
|
|
// Clamp to [0, 255]
|
|
|
|
|
R = std::max(0, std::min(255, R));
|
|
|
|
|
G = std::max(0, std::min(255, G));
|
|
|
|
|
B = std::max(0, std::min(255, B));
|
|
|
|
|
|
|
|
|
|
// Store as BGRA
|
|
|
|
|
bgra_row[x * 4 + 0] = static_cast<uint8_t>(B); // Blue
|
|
|
|
|
bgra_row[x * 4 + 1] = static_cast<uint8_t>(G); // Green
|
|
|
|
|
bgra_row[x * 4 + 2] = static_cast<uint8_t>(R); // Red
|
|
|
|
|
bgra_row[x * 4 + 3] = 255; // Alpha
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::UpdateStatus(winrt::hstring const& message)
|
|
|
|
|
{
|
|
|
|
|
m_status = message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::InitializeVideoRenderer()
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
// GPU rendering re-enabled for VavCore
|
2025-09-24 02:28:54 +09:00
|
|
|
// Try hardware rendering if enabled, fallback to software
|
|
|
|
|
bool useGPU = m_useHardwareRendering && TryInitializeGPURenderer();
|
|
|
|
|
SetRenderingMode(useGPU);
|
2025-09-25 21:54:50 +09:00
|
|
|
|
|
|
|
|
// If GPU initialization failed, ensure software rendering is ready
|
|
|
|
|
if (!useGPU) {
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
|
|
|
|
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
|
|
|
|
}
|
2025-09-24 02:28:54 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// GPU rendering methods re-enabled for VavCore
|
2025-09-24 02:28:54 +09:00
|
|
|
bool VideoPlayerControl::TryInitializeGPURenderer()
|
|
|
|
|
{
|
|
|
|
|
// Create GPU renderer if needed
|
|
|
|
|
if (!m_gpuRenderer) {
|
|
|
|
|
m_gpuRenderer = std::make_unique<SimpleGPURenderer>();
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
2025-09-23 00:31:16 +09:00
|
|
|
|
2025-09-24 02:28:54 +09:00
|
|
|
// Get container dimensions
|
|
|
|
|
auto container = VideoDisplayArea();
|
|
|
|
|
uint32_t width = static_cast<uint32_t>(container.ActualWidth());
|
|
|
|
|
uint32_t height = static_cast<uint32_t>(container.ActualHeight());
|
2025-09-23 00:31:16 +09:00
|
|
|
|
2025-09-24 02:28:54 +09:00
|
|
|
// Container must be ready with valid dimensions
|
|
|
|
|
if (width == 0 || height == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-09-23 02:25:59 +09:00
|
|
|
|
2025-09-24 02:28:54 +09:00
|
|
|
// Initialize GPU renderer
|
|
|
|
|
HRESULT hr = m_gpuRenderer->InitializeWithSwapChain(VideoSwapChainPanel(), width, height);
|
|
|
|
|
return SUCCEEDED(hr);
|
|
|
|
|
}
|
2025-09-23 02:25:59 +09:00
|
|
|
|
2025-09-24 02:28:54 +09:00
|
|
|
void VideoPlayerControl::SetRenderingMode(bool useGPU)
|
|
|
|
|
{
|
|
|
|
|
if (useGPU) {
|
|
|
|
|
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
|
|
|
|
} else {
|
|
|
|
|
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
|
|
|
|
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::ResetVideoState()
|
|
|
|
|
{
|
|
|
|
|
m_currentFrame = 0;
|
|
|
|
|
m_currentTime = 0.0;
|
|
|
|
|
m_isLoaded = false;
|
|
|
|
|
m_isPlaying = false;
|
|
|
|
|
|
2025-09-23 02:25:59 +09:00
|
|
|
// Reset AspectFit state
|
|
|
|
|
m_hasValidVideoSize = false;
|
|
|
|
|
m_videoWidth = 0;
|
|
|
|
|
m_videoHeight = 0;
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
// Stop and reset playback timer
|
|
|
|
|
if (m_playbackTimer)
|
|
|
|
|
{
|
|
|
|
|
m_playbackTimer.Stop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-09-23 02:25:59 +09:00
|
|
|
void VideoPlayerControl::ApplyAspectFitIfReady()
|
|
|
|
|
{
|
|
|
|
|
if (!m_hasValidVideoSize || !m_isLoaded) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto container = VideoDisplayArea();
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
double containerWidth = container.ActualWidth();
|
|
|
|
|
double containerHeight = container.ActualHeight();
|
|
|
|
|
|
|
|
|
|
if (containerWidth <= 0 || containerHeight <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateVideoImageAspectFit(m_videoWidth, m_videoHeight);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
void VideoPlayerControl::UpdateVideoImageAspectFit(int videoWidth, int videoHeight)
|
|
|
|
|
{
|
2025-09-23 02:25:59 +09:00
|
|
|
// Store video dimensions for future use
|
|
|
|
|
m_videoWidth = static_cast<uint32_t>(videoWidth);
|
|
|
|
|
m_videoHeight = static_cast<uint32_t>(videoHeight);
|
|
|
|
|
m_hasValidVideoSize = true;
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
// AspectFit calculation for proper video scaling
|
|
|
|
|
auto container = VideoDisplayArea();
|
2025-09-23 02:25:59 +09:00
|
|
|
if (!container) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
double containerWidth = container.ActualWidth();
|
|
|
|
|
double containerHeight = container.ActualHeight();
|
2025-09-23 02:25:59 +09:00
|
|
|
|
|
|
|
|
if (containerWidth <= 0 || containerHeight <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 00:31:16 +09:00
|
|
|
// Apply AspectFit to both CPU and GPU rendering controls
|
2025-09-22 22:01:53 +09:00
|
|
|
VideoImage().Width(displayWidth);
|
|
|
|
|
VideoImage().Height(displayHeight);
|
2025-09-23 02:25:59 +09:00
|
|
|
VideoImage().MaxWidth(displayWidth);
|
|
|
|
|
VideoImage().MaxHeight(displayHeight);
|
|
|
|
|
|
2025-09-23 00:31:16 +09:00
|
|
|
// Also apply to GPU rendering SwapChainPanel
|
|
|
|
|
VideoSwapChainPanel().Width(displayWidth);
|
|
|
|
|
VideoSwapChainPanel().Height(displayHeight);
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::Seek(double timeSeconds)
|
|
|
|
|
{
|
2025-09-25 21:54:50 +09:00
|
|
|
if (!m_isLoaded || !m_vavCorePlayer) return;
|
2025-09-22 22:01:53 +09:00
|
|
|
|
|
|
|
|
// Stop playback during seek
|
|
|
|
|
bool wasPlaying = m_isPlaying;
|
|
|
|
|
if (m_isPlaying) {
|
|
|
|
|
Pause();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 21:54:50 +09:00
|
|
|
// Seek to the specified time using VavCore API
|
|
|
|
|
VavCoreResult result = vavcore_seek_to_time(m_vavCorePlayer, timeSeconds);
|
|
|
|
|
if (result == VAVCORE_SUCCESS) {
|
2025-09-22 22:01:53 +09:00
|
|
|
m_currentTime = timeSeconds;
|
|
|
|
|
m_currentFrame = static_cast<uint64_t>(timeSeconds * m_frameRate);
|
|
|
|
|
|
|
|
|
|
// Process one frame to update display
|
|
|
|
|
ProcessSingleFrame();
|
|
|
|
|
|
|
|
|
|
// Resume playback if it was playing before seek
|
|
|
|
|
if (wasPlaying) {
|
|
|
|
|
Play();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateStatus(L"Seeked");
|
2025-09-25 21:54:50 +09:00
|
|
|
} else {
|
|
|
|
|
UpdateStatus(L"Seek failed");
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|
2025-09-22 02:15:47 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VideoPlayerControl::IsVideoPlaying() { return m_isPlaying; }
|
|
|
|
|
bool VideoPlayerControl::IsVideoLoaded() { return m_isLoaded; }
|
|
|
|
|
double VideoPlayerControl::CurrentTime() { return m_currentTime; }
|
|
|
|
|
double VideoPlayerControl::Duration() { return m_duration; }
|
|
|
|
|
winrt::hstring VideoPlayerControl::Status() { return m_status; }
|
2025-09-22 22:01:53 +09:00
|
|
|
|
2025-09-26 23:46:21 +09:00
|
|
|
void VideoPlayerControl::LoadDecoderSettings()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// Load from Windows.Storage.ApplicationData.Current.LocalSettings
|
|
|
|
|
auto localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
|
|
|
|
|
auto values = localSettings.Values();
|
|
|
|
|
|
|
|
|
|
// Load decoder type (default: AUTO)
|
|
|
|
|
if (values.HasKey(L"DecoderType")) {
|
|
|
|
|
auto decoderValue = values.Lookup(L"DecoderType");
|
|
|
|
|
if (decoderValue) {
|
|
|
|
|
int32_t decoderInt = winrt::unbox_value<int32_t>(decoderValue);
|
|
|
|
|
m_decoderType = static_cast<VavCoreDecoderType>(decoderInt);
|
|
|
|
|
|
|
|
|
|
// Log loaded decoder setting
|
|
|
|
|
std::wstring decoderName = L"Unknown";
|
|
|
|
|
switch (m_decoderType) {
|
|
|
|
|
case VAVCORE_DECODER_AUTO: decoderName = L"Auto"; break;
|
|
|
|
|
case VAVCORE_DECODER_DAV1D: decoderName = L"Software (dav1d)"; break;
|
|
|
|
|
case VAVCORE_DECODER_MEDIA_FOUNDATION: decoderName = L"Hardware (Media Foundation)"; break;
|
|
|
|
|
case VAVCORE_DECODER_NVDEC: decoderName = L"Hardware (NVDEC)"; break;
|
|
|
|
|
case VAVCORE_DECODER_VPL: decoderName = L"Hardware (Intel VPL)"; break;
|
|
|
|
|
case VAVCORE_DECODER_AMF: decoderName = L"Hardware (AMD AMF)"; break;
|
|
|
|
|
}
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Loaded decoder setting: " + decoderName, L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
m_decoderType = VAVCORE_DECODER_AUTO;
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Using default decoder: Auto", L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
} catch (...) {
|
|
|
|
|
// If settings loading fails, use default
|
|
|
|
|
m_decoderType = VAVCORE_DECODER_AUTO;
|
|
|
|
|
LogMgr::GetInstance().LogWarning(L"Failed to load decoder settings, using default: Auto", L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::RefreshDecoderSettings()
|
|
|
|
|
{
|
|
|
|
|
// Reload decoder settings from storage
|
|
|
|
|
LoadDecoderSettings();
|
|
|
|
|
|
|
|
|
|
// If a video is currently loaded, update the VavCore player with new decoder type
|
|
|
|
|
if (m_vavCorePlayer && m_isLoaded) {
|
|
|
|
|
vavcore_set_decoder_type(m_vavCorePlayer, m_decoderType);
|
|
|
|
|
|
|
|
|
|
std::wstring decoderName = L"Unknown";
|
|
|
|
|
switch (m_decoderType) {
|
|
|
|
|
case VAVCORE_DECODER_AUTO: decoderName = L"Auto"; break;
|
|
|
|
|
case VAVCORE_DECODER_DAV1D: decoderName = L"Software (dav1d)"; break;
|
|
|
|
|
case VAVCORE_DECODER_MEDIA_FOUNDATION: decoderName = L"Hardware (Media Foundation)"; break;
|
|
|
|
|
case VAVCORE_DECODER_NVDEC: decoderName = L"Hardware (NVDEC)"; break;
|
|
|
|
|
case VAVCORE_DECODER_VPL: decoderName = L"Hardware (Intel VPL)"; break;
|
|
|
|
|
case VAVCORE_DECODER_AMF: decoderName = L"Hardware (AMD AMF)"; break;
|
|
|
|
|
}
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Applied new decoder setting: " + decoderName, L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-23 03:39:44 +09:00
|
|
|
|
2025-09-27 08:44:28 +09:00
|
|
|
// D3D Surface Support Methods
|
|
|
|
|
bool VideoPlayerControl::InitializeD3DSurfaceSupport()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// Check if decoder supports any D3D surface types
|
|
|
|
|
VavCoreSurfaceType supportedTypes[] = {
|
|
|
|
|
VAVCORE_SURFACE_D3D11_TEXTURE,
|
|
|
|
|
VAVCORE_SURFACE_D3D12_RESOURCE,
|
|
|
|
|
VAVCORE_SURFACE_CUDA_DEVICE,
|
|
|
|
|
VAVCORE_SURFACE_AMF_SURFACE
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (auto surfaceType : supportedTypes) {
|
|
|
|
|
if (vavcore_supports_surface_type(m_vavCorePlayer, surfaceType)) {
|
|
|
|
|
m_supportedSurfaceType = surfaceType;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_supportedSurfaceType == VAVCORE_SURFACE_CPU) {
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"No D3D surface types supported, using CPU decoding", L"VideoPlayerControl");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For now, prioritize D3D11 texture support for SwapChainPanel compatibility
|
|
|
|
|
if (m_supportedSurfaceType == VAVCORE_SURFACE_D3D11_TEXTURE) {
|
|
|
|
|
// TODO: Get D3D11 device from SwapChainPanel or create one
|
|
|
|
|
// m_d3dDevice = GetD3D11DeviceFromSwapChainPanel();
|
|
|
|
|
|
|
|
|
|
// For now, set to nullptr - will be initialized when needed
|
|
|
|
|
VavCoreResult result = vavcore_set_d3d_device(m_vavCorePlayer, m_d3dDevice, m_supportedSurfaceType);
|
|
|
|
|
if (result == VAVCORE_SUCCESS) {
|
|
|
|
|
m_useD3DSurfaces = true;
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"D3D11 surface decoding enabled", L"VideoPlayerControl");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogMgr::GetInstance().LogWarning(L"Failed to initialize D3D surface support", L"VideoPlayerControl");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
LogMgr::GetInstance().LogError(L"Exception during D3D surface initialization", L"VideoPlayerControl");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::ProcessSingleFrameWithSurfaces()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// Simple validation
|
|
|
|
|
if (!m_isPlaying || !m_vavCorePlayer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto totalStart = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
// Create or reuse D3D texture for this frame
|
|
|
|
|
void* d3dTexture = nullptr;
|
|
|
|
|
if (!CreateD3DTexture(m_videoWidth, m_videoHeight, &d3dTexture)) {
|
|
|
|
|
LogMgr::GetInstance().LogError(L"Failed to create D3D texture", L"VideoPlayerControl");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode directly to D3D surface
|
|
|
|
|
VavCoreVideoFrame vavFrame;
|
|
|
|
|
VavCoreResult result = vavcore_decode_to_surface(m_vavCorePlayer, m_supportedSurfaceType, d3dTexture, &vavFrame);
|
|
|
|
|
|
|
|
|
|
if (result == VAVCORE_END_OF_STREAM) {
|
|
|
|
|
// End of video - stop playback
|
|
|
|
|
m_isPlaying = false;
|
|
|
|
|
if (m_playbackTimer) m_playbackTimer.Stop();
|
|
|
|
|
UpdateStatus(L"Playback completed");
|
|
|
|
|
LogMgr::GetInstance().LogInfo(L"Playback completed - End of stream reached", L"VideoPlayerControl");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result != VAVCORE_SUCCESS) {
|
|
|
|
|
// Decode error - count but continue processing
|
|
|
|
|
m_framesDecodeErrors++;
|
|
|
|
|
m_currentFrame++;
|
|
|
|
|
m_currentTime = m_currentFrame / m_frameRate;
|
|
|
|
|
|
|
|
|
|
// Log decode error occasionally
|
|
|
|
|
if (m_framesDecodeErrors % 10 == 1) {
|
|
|
|
|
LogMgr::GetInstance().LogError(L"D3D surface decode error count: " + std::to_wstring(m_framesDecodeErrors), L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render D3D surface directly to screen
|
|
|
|
|
RenderD3DSurfaceToScreen(d3dTexture, vavFrame);
|
|
|
|
|
|
|
|
|
|
// Update counters
|
|
|
|
|
m_currentFrame++;
|
|
|
|
|
m_currentTime = m_currentFrame / m_frameRate;
|
|
|
|
|
|
|
|
|
|
// Free VavCore frame (surface data remains in d3dTexture)
|
|
|
|
|
vavcore_free_frame(&vavFrame);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
LogMgr::GetInstance().LogError(L"Exception in ProcessSingleFrameWithSurfaces", L"VideoPlayerControl");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VideoPlayerControl::CreateD3DTexture(uint32_t width, uint32_t height, void** texture)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Implement D3D11 texture creation
|
|
|
|
|
// For now, return nullptr to indicate fallback to CPU decoding
|
|
|
|
|
*texture = nullptr;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoPlayerControl::RenderD3DSurfaceToScreen(void* d3dTexture, const VavCoreVideoFrame& frame)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Implement direct D3D surface rendering to SwapChainPanel
|
|
|
|
|
// For now, fall back to software rendering
|
|
|
|
|
RenderFrameSoftware(frame);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-22 22:01:53 +09:00
|
|
|
}
|