/src/serenity/Userland/Libraries/LibMedia/PlaybackManager.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, Gregory Bertilson <zaggy1024@gmail.com> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Format.h> |
8 | | #include <LibCore/Timer.h> |
9 | | #include <LibMedia/Containers/Matroska/MatroskaDemuxer.h> |
10 | | #include <LibMedia/Video/VP8/Decoder.h> |
11 | | #include <LibMedia/Video/VP9/Decoder.h> |
12 | | |
13 | | #include "PlaybackManager.h" |
14 | | |
15 | | namespace Media { |
16 | | |
17 | | #define TRY_OR_FATAL_ERROR(expression) \ |
18 | 0 | ({ \ |
19 | 0 | auto&& _fatal_expression = (expression); \ |
20 | 0 | if (_fatal_expression.is_error()) { \ |
21 | 0 | dispatch_fatal_error(_fatal_expression.release_error()); \ |
22 | 0 | return; \ |
23 | 0 | } \ |
24 | 0 | static_assert(!::AK::Detail::IsLvalueReference<decltype(_fatal_expression.release_value())>, \ |
25 | 0 | "Do not return a reference from a fallible expression"); \ |
26 | 0 | _fatal_expression.release_value(); \ |
27 | 0 | }) |
28 | | |
29 | | DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_file(StringView filename) |
30 | 0 | { |
31 | 0 | auto demuxer = TRY(Matroska::MatroskaDemuxer::from_file(filename)); |
32 | 0 | return create(move(demuxer)); |
33 | 0 | } |
34 | | |
35 | | DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_mapped_file(NonnullOwnPtr<Core::MappedFile> mapped_file) |
36 | 0 | { |
37 | 0 | auto demuxer = TRY(Matroska::MatroskaDemuxer::from_mapped_file(move(mapped_file))); |
38 | 0 | return create(move(demuxer)); |
39 | 0 | } |
40 | | |
41 | | DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_data(ReadonlyBytes data) |
42 | 0 | { |
43 | 0 | auto demuxer = TRY(Matroska::MatroskaDemuxer::from_data(data)); |
44 | 0 | return create(move(demuxer)); |
45 | 0 | } |
46 | | |
47 | | PlaybackManager::PlaybackManager(NonnullOwnPtr<Demuxer>& demuxer, Track video_track, NonnullOwnPtr<VideoDecoder>&& decoder, VideoFrameQueue&& frame_queue) |
48 | 0 | : m_demuxer(move(demuxer)) |
49 | 0 | , m_selected_video_track(video_track) |
50 | 0 | , m_frame_queue(move(frame_queue)) |
51 | 0 | , m_decoder(move(decoder)) |
52 | 0 | , m_decode_wait_condition(m_decode_wait_mutex) |
53 | 0 | { |
54 | 0 | } |
55 | | |
56 | | PlaybackManager::~PlaybackManager() |
57 | 0 | { |
58 | 0 | terminate_playback(); |
59 | 0 | } |
60 | | |
61 | | void PlaybackManager::resume_playback() |
62 | 0 | { |
63 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Resuming playback."); |
64 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->play()); |
65 | 0 | } |
66 | | |
67 | | void PlaybackManager::pause_playback() |
68 | 0 | { |
69 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Pausing playback."); |
70 | 0 | if (!m_playback_handler->is_playing()) |
71 | 0 | warnln("Cannot pause."); |
72 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->pause()); |
73 | 0 | } |
74 | | |
75 | | void PlaybackManager::terminate_playback() |
76 | 0 | { |
77 | 0 | m_stop_decoding.exchange(true); |
78 | 0 | m_decode_wait_condition.broadcast(); |
79 | |
|
80 | 0 | if (m_decode_thread->needs_to_be_joined()) { |
81 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Waiting for decode thread to end..."); |
82 | 0 | (void)m_decode_thread->join(); |
83 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Successfully destroyed PlaybackManager."); |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | | Duration PlaybackManager::current_playback_time() |
88 | 0 | { |
89 | 0 | return m_playback_handler->current_time(); |
90 | 0 | } |
91 | | |
92 | | Duration PlaybackManager::duration() |
93 | 0 | { |
94 | 0 | auto duration_result = ({ |
95 | 0 | auto demuxer_locker = Threading::MutexLocker(m_decoder_mutex); |
96 | 0 | m_demuxer->duration(); |
97 | 0 | }); |
98 | 0 | if (duration_result.is_error()) { |
99 | 0 | dispatch_decoder_error(duration_result.release_error()); |
100 | | // FIXME: We should determine the last sample that the demuxer knows is available and |
101 | | // use that as the current duration. The duration may change if the demuxer doesn't |
102 | | // know there is a fixed duration. |
103 | 0 | return Duration::zero(); |
104 | 0 | } |
105 | 0 | return duration_result.release_value(); |
106 | 0 | } |
107 | | |
108 | | void PlaybackManager::dispatch_fatal_error(Error error) |
109 | 0 | { |
110 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Encountered fatal error: {}", error.string_literal()); |
111 | | // FIXME: For threading, this will have to use a pre-allocated event to send to the main loop |
112 | | // to be able to gracefully handle OOM. |
113 | 0 | if (on_fatal_playback_error) |
114 | 0 | on_fatal_playback_error(move(error)); |
115 | 0 | } |
116 | | |
117 | | void PlaybackManager::dispatch_decoder_error(DecoderError error) |
118 | 0 | { |
119 | 0 | switch (error.category()) { |
120 | 0 | case DecoderErrorCategory::EndOfStream: |
121 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "{}", error.string_literal()); |
122 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->stop()); |
123 | 0 | break; |
124 | 0 | default: |
125 | 0 | dbgln("Playback error encountered: {}", error.string_literal()); |
126 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->stop()); |
127 | |
|
128 | 0 | if (on_decoder_error) |
129 | 0 | on_decoder_error(move(error)); |
130 | |
|
131 | 0 | break; |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | void PlaybackManager::dispatch_new_frame(RefPtr<Gfx::Bitmap> frame) |
136 | 0 | { |
137 | 0 | if (on_video_frame) |
138 | 0 | on_video_frame(move(frame)); |
139 | 0 | } |
140 | | |
141 | | bool PlaybackManager::dispatch_frame_queue_item(FrameQueueItem&& item) |
142 | 0 | { |
143 | 0 | if (item.is_error()) { |
144 | 0 | dispatch_decoder_error(item.release_error()); |
145 | 0 | return true; |
146 | 0 | } |
147 | | |
148 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Sent frame for presentation with timestamp {}ms, late by {}ms", item.timestamp().to_milliseconds(), (current_playback_time() - item.timestamp()).to_milliseconds()); |
149 | 0 | dispatch_new_frame(item.bitmap()); |
150 | 0 | return false; |
151 | 0 | } |
152 | | |
153 | | void PlaybackManager::dispatch_state_change() |
154 | 0 | { |
155 | 0 | if (on_playback_state_change) |
156 | 0 | on_playback_state_change(); |
157 | 0 | } |
158 | | |
159 | | void PlaybackManager::timer_callback() |
160 | 0 | { |
161 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->do_timed_state_update()); |
162 | 0 | } |
163 | | |
164 | | void PlaybackManager::seek_to_timestamp(Duration target_timestamp, SeekMode seek_mode) |
165 | 0 | { |
166 | 0 | TRY_OR_FATAL_ERROR(m_playback_handler->seek(target_timestamp, seek_mode)); |
167 | 0 | } |
168 | | |
169 | | DecoderErrorOr<Optional<Duration>> PlaybackManager::seek_demuxer_to_most_recent_keyframe(Duration timestamp, Optional<Duration> earliest_available_sample) |
170 | 0 | { |
171 | 0 | auto seeked_timestamp = TRY(m_demuxer->seek_to_most_recent_keyframe(m_selected_video_track, timestamp, move(earliest_available_sample))); |
172 | 0 | if (seeked_timestamp.has_value()) |
173 | 0 | m_decoder->flush(); |
174 | 0 | return seeked_timestamp; |
175 | 0 | } |
176 | | |
177 | | Optional<FrameQueueItem> PlaybackManager::dequeue_one_frame() |
178 | 0 | { |
179 | 0 | auto result = m_frame_queue.dequeue(); |
180 | 0 | m_decode_wait_condition.broadcast(); |
181 | 0 | if (result.is_error()) { |
182 | 0 | if (result.error() != VideoFrameQueue::QueueStatus::Empty) |
183 | 0 | dispatch_fatal_error(Error::from_string_literal("Dequeue failed with an unexpected error")); |
184 | 0 | return {}; |
185 | 0 | } |
186 | 0 | return result.release_value(); |
187 | 0 | } |
188 | | |
189 | | void PlaybackManager::set_state_update_timer(int delay_ms) |
190 | 0 | { |
191 | 0 | m_state_update_timer->start(delay_ms); |
192 | 0 | } |
193 | | |
194 | | void PlaybackManager::restart_playback() |
195 | 0 | { |
196 | 0 | seek_to_timestamp(Duration::zero()); |
197 | 0 | } |
198 | | |
199 | | void PlaybackManager::decode_and_queue_one_sample() |
200 | 0 | { |
201 | | #if PLAYBACK_MANAGER_DEBUG |
202 | | auto start_time = MonotonicTime::now(); |
203 | | #endif |
204 | |
|
205 | 0 | FrameQueueItem item_to_enqueue; |
206 | |
|
207 | 0 | while (item_to_enqueue.is_empty()) { |
208 | 0 | OwnPtr<VideoFrame> decoded_frame = nullptr; |
209 | 0 | CodingIndependentCodePoints container_cicp; |
210 | |
|
211 | 0 | { |
212 | 0 | Threading::MutexLocker decoder_locker(m_decoder_mutex); |
213 | | |
214 | | // Get a sample to decode. |
215 | 0 | auto sample_result = m_demuxer->get_next_sample_for_track(m_selected_video_track); |
216 | 0 | if (sample_result.is_error()) { |
217 | 0 | item_to_enqueue = FrameQueueItem::error_marker(sample_result.release_error(), FrameQueueItem::no_timestamp); |
218 | 0 | break; |
219 | 0 | } |
220 | 0 | auto sample = sample_result.release_value(); |
221 | 0 | container_cicp = sample.auxiliary_data().get<VideoSampleData>().container_cicp(); |
222 | | |
223 | | // Submit the sample to the decoder. |
224 | 0 | auto decode_result = m_decoder->receive_sample(sample.timestamp(), sample.data()); |
225 | 0 | if (decode_result.is_error()) { |
226 | 0 | item_to_enqueue = FrameQueueItem::error_marker(decode_result.release_error(), sample.timestamp()); |
227 | 0 | break; |
228 | 0 | } |
229 | | |
230 | | // Retrieve the last available frame to present. |
231 | 0 | while (true) { |
232 | 0 | auto frame_result = m_decoder->get_decoded_frame(); |
233 | |
|
234 | 0 | if (frame_result.is_error()) { |
235 | 0 | if (frame_result.error().category() == DecoderErrorCategory::NeedsMoreInput) { |
236 | 0 | break; |
237 | 0 | } |
238 | | |
239 | 0 | item_to_enqueue = FrameQueueItem::error_marker(frame_result.release_error(), sample.timestamp()); |
240 | 0 | break; |
241 | 0 | } |
242 | | |
243 | 0 | decoded_frame = frame_result.release_value(); |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | // Convert the frame for display. |
248 | 0 | if (decoded_frame != nullptr) { |
249 | 0 | auto& cicp = decoded_frame->cicp(); |
250 | 0 | cicp.adopt_specified_values(container_cicp); |
251 | 0 | cicp.default_code_points_if_unspecified({ ColorPrimaries::BT709, TransferCharacteristics::BT709, MatrixCoefficients::BT709, VideoFullRangeFlag::Studio }); |
252 | | |
253 | | // BT.470 M, B/G, BT.601, BT.709 and BT.2020 have a similar transfer function to sRGB, so other applications |
254 | | // (Chromium, VLC) forgo transfer characteristics conversion. We will emulate that behavior by |
255 | | // handling those as sRGB instead, which causes no transfer function change in the output, |
256 | | // unless display color management is later implemented. |
257 | 0 | switch (cicp.transfer_characteristics()) { |
258 | 0 | case TransferCharacteristics::BT470BG: |
259 | 0 | case TransferCharacteristics::BT470M: |
260 | 0 | case TransferCharacteristics::BT601: |
261 | 0 | case TransferCharacteristics::BT709: |
262 | 0 | case TransferCharacteristics::BT2020BitDepth10: |
263 | 0 | case TransferCharacteristics::BT2020BitDepth12: |
264 | 0 | cicp.set_transfer_characteristics(TransferCharacteristics::SRGB); |
265 | 0 | break; |
266 | 0 | default: |
267 | 0 | break; |
268 | 0 | } |
269 | | |
270 | 0 | auto bitmap_result = decoded_frame->to_bitmap(); |
271 | |
|
272 | 0 | if (bitmap_result.is_error()) |
273 | 0 | item_to_enqueue = FrameQueueItem::error_marker(bitmap_result.release_error(), decoded_frame->timestamp()); |
274 | 0 | else |
275 | 0 | item_to_enqueue = FrameQueueItem::frame(bitmap_result.release_value(), decoded_frame->timestamp()); |
276 | 0 | break; |
277 | 0 | } |
278 | 0 | } |
279 | | |
280 | 0 | VERIFY(!item_to_enqueue.is_empty()); |
281 | | #if PLAYBACK_MANAGER_DEBUG |
282 | | dbgln("Media Decoder: Sample at {}ms took {}ms to decode, queue contains ~{} items", item_to_enqueue.timestamp().to_milliseconds(), (MonotonicTime::now() - start_time).to_milliseconds(), m_frame_queue.weak_used()); |
283 | | #endif |
284 | | |
285 | 0 | auto wait = [&] { |
286 | 0 | auto wait_locker = Threading::MutexLocker(m_decode_wait_mutex); |
287 | 0 | m_decode_wait_condition.wait(); |
288 | 0 | }; |
289 | |
|
290 | 0 | bool had_error = item_to_enqueue.is_error(); |
291 | 0 | while (true) { |
292 | 0 | if (m_frame_queue.can_enqueue()) { |
293 | 0 | MUST(m_frame_queue.enqueue(move(item_to_enqueue))); |
294 | 0 | break; |
295 | 0 | } |
296 | | |
297 | 0 | if (m_stop_decoding.load()) { |
298 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Media Decoder: Received signal to stop, exiting decode function..."); |
299 | 0 | return; |
300 | 0 | } |
301 | | |
302 | 0 | m_buffer_is_full.exchange(true); |
303 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Media Decoder: Waiting for a frame to be dequeued..."); |
304 | 0 | wait(); |
305 | 0 | } |
306 | | |
307 | 0 | if (had_error) { |
308 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Media Decoder: Encountered {}, waiting...", item_to_enqueue.error().category() == DecoderErrorCategory::EndOfStream ? "end of stream"sv : "error"sv); |
309 | 0 | m_buffer_is_full.exchange(true); |
310 | 0 | wait(); |
311 | 0 | } |
312 | |
|
313 | 0 | m_buffer_is_full.exchange(false); |
314 | 0 | } |
315 | | |
316 | | Duration PlaybackManager::PlaybackStateHandler::current_time() const |
317 | 0 | { |
318 | 0 | return m_manager.m_last_present_in_media_time; |
319 | 0 | } |
320 | | |
321 | | ErrorOr<void> PlaybackManager::PlaybackStateHandler::seek(Duration target_timestamp, SeekMode seek_mode) |
322 | 0 | { |
323 | 0 | return replace_handler_and_delete_this<SeekingStateHandler>(is_playing(), target_timestamp, seek_mode); |
324 | 0 | } |
325 | | |
326 | | ErrorOr<void> PlaybackManager::PlaybackStateHandler::stop() |
327 | 0 | { |
328 | 0 | return replace_handler_and_delete_this<StoppedStateHandler>(); |
329 | 0 | } |
330 | | |
331 | | template<class T, class... Args> |
332 | | ErrorOr<void> PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this(Args... args) |
333 | 0 | { |
334 | 0 | OwnPtr<PlaybackStateHandler> temp_handler = TRY(try_make<T>(m_manager, args...)); |
335 | 0 | m_manager.m_playback_handler.swap(temp_handler); |
336 | | #if PLAYBACK_MANAGER_DEBUG |
337 | | m_has_exited = true; |
338 | | dbgln("Changing state from {} to {}", temp_handler->name(), m_manager.m_playback_handler->name()); |
339 | | #endif |
340 | 0 | TRY(m_manager.m_playback_handler->on_enter()); |
341 | 0 | m_manager.dispatch_state_change(); |
342 | 0 | return {}; |
343 | 0 | } Unexecuted instantiation: AK::ErrorOr<void, AK::Error> Media::PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this<Media::PlaybackManager::SeekingStateHandler, bool, AK::Duration, Media::PlaybackManager::SeekMode>(bool, AK::Duration, Media::PlaybackManager::SeekMode) Unexecuted instantiation: AK::ErrorOr<void, AK::Error> Media::PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this<Media::PlaybackManager::PausedStateHandler>() Unexecuted instantiation: AK::ErrorOr<void, AK::Error> Media::PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this<Media::PlaybackManager::PlayingStateHandler>() Unexecuted instantiation: AK::ErrorOr<void, AK::Error> Media::PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this<Media::PlaybackManager::BufferingStateHandler, bool>(bool) Unexecuted instantiation: AK::ErrorOr<void, AK::Error> Media::PlaybackManager::PlaybackStateHandler::replace_handler_and_delete_this<Media::PlaybackManager::StoppedStateHandler>() |
344 | | |
345 | | PlaybackManager& PlaybackManager::PlaybackStateHandler::manager() const |
346 | 0 | { |
347 | | #if PLAYBACK_MANAGER_DEBUG |
348 | | VERIFY(!m_has_exited); |
349 | | #endif |
350 | 0 | return m_manager; |
351 | 0 | } |
352 | | |
353 | | class PlaybackManager::ResumingStateHandler : public PlaybackManager::PlaybackStateHandler { |
354 | | public: |
355 | | ResumingStateHandler(PlaybackManager& manager, bool playing) |
356 | 0 | : PlaybackStateHandler(manager) |
357 | 0 | , m_playing(playing) |
358 | 0 | { |
359 | 0 | } |
360 | | ~ResumingStateHandler() override = default; |
361 | | |
362 | | protected: |
363 | | ErrorOr<void> assume_next_state() |
364 | 0 | { |
365 | 0 | if (!m_playing) |
366 | 0 | return replace_handler_and_delete_this<PausedStateHandler>(); |
367 | 0 | return replace_handler_and_delete_this<PlayingStateHandler>(); |
368 | 0 | } |
369 | | |
370 | | ErrorOr<void> play() override |
371 | 0 | { |
372 | 0 | m_playing = true; |
373 | 0 | manager().dispatch_state_change(); |
374 | 0 | return {}; |
375 | 0 | } |
376 | 0 | bool is_playing() const override { return m_playing; } |
377 | | ErrorOr<void> pause() override |
378 | 0 | { |
379 | 0 | m_playing = false; |
380 | 0 | manager().dispatch_state_change(); |
381 | 0 | return {}; |
382 | 0 | } |
383 | | |
384 | | bool m_playing { false }; |
385 | | }; |
386 | | |
387 | | class PlaybackManager::PlayingStateHandler : public PlaybackManager::PlaybackStateHandler { |
388 | | public: |
389 | | PlayingStateHandler(PlaybackManager& manager) |
390 | 0 | : PlaybackStateHandler(manager) |
391 | 0 | { |
392 | 0 | } |
393 | | ~PlayingStateHandler() override = default; |
394 | | |
395 | | private: |
396 | | ErrorOr<void> on_enter() override |
397 | 0 | { |
398 | 0 | m_last_present_in_real_time = MonotonicTime::now(); |
399 | 0 | return do_timed_state_update(); |
400 | 0 | } |
401 | | |
402 | 0 | StringView name() override { return "Playing"sv; } |
403 | | |
404 | 0 | bool is_playing() const override { return true; } |
405 | 0 | PlaybackState get_state() const override { return PlaybackState::Playing; } |
406 | | ErrorOr<void> pause() override |
407 | 0 | { |
408 | 0 | manager().m_last_present_in_media_time = current_time(); |
409 | 0 | return replace_handler_and_delete_this<PausedStateHandler>(); |
410 | 0 | } |
411 | | ErrorOr<void> buffer() override |
412 | 0 | { |
413 | 0 | manager().m_last_present_in_media_time = current_time(); |
414 | 0 | return replace_handler_and_delete_this<BufferingStateHandler>(true); |
415 | 0 | } |
416 | | |
417 | | Duration current_time() const override |
418 | 0 | { |
419 | 0 | return manager().m_last_present_in_media_time + (MonotonicTime::now() - m_last_present_in_real_time); |
420 | 0 | } |
421 | | |
422 | | ErrorOr<void> do_timed_state_update() override |
423 | 0 | { |
424 | 0 | auto set_presentation_timer = [&]() { |
425 | 0 | auto frame_time_ms = (manager().m_next_frame->timestamp() - current_time()).to_milliseconds(); |
426 | 0 | VERIFY(frame_time_ms <= NumericLimits<int>::max()); |
427 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Time until next frame is {}ms", frame_time_ms); |
428 | 0 | manager().set_state_update_timer(max(static_cast<int>(frame_time_ms), 0)); |
429 | 0 | }; |
430 | |
|
431 | 0 | if (manager().m_next_frame.has_value() && current_time() < manager().m_next_frame->timestamp()) { |
432 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Current time {}ms is too early to present the next frame at {}ms, delaying", current_time().to_milliseconds(), manager().m_next_frame->timestamp().to_milliseconds()); |
433 | 0 | set_presentation_timer(); |
434 | 0 | return {}; |
435 | 0 | } |
436 | | |
437 | 0 | Optional<FrameQueueItem> future_frame_item; |
438 | 0 | bool should_present_frame = false; |
439 | | |
440 | | // Skip frames until we find a frame past the current playback time, and keep the one that precedes it to display. |
441 | 0 | while (true) { |
442 | 0 | future_frame_item = manager().dequeue_one_frame(); |
443 | 0 | if (!future_frame_item.has_value()) |
444 | 0 | break; |
445 | | |
446 | 0 | if (future_frame_item->timestamp() >= current_time() || future_frame_item->timestamp() == FrameQueueItem::no_timestamp) { |
447 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Should present frame, future {} is error or after {}ms", future_frame_item->debug_string(), current_time().to_milliseconds()); |
448 | 0 | should_present_frame = true; |
449 | 0 | break; |
450 | 0 | } |
451 | | |
452 | 0 | if (manager().m_next_frame.has_value()) { |
453 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "At {}ms: Dropped {} in favor of {}", current_time().to_milliseconds(), manager().m_next_frame->debug_string(), future_frame_item->debug_string()); |
454 | 0 | manager().m_skipped_frames++; |
455 | 0 | } |
456 | 0 | manager().m_next_frame.emplace(future_frame_item.release_value()); |
457 | 0 | } |
458 | | |
459 | | // If we don't have both of these items, we can't present, since we need to set a timer for |
460 | | // the next frame. Check if we need to buffer based on the current state. |
461 | 0 | if (!manager().m_next_frame.has_value() || !future_frame_item.has_value()) { |
462 | | #if PLAYBACK_MANAGER_DEBUG |
463 | | StringBuilder debug_string_builder; |
464 | | debug_string_builder.append("We don't have "sv); |
465 | | if (!manager().m_next_frame.has_value()) { |
466 | | debug_string_builder.append("a frame to present"sv); |
467 | | if (!future_frame_item.has_value()) |
468 | | debug_string_builder.append(" or a future frame"sv); |
469 | | } else { |
470 | | debug_string_builder.append("a future frame"sv); |
471 | | } |
472 | | debug_string_builder.append(", checking for error and buffering"sv); |
473 | | dbgln_if(PLAYBACK_MANAGER_DEBUG, debug_string_builder.to_byte_string()); |
474 | | #endif |
475 | 0 | if (future_frame_item.has_value()) { |
476 | 0 | if (future_frame_item->is_error()) { |
477 | 0 | manager().dispatch_decoder_error(future_frame_item.release_value().release_error()); |
478 | 0 | return {}; |
479 | 0 | } |
480 | 0 | manager().m_next_frame.emplace(future_frame_item.release_value()); |
481 | 0 | } |
482 | 0 | TRY(buffer()); |
483 | 0 | return {}; |
484 | 0 | } |
485 | | |
486 | | // If we have a frame, send it for presentation. |
487 | 0 | if (should_present_frame) { |
488 | 0 | auto now = MonotonicTime::now(); |
489 | 0 | manager().m_last_present_in_media_time += now - m_last_present_in_real_time; |
490 | 0 | m_last_present_in_real_time = now; |
491 | |
|
492 | 0 | if (manager().dispatch_frame_queue_item(manager().m_next_frame.release_value())) |
493 | 0 | return {}; |
494 | 0 | } |
495 | | |
496 | | // Now that we've presented the current frame, we can throw whatever error is next in queue. |
497 | | // This way, we always display a frame before the stream ends, and should also show any frames |
498 | | // we already had when a real error occurs. |
499 | 0 | if (future_frame_item->is_error()) { |
500 | 0 | manager().dispatch_decoder_error(future_frame_item.release_value().release_error()); |
501 | 0 | return {}; |
502 | 0 | } |
503 | | |
504 | | // The future frame item becomes the next one to present. |
505 | 0 | manager().m_next_frame.emplace(future_frame_item.release_value()); |
506 | 0 | set_presentation_timer(); |
507 | 0 | return {}; |
508 | 0 | } |
509 | | |
510 | | MonotonicTime m_last_present_in_real_time = MonotonicTime::now_coarse(); |
511 | | }; |
512 | | |
513 | | class PlaybackManager::PausedStateHandler : public PlaybackManager::PlaybackStateHandler { |
514 | | public: |
515 | | PausedStateHandler(PlaybackManager& manager) |
516 | 0 | : PlaybackStateHandler(manager) |
517 | 0 | { |
518 | 0 | } |
519 | | ~PausedStateHandler() override = default; |
520 | | |
521 | | private: |
522 | 0 | StringView name() override { return "Paused"sv; } |
523 | | |
524 | | ErrorOr<void> play() override |
525 | 0 | { |
526 | 0 | return replace_handler_and_delete_this<PlayingStateHandler>(); |
527 | 0 | } |
528 | 0 | bool is_playing() const override { return false; } |
529 | 0 | PlaybackState get_state() const override { return PlaybackState::Paused; } |
530 | | }; |
531 | | |
532 | | // FIXME: This is a placeholder variable that could be scaled based on how long each frame decode takes to |
533 | | // avoid triggering the timer to check the queue constantly. However, doing so may reduce the speed |
534 | | // of seeking due to the decode thread having to wait for a signal to continue decoding. |
535 | | constexpr int buffering_or_seeking_decode_wait_time = 1; |
536 | | |
537 | | class PlaybackManager::BufferingStateHandler : public PlaybackManager::ResumingStateHandler { |
538 | | using PlaybackManager::ResumingStateHandler::ResumingStateHandler; |
539 | | |
540 | | ErrorOr<void> on_enter() override |
541 | 0 | { |
542 | 0 | manager().set_state_update_timer(buffering_or_seeking_decode_wait_time); |
543 | 0 | return {}; |
544 | 0 | } |
545 | | |
546 | 0 | StringView name() override { return "Buffering"sv; } |
547 | | |
548 | | ErrorOr<void> do_timed_state_update() override |
549 | 0 | { |
550 | 0 | auto buffer_is_full = manager().m_buffer_is_full.load(); |
551 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Buffering timer callback has been called. Buffer is {}.", buffer_is_full ? "full, exiting"sv : "not full, waiting"sv); |
552 | 0 | if (buffer_is_full) |
553 | 0 | return assume_next_state(); |
554 | | |
555 | 0 | manager().set_state_update_timer(buffering_or_seeking_decode_wait_time); |
556 | 0 | return {}; |
557 | 0 | } |
558 | | |
559 | 0 | PlaybackState get_state() const override { return PlaybackState::Buffering; } |
560 | | }; |
561 | | |
562 | | class PlaybackManager::SeekingStateHandler : public PlaybackManager::ResumingStateHandler { |
563 | | public: |
564 | | SeekingStateHandler(PlaybackManager& manager, bool playing, Duration target_timestamp, SeekMode seek_mode) |
565 | 0 | : ResumingStateHandler(manager, playing) |
566 | 0 | , m_target_timestamp(target_timestamp) |
567 | 0 | , m_seek_mode(seek_mode) |
568 | 0 | { |
569 | 0 | } |
570 | | ~SeekingStateHandler() override = default; |
571 | | |
572 | | private: |
573 | | ErrorOr<void> on_enter() override |
574 | 0 | { |
575 | 0 | auto earliest_available_sample = manager().m_last_present_in_media_time; |
576 | 0 | if (manager().m_next_frame.has_value() && manager().m_next_frame->timestamp() != FrameQueueItem::no_timestamp) { |
577 | 0 | earliest_available_sample = min(earliest_available_sample, manager().m_next_frame->timestamp()); |
578 | 0 | } |
579 | |
|
580 | 0 | { |
581 | 0 | Threading::MutexLocker demuxer_locker(manager().m_decoder_mutex); |
582 | |
|
583 | 0 | auto demuxer_seek_result = manager().seek_demuxer_to_most_recent_keyframe(m_target_timestamp, earliest_available_sample); |
584 | 0 | if (demuxer_seek_result.is_error()) { |
585 | 0 | manager().dispatch_decoder_error(demuxer_seek_result.release_error()); |
586 | 0 | return {}; |
587 | 0 | } |
588 | 0 | auto keyframe_timestamp = demuxer_seek_result.release_value(); |
589 | |
|
590 | | #if PLAYBACK_MANAGER_DEBUG |
591 | | auto seek_mode_name = m_seek_mode == SeekMode::Accurate ? "Accurate"sv : "Fast"sv; |
592 | | if (keyframe_timestamp.has_value()) |
593 | | dbgln("{} seeking to timestamp target {}ms, selected keyframe at {}ms", seek_mode_name, m_target_timestamp.to_milliseconds(), keyframe_timestamp->to_milliseconds()); |
594 | | else |
595 | | dbgln("{} seeking to timestamp target {}ms, demuxer kept its iterator position after {}ms", seek_mode_name, m_target_timestamp.to_milliseconds(), earliest_available_sample.to_milliseconds()); |
596 | | #endif |
597 | |
|
598 | 0 | if (m_seek_mode == SeekMode::Fast) |
599 | 0 | m_target_timestamp = keyframe_timestamp.value_or(manager().m_last_present_in_media_time); |
600 | |
|
601 | 0 | if (keyframe_timestamp.has_value()) { |
602 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Keyframe is nearer to the target than the current frames, emptying queue"); |
603 | 0 | while (manager().dequeue_one_frame().has_value()) { } |
604 | 0 | manager().m_next_frame.clear(); |
605 | 0 | manager().m_last_present_in_media_time = keyframe_timestamp.value(); |
606 | 0 | } else if (m_target_timestamp >= manager().m_last_present_in_media_time && manager().m_next_frame.has_value() && manager().m_next_frame.value().timestamp() > m_target_timestamp) { |
607 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Target timestamp is between the last presented frame and the next frame, exiting seek at {}ms", m_target_timestamp.to_milliseconds()); |
608 | 0 | manager().m_last_present_in_media_time = m_target_timestamp; |
609 | 0 | return assume_next_state(); |
610 | 0 | } |
611 | 0 | } |
612 | | |
613 | 0 | return skip_samples_until_timestamp(); |
614 | 0 | } |
615 | | |
616 | | ErrorOr<void> skip_samples_until_timestamp() |
617 | 0 | { |
618 | 0 | while (true) { |
619 | 0 | auto optional_item = manager().dequeue_one_frame(); |
620 | 0 | if (!optional_item.has_value()) |
621 | 0 | break; |
622 | 0 | auto item = optional_item.release_value(); |
623 | |
|
624 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Dequeuing frame at {}ms and comparing to seek target {}ms", item.timestamp().to_milliseconds(), m_target_timestamp.to_milliseconds()); |
625 | 0 | if (manager().m_next_frame.has_value() && (item.timestamp() > m_target_timestamp || item.timestamp() == FrameQueueItem::no_timestamp)) { |
626 | | // If the frame we're presenting is later than the target timestamp, skip the timestamp forward to it. |
627 | 0 | if (manager().m_next_frame->timestamp() > m_target_timestamp) { |
628 | 0 | manager().m_last_present_in_media_time = manager().m_next_frame->timestamp(); |
629 | 0 | } else { |
630 | 0 | manager().m_last_present_in_media_time = m_target_timestamp; |
631 | 0 | } |
632 | |
|
633 | 0 | if (manager().dispatch_frame_queue_item(manager().m_next_frame.release_value())) |
634 | 0 | return {}; |
635 | | |
636 | 0 | manager().m_next_frame.emplace(item); |
637 | |
|
638 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Exiting seek to {} state at {}ms", m_playing ? "Playing" : "Paused", manager().m_last_present_in_media_time.to_milliseconds()); |
639 | 0 | return assume_next_state(); |
640 | 0 | } |
641 | 0 | manager().m_next_frame.emplace(item); |
642 | 0 | } |
643 | | |
644 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Frame queue is empty while seeking, waiting for buffer to fill."); |
645 | 0 | manager().set_state_update_timer(buffering_or_seeking_decode_wait_time); |
646 | 0 | return {}; |
647 | 0 | } |
648 | | |
649 | 0 | StringView name() override { return "Seeking"sv; } |
650 | | |
651 | | ErrorOr<void> seek(Duration target_timestamp, SeekMode seek_mode) override |
652 | 0 | { |
653 | 0 | m_target_timestamp = target_timestamp; |
654 | 0 | m_seek_mode = seek_mode; |
655 | 0 | return on_enter(); |
656 | 0 | } |
657 | | |
658 | | Duration current_time() const override |
659 | 0 | { |
660 | 0 | return m_target_timestamp; |
661 | 0 | } |
662 | | |
663 | | // We won't need this override when threaded, the queue can pause us in on_enter(). |
664 | | ErrorOr<void> do_timed_state_update() override |
665 | 0 | { |
666 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Seeking wait finished, attempting to dequeue until timestamp."); |
667 | 0 | return skip_samples_until_timestamp(); |
668 | 0 | } |
669 | | |
670 | 0 | PlaybackState get_state() const override { return PlaybackState::Seeking; } |
671 | | |
672 | | Duration m_target_timestamp { Duration::zero() }; |
673 | | SeekMode m_seek_mode { SeekMode::Accurate }; |
674 | | }; |
675 | | |
676 | | class PlaybackManager::StoppedStateHandler : public PlaybackManager::PlaybackStateHandler { |
677 | | public: |
678 | | StoppedStateHandler(PlaybackManager& manager) |
679 | 0 | : PlaybackStateHandler(manager) |
680 | 0 | { |
681 | 0 | } |
682 | | ~StoppedStateHandler() override = default; |
683 | | |
684 | | private: |
685 | | ErrorOr<void> on_enter() override |
686 | 0 | { |
687 | 0 | return {}; |
688 | 0 | } |
689 | | |
690 | 0 | StringView name() override { return "Stopped"sv; } |
691 | | |
692 | | ErrorOr<void> play() override |
693 | 0 | { |
694 | | // When Stopped, the decoder thread will be waiting for a signal to start its loop going again. |
695 | 0 | manager().m_decode_wait_condition.broadcast(); |
696 | 0 | return replace_handler_and_delete_this<SeekingStateHandler>(true, Duration::zero(), SeekMode::Fast); |
697 | 0 | } |
698 | 0 | bool is_playing() const override { return false; } |
699 | 0 | PlaybackState get_state() const override { return PlaybackState::Stopped; } |
700 | | }; |
701 | | |
702 | | DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::create(NonnullOwnPtr<Demuxer> demuxer) |
703 | 0 | { |
704 | 0 | auto video_tracks = TRY(demuxer->get_tracks_for_type(TrackType::Video)); |
705 | 0 | if (video_tracks.is_empty()) |
706 | 0 | return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv); |
707 | 0 | auto track = video_tracks[0]; |
708 | |
|
709 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Selecting video track number {}", track.identifier()); |
710 | |
|
711 | 0 | auto codec_id = TRY(demuxer->get_codec_id_for_track(track)); |
712 | 0 | OwnPtr<VideoDecoder> decoder; |
713 | 0 | switch (codec_id) { |
714 | 0 | case CodecID::VP8: |
715 | 0 | decoder = DECODER_TRY_ALLOC(try_make<Video::VP8::Decoder>()); |
716 | 0 | break; |
717 | | |
718 | 0 | case CodecID::VP9: |
719 | 0 | decoder = DECODER_TRY_ALLOC(try_make<Video::VP9::Decoder>()); |
720 | 0 | break; |
721 | | |
722 | 0 | default: |
723 | 0 | return DecoderError::format(DecoderErrorCategory::Invalid, "Unsupported codec: {}", codec_id); |
724 | 0 | } |
725 | 0 | auto decoder_non_null = decoder.release_nonnull(); |
726 | 0 | auto frame_queue = DECODER_TRY_ALLOC(VideoFrameQueue::create()); |
727 | 0 | auto playback_manager = DECODER_TRY_ALLOC(try_make<PlaybackManager>(demuxer, track, move(decoder_non_null), move(frame_queue))); |
728 | |
|
729 | 0 | playback_manager->m_state_update_timer = Core::Timer::create_single_shot(0, [&self = *playback_manager] { self.timer_callback(); }); |
730 | |
|
731 | 0 | playback_manager->m_decode_thread = DECODER_TRY_ALLOC(Threading::Thread::try_create([&self = *playback_manager] { |
732 | 0 | while (!self.m_stop_decoding.load()) |
733 | 0 | self.decode_and_queue_one_sample(); |
734 | |
|
735 | 0 | dbgln_if(PLAYBACK_MANAGER_DEBUG, "Media Decoder thread ended."); |
736 | 0 | return 0; |
737 | 0 | }, |
738 | 0 | "Media Decoder"sv)); |
739 | |
|
740 | 0 | playback_manager->m_playback_handler = make<SeekingStateHandler>(*playback_manager, false, Duration::zero(), SeekMode::Fast); |
741 | 0 | DECODER_TRY_ALLOC(playback_manager->m_playback_handler->on_enter()); |
742 | |
|
743 | 0 | playback_manager->m_decode_thread->start(); |
744 | |
|
745 | 0 | return playback_manager; |
746 | 0 | } |
747 | | |
748 | | } |