799 lines
22 KiB
C++
799 lines
22 KiB
C++
|
|
#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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|