mirror of
https://github.com/RGBCube/serenity
synced 2025-05-18 21:35:06 +00:00

No longer will the video player explode with error dialogs that then lock the user out of closing them. To avoid issues where the playback state becomes invalid when an error occurs, I've made all decoder errors pass through the frame queue. This way, when a video is corrupted, there should be no chance that the playback state becomes invalid due to setting the state to Corrupted in the event handler while a presentation event is still pending. Or at least I think that was what caused some issues I was seeing :^) This system should be a lot more robust if any future errors need to be handled.
249 lines
8.8 KiB
C++
249 lines
8.8 KiB
C++
/*
|
|
* Copyright (c) 2022, Gregory Bertilson <zaggy1024@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Format.h>
|
|
#include <LibCore/Timer.h>
|
|
#include <LibVideo/MatroskaReader.h>
|
|
#include <LibVideo/VP9/Decoder.h>
|
|
|
|
#include "MatroskaDemuxer.h"
|
|
#include "PlaybackManager.h"
|
|
|
|
namespace Video {
|
|
|
|
DecoderErrorOr<NonnullRefPtr<PlaybackManager>> PlaybackManager::from_file(Object* event_handler, StringView filename)
|
|
{
|
|
NonnullOwnPtr<Demuxer> demuxer = TRY(MatroskaDemuxer::from_file(filename));
|
|
auto video_tracks = demuxer->get_tracks_for_type(TrackType::Video);
|
|
if (video_tracks.is_empty())
|
|
return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv);
|
|
auto track = video_tracks[0];
|
|
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Selecting video track number {}", track.identifier());
|
|
|
|
NonnullOwnPtr<VideoDecoder> decoder = make<VP9::Decoder>();
|
|
return PlaybackManager::construct(event_handler, demuxer, track, decoder);
|
|
}
|
|
|
|
PlaybackManager::PlaybackManager(Object* event_handler, NonnullOwnPtr<Demuxer>& demuxer, Track video_track, NonnullOwnPtr<VideoDecoder>& decoder)
|
|
: Object(event_handler)
|
|
, m_main_loop(Core::EventLoop::current())
|
|
, m_demuxer(move(demuxer))
|
|
, m_selected_video_track(video_track)
|
|
, m_decoder(move(decoder))
|
|
, m_frame_queue(make<VideoFrameQueue>())
|
|
, m_present_timer(Core::Timer::construct())
|
|
, m_decode_timer(Core::Timer::construct())
|
|
{
|
|
m_present_timer->set_single_shot(true);
|
|
m_present_timer->set_interval(0);
|
|
m_present_timer->on_timeout = [&] { update_presented_frame(); };
|
|
|
|
m_decode_timer->set_single_shot(true);
|
|
m_decode_timer->set_interval(0);
|
|
m_decode_timer->on_timeout = [&] { on_decode_timer(); };
|
|
}
|
|
|
|
void PlaybackManager::set_playback_status(PlaybackStatus status)
|
|
{
|
|
if (status != m_status) {
|
|
auto old_status = m_status;
|
|
m_status = status;
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Set playback status from {} to {}", playback_status_to_string(old_status), playback_status_to_string(m_status));
|
|
|
|
if (status == PlaybackStatus::Playing) {
|
|
if (old_status == PlaybackStatus::Stopped || old_status == PlaybackStatus::Corrupted) {
|
|
restart_playback();
|
|
m_frame_queue->clear();
|
|
m_skipped_frames = 0;
|
|
}
|
|
m_last_present_in_real_time = Time::now_monotonic();
|
|
m_present_timer->start();
|
|
} else {
|
|
m_last_present_in_media_time = current_playback_time();
|
|
m_last_present_in_real_time = Time::zero();
|
|
m_present_timer->stop();
|
|
}
|
|
|
|
m_main_loop.post_event(*this, make<PlaybackStatusChangeEvent>(status, old_status));
|
|
}
|
|
}
|
|
|
|
void PlaybackManager::event(Core::Event& event)
|
|
{
|
|
if (event.type() == DecoderErrorOccurred) {
|
|
auto& error_event = static_cast<DecoderErrorEvent&>(event);
|
|
VERIFY(error_event.error().category() != DecoderErrorCategory::EndOfStream);
|
|
set_playback_status(PlaybackStatus::Corrupted);
|
|
}
|
|
|
|
// Allow events to bubble up in all cases.
|
|
event.ignore();
|
|
}
|
|
|
|
void PlaybackManager::resume_playback()
|
|
{
|
|
set_playback_status(PlaybackStatus::Playing);
|
|
}
|
|
|
|
void PlaybackManager::pause_playback()
|
|
{
|
|
set_playback_status(PlaybackStatus::Paused);
|
|
}
|
|
|
|
bool PlaybackManager::prepare_next_frame()
|
|
{
|
|
if (m_next_frame.has_value())
|
|
return true;
|
|
if (m_frame_queue->is_empty())
|
|
return false;
|
|
auto frame_item = m_frame_queue->dequeue();
|
|
m_next_frame.emplace(move(frame_item));
|
|
m_decode_timer->start();
|
|
return true;
|
|
}
|
|
|
|
Time PlaybackManager::current_playback_time()
|
|
{
|
|
if (is_playing())
|
|
return m_last_present_in_media_time + (Time::now_monotonic() - m_last_present_in_real_time);
|
|
return m_last_present_in_media_time;
|
|
}
|
|
|
|
Time PlaybackManager::duration()
|
|
{
|
|
return m_demuxer->duration();
|
|
}
|
|
|
|
void PlaybackManager::on_decoder_error(DecoderError error)
|
|
{
|
|
dbgln("Playback error encountered: {}", error.string_literal());
|
|
switch (error.category()) {
|
|
case DecoderErrorCategory::EndOfStream:
|
|
set_playback_status(PlaybackStatus::Stopped);
|
|
break;
|
|
default:
|
|
set_playback_status(PlaybackStatus::Corrupted);
|
|
m_main_loop.post_event(*this, make<DecoderErrorEvent>(move(error)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PlaybackManager::update_presented_frame()
|
|
{
|
|
bool out_of_queued_frames = false;
|
|
Optional<FrameQueueItem> frame_item_to_display;
|
|
|
|
while (true) {
|
|
out_of_queued_frames = out_of_queued_frames || !prepare_next_frame();
|
|
if (out_of_queued_frames)
|
|
break;
|
|
VERIFY(m_next_frame.has_value());
|
|
if (m_next_frame->is_error() || m_next_frame->timestamp() > current_playback_time())
|
|
break;
|
|
|
|
if (frame_item_to_display.has_value()) {
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "At {}ms: Dropped {} in favor of {}", current_playback_time().to_milliseconds(), frame_item_to_display->debug_string(), m_next_frame->debug_string());
|
|
m_skipped_frames++;
|
|
}
|
|
frame_item_to_display = m_next_frame.release_value();
|
|
}
|
|
|
|
if (!out_of_queued_frames && frame_item_to_display.has_value()) {
|
|
m_main_loop.post_event(*this, make<VideoFramePresentEvent>(frame_item_to_display->bitmap()));
|
|
m_last_present_in_media_time = current_playback_time();
|
|
m_last_present_in_real_time = Time::now_monotonic();
|
|
frame_item_to_display.clear();
|
|
}
|
|
|
|
if (frame_item_to_display.has_value()) {
|
|
VERIFY(!m_next_frame.has_value());
|
|
m_next_frame = frame_item_to_display;
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Set next frame back to dequeued item {}", m_next_frame->debug_string());
|
|
}
|
|
|
|
if (!is_playing())
|
|
return;
|
|
|
|
if (!out_of_queued_frames) {
|
|
if (m_next_frame->is_error()) {
|
|
on_decoder_error(m_next_frame->release_error());
|
|
m_next_frame.clear();
|
|
return;
|
|
}
|
|
|
|
auto frame_time_ms = (m_next_frame->timestamp() - current_playback_time()).to_milliseconds();
|
|
VERIFY(frame_time_ms <= NumericLimits<int>::max());
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Time until next frame is {}ms", frame_time_ms);
|
|
m_present_timer->start(max(static_cast<int>(frame_time_ms), 0));
|
|
return;
|
|
}
|
|
|
|
set_playback_status(PlaybackStatus::Buffering);
|
|
m_decode_timer->start();
|
|
}
|
|
|
|
void PlaybackManager::restart_playback()
|
|
{
|
|
m_last_present_in_media_time = Time::zero();
|
|
m_last_present_in_real_time = Time::zero();
|
|
auto result = m_demuxer->seek_to_most_recent_keyframe(m_selected_video_track, 0);
|
|
if (result.is_error())
|
|
on_decoder_error(result.release_error());
|
|
}
|
|
|
|
bool PlaybackManager::decode_and_queue_one_sample()
|
|
{
|
|
if (m_frame_queue->size() >= FRAME_BUFFER_COUNT)
|
|
return false;
|
|
#if PLAYBACK_MANAGER_DEBUG
|
|
auto start_time = Time::now_monotonic();
|
|
#endif
|
|
|
|
#define TRY_OR_ENQUEUE_ERROR(expression) \
|
|
({ \
|
|
auto _temporary_result = ((expression)); \
|
|
if (_temporary_result.is_error()) { \
|
|
dbgln("Enqueued decoder error: {}", _temporary_result.error().string_literal()); \
|
|
m_frame_queue->enqueue(FrameQueueItem::error_marker(_temporary_result.release_error())); \
|
|
return false; \
|
|
} \
|
|
_temporary_result.release_value(); \
|
|
})
|
|
|
|
auto frame_sample = TRY_OR_ENQUEUE_ERROR(m_demuxer->get_next_video_sample_for_track(m_selected_video_track));
|
|
|
|
TRY_OR_ENQUEUE_ERROR(m_decoder->receive_sample(frame_sample->data()));
|
|
auto decoded_frame = TRY_OR_ENQUEUE_ERROR(m_decoder->get_decoded_frame());
|
|
|
|
auto& cicp = decoded_frame->cicp();
|
|
cicp.adopt_specified_values(frame_sample->container_cicp());
|
|
cicp.default_code_points_if_unspecified({ Video::ColorPrimaries::BT709, Video::TransferCharacteristics::BT709, Video::MatrixCoefficients::BT709, Video::ColorRange::Studio });
|
|
|
|
auto bitmap = TRY_OR_ENQUEUE_ERROR(decoded_frame->to_bitmap());
|
|
m_frame_queue->enqueue(FrameQueueItem::frame(bitmap, frame_sample->timestamp()));
|
|
|
|
#if PLAYBACK_MANAGER_DEBUG
|
|
auto end_time = Time::now_monotonic();
|
|
dbgln("Decoding took {}ms", (end_time - start_time).to_milliseconds());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void PlaybackManager::on_decode_timer()
|
|
{
|
|
if (!decode_and_queue_one_sample() && is_buffering()) {
|
|
set_playback_status(PlaybackStatus::Playing);
|
|
return;
|
|
}
|
|
|
|
// Continually decode until buffering is complete
|
|
if (is_buffering())
|
|
m_decode_timer->start();
|
|
}
|
|
|
|
}
|