|
1 | 1 | /* |
2 | | - * Copyright (c) 2022, Gregory Bertilson <zaggy1024@gmail.com> |
| 2 | + * Copyright (c) 2022-2025, Gregory Bertilson <gregory@ladybird.org> |
3 | 3 | * |
4 | 4 | * SPDX-License-Identifier: BSD-2-Clause |
5 | 5 | */ |
6 | 6 |
|
7 | 7 | #pragma once |
8 | 8 |
|
9 | | -#include <AK/Atomic.h> |
10 | | -#include <AK/Function.h> |
11 | | -#include <AK/NonnullOwnPtr.h> |
12 | | -#include <AK/Queue.h> |
| 9 | +#include <AK/AtomicRefCounted.h> |
| 10 | +#include <AK/Forward.h> |
| 11 | +#include <AK/NonnullRefPtr.h> |
| 12 | +#include <AK/Stream.h> |
13 | 13 | #include <AK/Time.h> |
14 | | -#include <LibCore/SharedCircularQueue.h> |
15 | | -#include <LibGfx/Bitmap.h> |
16 | | -#include <LibMedia/Demuxer.h> |
| 14 | +#include <AK/Vector.h> |
| 15 | +#include <LibMedia/DecoderError.h> |
17 | 16 | #include <LibMedia/Export.h> |
18 | | -#include <LibThreading/ConditionVariable.h> |
| 17 | +#include <LibMedia/Forward.h> |
| 18 | +#include <LibMedia/Providers/MediaTimeProvider.h> |
| 19 | +#include <LibMedia/Track.h> |
19 | 20 | #include <LibThreading/Mutex.h> |
20 | | -#include <LibThreading/Thread.h> |
21 | | - |
22 | | -#include "VideoDecoder.h" |
23 | 21 |
|
24 | 22 | namespace Media { |
25 | 23 |
|
26 | | -class FrameQueueItem { |
27 | | -public: |
28 | | - FrameQueueItem() |
29 | | - : m_data(Empty()) |
30 | | - , m_timestamp(AK::Duration::zero()) |
31 | | - { |
32 | | - } |
33 | | - |
34 | | - static constexpr AK::Duration no_timestamp = AK::Duration::min(); |
35 | | - |
36 | | - enum class Type { |
37 | | - Frame, |
38 | | - Error, |
39 | | - }; |
40 | | - |
41 | | - static FrameQueueItem frame(RefPtr<Gfx::Bitmap> bitmap, AK::Duration timestamp) |
42 | | - { |
43 | | - return FrameQueueItem(move(bitmap), timestamp); |
44 | | - } |
45 | | - |
46 | | - static FrameQueueItem error_marker(DecoderError&& error, AK::Duration timestamp) |
47 | | - { |
48 | | - return FrameQueueItem(move(error), timestamp); |
49 | | - } |
50 | | - |
51 | | - bool is_frame() const { return m_data.has<RefPtr<Gfx::Bitmap>>(); } |
52 | | - RefPtr<Gfx::Bitmap> bitmap() const { return m_data.get<RefPtr<Gfx::Bitmap>>(); } |
53 | | - AK::Duration timestamp() const { return m_timestamp; } |
54 | | - |
55 | | - bool is_error() const { return m_data.has<DecoderError>(); } |
56 | | - DecoderError const& error() const { return m_data.get<DecoderError>(); } |
57 | | - DecoderError release_error() |
58 | | - { |
59 | | - auto error = move(m_data.get<DecoderError>()); |
60 | | - m_data.set(Empty()); |
61 | | - return error; |
62 | | - } |
63 | | - |
64 | | - bool is_empty() const { return m_data.has<Empty>(); } |
65 | | - |
66 | | - ByteString debug_string() const |
67 | | - { |
68 | | - if (is_error()) |
69 | | - return ByteString::formatted("{} at {}ms", error().string_literal(), timestamp().to_milliseconds()); |
70 | | - return ByteString::formatted("frame at {}ms", timestamp().to_milliseconds()); |
71 | | - } |
72 | | - |
73 | | -private: |
74 | | - FrameQueueItem(RefPtr<Gfx::Bitmap> bitmap, AK::Duration timestamp) |
75 | | - : m_data(move(bitmap)) |
76 | | - , m_timestamp(timestamp) |
77 | | - { |
78 | | - VERIFY(m_timestamp != no_timestamp); |
79 | | - } |
80 | | - |
81 | | - FrameQueueItem(DecoderError&& error, AK::Duration timestamp) |
82 | | - : m_data(move(error)) |
83 | | - , m_timestamp(timestamp) |
84 | | - { |
85 | | - } |
86 | | - |
87 | | - Variant<Empty, RefPtr<Gfx::Bitmap>, DecoderError> m_data { Empty() }; |
88 | | - AK::Duration m_timestamp { no_timestamp }; |
89 | | -}; |
90 | | - |
91 | | -static constexpr size_t frame_buffer_count = 4; |
92 | | -using VideoFrameQueue = Core::SharedSingleProducerCircularQueue<FrameQueueItem, frame_buffer_count>; |
93 | | - |
94 | | -enum class PlaybackState { |
95 | | - Playing, |
96 | | - Paused, |
97 | | - Buffering, |
98 | | - Seeking, |
99 | | - Stopped, |
100 | | -}; |
101 | | - |
102 | | -class MEDIA_API PlaybackManager { |
| 24 | +class MEDIA_API PlaybackManager final : public AtomicRefCounted<PlaybackManager> { |
103 | 25 | AK_MAKE_NONCOPYABLE(PlaybackManager); |
104 | 26 | AK_MAKE_NONMOVABLE(PlaybackManager); |
105 | 27 |
|
106 | | -public: |
107 | | - enum class SeekMode { |
108 | | - Accurate, |
109 | | - Fast, |
110 | | - }; |
| 28 | + class WeakPlaybackManager; |
111 | 29 |
|
112 | | - static constexpr SeekMode DEFAULT_SEEK_MODE = SeekMode::Accurate; |
| 30 | +public: |
| 31 | + static constexpr size_t EXPECTED_VIDEO_TRACK_COUNT = 1; |
113 | 32 |
|
114 | | - static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> from_data(ReadonlyBytes data); |
115 | | - static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> from_stream(NonnullOwnPtr<SeekableStream> stream); |
| 33 | + using VideoTracks = Vector<Track, EXPECTED_VIDEO_TRACK_COUNT>; |
116 | 34 |
|
117 | | - PlaybackManager(NonnullRefPtr<Demuxer> const& demuxer, Track video_track, NonnullOwnPtr<VideoDecoder>&& decoder, VideoFrameQueue&& frame_queue); |
| 35 | + static DecoderErrorOr<NonnullRefPtr<PlaybackManager>> try_create(NonnullOwnPtr<SeekableStream>&& stream); |
118 | 36 | ~PlaybackManager(); |
119 | 37 |
|
120 | | - void resume_playback(); |
121 | | - void pause_playback(); |
122 | | - void restart_playback(); |
123 | | - void terminate_playback(); |
124 | | - void seek_to_timestamp(AK::Duration, SeekMode = DEFAULT_SEEK_MODE); |
125 | | - bool is_playing() const |
126 | | - { |
127 | | - return m_playback_handler->is_playing(); |
128 | | - } |
129 | | - PlaybackState get_state() const |
130 | | - { |
131 | | - return m_playback_handler->get_state(); |
132 | | - } |
| 38 | + AK::Duration current_time() const; |
| 39 | + AK::Duration duration() const; |
133 | 40 |
|
134 | | - u64 number_of_skipped_frames() const { return m_skipped_frames; } |
| 41 | + VideoTracks const& video_tracks() const { return m_video_tracks; } |
| 42 | + Optional<Track> preferred_video_track(); |
135 | 43 |
|
136 | | - AK::Duration current_playback_time(); |
137 | | - AK::Duration duration(); |
| 44 | + // Creates a DisplayingVideoSink for the specified track. |
| 45 | + // |
| 46 | + // Note that in order for the current frame to change based on the media time, users must call |
| 47 | + // DisplayingVideoSink::update(). It is recommended to drive this off of vertical sync. |
| 48 | + NonnullRefPtr<DisplayingVideoSink> get_or_create_the_displaying_video_sink_for_track(Track const& track); |
| 49 | + // Removes the DisplayingVideoSink for the specified track. This will prevent the sink from |
| 50 | + // retrieving any subsequent frames from the decoder. |
| 51 | + void remove_the_displaying_video_sink_for_track(Track const& track); |
138 | 52 |
|
139 | | - Function<void(RefPtr<Gfx::Bitmap>)> on_video_frame; |
140 | | - Function<void()> on_playback_state_change; |
141 | | - Function<void(DecoderError)> on_decoder_error; |
142 | | - Function<void(Error)> on_fatal_playback_error; |
143 | | - |
144 | | - Track const& selected_video_track() const { return m_selected_video_track; } |
| 53 | + Function<void(DecoderError&&)> on_error; |
145 | 54 |
|
146 | 55 | private: |
147 | | - class PlaybackStateHandler; |
148 | | - // Abstract class to allow resuming play/pause after the state is completed. |
149 | | - class ResumingStateHandler; |
150 | | - class PlayingStateHandler; |
151 | | - class PausedStateHandler; |
152 | | - class BufferingStateHandler; |
153 | | - class SeekingStateHandler; |
154 | | - class StoppedStateHandler; |
155 | | - |
156 | | - static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> create(NonnullRefPtr<Demuxer> const& demuxer); |
157 | | - |
158 | | - void timer_callback(); |
159 | | - // This must be called with m_demuxer_mutex locked! |
160 | | - DecoderErrorOr<Optional<AK::Duration>> seek_demuxer_to_most_recent_keyframe(AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()); |
161 | | - |
162 | | - Optional<FrameQueueItem> dequeue_one_frame(); |
163 | | - void set_state_update_timer(int delay_ms); |
164 | | - |
165 | | - void decode_and_queue_one_sample(); |
| 56 | + class WeakPlaybackManager final : public MediaTimeProvider { |
| 57 | + friend class PlaybackManager; |
166 | 58 |
|
167 | | - void dispatch_decoder_error(DecoderError error); |
168 | | - void dispatch_new_frame(RefPtr<Gfx::Bitmap> frame); |
169 | | - // Returns whether we changed playback states. If so, any PlaybackStateHandler processing must cease. |
170 | | - [[nodiscard]] bool dispatch_frame_queue_item(FrameQueueItem&&); |
171 | | - void dispatch_state_change(); |
172 | | - void dispatch_fatal_error(Error); |
173 | | - |
174 | | - AK::Duration m_last_present_in_media_time = AK::Duration::zero(); |
175 | | - |
176 | | - NonnullRefPtr<Demuxer> m_demuxer; |
177 | | - Threading::Mutex m_decoder_mutex; |
178 | | - Track m_selected_video_track; |
179 | | - |
180 | | - VideoFrameQueue m_frame_queue; |
181 | | - |
182 | | - RefPtr<Core::Timer> m_state_update_timer; |
183 | | - |
184 | | - RefPtr<Threading::Thread> m_decode_thread; |
185 | | - NonnullOwnPtr<VideoDecoder> m_decoder; |
186 | | - Atomic<bool> m_stop_decoding { false }; |
187 | | - Threading::Mutex m_decode_wait_mutex; |
188 | | - Threading::ConditionVariable m_decode_wait_condition; |
189 | | - Atomic<bool> m_buffer_is_full { false }; |
| 59 | + public: |
| 60 | + WeakPlaybackManager() = default; |
190 | 61 |
|
191 | | - OwnPtr<PlaybackStateHandler> m_playback_handler; |
192 | | - Optional<FrameQueueItem> m_next_frame; |
| 62 | + RefPtr<PlaybackManager> take_strong() const |
| 63 | + { |
| 64 | + Threading::MutexLocker locker { m_mutex }; |
| 65 | + return m_manager; |
| 66 | + } |
193 | 67 |
|
194 | | - u64 m_skipped_frames { 0 }; |
| 68 | + virtual AK::Duration current_time() const override |
| 69 | + { |
| 70 | + Threading::MutexLocker locker { m_mutex }; |
| 71 | + if (m_manager) |
| 72 | + return m_manager->current_time(); |
| 73 | + return AK::Duration::zero(); |
| 74 | + } |
195 | 75 |
|
196 | | - // This is a nested class to allow private access. |
197 | | - class PlaybackStateHandler { |
198 | | - public: |
199 | | - PlaybackStateHandler(PlaybackManager& manager) |
200 | | - : m_manager(manager) |
| 76 | + private: |
| 77 | + void revoke() |
201 | 78 | { |
| 79 | + Threading::MutexLocker locker { m_mutex }; |
| 80 | + m_manager = nullptr; |
202 | 81 | } |
203 | | - virtual ~PlaybackStateHandler() = default; |
204 | | - virtual StringView name() = 0; |
205 | 82 |
|
206 | | - virtual ErrorOr<void> on_enter() { return {}; } |
| 83 | + mutable Threading::Mutex m_mutex; |
| 84 | + PlaybackManager* m_manager { nullptr }; |
| 85 | + }; |
207 | 86 |
|
208 | | - virtual ErrorOr<void> play() { return {}; } |
209 | | - virtual bool is_playing() const = 0; |
210 | | - virtual PlaybackState get_state() const = 0; |
211 | | - virtual ErrorOr<void> pause() { return {}; } |
212 | | - virtual ErrorOr<void> buffer() { return {}; } |
213 | | - virtual ErrorOr<void> seek(AK::Duration target_timestamp, SeekMode); |
214 | | - virtual ErrorOr<void> stop(); |
| 87 | + struct VideoTrackData { |
| 88 | + Track track; |
| 89 | + NonnullRefPtr<VideoDataProvider> provider; |
| 90 | + RefPtr<DisplayingVideoSink> display; |
| 91 | + }; |
| 92 | + using VideoTrackDatas = Vector<VideoTrackData, EXPECTED_VIDEO_TRACK_COUNT>; |
215 | 93 |
|
216 | | - virtual AK::Duration current_time() const; |
| 94 | + PlaybackManager(NonnullRefPtr<MutexedDemuxer> const&, NonnullRefPtr<WeakPlaybackManager> const&, VideoTracks&&, VideoTrackDatas&&); |
217 | 95 |
|
218 | | - virtual ErrorOr<void> do_timed_state_update() { return {}; } |
| 96 | + void set_up_error_handlers(); |
| 97 | + void dispatch_error(DecoderError&&); |
219 | 98 |
|
220 | | - protected: |
221 | | - template<class T, class... Args> |
222 | | - ErrorOr<void> replace_handler_and_delete_this(Args... args); |
| 99 | + VideoTrackData& get_video_data_for_track(Track const& track); |
223 | 100 |
|
224 | | - PlaybackManager& manager() const; |
| 101 | + NonnullRefPtr<MutexedDemuxer> m_demuxer; |
225 | 102 |
|
226 | | - PlaybackManager& manager() |
227 | | - { |
228 | | - return const_cast<PlaybackManager&>(const_cast<PlaybackStateHandler const*>(this)->manager()); |
229 | | - } |
| 103 | + NonnullRefPtr<WeakPlaybackManager> m_weak_wrapper; |
230 | 104 |
|
231 | | - private: |
232 | | - PlaybackManager& m_manager; |
233 | | -#if PLAYBACK_MANAGER_DEBUG |
234 | | - bool m_has_exited { false }; |
235 | | -#endif |
236 | | - }; |
| 105 | + VideoTracks m_video_tracks; |
| 106 | + VideoTrackDatas m_video_track_datas; |
| 107 | + |
| 108 | + MonotonicTime m_real_time_base; |
| 109 | + |
| 110 | + bool m_is_in_error_state { false }; |
237 | 111 | }; |
238 | 112 |
|
239 | 113 | } |
0 commit comments