Files
video-v1/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.cpp

799 lines
22 KiB
C++
Raw Normal View History

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
#include <winrt/Microsoft.UI.Dispatching.h>
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()
{
InitializeComponent();
}
// Event Handlers
void VideoPlayerControl::UserControl_Loaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
{
try
{
m_isInitialized = true;
UpdateStatus(L"Ready");
// Set initial UI state
if (!m_showControls)
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
}
// Auto load video if source is set
if (!m_videoSource.empty())
{
LoadVideo(m_videoSource);
}
OutputDebugStringA("VideoPlayerControl loaded successfully\n");
}
catch (...)
{
UpdateStatus(L"Error during initialization");
}
}
void VideoPlayerControl::UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
{
try
{
Stop();
StopPlaybackTimer();
StopControlsHideTimer();
// Cleanup resources
m_decoder.reset();
m_fileReader.reset();
m_renderBitmap = nullptr;
m_isInitialized = false;
OutputDebugStringA("VideoPlayerControl unloaded\n");
}
catch (...)
{
// Ignore cleanup errors
}
}
void VideoPlayerControl::PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
{
if (m_isPlaying)
{
Pause();
}
else
{
Play();
}
}
void VideoPlayerControl::StopButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
{
Stop();
}
void VideoPlayerControl::ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e)
{
if (!m_isLoaded || !m_fileReader)
return;
// Avoid feedback loop during programmatic updates
if (e.OldValue() == e.NewValue())
return;
try
{
double percentage = e.NewValue();
double targetTime = (percentage / 100.0) * m_duration;
Seek(targetTime);
}
catch (...)
{
// Ignore seek errors
}
}
void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&)
{
if (m_showControls && m_isLoaded)
{
ShowControlsInternal();
StopControlsHideTimer();
}
}
void VideoPlayerControl::HoverDetector_PointerExited(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&)
{
if (m_showControls && m_isLoaded && m_isPlaying)
{
StartControlsHideTimer();
}
}
// 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)
{
if (value && m_isLoaded)
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
}
else
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
}
}
}
bool VideoPlayerControl::AutoPlay()
{
return m_autoPlay;
}
void VideoPlayerControl::AutoPlay(bool value)
{
m_autoPlay = value;
}
Vav2Player::VideoDecoderType VideoPlayerControl::DecoderType()
{
switch (m_decoderType)
{
case VideoDecoderFactory::DecoderType::AUTO:
return Vav2Player::VideoDecoderType::Auto;
case VideoDecoderFactory::DecoderType::SOFTWARE:
return Vav2Player::VideoDecoderType::Software;
case VideoDecoderFactory::DecoderType::HARDWARE_MF:
return Vav2Player::VideoDecoderType::HardwareMF;
default:
return Vav2Player::VideoDecoderType::Auto;
}
}
void VideoPlayerControl::DecoderType(Vav2Player::VideoDecoderType value)
{
VideoDecoderFactory::DecoderType newType;
switch (value)
{
case Vav2Player::VideoDecoderType::Auto:
newType = VideoDecoderFactory::DecoderType::AUTO;
break;
case Vav2Player::VideoDecoderType::Software:
newType = VideoDecoderFactory::DecoderType::SOFTWARE;
break;
case Vav2Player::VideoDecoderType::HardwareMF:
newType = VideoDecoderFactory::DecoderType::HARDWARE_MF;
break;
default:
newType = VideoDecoderFactory::DecoderType::AUTO;
break;
}
SetInternalDecoderType(newType);
}
VideoDecoderFactory::DecoderType VideoPlayerControl::GetInternalDecoderType()
{
return m_decoderType;
}
void VideoPlayerControl::SetInternalDecoderType(VideoDecoderFactory::DecoderType value)
{
if (m_decoderType != value)
{
m_decoderType = value;
// Reset decoder if currently loaded
if (m_isLoaded)
{
m_decoder.reset();
CreateDecoder();
}
}
}
// Public Methods
void VideoPlayerControl::LoadVideo(winrt::hstring const& filePath)
{
try
{
std::string filePathStr = winrt::to_string(filePath);
UpdateStatus(L"Loading video...");
LoadingRing().IsActive(true);
// Reset previous state
ResetVideoState();
// Create file reader
if (!m_fileReader)
{
m_fileReader = std::make_unique<WebMFileReader>();
}
// Open file
if (!m_fileReader->OpenFile(filePathStr))
{
UpdateStatus(L"Failed to open video file");
LoadingRing().IsActive(false);
return;
}
// Get video tracks
auto tracks = m_fileReader->GetVideoTracks();
if (tracks.empty())
{
UpdateStatus(L"No video tracks found");
LoadingRing().IsActive(false);
return;
}
// Select first video track
if (!m_fileReader->SelectVideoTrack(tracks[0].track_number))
{
UpdateStatus(L"Failed to select video track");
LoadingRing().IsActive(false);
return;
}
// Get metadata
auto metadata = m_fileReader->GetVideoMetadata();
m_totalFrames = metadata.total_frames;
m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0;
m_duration = m_totalFrames / m_frameRate;
// Create and initialize decoder
if (!CreateDecoder() || !InitializeDecoder())
{
UpdateStatus(L"Failed to initialize decoder");
LoadingRing().IsActive(false);
return;
}
// Initialize video renderer
InitializeVideoRenderer();
// Update UI state
m_isLoaded = true;
LoadingRing().IsActive(false);
PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
if (m_showControls)
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
}
// Update duration display
DurationText().Text(winrt::to_hstring(FormatTime(m_duration)));
UpdateStatus(L"Video loaded successfully");
OutputDebugStringA(("Video loaded: " + filePathStr + "\n").c_str());
// Auto play if enabled
if (m_autoPlay)
{
Play();
}
}
catch (...)
{
UpdateStatus(L"Error loading video");
LoadingRing().IsActive(false);
}
}
void VideoPlayerControl::Play()
{
if (!m_isLoaded || m_isPlaying)
return;
try
{
m_isPlaying = true;
UpdatePlaybackUI();
StartPlaybackTimer();
UpdateStatus(L"Playing");
if (m_showControls && m_isPlaying)
{
StartControlsHideTimer();
}
OutputDebugStringA("Playback started\n");
}
catch (...)
{
UpdateStatus(L"Error starting playback");
}
}
void VideoPlayerControl::Pause()
{
if (!m_isPlaying)
return;
try
{
m_isPlaying = false;
StopPlaybackTimer();
UpdatePlaybackUI();
UpdateStatus(L"Paused");
if (m_showControls)
{
ShowControlsInternal();
StopControlsHideTimer();
}
OutputDebugStringA("Playback paused\n");
}
catch (...)
{
UpdateStatus(L"Error pausing playback");
}
}
void VideoPlayerControl::Stop()
{
if (!m_isLoaded)
return;
try
{
m_isPlaying = false;
StopPlaybackTimer();
StopControlsHideTimer();
// Reset to beginning
m_currentFrame = 0;
m_currentTime = 0.0;
if (m_fileReader)
{
m_fileReader->Reset();
}
if (m_decoder)
{
m_decoder->Reset();
}
UpdatePlaybackUI();
UpdateProgress();
UpdateStatus(L"Stopped");
if (m_showControls)
{
ShowControlsInternal();
}
OutputDebugStringA("Playback stopped\n");
}
catch (...)
{
UpdateStatus(L"Error stopping playback");
}
}
void VideoPlayerControl::Seek(double timeSeconds)
{
if (!m_isLoaded || !m_fileReader)
return;
try
{
bool wasPlaying = m_isPlaying;
if (wasPlaying)
{
StopPlaybackTimer();
}
// Calculate target frame
uint64_t targetFrame = static_cast<uint64_t>(timeSeconds * m_frameRate);
targetFrame = std::min(targetFrame, m_totalFrames - 1);
// Seek in file reader
if (m_fileReader->SeekToFrame(targetFrame))
{
m_currentFrame = targetFrame;
m_currentTime = targetFrame / m_frameRate;
if (m_decoder)
{
m_decoder->Reset();
}
UpdateProgress();
ProcessSingleFrame(); // Render frame at seek position
}
if (wasPlaying)
{
StartPlaybackTimer();
}
OutputDebugStringA(("Seeked to time: " + std::to_string(timeSeconds) + "s\n").c_str());
}
catch (...)
{
UpdateStatus(L"Error during seek");
}
}
// Status Properties
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;
}
// Helper Methods
void VideoPlayerControl::InitializeVideoRenderer()
{
try
{
if (!m_fileReader || !m_fileReader->IsFileOpen())
return;
auto metadata = m_fileReader->GetVideoMetadata();
int width = metadata.width;
int height = metadata.height;
if (width <= 0 || height <= 0)
return;
// Create bitmap for rendering
m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(width, height);
m_bgraBuffer.resize(width * height * 4);
// Set as image source
VideoImage().Source(m_renderBitmap);
OutputDebugStringA(("Video renderer initialized: " + std::to_string(width) + "x" + std::to_string(height) + "\n").c_str());
}
catch (...)
{
UpdateStatus(L"Error initializing video renderer");
}
}
void VideoPlayerControl::ProcessSingleFrame()
{
if (!m_fileReader || !m_decoder || !m_fileReader->IsFileOpen())
return;
try
{
VideoPacket packet;
if (!m_fileReader->ReadNextPacket(packet))
{
// End of file
if (m_isPlaying)
{
Stop();
UpdateStatus(L"Playback completed");
}
return;
}
VideoFrame frame;
bool decodeSuccess = m_decoder->DecodeFrame(packet, frame);
if (decodeSuccess)
{
m_currentFrame++;
m_currentTime = m_currentFrame / m_frameRate;
RenderFrameToScreen(frame);
UpdateProgress();
}
}
catch (...)
{
// Continue playback on frame errors
}
}
void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame)
{
if (!frame.is_valid || !m_renderBitmap)
return;
try
{
// Convert YUV to BGRA
ConvertYUVToBGRA(frame, m_bgraBuffer.data(), frame.width, frame.height);
// Update bitmap
auto buffer = m_renderBitmap.PixelBuffer();
auto byteAccess = buffer.as<::IBufferByteAccess>();
uint8_t* pixels = nullptr;
byteAccess->Buffer(&pixels);
if (pixels)
{
memcpy(pixels, m_bgraBuffer.data(), m_bgraBuffer.size());
m_renderBitmap.Invalidate();
}
}
catch (...)
{
// Ignore rendering errors
}
}
void VideoPlayerControl::ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height)
{
const uint8_t* y_plane = yuv_frame.y_plane.get();
const uint8_t* u_plane = yuv_frame.u_plane.get();
const uint8_t* v_plane = yuv_frame.v_plane.get();
const int32_t y_stride = yuv_frame.y_stride;
const int32_t u_stride = yuv_frame.u_stride;
const int32_t v_stride = yuv_frame.v_stride;
for (uint32_t row = 0; row < height; ++row)
{
for (uint32_t col = 0; col < width; ++col)
{
int32_t y = y_plane[row * y_stride + col];
int32_t u = u_plane[(row / 2) * u_stride + (col / 2)];
int32_t v = v_plane[(row / 2) * v_stride + (col / 2)];
int c = y - 16;
int d = u - 128;
int e = v - 128;
int r = std::clamp((298 * c + 409 * e + 128) >> 8, 0, 255);
int g = std::clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255);
int b = std::clamp((298 * c + 516 * d + 128) >> 8, 0, 255);
uint32_t pixel_offset = (row * width + col) * 4;
bgra_buffer[pixel_offset + 0] = static_cast<uint8_t>(b);
bgra_buffer[pixel_offset + 1] = static_cast<uint8_t>(g);
bgra_buffer[pixel_offset + 2] = static_cast<uint8_t>(r);
bgra_buffer[pixel_offset + 3] = 255;
}
}
}
void VideoPlayerControl::UpdateStatus(winrt::hstring const& message)
{
m_status = message;
StatusText().Text(message);
// Show status overlay briefly
StatusOverlay().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
// Auto-hide after 2 seconds
auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();
auto timer = dispatcherQueue.CreateTimer();
timer.Interval(std::chrono::seconds(2));
timer.Tick([this](auto&&, auto&&)
{
StatusOverlay().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
});
timer.Start();
}
void VideoPlayerControl::UpdatePlaybackUI()
{
// Update play/pause button
if (m_isPlaying)
{
PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Pause);
}
else
{
PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Play);
}
// Update current time display
CurrentTimeText().Text(winrt::to_hstring(FormatTime(m_currentTime)));
}
void VideoPlayerControl::UpdateProgress()
{
if (m_duration > 0)
{
double progress = (m_currentTime / m_duration) * 100.0;
ProgressSlider().Value(progress);
}
}
void VideoPlayerControl::StartPlaybackTimer()
{
if (m_playbackTimer)
{
m_playbackTimer.Stop();
}
auto intervalMs = std::chrono::milliseconds(static_cast<int>(1000.0 / m_frameRate));
auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();
m_playbackTimer = dispatcherQueue.CreateTimer();
m_playbackTimer.Tick([this](auto&&, auto&&)
{
if (m_isPlaying)
{
ProcessSingleFrame();
UpdatePlaybackUI();
}
});
m_playbackTimer.Interval(intervalMs);
m_playbackTimer.Start();
}
void VideoPlayerControl::StopPlaybackTimer()
{
if (m_playbackTimer)
{
m_playbackTimer.Stop();
}
}
void VideoPlayerControl::ShowControlsInternal()
{
if (m_showControls && m_isLoaded)
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
}
}
void VideoPlayerControl::HideControls()
{
if (m_showControls && m_isPlaying)
{
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
}
}
void VideoPlayerControl::StartControlsHideTimer()
{
StopControlsHideTimer();
auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();
m_controlsHideTimer = dispatcherQueue.CreateTimer();
m_controlsHideTimer.Interval(std::chrono::seconds(3));
m_controlsHideTimer.Tick([this](auto&&, auto&&)
{
HideControls();
});
m_controlsHideTimer.Start();
}
void VideoPlayerControl::StopControlsHideTimer()
{
if (m_controlsHideTimer)
{
m_controlsHideTimer.Stop();
}
}
std::string VideoPlayerControl::FormatTime(double seconds)
{
int mins = static_cast<int>(seconds) / 60;
int secs = static_cast<int>(seconds) % 60;
char buffer[16];
sprintf_s(buffer, "%02d:%02d", mins, secs);
return std::string(buffer);
}
void VideoPlayerControl::ResetVideoState()
{
m_isLoaded = false;
m_isPlaying = false;
m_currentFrame = 0;
m_totalFrames = 0;
m_currentTime = 0.0;
m_duration = 0.0;
StopPlaybackTimer();
StopControlsHideTimer();
if (m_decoder)
{
m_decoder.reset();
}
if (m_fileReader)
{
m_fileReader.reset();
}
m_renderBitmap = nullptr;
VideoImage().Source(nullptr);
PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
}
bool VideoPlayerControl::CreateDecoder()
{
try
{
if (!m_fileReader)
return false;
auto metadata = m_fileReader->GetVideoMetadata();
m_decoder = VideoDecoderFactory::CreateDecoder(metadata.codec_type, m_decoderType);
return m_decoder != nullptr;
}
catch (...)
{
return false;
}
}
bool VideoPlayerControl::InitializeDecoder()
{
try
{
if (!m_decoder || !m_fileReader)
return false;
auto metadata = m_fileReader->GetVideoMetadata();
return m_decoder->Initialize(metadata);
}
catch (...)
{
return false;
}
}
}