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