/* * Copyright (c) 2022-2025, Gregory Bertilson * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Media { class WeakPlaybackManagerLink; class WeakPlaybackManager; class MEDIA_API PlaybackManager final { AK_MAKE_NONCOPYABLE(PlaybackManager); AK_MAKE_NONMOVABLE(PlaybackManager); #define __MAKE_PLAYBACK_STATE_HANDLER_FRIEND(clazz) \ friend class clazz; ENUMERATE_PLAYBACK_STATE_HANDLERS(__MAKE_PLAYBACK_STATE_HANDLER_FRIEND) #undef __MAKE_PLAYBACK_STATE_HANDLER_FRIEND public: static constexpr size_t EXPECTED_VIDEO_TRACK_COUNT = 1; using VideoTracks = Vector; static constexpr size_t EXPECTED_AUDIO_TRACK_COUNT = 1; using AudioTracks = Vector; static NonnullOwnPtr create(); ~PlaybackManager(); void set_audio_output_disabled(bool disabled) { m_audio_output_disabled = disabled; } AK::Duration duration() const { return m_duration; } void set_duration(AK::Duration duration) { m_duration = duration; } AK::Duration current_time() const; Optional start_time_realtime() const { return m_start_time_realtime; } auto const& video_tracks() const { return m_video_tracks; } auto const& audio_tracks() const { return m_audio_tracks; } Optional preferred_video_track() { return m_preferred_video_track; } Optional preferred_audio_track() { return m_preferred_audio_track; } // Creates a DisplayingVideoSink for the specified track. // // Note that in order for the current frame to change based on the media time, users must call // DisplayingVideoSink::update(). It is recommended to drive this off of vertical sync. NonnullRefPtr get_or_create_the_displaying_video_sink_for_track(Track const&); // Removes the DisplayingVideoSink for the specified track. This will prevent the sink from // retrieving any subsequent frames from the decoder. void remove_the_displaying_video_sink_for_track(Track const&); void enable_an_audio_track(Track const&); void disable_an_audio_track(Track const&); bool track_is_enabled(Track const&) const; void start(); void play(); void pause(); void seek(AK::Duration timestamp, SeekMode); bool is_playing(); PlaybackState state(); AvailableData available_data(); TimeRanges buffered_time_ranges() const; void set_volume(double); void set_playback_rate(float); Function on_metadata_parsed; Function on_unsupported_format_error; Function on_track_added; Function on_playback_state_change; Function on_duration_change; Function on_error; void add_media_source(NonnullRefPtr const&); void add_media_source(NonnullRefPtr const&); private: struct VideoTrackData { Track track; NonnullRefPtr producer; RefPtr display; PipelineStatus sink_status { PipelineStatus::HaveData }; }; using VideoTrackDatas = Vector; struct AudioTrackData { Track track; NonnullRefPtr producer; bool enabled { false }; }; using AudioTrackDatas = Vector; PlaybackManager(); WeakPlaybackManager weak(); void set_time_provider(NonnullRefPtr const&); void disable_audio(); void set_up_producers(); void on_audio_sink_state_changed(PipelineStatus); void on_video_sink_state_changed(Track const&, PipelineStatus); void update_pipeline_state(); void reset_pipeline_state(); PipelineStatus combined_pipeline_status() const; void check_for_duration_change(AK::Duration); void dispatch_error(DecoderError&&); template decltype(auto) get_video_data_for_track(this Self&& self, Track const& track) { for (auto& track_data : self.m_video_track_datas) { if (track_data.track == track) return track_data; } VERIFY_NOT_REACHED(); } template decltype(auto) get_audio_data_for_track(this Self&& self, Track const& track) { for (auto& track_data : self.m_audio_track_datas) { if (track_data.track == track) return track_data; } VERIFY_NOT_REACHED(); } static DecoderErrorOr> create_demuxer_for_stream(NonnullRefPtr const&); static DecoderErrorOr prepare_playback_from_demuxer(WeakPlaybackManager const&, NonnullRefPtr const&, Core::EventLoop&); template void replace_state_handler(Args&&... args); inline void dispatch_state_change() const; OwnPtr m_handler; NonnullRefPtr m_weak_link; NonnullRefPtr m_time_provider; float m_playback_rate { 1.0f }; bool m_audio_output_disabled { false }; VideoTracks m_video_tracks; VideoTrackDatas m_video_track_datas; RefPtr m_audio_mixer; RefPtr m_audio_time_stretch_processor; RefPtr m_audio_sink; AudioTracks m_audio_tracks; AudioTrackDatas m_audio_track_datas; Optional m_preferred_video_track; Optional m_preferred_audio_track; AK::Duration m_duration; Optional m_start_time_realtime; PipelineStatus m_audio_sink_status { PipelineStatus::HaveData }; bool m_is_in_error_state { false }; }; template void PlaybackManager::replace_state_handler(Args&&... args) { m_handler->on_exit(); OwnPtr new_handler = make(*this, args...); m_handler.swap(new_handler); m_handler->on_enter(); dispatch_state_change(); } void PlaybackManager::dispatch_state_change() const { if (on_playback_state_change) on_playback_state_change(); } class WeakPlaybackManagerLink : public AtomicRefCounted { public: WeakPlaybackManagerLink(PlaybackManager& manager) : m_manager(&manager) , m_originating_event_loop(Core::EventLoop::current()) { } bool is_alive() const { verify_thread_is_originating_thread(); return m_manager != nullptr; } PlaybackManager& get() const { VERIFY(is_alive()); return *m_manager; } void revoke(Badge) { Sync::MutexLocker locker { m_mutex }; m_manager = nullptr; } private: void verify_thread_is_originating_thread() const { VERIFY(Core::EventLoop::is_running()); VERIFY(&Core::EventLoop::current() == &m_originating_event_loop); } mutable Sync::Mutex m_mutex; PlaybackManager* m_manager { nullptr }; Core::EventLoop& m_originating_event_loop; }; class WeakPlaybackManager { AK_MAKE_DEFAULT_COPYABLE(WeakPlaybackManager); AK_MAKE_DEFAULT_MOVABLE(WeakPlaybackManager); public: WeakPlaybackManager(WeakPlaybackManagerLink& link) : m_link(link) { } operator bool() const { return m_link->is_alive(); } PlaybackManager& operator*() const { return m_link->get(); } PlaybackManager* operator->() const { return &m_link->get(); } private: NonnullRefPtr m_link; }; }