/* * Copyright (c) 2022, Gregory Bertilson * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include "PlaybackManager.h" namespace Video { #define TRY_OR_FATAL_ERROR(expression) \ ({ \ auto&& _fatal_expression = (expression); \ if (_fatal_expression.is_error()) { \ dispatch_fatal_error(_fatal_expression.release_error()); \ return; \ } \ static_assert(!::AK::Detail::IsLvalueReference, \ "Do not return a reference from a fallible expression"); \ _fatal_expression.release_value(); \ }) DecoderErrorOr> PlaybackManager::from_file(Core::Object& event_handler, StringView filename) { NonnullOwnPtr demuxer = TRY(Matroska::MatroskaDemuxer::from_file(filename)); auto video_tracks = TRY(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()); return make(event_handler, demuxer, track, make()); } PlaybackManager::PlaybackManager(Core::Object& event_handler, NonnullOwnPtr& demuxer, Track video_track, NonnullOwnPtr&& decoder) : m_event_handler(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()) , m_playback_handler(make(*this, false)) { m_present_timer = Core::Timer::create_single_shot(0, [&] { timer_callback(); }).release_value_but_fixme_should_propagate_errors(); m_decode_timer = Core::Timer::create_single_shot(0, [&] { on_decode_timer(); }).release_value_but_fixme_should_propagate_errors(); TRY_OR_FATAL_ERROR(m_playback_handler->on_enter()); } void PlaybackManager::resume_playback() { dbgln_if(PLAYBACK_MANAGER_DEBUG, "Resuming playback."); TRY_OR_FATAL_ERROR(m_playback_handler->play()); } void PlaybackManager::pause_playback() { dbgln_if(PLAYBACK_MANAGER_DEBUG, "Pausing playback."); if (!m_playback_handler->is_playing()) warnln("Cannot pause."); TRY_OR_FATAL_ERROR(m_playback_handler->pause()); } Time PlaybackManager::current_playback_time() { return m_playback_handler->current_time(); } Time PlaybackManager::duration() { auto duration_result = m_demuxer->duration(); if (duration_result.is_error()) dispatch_decoder_error(duration_result.release_error()); return duration_result.release_value(); } void PlaybackManager::dispatch_fatal_error(Error error) { dbgln_if(PLAYBACK_MANAGER_DEBUG, "Encountered fatal error: {}", error.string_literal()); // FIXME: For threading, this will have to use a pre-allocated event to send to the main loop // to be able to gracefully handle OOM. VERIFY(&m_main_loop == &Core::EventLoop::current()); FatalPlaybackErrorEvent event { move(error) }; m_event_handler.dispatch_event(event); } void PlaybackManager::dispatch_decoder_error(DecoderError error) { switch (error.category()) { case DecoderErrorCategory::EndOfStream: dbgln_if(PLAYBACK_MANAGER_DEBUG, "{}", error.string_literal()); TRY_OR_FATAL_ERROR(m_playback_handler->stop()); break; default: dbgln("Playback error encountered: {}", error.string_literal()); TRY_OR_FATAL_ERROR(m_playback_handler->stop()); m_main_loop.post_event(m_event_handler, make(move(error))); break; } } void PlaybackManager::dispatch_new_frame(RefPtr frame) { m_main_loop.post_event(m_event_handler, make(frame)); } bool PlaybackManager::dispatch_frame_queue_item(FrameQueueItem&& item) { if (item.is_error()) { dispatch_decoder_error(item.release_error()); return true; } dbgln_if(PLAYBACK_MANAGER_DEBUG, "Sent frame for presentation"); dispatch_new_frame(item.bitmap()); return false; } void PlaybackManager::dispatch_state_change() { m_main_loop.post_event(m_event_handler, TRY_OR_FATAL_ERROR(try_make())); } void PlaybackManager::timer_callback() { TRY_OR_FATAL_ERROR(m_playback_handler->on_timer_callback()); } void PlaybackManager::seek_to_timestamp(Time target_timestamp, SeekMode seek_mode) { TRY_OR_FATAL_ERROR(m_playback_handler->seek(target_timestamp, seek_mode)); } Optional