Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / media / MediaDecoderStateMachine.cpp
blob7c84690832c2b278a3ef9504efd400529efb4b51
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <algorithm>
8 #include <stdint.h>
9 #include <utility>
11 #include "mediasink/AudioSink.h"
12 #include "mediasink/AudioSinkWrapper.h"
13 #include "mediasink/DecodedStream.h"
14 #include "mediasink/VideoSink.h"
15 #include "mozilla/Logging.h"
16 #include "mozilla/MathAlgorithms.h"
17 #include "mozilla/NotNull.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/ProfilerLabels.h"
20 #include "mozilla/ProfilerMarkers.h"
21 #include "mozilla/ProfilerMarkerTypes.h"
22 #include "mozilla/SharedThreadPool.h"
23 #include "mozilla/Sprintf.h"
24 #include "mozilla/StaticPrefs_media.h"
25 #include "mozilla/Telemetry.h"
26 #include "mozilla/TaskQueue.h"
28 #include "nsIMemoryReporter.h"
29 #include "nsPrintfCString.h"
30 #include "nsTArray.h"
31 #include "AudioSegment.h"
32 #include "DOMMediaStream.h"
33 #include "ImageContainer.h"
34 #include "MediaDecoder.h"
35 #include "MediaDecoderStateMachine.h"
36 #include "MediaShutdownManager.h"
37 #include "MediaTrackGraph.h"
38 #include "MediaTimer.h"
39 #include "PerformanceRecorder.h"
40 #include "ReaderProxy.h"
41 #include "TimeUnits.h"
42 #include "VideoSegment.h"
43 #include "VideoUtils.h"
45 namespace mozilla {
47 using namespace mozilla::media;
49 #define NS_DispatchToMainThread(...) \
50 CompileError_UseAbstractThreadDispatchInstead
52 // avoid redefined macro in unified build
53 #undef FMT
54 #undef LOG
55 #undef LOGV
56 #undef LOGW
57 #undef LOGE
58 #undef SFMT
59 #undef SLOG
60 #undef SLOGW
61 #undef SLOGE
63 #define FMT(x, ...) "Decoder=%p " x, mDecoderID, ##__VA_ARGS__
64 #define LOG(x, ...) \
65 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, "Decoder=%p " x, mDecoderID, \
66 ##__VA_ARGS__)
67 #define LOGV(x, ...) \
68 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, "Decoder=%p " x, mDecoderID, \
69 ##__VA_ARGS__)
70 #define LOGW(x, ...) NS_WARNING(nsPrintfCString(FMT(x, ##__VA_ARGS__)).get())
71 #define LOGE(x, ...) \
72 NS_DebugBreak(NS_DEBUG_WARNING, \
73 nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, \
74 __FILE__, __LINE__)
76 // Used by StateObject and its sub-classes
77 #define SFMT(x, ...) \
78 "Decoder=%p state=%s " x, mMaster->mDecoderID, ToStateStr(GetState()), \
79 ##__VA_ARGS__
80 #define SLOG(x, ...) \
81 DDMOZ_LOGEX(mMaster, gMediaDecoderLog, LogLevel::Debug, "state=%s " x, \
82 ToStateStr(GetState()), ##__VA_ARGS__)
83 #define SLOGW(x, ...) NS_WARNING(nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get())
84 #define SLOGE(x, ...) \
85 NS_DebugBreak(NS_DEBUG_WARNING, \
86 nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get(), nullptr, \
87 __FILE__, __LINE__)
89 // Certain constants get stored as member variables and then adjusted by various
90 // scale factors on a per-decoder basis. We want to make sure to avoid using
91 // these constants directly, so we put them in a namespace.
92 namespace detail {
94 // Resume a suspended video decoder to the current playback position plus this
95 // time premium for compensating the seeking delay.
96 static constexpr auto RESUME_VIDEO_PREMIUM = TimeUnit::FromMicroseconds(125000);
98 static const int64_t AMPLE_AUDIO_USECS = 2000000;
100 // If more than this much decoded audio is queued, we'll hold off
101 // decoding more audio.
102 static constexpr auto AMPLE_AUDIO_THRESHOLD =
103 TimeUnit::FromMicroseconds(AMPLE_AUDIO_USECS);
105 } // namespace detail
107 // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
108 // we're not "prerolling video", we'll skip the video up to the next keyframe
109 // which is at or after the current playback position.
110 static const uint32_t LOW_VIDEO_FRAMES = 2;
112 // Arbitrary "frame duration" when playing only audio.
113 static const uint32_t AUDIO_DURATION_USECS = 40000;
115 namespace detail {
117 // If we have less than this much buffered data available, we'll consider
118 // ourselves to be running low on buffered data. We determine how much
119 // buffered data we have remaining using the reader's GetBuffered()
120 // implementation.
121 static const int64_t LOW_BUFFER_THRESHOLD_USECS = 5000000;
123 static constexpr auto LOW_BUFFER_THRESHOLD =
124 TimeUnit::FromMicroseconds(LOW_BUFFER_THRESHOLD_USECS);
126 // LOW_BUFFER_THRESHOLD_USECS needs to be greater than AMPLE_AUDIO_USECS,
127 // otherwise the skip-to-keyframe logic can activate when we're running low on
128 // data.
129 static_assert(LOW_BUFFER_THRESHOLD_USECS > AMPLE_AUDIO_USECS,
130 "LOW_BUFFER_THRESHOLD_USECS is too small");
132 } // namespace detail
134 // Amount of excess data to add in to the "should we buffer" calculation.
135 static constexpr auto EXHAUSTED_DATA_MARGIN =
136 TimeUnit::FromMicroseconds(100000);
138 static const uint32_t MIN_VIDEO_QUEUE_SIZE = 3;
139 static const uint32_t MAX_VIDEO_QUEUE_SIZE = 10;
140 #ifdef MOZ_APPLEMEDIA
141 static const uint32_t HW_VIDEO_QUEUE_SIZE = 10;
142 #else
143 static const uint32_t HW_VIDEO_QUEUE_SIZE = 3;
144 #endif
145 static const uint32_t VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE = 9999;
147 static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
148 static uint32_t sVideoQueueHWAccelSize = HW_VIDEO_QUEUE_SIZE;
149 static uint32_t sVideoQueueSendToCompositorSize =
150 VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE;
152 static void InitVideoQueuePrefs() {
153 MOZ_ASSERT(NS_IsMainThread());
154 static bool sPrefInit = false;
155 if (!sPrefInit) {
156 sPrefInit = true;
157 sVideoQueueDefaultSize = Preferences::GetUint(
158 "media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE);
159 sVideoQueueHWAccelSize = Preferences::GetUint(
160 "media.video-queue.hw-accel-size", HW_VIDEO_QUEUE_SIZE);
161 sVideoQueueSendToCompositorSize =
162 Preferences::GetUint("media.video-queue.send-to-compositor-size",
163 VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE);
167 template <typename Type, typename Function>
168 static void DiscardFramesFromTail(MediaQueue<Type>& aQueue,
169 const Function&& aTest) {
170 while (aQueue.GetSize()) {
171 if (aTest(aQueue.PeekBack()->mTime.ToMicroseconds())) {
172 RefPtr<Type> releaseMe = aQueue.PopBack();
173 continue;
175 break;
179 // Delay, in milliseconds, that tabs needs to be in background before video
180 // decoding is suspended.
181 static TimeDuration SuspendBackgroundVideoDelay() {
182 return TimeDuration::FromMilliseconds(
183 StaticPrefs::media_suspend_background_video_delay_ms());
186 class MediaDecoderStateMachine::StateObject {
187 public:
188 virtual ~StateObject() = default;
189 virtual void Exit() {} // Exit action.
190 virtual void Step() {} // Perform a 'cycle' of this state object.
191 virtual State GetState() const = 0;
193 // Event handlers for various events.
194 virtual void HandleAudioCaptured() {}
195 virtual void HandleAudioDecoded(AudioData* aAudio) {
196 Crash("Unexpected event!", __func__);
198 virtual void HandleVideoDecoded(VideoData* aVideo) {
199 Crash("Unexpected event!", __func__);
201 virtual void HandleAudioWaited(MediaData::Type aType) {
202 Crash("Unexpected event!", __func__);
204 virtual void HandleVideoWaited(MediaData::Type aType) {
205 Crash("Unexpected event!", __func__);
207 virtual void HandleWaitingForAudio() { Crash("Unexpected event!", __func__); }
208 virtual void HandleAudioCanceled() { Crash("Unexpected event!", __func__); }
209 virtual void HandleEndOfAudio() { Crash("Unexpected event!", __func__); }
210 virtual void HandleWaitingForVideo() { Crash("Unexpected event!", __func__); }
211 virtual void HandleVideoCanceled() { Crash("Unexpected event!", __func__); }
212 virtual void HandleEndOfVideo() { Crash("Unexpected event!", __func__); }
214 virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(
215 const SeekTarget& aTarget);
217 virtual RefPtr<ShutdownPromise> HandleShutdown();
219 virtual void HandleVideoSuspendTimeout() = 0;
221 virtual void HandleResumeVideoDecoding(const TimeUnit& aTarget);
223 virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
225 virtual void GetDebugInfo(
226 dom::MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) {}
228 virtual void HandleLoopingChanged() {}
230 private:
231 template <class S, typename R, typename... As>
232 auto ReturnTypeHelper(R (S::*)(As...)) -> R;
234 void Crash(const char* aReason, const char* aSite) {
235 char buf[1024];
236 SprintfLiteral(buf, "%s state=%s callsite=%s", aReason,
237 ToStateStr(GetState()), aSite);
238 MOZ_ReportAssertionFailure(buf, __FILE__, __LINE__);
239 MOZ_CRASH();
242 protected:
243 enum class EventVisibility : int8_t { Observable, Suppressed };
245 using Master = MediaDecoderStateMachine;
246 explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
247 TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
248 ReaderProxy* Reader() const { return mMaster->mReader; }
249 const MediaInfo& Info() const { return mMaster->Info(); }
250 MediaQueue<AudioData>& AudioQueue() const { return mMaster->mAudioQueue; }
251 MediaQueue<VideoData>& VideoQueue() const { return mMaster->mVideoQueue; }
253 template <class S, typename... Args, size_t... Indexes>
254 auto CallEnterMemberFunction(S* aS, std::tuple<Args...>& aTuple,
255 std::index_sequence<Indexes...>)
256 -> decltype(ReturnTypeHelper(&S::Enter)) {
257 AUTO_PROFILER_LABEL("StateObject::CallEnterMemberFunction", MEDIA_PLAYBACK);
258 return aS->Enter(std::move(std::get<Indexes>(aTuple))...);
261 // Note this function will delete the current state object.
262 // Don't access members to avoid UAF after this call.
263 template <class S, typename... Ts>
264 auto SetState(Ts&&... aArgs) -> decltype(ReturnTypeHelper(&S::Enter)) {
265 // |aArgs| must be passed by reference to avoid passing MOZ_NON_PARAM class
266 // SeekJob by value. See bug 1287006 and bug 1338374. But we still *must*
267 // copy the parameters, because |Exit()| can modify them. See bug 1312321.
268 // So we 1) pass the parameters by reference, but then 2) immediately copy
269 // them into a Tuple to be safe against modification, and finally 3) move
270 // the elements of the Tuple into the final function call.
271 auto copiedArgs = std::make_tuple(std::forward<Ts>(aArgs)...);
273 // Copy mMaster which will reset to null.
274 auto* master = mMaster;
276 auto* s = new S(master);
278 // It's possible to seek again during seeking, otherwise the new state
279 // should always be different from the original one.
280 MOZ_ASSERT(GetState() != s->GetState() ||
281 GetState() == DECODER_STATE_SEEKING_ACCURATE ||
282 GetState() == DECODER_STATE_SEEKING_FROMDORMANT ||
283 GetState() == DECODER_STATE_SEEKING_NEXTFRAMESEEKING ||
284 GetState() == DECODER_STATE_SEEKING_VIDEOONLY);
286 SLOG("change state to: %s", ToStateStr(s->GetState()));
287 PROFILER_MARKER_TEXT("MDSM::StateChange", MEDIA_PLAYBACK, {},
288 nsPrintfCString("%s", ToStateStr(s->GetState())));
290 Exit();
292 // Delete the old state asynchronously to avoid UAF if the caller tries to
293 // access its members after SetState() returns.
294 master->OwnerThread()->DispatchDirectTask(
295 NS_NewRunnableFunction("MDSM::StateObject::DeleteOldState",
296 [toDelete = std::move(master->mStateObj)]() {}));
297 // Also reset mMaster to catch potentail UAF.
298 mMaster = nullptr;
300 master->mStateObj.reset(s);
301 return CallEnterMemberFunction(s, copiedArgs,
302 std::index_sequence_for<Ts...>{});
305 RefPtr<MediaDecoder::SeekPromise> SetSeekingState(
306 SeekJob&& aSeekJob, EventVisibility aVisibility);
308 void SetDecodingState();
310 // Take a raw pointer in order not to change the life cycle of MDSM.
311 // It is guaranteed to be valid by MDSM.
312 Master* mMaster;
316 * Purpose: decode metadata like duration and dimensions of the media resource.
318 * Transition to other states when decoding metadata is done:
319 * SHUTDOWN if failing to decode metadata.
320 * DECODING_FIRSTFRAME otherwise.
322 class MediaDecoderStateMachine::DecodeMetadataState
323 : public MediaDecoderStateMachine::StateObject {
324 public:
325 explicit DecodeMetadataState(Master* aPtr) : StateObject(aPtr) {}
327 void Enter() {
328 MOZ_ASSERT(!mMaster->mVideoDecodeSuspended);
329 MOZ_ASSERT(!mMetadataRequest.Exists());
330 SLOG("Dispatching AsyncReadMetadata");
332 // We disconnect mMetadataRequest in Exit() so it is fine to capture
333 // a raw pointer here.
334 Reader()
335 ->ReadMetadata()
336 ->Then(
337 OwnerThread(), __func__,
338 [this](MetadataHolder&& aMetadata) {
339 OnMetadataRead(std::move(aMetadata));
341 [this](const MediaResult& aError) { OnMetadataNotRead(aError); })
342 ->Track(mMetadataRequest);
345 void Exit() override { mMetadataRequest.DisconnectIfExists(); }
347 State GetState() const override { return DECODER_STATE_DECODING_METADATA; }
349 RefPtr<MediaDecoder::SeekPromise> HandleSeek(
350 const SeekTarget& aTarget) override {
351 MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek while decoding metadata.");
352 return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
355 void HandleVideoSuspendTimeout() override {
356 // Do nothing since no decoders are created yet.
359 void HandleResumeVideoDecoding(const TimeUnit&) override {
360 // We never suspend video decoding in this state.
361 MOZ_ASSERT(false, "Shouldn't have suspended video decoding.");
364 private:
365 void OnMetadataRead(MetadataHolder&& aMetadata);
367 void OnMetadataNotRead(const MediaResult& aError) {
368 AUTO_PROFILER_LABEL("DecodeMetadataState::OnMetadataNotRead",
369 MEDIA_PLAYBACK);
371 mMetadataRequest.Complete();
372 SLOGE("Decode metadata failed, shutting down decoder");
373 mMaster->DecodeError(aError);
376 MozPromiseRequestHolder<MediaFormatReader::MetadataPromise> mMetadataRequest;
380 * Purpose: release decoder resources to save memory and hardware resources.
382 * Transition to:
383 * SEEKING if any seek request or play state changes to PLAYING.
385 class MediaDecoderStateMachine::DormantState
386 : public MediaDecoderStateMachine::StateObject {
387 public:
388 explicit DormantState(Master* aPtr) : StateObject(aPtr) {}
390 void Enter() {
391 if (mMaster->IsPlaying()) {
392 mMaster->StopPlayback();
395 // Calculate the position to seek to when exiting dormant.
396 auto t = mMaster->mMediaSink->IsStarted() ? mMaster->GetClock()
397 : mMaster->GetMediaTime();
398 mMaster->AdjustByLooping(t);
399 mPendingSeek.mTarget.emplace(t, SeekTarget::Accurate);
400 // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
401 // need to create the promise even it is not used at all.
402 // The promise may be used when coming out of DormantState into
403 // SeekingState.
404 RefPtr<MediaDecoder::SeekPromise> x =
405 mPendingSeek.mPromise.Ensure(__func__);
407 // Reset the decoding state to ensure that any queued video frames are
408 // released and don't consume video memory.
409 mMaster->ResetDecode();
411 // No need to call StopMediaSink() here.
412 // We will do it during seeking when exiting dormant.
414 // Ignore WAIT_FOR_DATA since we won't decode in dormant.
415 mMaster->mAudioWaitRequest.DisconnectIfExists();
416 mMaster->mVideoWaitRequest.DisconnectIfExists();
418 MaybeReleaseResources();
421 void Exit() override {
422 // mPendingSeek is either moved when exiting dormant or
423 // should be rejected here before transition to SHUTDOWN.
424 mPendingSeek.RejectIfExists(__func__);
427 State GetState() const override { return DECODER_STATE_DORMANT; }
429 RefPtr<MediaDecoder::SeekPromise> HandleSeek(
430 const SeekTarget& aTarget) override;
432 void HandleVideoSuspendTimeout() override {
433 // Do nothing since we've released decoders in Enter().
436 void HandleResumeVideoDecoding(const TimeUnit&) override {
437 // Do nothing since we won't resume decoding until exiting dormant.
440 void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override;
442 void HandleAudioDecoded(AudioData*) override { MaybeReleaseResources(); }
443 void HandleVideoDecoded(VideoData*) override { MaybeReleaseResources(); }
444 void HandleWaitingForAudio() override { MaybeReleaseResources(); }
445 void HandleWaitingForVideo() override { MaybeReleaseResources(); }
446 void HandleAudioCanceled() override { MaybeReleaseResources(); }
447 void HandleVideoCanceled() override { MaybeReleaseResources(); }
448 void HandleEndOfAudio() override { MaybeReleaseResources(); }
449 void HandleEndOfVideo() override { MaybeReleaseResources(); }
451 private:
452 void MaybeReleaseResources() {
453 if (!mMaster->mAudioDataRequest.Exists() &&
454 !mMaster->mVideoDataRequest.Exists()) {
455 // Release decoders only when they are idle. Otherwise it might cause
456 // decode error later when resetting decoders during seeking.
457 mMaster->mReader->ReleaseResources();
461 SeekJob mPendingSeek;
465 * Purpose: decode the 1st audio and video frames to fire the 'loadeddata'
466 * event.
468 * Transition to:
469 * SHUTDOWN if any decode error.
470 * SEEKING if any seek request.
471 * DECODING/LOOPING_DECODING when the 'loadeddata' event is fired.
473 class MediaDecoderStateMachine::DecodingFirstFrameState
474 : public MediaDecoderStateMachine::StateObject {
475 public:
476 explicit DecodingFirstFrameState(Master* aPtr) : StateObject(aPtr) {}
478 void Enter();
480 void Exit() override {
481 // mPendingSeek is either moved in MaybeFinishDecodeFirstFrame()
482 // or should be rejected here before transition to SHUTDOWN.
483 mPendingSeek.RejectIfExists(__func__);
486 State GetState() const override { return DECODER_STATE_DECODING_FIRSTFRAME; }
488 void HandleAudioDecoded(AudioData* aAudio) override {
489 mMaster->PushAudio(aAudio);
490 MaybeFinishDecodeFirstFrame();
493 void HandleVideoDecoded(VideoData* aVideo) override {
494 mMaster->PushVideo(aVideo);
495 MaybeFinishDecodeFirstFrame();
498 void HandleWaitingForAudio() override {
499 mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
502 void HandleAudioCanceled() override { mMaster->RequestAudioData(); }
504 void HandleEndOfAudio() override {
505 AudioQueue().Finish();
506 MaybeFinishDecodeFirstFrame();
509 void HandleWaitingForVideo() override {
510 mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
513 void HandleVideoCanceled() override {
514 mMaster->RequestVideoData(media::TimeUnit());
517 void HandleEndOfVideo() override {
518 VideoQueue().Finish();
519 MaybeFinishDecodeFirstFrame();
522 void HandleAudioWaited(MediaData::Type aType) override {
523 mMaster->RequestAudioData();
526 void HandleVideoWaited(MediaData::Type aType) override {
527 mMaster->RequestVideoData(media::TimeUnit());
530 void HandleVideoSuspendTimeout() override {
531 // Do nothing for we need to decode the 1st video frame to get the
532 // dimensions.
535 void HandleResumeVideoDecoding(const TimeUnit&) override {
536 // We never suspend video decoding in this state.
537 MOZ_ASSERT(false, "Shouldn't have suspended video decoding.");
540 RefPtr<MediaDecoder::SeekPromise> HandleSeek(
541 const SeekTarget& aTarget) override {
542 if (mMaster->mIsMSE) {
543 return StateObject::HandleSeek(aTarget);
545 // Delay seek request until decoding first frames for non-MSE media.
546 SLOG("Not Enough Data to seek at this stage, queuing seek");
547 mPendingSeek.RejectIfExists(__func__);
548 mPendingSeek.mTarget.emplace(aTarget);
549 return mPendingSeek.mPromise.Ensure(__func__);
552 private:
553 // Notify FirstFrameLoaded if having decoded first frames and
554 // transition to SEEKING if there is any pending seek, or DECODING otherwise.
555 void MaybeFinishDecodeFirstFrame();
557 SeekJob mPendingSeek;
561 * Purpose: decode audio/video data for playback.
563 * Transition to:
564 * DORMANT if playback is paused for a while.
565 * SEEKING if any seek request.
566 * SHUTDOWN if any decode error.
567 * BUFFERING if playback can't continue due to lack of decoded data.
568 * COMPLETED when having decoded all audio/video data.
569 * LOOPING_DECODING when media start seamless looping
571 class MediaDecoderStateMachine::DecodingState
572 : public MediaDecoderStateMachine::StateObject {
573 public:
574 explicit DecodingState(Master* aPtr)
575 : StateObject(aPtr), mDormantTimer(OwnerThread()) {}
577 void Enter();
579 void Exit() override {
580 if (!mDecodeStartTime.IsNull()) {
581 TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
582 SLOG("Exiting DECODING, decoded for %.3lfs", decodeDuration.ToSeconds());
584 mDormantTimer.Reset();
585 mOnAudioPopped.DisconnectIfExists();
586 mOnVideoPopped.DisconnectIfExists();
589 void Step() override;
591 State GetState() const override { return DECODER_STATE_DECODING; }
593 void HandleAudioDecoded(AudioData* aAudio) override {
594 mMaster->PushAudio(aAudio);
595 DispatchDecodeTasksIfNeeded();
596 MaybeStopPrerolling();
599 void HandleVideoDecoded(VideoData* aVideo) override {
600 // We only do this check when we're not looping, which can be known by
601 // checking the queue's offset.
602 const auto currentTime = mMaster->GetMediaTime();
603 if (aVideo->GetEndTime() < currentTime &&
604 VideoQueue().GetOffset() == media::TimeUnit::Zero()) {
605 if (!mVideoFirstLateTime) {
606 mVideoFirstLateTime = Some(TimeStamp::Now());
608 PROFILER_MARKER("Video falling behind", MEDIA_PLAYBACK, {},
609 VideoFallingBehindMarker, aVideo->mTime.ToMicroseconds(),
610 currentTime.ToMicroseconds());
611 SLOG("video %" PRId64 " starts being late (current=%" PRId64 ")",
612 aVideo->mTime.ToMicroseconds(), currentTime.ToMicroseconds());
613 } else {
614 mVideoFirstLateTime.reset();
616 mMaster->PushVideo(aVideo);
617 DispatchDecodeTasksIfNeeded();
618 MaybeStopPrerolling();
621 void HandleAudioCanceled() override { mMaster->RequestAudioData(); }
623 void HandleVideoCanceled() override {
624 mMaster->RequestVideoData(mMaster->GetMediaTime(),
625 ShouldRequestNextKeyFrame());
628 void HandleEndOfAudio() override;
629 void HandleEndOfVideo() override;
631 void HandleWaitingForAudio() override {
632 mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
633 MaybeStopPrerolling();
636 void HandleWaitingForVideo() override {
637 mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
638 MaybeStopPrerolling();
641 void HandleAudioWaited(MediaData::Type aType) override {
642 mMaster->RequestAudioData();
645 void HandleVideoWaited(MediaData::Type aType) override {
646 mMaster->RequestVideoData(mMaster->GetMediaTime(),
647 ShouldRequestNextKeyFrame());
650 void HandleAudioCaptured() override {
651 MaybeStopPrerolling();
652 // MediaSink is changed. Schedule Step() to check if we can start playback.
653 mMaster->ScheduleStateMachine();
656 void HandleVideoSuspendTimeout() override {
657 // No video, so nothing to suspend.
658 if (!mMaster->HasVideo()) {
659 return;
662 PROFILER_MARKER_UNTYPED("MDSM::EnterVideoSuspend", MEDIA_PLAYBACK);
663 mMaster->mVideoDecodeSuspended = true;
664 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::EnterVideoSuspend);
665 Reader()->SetVideoBlankDecode(true);
668 void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override {
669 if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
670 // Schedule Step() to check if we can start playback.
671 mMaster->ScheduleStateMachine();
672 // Try to dispatch decoding tasks for mMinimizePreroll might be reset.
673 DispatchDecodeTasksIfNeeded();
676 if (aPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
677 StartDormantTimer();
678 mVideoFirstLateTime.reset();
679 } else {
680 mDormantTimer.Reset();
684 void GetDebugInfo(
685 dom::MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) override {
686 aInfo.mIsPrerolling = mIsPrerolling;
689 void HandleLoopingChanged() override { SetDecodingState(); }
691 protected:
692 virtual void EnsureAudioDecodeTaskQueued();
693 virtual void EnsureVideoDecodeTaskQueued();
695 virtual bool ShouldStopPrerolling() const {
696 return mIsPrerolling &&
697 (DonePrerollingAudio() ||
698 IsWaitingData(MediaData::Type::AUDIO_DATA)) &&
699 (DonePrerollingVideo() ||
700 IsWaitingData(MediaData::Type::VIDEO_DATA));
703 virtual bool IsWaitingData(MediaData::Type aType) const {
704 if (aType == MediaData::Type::AUDIO_DATA) {
705 return mMaster->IsWaitingAudioData();
707 MOZ_ASSERT(aType == MediaData::Type::VIDEO_DATA);
708 return mMaster->IsWaitingVideoData();
711 void MaybeStopPrerolling() {
712 if (ShouldStopPrerolling()) {
713 mIsPrerolling = false;
714 // Check if we can start playback.
715 mMaster->ScheduleStateMachine();
719 bool ShouldRequestNextKeyFrame() const {
720 if (!mVideoFirstLateTime) {
721 return false;
723 const double elapsedTimeMs =
724 (TimeStamp::Now() - *mVideoFirstLateTime).ToMilliseconds();
725 const bool rv = elapsedTimeMs >=
726 StaticPrefs::media_decoder_skip_when_video_too_slow_ms();
727 if (rv) {
728 PROFILER_MARKER_UNTYPED("Skipping to next keyframe", MEDIA_PLAYBACK);
729 SLOG(
730 "video has been late behind media time for %f ms, should skip to "
731 "next key frame",
732 elapsedTimeMs);
734 return rv;
737 virtual bool IsBufferingAllowed() const { return true; }
739 private:
740 void DispatchDecodeTasksIfNeeded();
741 void MaybeStartBuffering();
743 // At the start of decoding we want to "preroll" the decode until we've
744 // got a few frames decoded before we consider whether decode is falling
745 // behind. Otherwise our "we're falling behind" logic will trigger
746 // unnecessarily if we start playing as soon as the first sample is
747 // decoded. These two fields store how many video frames and audio
748 // samples we must consume before are considered to be finished prerolling.
749 TimeUnit AudioPrerollThreshold() const {
750 return (mMaster->mAmpleAudioThreshold / 2)
751 .MultDouble(mMaster->mPlaybackRate);
754 uint32_t VideoPrerollFrames() const {
755 return std::min(
756 static_cast<uint32_t>(
757 mMaster->GetAmpleVideoFrames() / 2. * mMaster->mPlaybackRate + 1),
758 sVideoQueueDefaultSize);
761 bool DonePrerollingAudio() const {
762 return !mMaster->IsAudioDecoding() ||
763 mMaster->GetDecodedAudioDuration() >= AudioPrerollThreshold();
766 bool DonePrerollingVideo() const {
767 return !mMaster->IsVideoDecoding() ||
768 static_cast<uint32_t>(mMaster->VideoQueue().GetSize()) >=
769 VideoPrerollFrames();
772 void StartDormantTimer() {
773 if (!mMaster->mMediaSeekable) {
774 // Don't enter dormant if the media is not seekable because we need to
775 // seek when exiting dormant.
776 return;
779 auto timeout = StaticPrefs::media_dormant_on_pause_timeout_ms();
780 if (timeout < 0) {
781 // Disabled when timeout is negative.
782 return;
785 if (timeout == 0) {
786 // Enter dormant immediately without scheduling a timer.
787 SetState<DormantState>();
788 return;
791 if (mMaster->mMinimizePreroll) {
792 SetState<DormantState>();
793 return;
796 TimeStamp target =
797 TimeStamp::Now() + TimeDuration::FromMilliseconds(timeout);
799 mDormantTimer.Ensure(
800 target,
801 [this]() {
802 AUTO_PROFILER_LABEL("DecodingState::StartDormantTimer:SetDormant",
803 MEDIA_PLAYBACK);
804 mDormantTimer.CompleteRequest();
805 SetState<DormantState>();
807 [this]() { mDormantTimer.CompleteRequest(); });
810 // Time at which we started decoding.
811 TimeStamp mDecodeStartTime;
813 // When we start decoding (either for the first time, or after a pause)
814 // we may be low on decoded data. We don't want our "low data" logic to
815 // kick in and decide that we're low on decoded data because the download
816 // can't keep up with the decode, and cause us to pause playback. So we
817 // have a "preroll" stage, where we ignore the results of our "low data"
818 // logic during the first few frames of our decode. This occurs during
819 // playback.
820 bool mIsPrerolling = true;
822 // Fired when playback is paused for a while to enter dormant.
823 DelayedScheduler mDormantTimer;
825 MediaEventListener mOnAudioPopped;
826 MediaEventListener mOnVideoPopped;
828 // If video has been later than the media time, this will records when the
829 // video started being late. It will be reset once video catches up with the
830 // media time.
831 Maybe<TimeStamp> mVideoFirstLateTime;
835 * Purpose: decode audio data for playback when media is in seamless
836 * looping, we will adjust media time to make samples time monotonically
837 * increasing. All its methods runs on its owner thread (MDSM thread).
839 * Transition to:
840 * DORMANT if playback is paused for a while.
841 * SEEKING if any seek request.
842 * SHUTDOWN if any decode error.
843 * BUFFERING if playback can't continue due to lack of decoded data.
844 * COMPLETED when the media resource is closed and no data is available
845 * anymore.
846 * DECODING when media stops seamless looping.
848 class MediaDecoderStateMachine::LoopingDecodingState
849 : public MediaDecoderStateMachine::DecodingState {
850 public:
851 explicit LoopingDecodingState(Master* aPtr)
852 : DecodingState(aPtr),
853 mIsReachingAudioEOS(!mMaster->IsAudioDecoding()),
854 mIsReachingVideoEOS(!mMaster->IsVideoDecoding()),
855 mAudioEndedBeforeEnteringStateWithoutDuration(false),
856 mVideoEndedBeforeEnteringStateWithoutDuration(false) {
857 MOZ_ASSERT(mMaster->mLooping);
858 SLOG(
859 "LoopingDecodingState ctor, mIsReachingAudioEOS=%d, "
860 "mIsReachingVideoEOS=%d",
861 mIsReachingAudioEOS, mIsReachingVideoEOS);
862 // If the track has reached EOS and we already have its last data, then we
863 // can know its duration. But if playback starts from EOS (due to seeking),
864 // the decoded end time would be zero because none of data gets decoded yet.
865 if (mIsReachingAudioEOS) {
866 if (mMaster->HasLastDecodedData(MediaData::Type::AUDIO_DATA) &&
867 !mMaster->mAudioTrackDecodedDuration) {
868 mMaster->mAudioTrackDecodedDuration.emplace(
869 mMaster->mDecodedAudioEndTime);
870 SLOG("determine mAudioTrackDecodedDuration");
871 } else {
872 mAudioEndedBeforeEnteringStateWithoutDuration = true;
873 SLOG("still don't know mAudioTrackDecodedDuration");
877 if (mIsReachingVideoEOS) {
878 if (mMaster->HasLastDecodedData(MediaData::Type::VIDEO_DATA) &&
879 !mMaster->mVideoTrackDecodedDuration) {
880 mMaster->mVideoTrackDecodedDuration.emplace(
881 mMaster->mDecodedVideoEndTime);
882 SLOG("determine mVideoTrackDecodedDuration");
883 } else {
884 mVideoEndedBeforeEnteringStateWithoutDuration = true;
885 SLOG("still don't know mVideoTrackDecodedDuration");
889 // We might be able to determine the duration already, let's check.
890 if (mIsReachingAudioEOS || mIsReachingVideoEOS) {
891 Unused << DetermineOriginalDecodedDurationIfNeeded();
894 // If we've looped at least once before, then we need to update queue offset
895 // correctly to make the media data time and the clock time consistent.
896 // Otherwise, it would cause a/v desync.
897 if (mMaster->mOriginalDecodedDuration != media::TimeUnit::Zero()) {
898 if (mIsReachingAudioEOS && mMaster->HasAudio()) {
899 AudioQueue().SetOffset(AudioQueue().GetOffset() +
900 mMaster->mOriginalDecodedDuration);
902 if (mIsReachingVideoEOS && mMaster->HasVideo()) {
903 VideoQueue().SetOffset(VideoQueue().GetOffset() +
904 mMaster->mOriginalDecodedDuration);
909 void Enter() {
910 if (mMaster->HasAudio() && mIsReachingAudioEOS) {
911 SLOG("audio has ended, request the data again.");
912 RequestDataFromStartPosition(TrackInfo::TrackType::kAudioTrack);
914 if (mMaster->HasVideo() && mIsReachingVideoEOS) {
915 SLOG("video has ended, request the data again.");
916 RequestDataFromStartPosition(TrackInfo::TrackType::kVideoTrack);
918 DecodingState::Enter();
921 void Exit() override {
922 MOZ_DIAGNOSTIC_ASSERT(mMaster->OnTaskQueue());
923 SLOG("Leaving looping state, offset [a=%" PRId64 ",v=%" PRId64
924 "], endtime [a=%" PRId64 ",v=%" PRId64 "], track duration [a=%" PRId64
925 ",v=%" PRId64 "], waiting=%s",
926 AudioQueue().GetOffset().ToMicroseconds(),
927 VideoQueue().GetOffset().ToMicroseconds(),
928 mMaster->mDecodedAudioEndTime.ToMicroseconds(),
929 mMaster->mDecodedVideoEndTime.ToMicroseconds(),
930 mMaster->mAudioTrackDecodedDuration
931 ? mMaster->mAudioTrackDecodedDuration->ToMicroseconds()
932 : 0,
933 mMaster->mVideoTrackDecodedDuration
934 ? mMaster->mVideoTrackDecodedDuration->ToMicroseconds()
935 : 0,
936 mDataWaitingTimestampAdjustment
937 ? MediaData::TypeToStr(mDataWaitingTimestampAdjustment->mType)
938 : "none");
939 if (ShouldDiscardLoopedData(MediaData::Type::AUDIO_DATA)) {
940 DiscardLoopedData(MediaData::Type::AUDIO_DATA);
942 if (ShouldDiscardLoopedData(MediaData::Type::VIDEO_DATA)) {
943 DiscardLoopedData(MediaData::Type::VIDEO_DATA);
946 if (mMaster->HasAudio() && HasDecodedLastAudioFrame()) {
947 SLOG("Mark audio queue as finished");
948 mMaster->mAudioDataRequest.DisconnectIfExists();
949 mMaster->mAudioWaitRequest.DisconnectIfExists();
950 AudioQueue().Finish();
952 if (mMaster->HasVideo() && HasDecodedLastVideoFrame()) {
953 SLOG("Mark video queue as finished");
954 mMaster->mVideoDataRequest.DisconnectIfExists();
955 mMaster->mVideoWaitRequest.DisconnectIfExists();
956 VideoQueue().Finish();
959 // Clear waiting data should be done after marking queue as finished.
960 mDataWaitingTimestampAdjustment = nullptr;
962 mAudioDataRequest.DisconnectIfExists();
963 mVideoDataRequest.DisconnectIfExists();
964 mAudioSeekRequest.DisconnectIfExists();
965 mVideoSeekRequest.DisconnectIfExists();
966 DecodingState::Exit();
969 ~LoopingDecodingState() {
970 MOZ_DIAGNOSTIC_ASSERT(!mAudioDataRequest.Exists());
971 MOZ_DIAGNOSTIC_ASSERT(!mVideoDataRequest.Exists());
972 MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
973 MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
976 State GetState() const override { return DECODER_STATE_LOOPING_DECODING; }
978 void HandleAudioDecoded(AudioData* aAudio) override {
979 // TODO : check if we need to update mOriginalDecodedDuration
981 // After pushing data to the queue, timestamp might be adjusted.
982 DecodingState::HandleAudioDecoded(aAudio);
983 mMaster->mDecodedAudioEndTime =
984 std::max(aAudio->GetEndTime(), mMaster->mDecodedAudioEndTime);
985 SLOG("audio sample after time-adjustment [%" PRId64 ",%" PRId64 "]",
986 aAudio->mTime.ToMicroseconds(), aAudio->GetEndTime().ToMicroseconds());
989 void HandleVideoDecoded(VideoData* aVideo) override {
990 // TODO : check if we need to update mOriginalDecodedDuration
992 // Here sample still keeps its original timestamp.
994 // This indicates there is a shorter audio track, and it's the first time in
995 // the looping (audio ends but video is playing) so that we haven't been
996 // able to determine the decoded duration. Therefore, we fill the gap
997 // between two tracks before video ends. Afterward, this adjustment will be
998 // done in `HandleEndOfAudio()`.
999 if (mMaster->mOriginalDecodedDuration == media::TimeUnit::Zero() &&
1000 mMaster->mAudioTrackDecodedDuration &&
1001 aVideo->GetEndTime() > *mMaster->mAudioTrackDecodedDuration) {
1002 media::TimeUnit gap;
1003 // First time we fill gap between the video frame to the last audio.
1004 if (auto prevVideo = VideoQueue().PeekBack();
1005 prevVideo &&
1006 prevVideo->GetEndTime() < *mMaster->mAudioTrackDecodedDuration) {
1007 gap =
1008 aVideo->GetEndTime().ToBase(*mMaster->mAudioTrackDecodedDuration) -
1009 *mMaster->mAudioTrackDecodedDuration;
1011 // Then fill the gap for all following videos.
1012 else {
1013 gap = aVideo->mDuration.ToBase(*mMaster->mAudioTrackDecodedDuration);
1015 SLOG("Longer video %" PRId64 "%s (audio-durtaion=%" PRId64
1016 "%s), insert silence to fill the gap %" PRId64 "%s",
1017 aVideo->GetEndTime().ToMicroseconds(),
1018 aVideo->GetEndTime().ToString().get(),
1019 mMaster->mAudioTrackDecodedDuration->ToMicroseconds(),
1020 mMaster->mAudioTrackDecodedDuration->ToString().get(),
1021 gap.ToMicroseconds(), gap.ToString().get());
1022 PushFakeAudioDataIfNeeded(gap);
1025 // After pushing data to the queue, timestamp might be adjusted.
1026 DecodingState::HandleVideoDecoded(aVideo);
1027 mMaster->mDecodedVideoEndTime =
1028 std::max(aVideo->GetEndTime(), mMaster->mDecodedVideoEndTime);
1029 SLOG("video sample after time-adjustment [%" PRId64 ",%" PRId64 "]",
1030 aVideo->mTime.ToMicroseconds(), aVideo->GetEndTime().ToMicroseconds());
1033 void HandleEndOfAudio() override {
1034 mIsReachingAudioEOS = true;
1035 if (!mMaster->mAudioTrackDecodedDuration &&
1036 mMaster->HasLastDecodedData(MediaData::Type::AUDIO_DATA)) {
1037 mMaster->mAudioTrackDecodedDuration.emplace(
1038 mMaster->mDecodedAudioEndTime);
1040 if (DetermineOriginalDecodedDurationIfNeeded()) {
1041 AudioQueue().SetOffset(AudioQueue().GetOffset() +
1042 mMaster->mOriginalDecodedDuration);
1045 // This indicates that the audio track is shorter than the video track, so
1046 // we need to add some silence to fill the gap.
1047 if (mMaster->mAudioTrackDecodedDuration &&
1048 mMaster->mOriginalDecodedDuration >
1049 *mMaster->mAudioTrackDecodedDuration) {
1050 MOZ_ASSERT(mMaster->HasVideo());
1051 MOZ_ASSERT(mMaster->mVideoTrackDecodedDuration);
1052 MOZ_ASSERT(mMaster->mOriginalDecodedDuration ==
1053 *mMaster->mVideoTrackDecodedDuration);
1054 auto gap = mMaster->mOriginalDecodedDuration.ToBase(
1055 *mMaster->mAudioTrackDecodedDuration) -
1056 *mMaster->mAudioTrackDecodedDuration;
1057 SLOG(
1058 "Audio track is shorter than the original decoded duration "
1059 "(a=%" PRId64 "%s, t=%" PRId64
1060 "%s), insert silence to fill the gap %" PRId64 "%s",
1061 mMaster->mAudioTrackDecodedDuration->ToMicroseconds(),
1062 mMaster->mAudioTrackDecodedDuration->ToString().get(),
1063 mMaster->mOriginalDecodedDuration.ToMicroseconds(),
1064 mMaster->mOriginalDecodedDuration.ToString().get(),
1065 gap.ToMicroseconds(), gap.ToString().get());
1066 PushFakeAudioDataIfNeeded(gap);
1069 SLOG(
1070 "received audio EOS when seamless looping, starts seeking, "
1071 "audioLoopingOffset=[%" PRId64 "], mAudioTrackDecodedDuration=[%" PRId64
1072 "]",
1073 AudioQueue().GetOffset().ToMicroseconds(),
1074 mMaster->mAudioTrackDecodedDuration->ToMicroseconds());
1075 if (!IsRequestingDataFromStartPosition(MediaData::Type::AUDIO_DATA)) {
1076 RequestDataFromStartPosition(TrackInfo::TrackType::kAudioTrack);
1078 ProcessSamplesWaitingAdjustmentIfAny();
1081 void HandleEndOfVideo() override {
1082 mIsReachingVideoEOS = true;
1083 if (!mMaster->mVideoTrackDecodedDuration &&
1084 mMaster->HasLastDecodedData(MediaData::Type::VIDEO_DATA)) {
1085 mMaster->mVideoTrackDecodedDuration.emplace(
1086 mMaster->mDecodedVideoEndTime);
1088 if (DetermineOriginalDecodedDurationIfNeeded()) {
1089 VideoQueue().SetOffset(VideoQueue().GetOffset() +
1090 mMaster->mOriginalDecodedDuration);
1093 SLOG(
1094 "received video EOS when seamless looping, starts seeking, "
1095 "videoLoopingOffset=[%" PRId64 "], mVideoTrackDecodedDuration=[%" PRId64
1096 "]",
1097 VideoQueue().GetOffset().ToMicroseconds(),
1098 mMaster->mVideoTrackDecodedDuration->ToMicroseconds());
1099 if (!IsRequestingDataFromStartPosition(MediaData::Type::VIDEO_DATA)) {
1100 RequestDataFromStartPosition(TrackInfo::TrackType::kVideoTrack);
1102 ProcessSamplesWaitingAdjustmentIfAny();
1105 private:
1106 void RequestDataFromStartPosition(TrackInfo::TrackType aType) {
1107 MOZ_DIAGNOSTIC_ASSERT(aType == TrackInfo::TrackType::kAudioTrack ||
1108 aType == TrackInfo::TrackType::kVideoTrack);
1110 const bool isAudio = aType == TrackInfo::TrackType::kAudioTrack;
1111 MOZ_ASSERT_IF(isAudio, mMaster->HasAudio());
1112 MOZ_ASSERT_IF(!isAudio, mMaster->HasVideo());
1114 if (IsReaderSeeking()) {
1115 MOZ_ASSERT(!mPendingSeekingType);
1116 mPendingSeekingType = Some(aType);
1117 SLOG("Delay %s seeking until the reader finishes current seeking",
1118 isAudio ? "audio" : "video");
1119 return;
1122 auto& seekRequest = isAudio ? mAudioSeekRequest : mVideoSeekRequest;
1123 Reader()->ResetDecode(aType);
1124 Reader()
1125 ->Seek(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Type::Accurate,
1126 isAudio ? SeekTarget::Track::AudioOnly
1127 : SeekTarget::Track::VideoOnly))
1128 ->Then(
1129 OwnerThread(), __func__,
1130 [this, isAudio, master = RefPtr{mMaster}]() mutable -> void {
1131 AUTO_PROFILER_LABEL(
1132 nsPrintfCString(
1133 "LoopingDecodingState::RequestDataFromStartPosition(%s)::"
1134 "SeekResolved",
1135 isAudio ? "audio" : "video")
1136 .get(),
1137 MEDIA_PLAYBACK);
1138 if (auto& state = master->mStateObj;
1139 state &&
1140 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1141 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1142 return;
1144 if (isAudio) {
1145 mAudioSeekRequest.Complete();
1146 } else {
1147 mVideoSeekRequest.Complete();
1149 SLOG(
1150 "seeking completed, start to request first %s sample "
1151 "(queued=%zu, decoder-queued=%zu)",
1152 isAudio ? "audio" : "video",
1153 isAudio ? AudioQueue().GetSize() : VideoQueue().GetSize(),
1154 isAudio ? Reader()->SizeOfAudioQueueInFrames()
1155 : Reader()->SizeOfVideoQueueInFrames());
1156 if (isAudio) {
1157 RequestAudioDataFromReaderAfterEOS();
1158 } else {
1159 RequestVideoDataFromReaderAfterEOS();
1161 if (mPendingSeekingType) {
1162 auto seekingType = *mPendingSeekingType;
1163 mPendingSeekingType.reset();
1164 SLOG("Perform pending %s seeking", TrackTypeToStr(seekingType));
1165 RequestDataFromStartPosition(seekingType);
1168 [this, isAudio, master = RefPtr{mMaster}](
1169 const SeekRejectValue& aReject) mutable -> void {
1170 AUTO_PROFILER_LABEL(
1171 nsPrintfCString("LoopingDecodingState::"
1172 "RequestDataFromStartPosition(%s)::"
1173 "SeekRejected",
1174 isAudio ? "audio" : "video")
1175 .get(),
1176 MEDIA_PLAYBACK);
1177 if (auto& state = master->mStateObj;
1178 state &&
1179 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1180 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1181 return;
1183 if (isAudio) {
1184 mAudioSeekRequest.Complete();
1185 } else {
1186 mVideoSeekRequest.Complete();
1188 HandleError(aReject.mError, isAudio);
1190 ->Track(seekRequest);
1193 void RequestAudioDataFromReaderAfterEOS() {
1194 MOZ_ASSERT(mMaster->HasAudio());
1195 Reader()
1196 ->RequestAudioData()
1197 ->Then(
1198 OwnerThread(), __func__,
1199 [this, master = RefPtr{mMaster}](const RefPtr<AudioData>& aAudio) {
1200 AUTO_PROFILER_LABEL(
1201 "LoopingDecodingState::"
1202 "RequestAudioDataFromReader::"
1203 "RequestDataResolved",
1204 MEDIA_PLAYBACK);
1205 if (auto& state = master->mStateObj;
1206 state &&
1207 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1208 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1209 return;
1211 mIsReachingAudioEOS = false;
1212 mAudioDataRequest.Complete();
1213 SLOG(
1214 "got audio decoded sample "
1215 "[%" PRId64 ",%" PRId64 "]",
1216 aAudio->mTime.ToMicroseconds(),
1217 aAudio->GetEndTime().ToMicroseconds());
1218 if (ShouldPutDataOnWaiting(MediaData::Type::AUDIO_DATA)) {
1219 SLOG(
1220 "decoded audio sample needs to wait for timestamp "
1221 "adjustment after EOS");
1222 PutDataOnWaiting(aAudio);
1223 return;
1225 HandleAudioDecoded(aAudio);
1226 ProcessSamplesWaitingAdjustmentIfAny();
1228 [this, master = RefPtr{mMaster}](const MediaResult& aError) {
1229 AUTO_PROFILER_LABEL(
1230 "LoopingDecodingState::"
1231 "RequestAudioDataFromReader::"
1232 "RequestDataRejected",
1233 MEDIA_PLAYBACK);
1234 if (auto& state = master->mStateObj;
1235 state &&
1236 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1237 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1238 return;
1240 mAudioDataRequest.Complete();
1241 HandleError(aError, true /* isAudio */);
1243 ->Track(mAudioDataRequest);
1246 void RequestVideoDataFromReaderAfterEOS() {
1247 MOZ_ASSERT(mMaster->HasVideo());
1248 Reader()
1249 ->RequestVideoData(media::TimeUnit(),
1250 false /* aRequestNextVideoKeyFrame */)
1251 ->Then(
1252 OwnerThread(), __func__,
1253 [this, master = RefPtr{mMaster}](const RefPtr<VideoData>& aVideo) {
1254 AUTO_PROFILER_LABEL(
1255 "LoopingDecodingState::"
1256 "RequestVideoDataFromReaderAfterEOS()::"
1257 "RequestDataResolved",
1258 MEDIA_PLAYBACK);
1259 if (auto& state = master->mStateObj;
1260 state &&
1261 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1262 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1263 return;
1265 mIsReachingVideoEOS = false;
1266 mVideoDataRequest.Complete();
1267 SLOG(
1268 "got video decoded sample "
1269 "[%" PRId64 ",%" PRId64 "]",
1270 aVideo->mTime.ToMicroseconds(),
1271 aVideo->GetEndTime().ToMicroseconds());
1272 if (ShouldPutDataOnWaiting(MediaData::Type::VIDEO_DATA)) {
1273 SLOG(
1274 "decoded video sample needs to wait for timestamp "
1275 "adjustment after EOS");
1276 PutDataOnWaiting(aVideo);
1277 return;
1279 mMaster->mBypassingSkipToNextKeyFrameCheck = true;
1280 HandleVideoDecoded(aVideo);
1281 ProcessSamplesWaitingAdjustmentIfAny();
1283 [this, master = RefPtr{mMaster}](const MediaResult& aError) {
1284 AUTO_PROFILER_LABEL(
1285 "LoopingDecodingState::"
1286 "RequestVideoDataFromReaderAfterEOS()::"
1287 "RequestDataRejected",
1288 MEDIA_PLAYBACK);
1289 if (auto& state = master->mStateObj;
1290 state &&
1291 state->GetState() != DECODER_STATE_LOOPING_DECODING) {
1292 MOZ_RELEASE_ASSERT(false, "This shouldn't happen!");
1293 return;
1295 mVideoDataRequest.Complete();
1296 HandleError(aError, false /* isAudio */);
1298 ->Track(mVideoDataRequest);
1301 void HandleError(const MediaResult& aError, bool aIsAudio);
1303 bool ShouldRequestData(MediaData::Type aType) const {
1304 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
1305 aType == MediaData::Type::VIDEO_DATA);
1307 if (aType == MediaData::Type::AUDIO_DATA &&
1308 (mAudioSeekRequest.Exists() || mAudioDataRequest.Exists() ||
1309 IsDataWaitingForTimestampAdjustment(MediaData::Type::AUDIO_DATA) ||
1310 mMaster->IsWaitingAudioData())) {
1311 return false;
1313 if (aType == MediaData::Type::VIDEO_DATA &&
1314 (mVideoSeekRequest.Exists() || mVideoDataRequest.Exists() ||
1315 IsDataWaitingForTimestampAdjustment(MediaData::Type::VIDEO_DATA) ||
1316 mMaster->IsWaitingVideoData())) {
1317 return false;
1319 return true;
1322 void HandleAudioCanceled() override {
1323 if (ShouldRequestData(MediaData::Type::AUDIO_DATA)) {
1324 mMaster->RequestAudioData();
1328 void HandleAudioWaited(MediaData::Type aType) override {
1329 if (ShouldRequestData(MediaData::Type::AUDIO_DATA)) {
1330 mMaster->RequestAudioData();
1334 void HandleVideoCanceled() override {
1335 if (ShouldRequestData(MediaData::Type::VIDEO_DATA)) {
1336 mMaster->RequestVideoData(mMaster->GetMediaTime(),
1337 ShouldRequestNextKeyFrame());
1341 void HandleVideoWaited(MediaData::Type aType) override {
1342 if (ShouldRequestData(MediaData::Type::VIDEO_DATA)) {
1343 mMaster->RequestVideoData(mMaster->GetMediaTime(),
1344 ShouldRequestNextKeyFrame());
1348 void EnsureAudioDecodeTaskQueued() override {
1349 if (!ShouldRequestData(MediaData::Type::AUDIO_DATA)) {
1350 return;
1352 DecodingState::EnsureAudioDecodeTaskQueued();
1355 void EnsureVideoDecodeTaskQueued() override {
1356 if (!ShouldRequestData(MediaData::Type::VIDEO_DATA)) {
1357 return;
1359 DecodingState::EnsureVideoDecodeTaskQueued();
1362 bool DetermineOriginalDecodedDurationIfNeeded() {
1363 // Duration would only need to be set once, unless we get more data which is
1364 // larger than the duration. That can happen on MSE (reopen stream).
1365 if (mMaster->mOriginalDecodedDuration != media::TimeUnit::Zero()) {
1366 return true;
1369 // Single track situations
1370 if (mMaster->HasAudio() && !mMaster->HasVideo() &&
1371 mMaster->mAudioTrackDecodedDuration) {
1372 mMaster->mOriginalDecodedDuration = *mMaster->mAudioTrackDecodedDuration;
1373 SLOG("audio only, duration=%" PRId64,
1374 mMaster->mOriginalDecodedDuration.ToMicroseconds());
1375 return true;
1377 if (mMaster->HasVideo() && !mMaster->HasAudio() &&
1378 mMaster->mVideoTrackDecodedDuration) {
1379 mMaster->mOriginalDecodedDuration = *mMaster->mVideoTrackDecodedDuration;
1380 SLOG("video only, duration=%" PRId64,
1381 mMaster->mOriginalDecodedDuration.ToMicroseconds());
1382 return true;
1384 // Two tracks situation
1385 if (mMaster->HasAudio() && mMaster->HasVideo()) {
1386 // Both tracks have ended so that we can check which track is longer.
1387 if (mMaster->mAudioTrackDecodedDuration &&
1388 mMaster->mVideoTrackDecodedDuration) {
1389 mMaster->mOriginalDecodedDuration =
1390 std::max(*mMaster->mVideoTrackDecodedDuration,
1391 *mMaster->mAudioTrackDecodedDuration);
1392 SLOG("Both tracks ended, original duration=%" PRId64 " (a=%" PRId64
1393 ", v=%" PRId64 ")",
1394 mMaster->mOriginalDecodedDuration.ToMicroseconds(),
1395 mMaster->mAudioTrackDecodedDuration->ToMicroseconds(),
1396 mMaster->mVideoTrackDecodedDuration->ToMicroseconds());
1397 return true;
1399 // When entering the state, video has ended but audio hasn't, which means
1400 // audio is longer.
1401 if (mMaster->mAudioTrackDecodedDuration &&
1402 mVideoEndedBeforeEnteringStateWithoutDuration) {
1403 mMaster->mOriginalDecodedDuration =
1404 *mMaster->mAudioTrackDecodedDuration;
1405 mVideoEndedBeforeEnteringStateWithoutDuration = false;
1406 SLOG("audio is longer, duration=%" PRId64,
1407 mMaster->mOriginalDecodedDuration.ToMicroseconds());
1408 return true;
1410 // When entering the state, audio has ended but video hasn't, which means
1411 // video is longer.
1412 if (mMaster->mVideoTrackDecodedDuration &&
1413 mAudioEndedBeforeEnteringStateWithoutDuration) {
1414 mMaster->mOriginalDecodedDuration =
1415 *mMaster->mVideoTrackDecodedDuration;
1416 mAudioEndedBeforeEnteringStateWithoutDuration = false;
1417 SLOG("video is longer, duration=%" PRId64,
1418 mMaster->mOriginalDecodedDuration.ToMicroseconds());
1419 return true;
1421 SLOG("Still waiting for another track ends...");
1422 MOZ_ASSERT(!mMaster->mAudioTrackDecodedDuration ||
1423 !mMaster->mVideoTrackDecodedDuration);
1425 SLOG("can't determine the original decoded duration yet");
1426 MOZ_ASSERT(mMaster->mOriginalDecodedDuration == media::TimeUnit::Zero());
1427 return false;
1430 void ProcessSamplesWaitingAdjustmentIfAny() {
1431 if (!mDataWaitingTimestampAdjustment) {
1432 return;
1435 RefPtr<MediaData> data = mDataWaitingTimestampAdjustment;
1436 mDataWaitingTimestampAdjustment = nullptr;
1437 const bool isAudio = data->mType == MediaData::Type::AUDIO_DATA;
1438 SLOG("process %s sample waiting for timestamp adjustment",
1439 isAudio ? "audio" : "video");
1440 if (isAudio) {
1441 // Waiting sample is for next round of looping, so the queue offset
1442 // shouldn't be zero. This happens when the track has reached EOS before
1443 // entering the state (and looping never happens before). Same for below
1444 // video case.
1445 if (AudioQueue().GetOffset() == media::TimeUnit::Zero()) {
1446 AudioQueue().SetOffset(mMaster->mOriginalDecodedDuration);
1448 HandleAudioDecoded(data->As<AudioData>());
1449 } else {
1450 MOZ_DIAGNOSTIC_ASSERT(data->mType == MediaData::Type::VIDEO_DATA);
1451 if (VideoQueue().GetOffset() == media::TimeUnit::Zero()) {
1452 VideoQueue().SetOffset(mMaster->mOriginalDecodedDuration);
1454 HandleVideoDecoded(data->As<VideoData>());
1458 bool IsDataWaitingForTimestampAdjustment(MediaData::Type aType) const {
1459 return mDataWaitingTimestampAdjustment &&
1460 mDataWaitingTimestampAdjustment->mType == aType;
1463 bool ShouldPutDataOnWaiting(MediaData::Type aType) const {
1464 // If another track is already waiting, this track shouldn't be waiting.
1465 // This case only happens when both tracks reached EOS before entering the
1466 // looping decoding state, so we don't know the decoded duration yet (used
1467 // to adjust timestamp) But this is fine, because both tracks will start
1468 // from 0 so we don't need to adjust them now.
1469 if (mDataWaitingTimestampAdjustment &&
1470 !IsDataWaitingForTimestampAdjustment(aType)) {
1471 return false;
1474 // Only have one track, no need to wait.
1475 if ((aType == MediaData::Type::AUDIO_DATA && !mMaster->HasVideo()) ||
1476 (aType == MediaData::Type::VIDEO_DATA && !mMaster->HasAudio())) {
1477 return false;
1480 // We don't know the duration yet, so we can't calculate the looping offset.
1481 return mMaster->mOriginalDecodedDuration == media::TimeUnit::Zero();
1484 void PutDataOnWaiting(MediaData* aData) {
1485 MOZ_ASSERT(!mDataWaitingTimestampAdjustment);
1486 mDataWaitingTimestampAdjustment = aData;
1487 SLOG("put %s [%" PRId64 ",%" PRId64 "] on waiting",
1488 MediaData::TypeToStr(aData->mType), aData->mTime.ToMicroseconds(),
1489 aData->GetEndTime().ToMicroseconds());
1490 MaybeStopPrerolling();
1493 bool ShouldDiscardLoopedData(MediaData::Type aType) const {
1494 if (!mMaster->mMediaSink->IsStarted()) {
1495 return false;
1498 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
1499 aType == MediaData::Type::VIDEO_DATA);
1500 const bool isAudio = aType == MediaData::Type::AUDIO_DATA;
1501 if (isAudio && !mMaster->HasAudio()) {
1502 return false;
1504 if (!isAudio && !mMaster->HasVideo()) {
1505 return false;
1509 * If media cancels looping, we should check whether there is media data
1510 * whose time is later than EOS. If so, we should discard them because we
1511 * won't have a chance to play them.
1513 * playback last decoded
1514 * position EOS data time
1515 * ----|---------------|------------|---------> (Increasing timeline)
1516 * mCurrent looping mMaster's
1517 * ClockTime offset mDecodedXXXEndTime
1520 const auto offset =
1521 isAudio ? AudioQueue().GetOffset() : VideoQueue().GetOffset();
1522 const auto endTime =
1523 isAudio ? mMaster->mDecodedAudioEndTime : mMaster->mDecodedVideoEndTime;
1524 const auto clockTime = mMaster->GetClock();
1525 return (offset != media::TimeUnit::Zero() && clockTime < offset &&
1526 offset < endTime);
1529 void DiscardLoopedData(MediaData::Type aType) {
1530 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
1531 aType == MediaData::Type::VIDEO_DATA);
1532 const bool isAudio = aType == MediaData::Type::AUDIO_DATA;
1533 const auto offset =
1534 isAudio ? AudioQueue().GetOffset() : VideoQueue().GetOffset();
1535 if (offset == media::TimeUnit::Zero()) {
1536 return;
1539 SLOG("Discard %s frames after the time=%" PRId64,
1540 isAudio ? "audio" : "video", offset.ToMicroseconds());
1541 if (isAudio) {
1542 DiscardFramesFromTail(AudioQueue(), [&](int64_t aSampleTime) {
1543 return aSampleTime > offset.ToMicroseconds();
1545 } else {
1546 DiscardFramesFromTail(VideoQueue(), [&](int64_t aSampleTime) {
1547 return aSampleTime > offset.ToMicroseconds();
1552 void PushFakeAudioDataIfNeeded(const media::TimeUnit& aDuration) {
1553 MOZ_ASSERT(Info().HasAudio());
1555 const auto& audioInfo = Info().mAudio;
1556 CheckedInt64 frames = aDuration.ToTicksAtRate(audioInfo.mRate);
1557 if (!frames.isValid() || !audioInfo.mChannels || !audioInfo.mRate) {
1558 NS_WARNING("Can't create fake audio, invalid frames/channel/rate?");
1559 return;
1562 if (!frames.value()) {
1563 NS_WARNING(nsPrintfCString("Duration (%s) too short, no frame needed",
1564 aDuration.ToString().get())
1565 .get());
1566 return;
1569 // If we can get the last sample, use its frame. Otherwise, use common 1024.
1570 int64_t typicalPacketFrameCount = 1024;
1571 if (RefPtr<AudioData> audio = AudioQueue().PeekBack()) {
1572 typicalPacketFrameCount = audio->Frames();
1575 media::TimeUnit totalDuration = TimeUnit::Zero(audioInfo.mRate);
1576 // Generate fake audio in a smaller size of audio chunk.
1577 while (frames.value()) {
1578 int64_t packetFrameCount =
1579 std::min(frames.value(), typicalPacketFrameCount);
1580 frames -= packetFrameCount;
1581 AlignedAudioBuffer samples(packetFrameCount * audioInfo.mChannels);
1582 if (!samples) {
1583 NS_WARNING("Can't create audio buffer, OOM?");
1584 return;
1586 // `mDecodedAudioEndTime` is adjusted time, and we want unadjusted time
1587 // otherwise the time would be adjusted twice when pushing sample into the
1588 // media queue.
1589 media::TimeUnit startTime = mMaster->mDecodedAudioEndTime;
1590 if (AudioQueue().GetOffset() != media::TimeUnit::Zero()) {
1591 startTime -= AudioQueue().GetOffset();
1593 RefPtr<AudioData> data(new AudioData(0, startTime, std::move(samples),
1594 audioInfo.mChannels,
1595 audioInfo.mRate));
1596 SLOG("Created fake audio data (duration=%s, frame-left=%" PRId64 ")",
1597 data->mDuration.ToString().get(), frames.value());
1598 totalDuration += data->mDuration;
1599 HandleAudioDecoded(data);
1601 SLOG("Pushed fake silence audio data in total duration=%" PRId64 "%s",
1602 totalDuration.ToMicroseconds(), totalDuration.ToString().get());
1605 bool HasDecodedLastAudioFrame() const {
1606 // when we're going to leave looping state and have got EOS before, we
1607 // should mark audio queue as ended because we have got all data we need.
1608 return mAudioDataRequest.Exists() || mAudioSeekRequest.Exists() ||
1609 ShouldDiscardLoopedData(MediaData::Type::AUDIO_DATA) ||
1610 IsDataWaitingForTimestampAdjustment(MediaData::Type::AUDIO_DATA) ||
1611 mIsReachingAudioEOS;
1614 bool HasDecodedLastVideoFrame() const {
1615 // when we're going to leave looping state and have got EOS before, we
1616 // should mark video queue as ended because we have got all data we need.
1617 return mVideoDataRequest.Exists() || mVideoSeekRequest.Exists() ||
1618 ShouldDiscardLoopedData(MediaData::Type::VIDEO_DATA) ||
1619 IsDataWaitingForTimestampAdjustment(MediaData::Type::VIDEO_DATA) ||
1620 mIsReachingVideoEOS;
1623 bool ShouldStopPrerolling() const override {
1624 // These checks is used to handle the media queue aren't opened correctly
1625 // because they've been close before entering the looping state. Therefore,
1626 // we need to preroll data in order to let new data to reopen the queue
1627 // automatically. Otherwise, playback can't start successfully.
1628 bool isWaitingForNewData = false;
1629 if (mMaster->HasAudio()) {
1630 isWaitingForNewData |= (mIsReachingAudioEOS && AudioQueue().IsFinished());
1632 if (mMaster->HasVideo()) {
1633 isWaitingForNewData |= (mIsReachingVideoEOS && VideoQueue().IsFinished());
1635 return !isWaitingForNewData && DecodingState::ShouldStopPrerolling();
1638 bool IsReaderSeeking() const {
1639 return mAudioSeekRequest.Exists() || mVideoSeekRequest.Exists();
1642 bool IsWaitingData(MediaData::Type aType) const override {
1643 if (aType == MediaData::Type::AUDIO_DATA) {
1644 return mMaster->IsWaitingAudioData() ||
1645 IsDataWaitingForTimestampAdjustment(MediaData::Type::AUDIO_DATA);
1647 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::VIDEO_DATA);
1648 return mMaster->IsWaitingVideoData() ||
1649 IsDataWaitingForTimestampAdjustment(MediaData::Type::VIDEO_DATA);
1652 bool IsRequestingDataFromStartPosition(MediaData::Type aType) const {
1653 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
1654 aType == MediaData::Type::VIDEO_DATA);
1655 if (aType == MediaData::Type::AUDIO_DATA) {
1656 return mAudioSeekRequest.Exists() || mAudioDataRequest.Exists();
1658 return mVideoSeekRequest.Exists() || mVideoDataRequest.Exists();
1661 bool IsBufferingAllowed() const override {
1662 return !mIsReachingAudioEOS && !mIsReachingVideoEOS;
1665 bool mIsReachingAudioEOS;
1666 bool mIsReachingVideoEOS;
1669 * If we have both tracks which have different length, when one track ends
1670 * first, we can't adjust new data from that track if another longer track
1671 * hasn't ended yet. The adjusted timestamp needs to be based off the longer
1672 * track's last data's timestamp, because otherwise it would cause a deviation
1673 * and eventually a/v unsync. Those sample needs to be stored and we will
1674 * adjust their timestamp later.
1676 * Following graph explains the situation in details.
1677 * o : decoded data with timestamp adjusted or no adjustment (not looping yet)
1678 * x : decoded data without timestamp adjustment.
1679 * - : stop decoding and nothing happens
1680 * EOS : the track reaches to the end. We now know the offset of the track.
1682 * Timeline ----------------------------------->
1683 * Track1 : o EOS x - - o
1684 * Track2 : o o o EOS o o
1686 * Before reaching track2's EOS, we can't adjust samples from track1 because
1687 * track2 might have longer duration than track1. The sample X would be
1688 * stored in `mDataWaitingTimestampAdjustment` and we would also stop decoding
1689 * for track1.
1691 * After reaching track2's EOS, now we know another track's offset, and the
1692 * larger one would be used for `mOriginalDecodedDuration`. Once that duration
1693 * has been determined, we will no longer need to put samples on waiting
1694 * because we already know how to adjust timestamp.
1696 RefPtr<MediaData> mDataWaitingTimestampAdjustment;
1698 MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mAudioSeekRequest;
1699 MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mVideoSeekRequest;
1700 MozPromiseRequestHolder<AudioDataPromise> mAudioDataRequest;
1701 MozPromiseRequestHolder<VideoDataPromise> mVideoDataRequest;
1703 // The media format reader only allows seeking a track at a time, if we're
1704 // already in seeking, then delay the new seek until the current one finishes.
1705 Maybe<TrackInfo::TrackType> mPendingSeekingType;
1707 // These are used to track a special case where the playback starts from EOS
1708 // position via seeking. So even if EOS has reached, none of data has been
1709 // decoded yet. They will be reset when `mOriginalDecodedDuration` is
1710 // determined.
1711 bool mAudioEndedBeforeEnteringStateWithoutDuration;
1712 bool mVideoEndedBeforeEnteringStateWithoutDuration;
1716 * Purpose: seek to a particular new playback position.
1718 * Transition to:
1719 * SEEKING if any new seek request.
1720 * SHUTDOWN if seek failed.
1721 * COMPLETED if the new playback position is the end of the media resource.
1722 * NextFrameSeekingState if completing a NextFrameSeekingFromDormantState.
1723 * DECODING/LOOPING_DECODING otherwise.
1725 class MediaDecoderStateMachine::SeekingState
1726 : public MediaDecoderStateMachine::StateObject {
1727 public:
1728 explicit SeekingState(Master* aPtr)
1729 : StateObject(aPtr), mVisibility(static_cast<EventVisibility>(0)) {}
1731 RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
1732 EventVisibility aVisibility) {
1733 mSeekJob = std::move(aSeekJob);
1734 mVisibility = aVisibility;
1736 // Suppressed visibility comes from two cases: (1) leaving dormant state,
1737 // and (2) resuming suspended video decoder. We want both cases to be
1738 // transparent to the user. So we only notify the change when the seek
1739 // request is from the user.
1740 if (mVisibility == EventVisibility::Observable) {
1741 // Don't stop playback for a video-only seek since we want to keep playing
1742 // audio and we don't need to stop playback while leaving dormant for the
1743 // playback should has been stopped.
1744 mMaster->StopPlayback();
1745 mMaster->UpdatePlaybackPositionInternal(mSeekJob.mTarget->GetTime());
1746 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::SeekStarted);
1747 mMaster->mOnNextFrameStatus.Notify(
1748 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
1751 RefPtr<MediaDecoder::SeekPromise> p = mSeekJob.mPromise.Ensure(__func__);
1753 DoSeek();
1755 return p;
1758 virtual void Exit() override = 0;
1760 State GetState() const override = 0;
1762 void HandleAudioDecoded(AudioData* aAudio) override = 0;
1763 void HandleVideoDecoded(VideoData* aVideo) override = 0;
1764 void HandleAudioWaited(MediaData::Type aType) override = 0;
1765 void HandleVideoWaited(MediaData::Type aType) override = 0;
1767 void HandleVideoSuspendTimeout() override {
1768 // Do nothing since we want a valid video frame to show when seek is done.
1771 void HandleResumeVideoDecoding(const TimeUnit&) override {
1772 // Do nothing. We will resume video decoding in the decoding state.
1775 // We specially handle next frame seeks by ignoring them if we're already
1776 // seeking.
1777 RefPtr<MediaDecoder::SeekPromise> HandleSeek(
1778 const SeekTarget& aTarget) override {
1779 if (aTarget.IsNextFrame()) {
1780 // We ignore next frame seeks if we already have a seek pending
1781 SLOG("Already SEEKING, ignoring seekToNextFrame");
1782 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1783 return MediaDecoder::SeekPromise::CreateAndReject(
1784 /* aRejectValue = */ true, __func__);
1787 return StateObject::HandleSeek(aTarget);
1790 protected:
1791 SeekJob mSeekJob;
1792 EventVisibility mVisibility;
1794 virtual void DoSeek() = 0;
1795 // Transition to the next state (defined by the subclass) when seek is
1796 // completed.
1797 virtual void GoToNextState() { SetDecodingState(); }
1798 void SeekCompleted();
1799 virtual TimeUnit CalculateNewCurrentTime() const = 0;
1802 class MediaDecoderStateMachine::AccurateSeekingState
1803 : public MediaDecoderStateMachine::SeekingState {
1804 public:
1805 explicit AccurateSeekingState(Master* aPtr) : SeekingState(aPtr) {}
1807 State GetState() const override { return DECODER_STATE_SEEKING_ACCURATE; }
1809 RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
1810 EventVisibility aVisibility) {
1811 MOZ_ASSERT(aSeekJob.mTarget->IsAccurate() || aSeekJob.mTarget->IsFast());
1812 mCurrentTimeBeforeSeek = mMaster->GetMediaTime();
1813 return SeekingState::Enter(std::move(aSeekJob), aVisibility);
1816 void Exit() override {
1817 // Disconnect MediaDecoder.
1818 mSeekJob.RejectIfExists(__func__);
1820 // Disconnect ReaderProxy.
1821 mSeekRequest.DisconnectIfExists();
1823 mWaitRequest.DisconnectIfExists();
1826 void HandleAudioDecoded(AudioData* aAudio) override {
1827 MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1828 "Seek shouldn't be finished");
1829 MOZ_ASSERT(aAudio);
1831 AdjustFastSeekIfNeeded(aAudio);
1833 if (mSeekJob.mTarget->IsFast()) {
1834 // Non-precise seek; we can stop the seek at the first sample.
1835 mMaster->PushAudio(aAudio);
1836 mDoneAudioSeeking = true;
1837 } else {
1838 nsresult rv = DropAudioUpToSeekTarget(aAudio);
1839 if (NS_FAILED(rv)) {
1840 mMaster->DecodeError(rv);
1841 return;
1845 if (!mDoneAudioSeeking) {
1846 RequestAudioData();
1847 return;
1849 MaybeFinishSeek();
1852 void HandleVideoDecoded(VideoData* aVideo) override {
1853 MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1854 "Seek shouldn't be finished");
1855 MOZ_ASSERT(aVideo);
1857 AdjustFastSeekIfNeeded(aVideo);
1859 if (mSeekJob.mTarget->IsFast()) {
1860 // Non-precise seek. We can stop the seek at the first sample.
1861 mMaster->PushVideo(aVideo);
1862 mDoneVideoSeeking = true;
1863 } else {
1864 nsresult rv = DropVideoUpToSeekTarget(aVideo);
1865 if (NS_FAILED(rv)) {
1866 mMaster->DecodeError(rv);
1867 return;
1871 if (!mDoneVideoSeeking) {
1872 RequestVideoData();
1873 return;
1875 MaybeFinishSeek();
1878 void HandleWaitingForAudio() override {
1879 MOZ_ASSERT(!mDoneAudioSeeking);
1880 mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
1883 void HandleAudioCanceled() override {
1884 MOZ_ASSERT(!mDoneAudioSeeking);
1885 RequestAudioData();
1888 void HandleEndOfAudio() override {
1889 HandleEndOfAudioInternal();
1890 MaybeFinishSeek();
1893 void HandleWaitingForVideo() override {
1894 MOZ_ASSERT(!mDoneVideoSeeking);
1895 mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
1898 void HandleVideoCanceled() override {
1899 MOZ_ASSERT(!mDoneVideoSeeking);
1900 RequestVideoData();
1903 void HandleEndOfVideo() override {
1904 HandleEndOfVideoInternal();
1905 MaybeFinishSeek();
1908 void HandleAudioWaited(MediaData::Type aType) override {
1909 MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1910 "Seek shouldn't be finished");
1912 RequestAudioData();
1915 void HandleVideoWaited(MediaData::Type aType) override {
1916 MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1917 "Seek shouldn't be finished");
1919 RequestVideoData();
1922 void DoSeek() override {
1923 mDoneAudioSeeking = !Info().HasAudio();
1924 mDoneVideoSeeking = !Info().HasVideo();
1926 // Resetting decode should be called after stopping media sink, which can
1927 // ensure that we have an empty media queue before seeking the demuxer.
1928 mMaster->StopMediaSink();
1929 mMaster->ResetDecode();
1931 DemuxerSeek();
1934 TimeUnit CalculateNewCurrentTime() const override {
1935 const auto seekTime = mSeekJob.mTarget->GetTime();
1937 // For the accurate seek, we always set the newCurrentTime = seekTime so
1938 // that the updated HTMLMediaElement.currentTime will always be the seek
1939 // target; we rely on the MediaSink to handles the gap between the
1940 // newCurrentTime and the real decoded samples' start time.
1941 if (mSeekJob.mTarget->IsAccurate()) {
1942 return seekTime;
1945 // For the fast seek, we update the newCurrentTime with the decoded audio
1946 // and video samples, set it to be the one which is closet to the seekTime.
1947 if (mSeekJob.mTarget->IsFast()) {
1948 RefPtr<AudioData> audio = AudioQueue().PeekFront();
1949 RefPtr<VideoData> video = VideoQueue().PeekFront();
1951 // A situation that both audio and video approaches the end.
1952 if (!audio && !video) {
1953 return seekTime;
1956 const int64_t audioStart =
1957 audio ? audio->mTime.ToMicroseconds() : INT64_MAX;
1958 const int64_t videoStart =
1959 video ? video->mTime.ToMicroseconds() : INT64_MAX;
1960 const int64_t audioGap = std::abs(audioStart - seekTime.ToMicroseconds());
1961 const int64_t videoGap = std::abs(videoStart - seekTime.ToMicroseconds());
1962 return TimeUnit::FromMicroseconds(audioGap <= videoGap ? audioStart
1963 : videoStart);
1966 MOZ_ASSERT(false, "AccurateSeekTask doesn't handle other seek types.");
1967 return TimeUnit::Zero();
1970 protected:
1971 void DemuxerSeek() {
1972 // Request the demuxer to perform seek.
1973 Reader()
1974 ->Seek(mSeekJob.mTarget.ref())
1975 ->Then(
1976 OwnerThread(), __func__,
1977 [this](const media::TimeUnit& aUnit) { OnSeekResolved(aUnit); },
1978 [this](const SeekRejectValue& aReject) { OnSeekRejected(aReject); })
1979 ->Track(mSeekRequest);
1982 void OnSeekResolved(media::TimeUnit) {
1983 AUTO_PROFILER_LABEL("AccurateSeekingState::OnSeekResolved", MEDIA_PLAYBACK);
1984 mSeekRequest.Complete();
1986 // We must decode the first samples of active streams, so we can determine
1987 // the new stream time. So dispatch tasks to do that.
1988 if (!mDoneVideoSeeking) {
1989 RequestVideoData();
1991 if (!mDoneAudioSeeking) {
1992 RequestAudioData();
1996 void OnSeekRejected(const SeekRejectValue& aReject) {
1997 AUTO_PROFILER_LABEL("AccurateSeekingState::OnSeekRejected", MEDIA_PLAYBACK);
1998 mSeekRequest.Complete();
2000 if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
2001 SLOG("OnSeekRejected reason=WAITING_FOR_DATA type=%s",
2002 MediaData::TypeToStr(aReject.mType));
2003 MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
2004 !mMaster->IsRequestingAudioData());
2005 MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
2006 !mMaster->IsRequestingVideoData());
2007 MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
2008 !mMaster->IsWaitingAudioData());
2009 MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
2010 !mMaster->IsWaitingVideoData());
2012 // Fire 'waiting' to notify the player that we are waiting for data.
2013 mMaster->mOnNextFrameStatus.Notify(
2014 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
2016 Reader()
2017 ->WaitForData(aReject.mType)
2018 ->Then(
2019 OwnerThread(), __func__,
2020 [this](MediaData::Type aType) {
2021 AUTO_PROFILER_LABEL(
2022 "AccurateSeekingState::OnSeekRejected:WaitDataResolved",
2023 MEDIA_PLAYBACK);
2024 SLOG("OnSeekRejected wait promise resolved");
2025 mWaitRequest.Complete();
2026 DemuxerSeek();
2028 [this](const WaitForDataRejectValue& aRejection) {
2029 AUTO_PROFILER_LABEL(
2030 "AccurateSeekingState::OnSeekRejected:WaitDataRejected",
2031 MEDIA_PLAYBACK);
2032 SLOG("OnSeekRejected wait promise rejected");
2033 mWaitRequest.Complete();
2034 mMaster->DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
2036 ->Track(mWaitRequest);
2037 return;
2040 if (aReject.mError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
2041 if (!mDoneAudioSeeking) {
2042 HandleEndOfAudioInternal();
2044 if (!mDoneVideoSeeking) {
2045 HandleEndOfVideoInternal();
2047 MaybeFinishSeek();
2048 return;
2051 MOZ_ASSERT(NS_FAILED(aReject.mError),
2052 "Cancels should also disconnect mSeekRequest");
2053 mMaster->DecodeError(aReject.mError);
2056 void RequestAudioData() {
2057 MOZ_ASSERT(!mDoneAudioSeeking);
2058 mMaster->RequestAudioData();
2061 virtual void RequestVideoData() {
2062 MOZ_ASSERT(!mDoneVideoSeeking);
2063 mMaster->RequestVideoData(media::TimeUnit());
2066 void AdjustFastSeekIfNeeded(MediaData* aSample) {
2067 if (mSeekJob.mTarget->IsFast() &&
2068 mSeekJob.mTarget->GetTime() > mCurrentTimeBeforeSeek &&
2069 aSample->mTime < mCurrentTimeBeforeSeek) {
2070 // We are doing a fastSeek, but we ended up *before* the previous
2071 // playback position. This is surprising UX, so switch to an accurate
2072 // seek and decode to the seek target. This is not conformant to the
2073 // spec, fastSeek should always be fast, but until we get the time to
2074 // change all Readers to seek to the keyframe after the currentTime
2075 // in this case, we'll just decode forward. Bug 1026330.
2076 mSeekJob.mTarget->SetType(SeekTarget::Accurate);
2080 nsresult DropAudioUpToSeekTarget(AudioData* aAudio) {
2081 MOZ_ASSERT(aAudio && mSeekJob.mTarget->IsAccurate());
2083 if (mSeekJob.mTarget->GetTime() >= aAudio->GetEndTime()) {
2084 // Our seek target lies after the frames in this AudioData. Don't
2085 // push it onto the audio queue, and keep decoding forwards.
2086 return NS_OK;
2089 if (aAudio->mTime > mSeekJob.mTarget->GetTime()) {
2090 // The seek target doesn't lie in the audio block just after the last
2091 // audio frames we've seen which were before the seek target. This
2092 // could have been the first audio data we've seen after seek, i.e. the
2093 // seek terminated after the seek target in the audio stream. Just
2094 // abort the audio decode-to-target, the state machine will play
2095 // silence to cover the gap. Typically this happens in poorly muxed
2096 // files.
2097 SLOGW("Audio not synced after seek, maybe a poorly muxed file?");
2098 mMaster->PushAudio(aAudio);
2099 mDoneAudioSeeking = true;
2100 return NS_OK;
2103 bool ok = aAudio->SetTrimWindow(
2104 {mSeekJob.mTarget->GetTime().ToBase(aAudio->mTime),
2105 aAudio->GetEndTime()});
2106 if (!ok) {
2107 return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
2110 MOZ_ASSERT(AudioQueue().GetSize() == 0,
2111 "Should be the 1st sample after seeking");
2112 mMaster->PushAudio(aAudio);
2113 mDoneAudioSeeking = true;
2115 return NS_OK;
2118 nsresult DropVideoUpToSeekTarget(VideoData* aVideo) {
2119 MOZ_ASSERT(aVideo);
2120 SLOG("DropVideoUpToSeekTarget() frame [%" PRId64 ", %" PRId64 "]",
2121 aVideo->mTime.ToMicroseconds(), aVideo->GetEndTime().ToMicroseconds());
2122 const auto target = GetSeekTarget();
2124 // If the frame end time is less than the seek target, we won't want
2125 // to display this frame after the seek, so discard it.
2126 if (target >= aVideo->GetEndTime()) {
2127 SLOG("DropVideoUpToSeekTarget() pop video frame [%" PRId64 ", %" PRId64
2128 "] target=%" PRId64,
2129 aVideo->mTime.ToMicroseconds(),
2130 aVideo->GetEndTime().ToMicroseconds(), target.ToMicroseconds());
2131 PROFILER_MARKER_UNTYPED("MDSM::DropVideoUpToSeekTarget", MEDIA_PLAYBACK);
2132 mFirstVideoFrameAfterSeek = aVideo;
2133 } else {
2134 if (target >= aVideo->mTime && aVideo->GetEndTime() >= target) {
2135 // The seek target lies inside this frame's time slice. Adjust the
2136 // frame's start time to match the seek target.
2137 aVideo->UpdateTimestamp(target);
2139 mFirstVideoFrameAfterSeek = nullptr;
2141 SLOG("DropVideoUpToSeekTarget() found video frame [%" PRId64 ", %" PRId64
2142 "] containing target=%" PRId64,
2143 aVideo->mTime.ToMicroseconds(),
2144 aVideo->GetEndTime().ToMicroseconds(), target.ToMicroseconds());
2146 MOZ_ASSERT(VideoQueue().GetSize() == 0,
2147 "Should be the 1st sample after seeking");
2148 mMaster->PushVideo(aVideo);
2149 mDoneVideoSeeking = true;
2152 return NS_OK;
2155 void HandleEndOfAudioInternal() {
2156 MOZ_ASSERT(!mDoneAudioSeeking);
2157 AudioQueue().Finish();
2158 mDoneAudioSeeking = true;
2161 void HandleEndOfVideoInternal() {
2162 MOZ_ASSERT(!mDoneVideoSeeking);
2163 if (mFirstVideoFrameAfterSeek) {
2164 // Hit the end of stream. Move mFirstVideoFrameAfterSeek into
2165 // mSeekedVideoData so we have something to display after seeking.
2166 mMaster->PushVideo(mFirstVideoFrameAfterSeek);
2168 VideoQueue().Finish();
2169 mDoneVideoSeeking = true;
2172 void MaybeFinishSeek() {
2173 if (mDoneAudioSeeking && mDoneVideoSeeking) {
2174 SeekCompleted();
2179 * Track the current seek promise made by the reader.
2181 MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mSeekRequest;
2184 * Internal state.
2186 media::TimeUnit mCurrentTimeBeforeSeek;
2187 bool mDoneAudioSeeking = false;
2188 bool mDoneVideoSeeking = false;
2189 MozPromiseRequestHolder<WaitForDataPromise> mWaitRequest;
2191 // This temporarily stores the first frame we decode after we seek.
2192 // This is so that if we hit end of stream while we're decoding to reach
2193 // the seek target, we will still have a frame that we can display as the
2194 // last frame in the media.
2195 RefPtr<VideoData> mFirstVideoFrameAfterSeek;
2197 private:
2198 virtual media::TimeUnit GetSeekTarget() const {
2199 return mSeekJob.mTarget->GetTime();
2204 * Remove samples from the queue until aCompare() returns false.
2205 * aCompare A function object with the signature bool(int64_t) which returns
2206 * true for samples that should be removed.
2208 template <typename Type, typename Function>
2209 static void DiscardFrames(MediaQueue<Type>& aQueue, const Function& aCompare) {
2210 while (aQueue.GetSize() > 0) {
2211 if (aCompare(aQueue.PeekFront()->mTime.ToMicroseconds())) {
2212 RefPtr<Type> releaseMe = aQueue.PopFront();
2213 continue;
2215 break;
2219 class MediaDecoderStateMachine::NextFrameSeekingState
2220 : public MediaDecoderStateMachine::SeekingState {
2221 public:
2222 explicit NextFrameSeekingState(Master* aPtr) : SeekingState(aPtr) {}
2224 State GetState() const override {
2225 return DECODER_STATE_SEEKING_NEXTFRAMESEEKING;
2228 RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
2229 EventVisibility aVisibility) {
2230 MOZ_ASSERT(aSeekJob.mTarget->IsNextFrame());
2231 mCurrentTime = mMaster->GetMediaTime();
2232 mDuration = mMaster->Duration();
2233 return SeekingState::Enter(std::move(aSeekJob), aVisibility);
2236 void Exit() override {
2237 // Disconnect my async seek operation.
2238 if (mAsyncSeekTask) {
2239 mAsyncSeekTask->Cancel();
2242 // Disconnect MediaDecoder.
2243 mSeekJob.RejectIfExists(__func__);
2246 void HandleAudioDecoded(AudioData* aAudio) override {
2247 mMaster->PushAudio(aAudio);
2250 void HandleVideoDecoded(VideoData* aVideo) override {
2251 MOZ_ASSERT(aVideo);
2252 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2253 MOZ_ASSERT(NeedMoreVideo());
2255 if (aVideo->mTime > mCurrentTime) {
2256 mMaster->PushVideo(aVideo);
2257 FinishSeek();
2258 } else {
2259 RequestVideoData();
2263 void HandleWaitingForAudio() override {
2264 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2265 // We don't care about audio decode errors in this state which will be
2266 // handled by other states after seeking.
2269 void HandleAudioCanceled() override {
2270 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2271 // We don't care about audio decode errors in this state which will be
2272 // handled by other states after seeking.
2275 void HandleEndOfAudio() override {
2276 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2277 // We don't care about audio decode errors in this state which will be
2278 // handled by other states after seeking.
2281 void HandleWaitingForVideo() override {
2282 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2283 MOZ_ASSERT(NeedMoreVideo());
2284 mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
2287 void HandleVideoCanceled() override {
2288 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2289 MOZ_ASSERT(NeedMoreVideo());
2290 RequestVideoData();
2293 void HandleEndOfVideo() override {
2294 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2295 MOZ_ASSERT(NeedMoreVideo());
2296 VideoQueue().Finish();
2297 FinishSeek();
2300 void HandleAudioWaited(MediaData::Type aType) override {
2301 // We don't care about audio in this state.
2304 void HandleVideoWaited(MediaData::Type aType) override {
2305 MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
2306 MOZ_ASSERT(NeedMoreVideo());
2307 RequestVideoData();
2310 TimeUnit CalculateNewCurrentTime() const override {
2311 // The HTMLMediaElement.currentTime should be updated to the seek target
2312 // which has been updated to the next frame's time.
2313 return mSeekJob.mTarget->GetTime();
2316 void DoSeek() override {
2317 mMaster->StopMediaSink();
2319 auto currentTime = mCurrentTime;
2320 DiscardFrames(VideoQueue(), [currentTime](int64_t aSampleTime) {
2321 return aSampleTime <= currentTime.ToMicroseconds();
2324 // If there is a pending video request, finish the seeking if we don't need
2325 // more data, or wait for HandleVideoDecoded() to finish seeking.
2326 if (mMaster->IsRequestingVideoData()) {
2327 if (!NeedMoreVideo()) {
2328 FinishSeek();
2330 return;
2333 // Otherwise, we need to do the seek operation asynchronously for a special
2334 // case (video with no data)which has no data at all, the 1st
2335 // seekToNextFrame() operation reaches the end of the media. If we did the
2336 // seek operation synchronously, we immediately resolve the SeekPromise in
2337 // mSeekJob and then switch to the CompletedState which dispatches an
2338 // "ended" event. However, the ThenValue of the SeekPromise has not yet been
2339 // set, so the promise resolving is postponed and then the JS developer
2340 // receives the "ended" event before the seek promise is resolved. An
2341 // asynchronous seek operation helps to solve this issue since while the
2342 // seek is actually performed, the ThenValue of SeekPromise has already been
2343 // set so that it won't be postponed.
2344 RefPtr<Runnable> r = mAsyncSeekTask = new AysncNextFrameSeekTask(this);
2345 nsresult rv = OwnerThread()->Dispatch(r.forget());
2346 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
2347 Unused << rv;
2350 private:
2351 void DoSeekInternal() {
2352 // We don't need to discard frames to the mCurrentTime here because we have
2353 // done it at DoSeek() and any video data received in between either
2354 // finishes the seek operation or be discarded, see HandleVideoDecoded().
2356 if (!NeedMoreVideo()) {
2357 FinishSeek();
2358 } else if (!mMaster->IsRequestingVideoData() &&
2359 !mMaster->IsWaitingVideoData()) {
2360 RequestVideoData();
2364 class AysncNextFrameSeekTask : public Runnable {
2365 public:
2366 explicit AysncNextFrameSeekTask(NextFrameSeekingState* aStateObject)
2367 : Runnable(
2368 "MediaDecoderStateMachine::NextFrameSeekingState::"
2369 "AysncNextFrameSeekTask"),
2370 mStateObj(aStateObject) {}
2372 void Cancel() { mStateObj = nullptr; }
2374 NS_IMETHOD Run() override {
2375 if (mStateObj) {
2376 AUTO_PROFILER_LABEL("AysncNextFrameSeekTask::Run", MEDIA_PLAYBACK);
2377 mStateObj->DoSeekInternal();
2379 return NS_OK;
2382 private:
2383 NextFrameSeekingState* mStateObj;
2386 void RequestVideoData() { mMaster->RequestVideoData(media::TimeUnit()); }
2388 bool NeedMoreVideo() const {
2389 // Need to request video when we have none and video queue is not finished.
2390 return VideoQueue().GetSize() == 0 && !VideoQueue().IsFinished();
2393 // Update the seek target's time before resolving this seek task, the updated
2394 // time will be used in the MDSM::SeekCompleted() to update the MDSM's
2395 // position.
2396 void UpdateSeekTargetTime() {
2397 RefPtr<VideoData> data = VideoQueue().PeekFront();
2398 if (data) {
2399 mSeekJob.mTarget->SetTime(data->mTime);
2400 } else {
2401 MOZ_ASSERT(VideoQueue().AtEndOfStream());
2402 mSeekJob.mTarget->SetTime(mDuration);
2406 void FinishSeek() {
2407 MOZ_ASSERT(!NeedMoreVideo());
2408 UpdateSeekTargetTime();
2409 auto time = mSeekJob.mTarget->GetTime().ToMicroseconds();
2410 DiscardFrames(AudioQueue(),
2411 [time](int64_t aSampleTime) { return aSampleTime < time; });
2412 SeekCompleted();
2416 * Internal state.
2418 TimeUnit mCurrentTime;
2419 TimeUnit mDuration;
2420 RefPtr<AysncNextFrameSeekTask> mAsyncSeekTask;
2423 class MediaDecoderStateMachine::NextFrameSeekingFromDormantState
2424 : public MediaDecoderStateMachine::AccurateSeekingState {
2425 public:
2426 explicit NextFrameSeekingFromDormantState(Master* aPtr)
2427 : AccurateSeekingState(aPtr) {}
2429 State GetState() const override { return DECODER_STATE_SEEKING_FROMDORMANT; }
2431 RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aCurrentSeekJob,
2432 SeekJob&& aFutureSeekJob) {
2433 mFutureSeekJob = std::move(aFutureSeekJob);
2435 AccurateSeekingState::Enter(std::move(aCurrentSeekJob),
2436 EventVisibility::Suppressed);
2438 // Once seekToNextFrame() is called, we assume the user is likely to keep
2439 // calling seekToNextFrame() repeatedly, and so, we should prevent the MDSM
2440 // from getting into Dormant state.
2441 mMaster->mMinimizePreroll = false;
2443 return mFutureSeekJob.mPromise.Ensure(__func__);
2446 void Exit() override {
2447 mFutureSeekJob.RejectIfExists(__func__);
2448 AccurateSeekingState::Exit();
2451 private:
2452 SeekJob mFutureSeekJob;
2454 // We don't want to transition to DecodingState once this seek completes,
2455 // instead, we transition to NextFrameSeekingState.
2456 void GoToNextState() override {
2457 SetState<NextFrameSeekingState>(std::move(mFutureSeekJob),
2458 EventVisibility::Observable);
2462 class MediaDecoderStateMachine::VideoOnlySeekingState
2463 : public MediaDecoderStateMachine::AccurateSeekingState {
2464 public:
2465 explicit VideoOnlySeekingState(Master* aPtr) : AccurateSeekingState(aPtr) {}
2467 State GetState() const override { return DECODER_STATE_SEEKING_VIDEOONLY; }
2469 RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
2470 EventVisibility aVisibility) {
2471 MOZ_ASSERT(aSeekJob.mTarget->IsVideoOnly());
2472 MOZ_ASSERT(aVisibility == EventVisibility::Suppressed);
2474 RefPtr<MediaDecoder::SeekPromise> p =
2475 AccurateSeekingState::Enter(std::move(aSeekJob), aVisibility);
2477 // Dispatch a mozvideoonlyseekbegin event to indicate UI for corresponding
2478 // changes.
2479 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::VideoOnlySeekBegin);
2481 return p;
2484 void Exit() override {
2485 // We are completing or discarding this video-only seek operation now,
2486 // dispatch an event so that the UI can change in response to the end
2487 // of video-only seek.
2488 mMaster->mOnPlaybackEvent.Notify(
2489 MediaPlaybackEvent::VideoOnlySeekCompleted);
2491 AccurateSeekingState::Exit();
2494 void HandleAudioDecoded(AudioData* aAudio) override {
2495 MOZ_ASSERT(mDoneAudioSeeking && !mDoneVideoSeeking,
2496 "Seek shouldn't be finished");
2497 MOZ_ASSERT(aAudio);
2499 // Video-only seek doesn't reset audio decoder. There might be pending audio
2500 // requests when AccurateSeekTask::Seek() begins. We will just store the
2501 // data without checking |mDiscontinuity| or calling
2502 // DropAudioUpToSeekTarget().
2503 mMaster->PushAudio(aAudio);
2506 void HandleWaitingForAudio() override {}
2508 void HandleAudioCanceled() override {}
2510 void HandleEndOfAudio() override {}
2512 void HandleAudioWaited(MediaData::Type aType) override {
2513 MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
2514 "Seek shouldn't be finished");
2516 // Ignore pending requests from video-only seek.
2519 void DoSeek() override {
2520 // TODO: keep decoding audio.
2521 mDoneAudioSeeking = true;
2522 mDoneVideoSeeking = !Info().HasVideo();
2524 const auto offset = VideoQueue().GetOffset();
2525 mMaster->ResetDecode(TrackInfo::kVideoTrack);
2527 // Entering video-only state and we've looped at least once before, so we
2528 // need to set offset in order to let new video frames catch up with the
2529 // clock time.
2530 if (offset != media::TimeUnit::Zero()) {
2531 VideoQueue().SetOffset(offset);
2534 DemuxerSeek();
2537 protected:
2538 // Allow skip-to-next-key-frame to kick in if we fall behind the current
2539 // playback position so decoding has a better chance to catch up.
2540 void RequestVideoData() override {
2541 MOZ_ASSERT(!mDoneVideoSeeking);
2543 auto clock = mMaster->mMediaSink->IsStarted() ? mMaster->GetClock()
2544 : mMaster->GetMediaTime();
2545 mMaster->AdjustByLooping(clock);
2546 const auto& nextKeyFrameTime = GetNextKeyFrameTime();
2548 auto threshold = clock;
2550 if (nextKeyFrameTime.IsValid() &&
2551 clock >= (nextKeyFrameTime - sSkipToNextKeyFrameThreshold)) {
2552 threshold = nextKeyFrameTime;
2555 mMaster->RequestVideoData(threshold);
2558 private:
2559 // Trigger skip to next key frame if the current playback position is very
2560 // close the next key frame's time.
2561 static constexpr TimeUnit sSkipToNextKeyFrameThreshold =
2562 TimeUnit::FromMicroseconds(5000);
2564 // If the media is playing, drop video until catch up playback position.
2565 media::TimeUnit GetSeekTarget() const override {
2566 auto target = mMaster->mMediaSink->IsStarted()
2567 ? mMaster->GetClock()
2568 : mSeekJob.mTarget->GetTime();
2569 mMaster->AdjustByLooping(target);
2570 return target;
2573 media::TimeUnit GetNextKeyFrameTime() const {
2574 // We only call this method in RequestVideoData() and we only request video
2575 // data if we haven't done video seeking.
2576 MOZ_DIAGNOSTIC_ASSERT(!mDoneVideoSeeking);
2577 MOZ_DIAGNOSTIC_ASSERT(mMaster->VideoQueue().GetSize() == 0);
2579 if (mFirstVideoFrameAfterSeek) {
2580 return mFirstVideoFrameAfterSeek->NextKeyFrameTime();
2583 return TimeUnit::Invalid();
2587 constexpr TimeUnit MediaDecoderStateMachine::VideoOnlySeekingState::
2588 sSkipToNextKeyFrameThreshold;
2590 RefPtr<MediaDecoder::SeekPromise>
2591 MediaDecoderStateMachine::DormantState::HandleSeek(const SeekTarget& aTarget) {
2592 if (aTarget.IsNextFrame()) {
2593 // NextFrameSeekingState doesn't reset the decoder unlike
2594 // AccurateSeekingState. So we first must come out of dormant by seeking to
2595 // mPendingSeek and continue later with the NextFrameSeek
2596 SLOG("Changed state to SEEKING (to %" PRId64 ")",
2597 aTarget.GetTime().ToMicroseconds());
2598 SeekJob seekJob;
2599 seekJob.mTarget = Some(aTarget);
2600 return StateObject::SetState<NextFrameSeekingFromDormantState>(
2601 std::move(mPendingSeek), std::move(seekJob));
2604 return StateObject::HandleSeek(aTarget);
2608 * Purpose: stop playback until enough data is decoded to continue playback.
2610 * Transition to:
2611 * SEEKING if any seek request.
2612 * SHUTDOWN if any decode error.
2613 * COMPLETED when having decoded all audio/video data.
2614 * DECODING/LOOPING_DECODING when having decoded enough data to continue
2615 * playback.
2617 class MediaDecoderStateMachine::BufferingState
2618 : public MediaDecoderStateMachine::StateObject {
2619 public:
2620 explicit BufferingState(Master* aPtr) : StateObject(aPtr) {}
2622 void Enter() {
2623 if (mMaster->IsPlaying()) {
2624 mMaster->StopPlayback();
2627 mBufferingStart = TimeStamp::Now();
2628 mMaster->ScheduleStateMachineIn(TimeUnit::FromMicroseconds(USECS_PER_S));
2629 mMaster->mOnNextFrameStatus.Notify(
2630 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING);
2633 void Step() override;
2635 State GetState() const override { return DECODER_STATE_BUFFERING; }
2637 void HandleAudioDecoded(AudioData* aAudio) override {
2638 mMaster->PushAudio(aAudio);
2639 if (!mMaster->HaveEnoughDecodedAudio()) {
2640 mMaster->RequestAudioData();
2642 // This might be the sample we need to exit buffering.
2643 // Schedule Step() to check it.
2644 mMaster->ScheduleStateMachine();
2647 void HandleVideoDecoded(VideoData* aVideo) override {
2648 mMaster->PushVideo(aVideo);
2649 if (!mMaster->HaveEnoughDecodedVideo()) {
2650 mMaster->RequestVideoData(media::TimeUnit());
2652 // This might be the sample we need to exit buffering.
2653 // Schedule Step() to check it.
2654 mMaster->ScheduleStateMachine();
2657 void HandleAudioCanceled() override { mMaster->RequestAudioData(); }
2659 void HandleVideoCanceled() override {
2660 mMaster->RequestVideoData(media::TimeUnit());
2663 void HandleWaitingForAudio() override {
2664 mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
2667 void HandleWaitingForVideo() override {
2668 mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
2671 void HandleAudioWaited(MediaData::Type aType) override {
2672 mMaster->RequestAudioData();
2675 void HandleVideoWaited(MediaData::Type aType) override {
2676 mMaster->RequestVideoData(media::TimeUnit());
2679 void HandleEndOfAudio() override;
2680 void HandleEndOfVideo() override;
2682 void HandleVideoSuspendTimeout() override {
2683 // No video, so nothing to suspend.
2684 if (!mMaster->HasVideo()) {
2685 return;
2688 mMaster->mVideoDecodeSuspended = true;
2689 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::EnterVideoSuspend);
2690 Reader()->SetVideoBlankDecode(true);
2693 private:
2694 TimeStamp mBufferingStart;
2696 // The maximum number of second we spend buffering when we are short on
2697 // unbuffered data.
2698 const uint32_t mBufferingWait = 15;
2702 * Purpose: play all the decoded data and fire the 'ended' event.
2704 * Transition to:
2705 * SEEKING if any seek request.
2706 * LOOPING_DECODING if MDSM enable looping.
2708 class MediaDecoderStateMachine::CompletedState
2709 : public MediaDecoderStateMachine::StateObject {
2710 public:
2711 explicit CompletedState(Master* aPtr) : StateObject(aPtr) {}
2713 void Enter() {
2714 // On Android, the life cycle of graphic buffer is equal to Android's codec,
2715 // we couldn't release it if we still need to render the frame.
2716 #ifndef MOZ_WIDGET_ANDROID
2717 if (!mMaster->mLooping) {
2718 // We've decoded all samples.
2719 // We don't need decoders anymore if not looping.
2720 Reader()->ReleaseResources();
2722 #endif
2723 bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted) &&
2724 (!mMaster->HasVideo() || !mMaster->mVideoCompleted);
2726 mMaster->mOnNextFrameStatus.Notify(
2727 hasNextFrame ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
2728 : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
2730 Step();
2733 void Exit() override { mSentPlaybackEndedEvent = false; }
2735 void Step() override {
2736 if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
2737 mMaster->IsPlaying()) {
2738 mMaster->StopPlayback();
2741 // Play the remaining media. We want to run AdvanceFrame() at least
2742 // once to ensure the current playback position is advanced to the
2743 // end of the media, and so that we update the readyState.
2744 if ((mMaster->HasVideo() && !mMaster->mVideoCompleted) ||
2745 (mMaster->HasAudio() && !mMaster->mAudioCompleted)) {
2746 // Start playback if necessary to play the remaining media.
2747 mMaster->MaybeStartPlayback();
2748 mMaster->UpdatePlaybackPositionPeriodically();
2749 MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
2750 "Must have timer scheduled");
2751 return;
2754 // StopPlayback in order to reset the IsPlaying() state so audio
2755 // is restarted correctly.
2756 mMaster->StopPlayback();
2758 if (!mSentPlaybackEndedEvent) {
2759 auto clockTime =
2760 std::max(mMaster->AudioEndTime(), mMaster->VideoEndTime());
2761 // Correct the time over the end once looping was turned on.
2762 mMaster->AdjustByLooping(clockTime);
2763 if (mMaster->mDuration.Ref()->IsInfinite()) {
2764 // We have a finite duration when playback reaches the end.
2765 mMaster->mDuration = Some(clockTime);
2766 DDLOGEX(mMaster, DDLogCategory::Property, "duration_us",
2767 mMaster->mDuration.Ref()->ToMicroseconds());
2769 mMaster->UpdatePlaybackPosition(clockTime);
2771 // Ensure readyState is updated before firing the 'ended' event.
2772 mMaster->mOnNextFrameStatus.Notify(
2773 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
2775 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::PlaybackEnded);
2777 mSentPlaybackEndedEvent = true;
2779 // MediaSink::GetEndTime() must be called before stopping playback.
2780 mMaster->StopMediaSink();
2784 State GetState() const override { return DECODER_STATE_COMPLETED; }
2786 void HandleLoopingChanged() override {
2787 if (mMaster->mLooping) {
2788 SetDecodingState();
2792 void HandleAudioCaptured() override {
2793 // MediaSink is changed. Schedule Step() to check if we can start playback.
2794 mMaster->ScheduleStateMachine();
2797 void HandleVideoSuspendTimeout() override {
2798 // Do nothing since no decoding is going on.
2801 void HandleResumeVideoDecoding(const TimeUnit&) override {
2802 // Resume the video decoder and seek to the last video frame.
2803 // This triggers a video-only seek which won't update the playback position.
2804 auto target = mMaster->mDecodedVideoEndTime;
2805 mMaster->AdjustByLooping(target);
2806 StateObject::HandleResumeVideoDecoding(target);
2809 void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override {
2810 if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
2811 // Schedule Step() to check if we can start playback.
2812 mMaster->ScheduleStateMachine();
2816 private:
2817 bool mSentPlaybackEndedEvent = false;
2821 * Purpose: release all resources allocated by MDSM.
2823 * Transition to:
2824 * None since this is the final state.
2826 * Transition from:
2827 * Any states other than SHUTDOWN.
2829 class MediaDecoderStateMachine::ShutdownState
2830 : public MediaDecoderStateMachine::StateObject {
2831 public:
2832 explicit ShutdownState(Master* aPtr) : StateObject(aPtr) {}
2834 RefPtr<ShutdownPromise> Enter();
2836 void Exit() override {
2837 MOZ_DIAGNOSTIC_ASSERT(false, "Shouldn't escape the SHUTDOWN state.");
2840 State GetState() const override { return DECODER_STATE_SHUTDOWN; }
2842 RefPtr<MediaDecoder::SeekPromise> HandleSeek(
2843 const SeekTarget& aTarget) override {
2844 MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek in shutdown state.");
2845 return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
2848 RefPtr<ShutdownPromise> HandleShutdown() override {
2849 MOZ_DIAGNOSTIC_ASSERT(false, "Already shutting down.");
2850 return nullptr;
2853 void HandleVideoSuspendTimeout() override {
2854 MOZ_DIAGNOSTIC_ASSERT(false, "Already shutting down.");
2857 void HandleResumeVideoDecoding(const TimeUnit&) override {
2858 MOZ_DIAGNOSTIC_ASSERT(false, "Already shutting down.");
2862 RefPtr<MediaDecoder::SeekPromise>
2863 MediaDecoderStateMachine::StateObject::HandleSeek(const SeekTarget& aTarget) {
2864 SLOG("Changed state to SEEKING (to %" PRId64 ")",
2865 aTarget.GetTime().ToMicroseconds());
2866 SeekJob seekJob;
2867 seekJob.mTarget = Some(aTarget);
2868 return SetSeekingState(std::move(seekJob), EventVisibility::Observable);
2871 RefPtr<ShutdownPromise>
2872 MediaDecoderStateMachine::StateObject::HandleShutdown() {
2873 return SetState<ShutdownState>();
2876 static void ReportRecoveryTelemetry(const TimeStamp& aRecoveryStart,
2877 const MediaInfo& aMediaInfo,
2878 bool aIsHardwareAccelerated) {
2879 MOZ_ASSERT(NS_IsMainThread());
2880 if (!aMediaInfo.HasVideo()) {
2881 return;
2884 // Keyed by audio+video or video alone, hardware acceleration,
2885 // and by a resolution range.
2886 nsCString key(aMediaInfo.HasAudio() ? "AV" : "V");
2887 key.AppendASCII(aIsHardwareAccelerated ? "(hw)," : ",");
2888 static const struct {
2889 int32_t mH;
2890 const char* mRes;
2891 } sResolutions[] = {{240, "0-240"},
2892 {480, "241-480"},
2893 {720, "481-720"},
2894 {1080, "721-1080"},
2895 {2160, "1081-2160"}};
2896 const char* resolution = "2161+";
2897 int32_t height = aMediaInfo.mVideo.mImage.height;
2898 for (const auto& res : sResolutions) {
2899 if (height <= res.mH) {
2900 resolution = res.mRes;
2901 break;
2904 key.AppendASCII(resolution);
2906 TimeDuration duration = TimeStamp::Now() - aRecoveryStart;
2907 double duration_ms = duration.ToMilliseconds();
2908 Telemetry::Accumulate(Telemetry::VIDEO_SUSPEND_RECOVERY_TIME_MS, key,
2909 static_cast<uint32_t>(lround(duration_ms)));
2910 Telemetry::Accumulate(Telemetry::VIDEO_SUSPEND_RECOVERY_TIME_MS, "All"_ns,
2911 static_cast<uint32_t>(lround(duration_ms)));
2914 void MediaDecoderStateMachine::StateObject::HandleResumeVideoDecoding(
2915 const TimeUnit& aTarget) {
2916 MOZ_ASSERT(mMaster->mVideoDecodeSuspended);
2918 mMaster->mVideoDecodeSuspended = false;
2919 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::ExitVideoSuspend);
2920 Reader()->SetVideoBlankDecode(false);
2922 // Start counting recovery time from right now.
2923 TimeStamp start = TimeStamp::Now();
2925 // Local reference to mInfo, so that it will be copied in the lambda below.
2926 const auto& info = Info();
2927 bool hw = Reader()->VideoIsHardwareAccelerated();
2929 // Start video-only seek to the current time.
2930 SeekJob seekJob;
2932 // We use fastseek to optimize the resuming time.
2933 // FastSeek is only used for video-only media since we don't need to worry
2934 // about A/V sync.
2935 // Don't use fastSeek if we want to seek to the end because it might seek to a
2936 // keyframe before the last frame (if the last frame itself is not a keyframe)
2937 // and we always want to present the final frame to the user when seeking to
2938 // the end.
2939 const auto type = mMaster->HasAudio() || aTarget == mMaster->Duration()
2940 ? SeekTarget::Type::Accurate
2941 : SeekTarget::Type::PrevSyncPoint;
2943 seekJob.mTarget.emplace(aTarget, type, SeekTarget::Track::VideoOnly);
2944 SLOG("video-only seek target=%" PRId64 ", current time=%" PRId64,
2945 aTarget.ToMicroseconds(), mMaster->GetMediaTime().ToMicroseconds());
2947 // Hold mMaster->mAbstractMainThread here because this->mMaster will be
2948 // invalid after the current state object is deleted in SetState();
2949 RefPtr<AbstractThread> mainThread = mMaster->mAbstractMainThread;
2951 SetSeekingState(std::move(seekJob), EventVisibility::Suppressed)
2952 ->Then(
2953 mainThread, __func__,
2954 [start, info, hw]() { ReportRecoveryTelemetry(start, info, hw); },
2955 []() {});
2958 RefPtr<MediaDecoder::SeekPromise>
2959 MediaDecoderStateMachine::StateObject::SetSeekingState(
2960 SeekJob&& aSeekJob, EventVisibility aVisibility) {
2961 if (aSeekJob.mTarget->IsAccurate() || aSeekJob.mTarget->IsFast()) {
2962 if (aSeekJob.mTarget->IsVideoOnly()) {
2963 return SetState<VideoOnlySeekingState>(std::move(aSeekJob), aVisibility);
2965 return SetState<AccurateSeekingState>(std::move(aSeekJob), aVisibility);
2968 if (aSeekJob.mTarget->IsNextFrame()) {
2969 return SetState<NextFrameSeekingState>(std::move(aSeekJob), aVisibility);
2972 MOZ_ASSERT_UNREACHABLE("Unknown SeekTarget::Type.");
2973 return nullptr;
2976 void MediaDecoderStateMachine::StateObject::SetDecodingState() {
2977 if (mMaster->IsInSeamlessLooping()) {
2978 SetState<LoopingDecodingState>();
2979 return;
2981 SetState<DecodingState>();
2984 void MediaDecoderStateMachine::DecodeMetadataState::OnMetadataRead(
2985 MetadataHolder&& aMetadata) {
2986 mMetadataRequest.Complete();
2988 AUTO_PROFILER_LABEL("DecodeMetadataState::OnMetadataRead", MEDIA_PLAYBACK);
2989 mMaster->mInfo.emplace(*aMetadata.mInfo);
2990 mMaster->mMediaSeekable = Info().mMediaSeekable;
2991 mMaster->mMediaSeekableOnlyInBufferedRanges =
2992 Info().mMediaSeekableOnlyInBufferedRanges;
2994 if (Info().mMetadataDuration.isSome()) {
2995 mMaster->mDuration = Info().mMetadataDuration;
2996 } else if (Info().mUnadjustedMetadataEndTime.isSome()) {
2997 const TimeUnit unadjusted = Info().mUnadjustedMetadataEndTime.ref();
2998 const TimeUnit adjustment = Info().mStartTime;
2999 mMaster->mInfo->mMetadataDuration.emplace(unadjusted - adjustment);
3000 mMaster->mDuration = Info().mMetadataDuration;
3003 // If we don't know the duration by this point, we assume infinity, per spec.
3004 if (mMaster->mDuration.Ref().isNothing()) {
3005 mMaster->mDuration = Some(TimeUnit::FromInfinity());
3008 DDLOGEX(mMaster, DDLogCategory::Property, "duration_us",
3009 mMaster->mDuration.Ref()->ToMicroseconds());
3011 if (mMaster->HasVideo()) {
3012 SLOG("Video decode HWAccel=%d videoQueueSize=%d",
3013 Reader()->VideoIsHardwareAccelerated(),
3014 mMaster->GetAmpleVideoFrames());
3017 MOZ_ASSERT(mMaster->mDuration.Ref().isSome());
3019 mMaster->mMetadataLoadedEvent.Notify(std::move(aMetadata.mInfo),
3020 std::move(aMetadata.mTags),
3021 MediaDecoderEventVisibility::Observable);
3023 // Check whether the media satisfies the requirement of seamless looping.
3024 // TODO : after we ensure video seamless looping is stable enough, then we can
3025 // remove this to make the condition always true.
3026 mMaster->mSeamlessLoopingAllowed = StaticPrefs::media_seamless_looping();
3027 if (mMaster->HasVideo()) {
3028 mMaster->mSeamlessLoopingAllowed =
3029 StaticPrefs::media_seamless_looping_video();
3032 SetState<DecodingFirstFrameState>();
3035 void MediaDecoderStateMachine::DormantState::HandlePlayStateChanged(
3036 MediaDecoder::PlayState aPlayState) {
3037 if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
3038 // Exit dormant when the user wants to play.
3039 MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
3040 SetSeekingState(std::move(mPendingSeek), EventVisibility::Suppressed);
3044 void MediaDecoderStateMachine::DecodingFirstFrameState::Enter() {
3045 // Transition to DECODING if we've decoded first frames.
3046 if (mMaster->mSentFirstFrameLoadedEvent) {
3047 SetDecodingState();
3048 return;
3051 MOZ_ASSERT(!mMaster->mVideoDecodeSuspended);
3053 // Dispatch tasks to decode first frames.
3054 if (mMaster->HasAudio()) {
3055 mMaster->RequestAudioData();
3057 if (mMaster->HasVideo()) {
3058 mMaster->RequestVideoData(media::TimeUnit());
3062 void MediaDecoderStateMachine::DecodingFirstFrameState::
3063 MaybeFinishDecodeFirstFrame() {
3064 MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
3066 if ((mMaster->IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
3067 (mMaster->IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
3068 return;
3071 mMaster->FinishDecodeFirstFrame();
3072 if (mPendingSeek.Exists()) {
3073 SetSeekingState(std::move(mPendingSeek), EventVisibility::Observable);
3074 } else {
3075 SetDecodingState();
3079 void MediaDecoderStateMachine::DecodingState::Enter() {
3080 MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
3082 if (mMaster->mVideoDecodeSuspended &&
3083 mMaster->mVideoDecodeMode == VideoDecodeMode::Normal) {
3084 StateObject::HandleResumeVideoDecoding(mMaster->GetMediaTime());
3085 return;
3088 if (mMaster->mVideoDecodeMode == VideoDecodeMode::Suspend &&
3089 !mMaster->mVideoDecodeSuspendTimer.IsScheduled() &&
3090 !mMaster->mVideoDecodeSuspended) {
3091 // If the VideoDecodeMode is Suspend and the timer is not schedule, it means
3092 // the timer has timed out and we should suspend video decoding now if
3093 // necessary.
3094 HandleVideoSuspendTimeout();
3097 // If we're in the normal decoding mode and the decoding has finished, then we
3098 // should go to `completed` state because we don't need to decode anything
3099 // later. However, if we're in the saemless decoding mode, we will restart
3100 // decoding ASAP so we can still stay in `decoding` state.
3101 if (!mMaster->IsVideoDecoding() && !mMaster->IsAudioDecoding() &&
3102 !mMaster->IsInSeamlessLooping()) {
3103 SetState<CompletedState>();
3104 return;
3107 mOnAudioPopped =
3108 AudioQueue().PopFrontEvent().Connect(OwnerThread(), [this]() {
3109 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnAudioPopped",
3110 MEDIA_PLAYBACK);
3111 if (mMaster->IsAudioDecoding() && !mMaster->HaveEnoughDecodedAudio()) {
3112 EnsureAudioDecodeTaskQueued();
3115 mOnVideoPopped =
3116 VideoQueue().PopFrontEvent().Connect(OwnerThread(), [this]() {
3117 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnVideoPopped",
3118 MEDIA_PLAYBACK);
3119 if (mMaster->IsVideoDecoding() && !mMaster->HaveEnoughDecodedVideo()) {
3120 EnsureVideoDecodeTaskQueued();
3124 mMaster->mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
3126 mDecodeStartTime = TimeStamp::Now();
3128 MaybeStopPrerolling();
3130 // Ensure that we've got tasks enqueued to decode data if we need to.
3131 DispatchDecodeTasksIfNeeded();
3133 mMaster->ScheduleStateMachine();
3135 // Will enter dormant when playback is paused for a while.
3136 if (mMaster->mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
3137 StartDormantTimer();
3141 void MediaDecoderStateMachine::DecodingState::Step() {
3142 if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
3143 mMaster->IsPlaying()) {
3144 // We're playing, but the element/decoder is in paused state. Stop
3145 // playing!
3146 mMaster->StopPlayback();
3149 // Start playback if necessary so that the clock can be properly queried.
3150 if (!mIsPrerolling) {
3151 mMaster->MaybeStartPlayback();
3154 mMaster->UpdatePlaybackPositionPeriodically();
3155 MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
3156 "Must have timer scheduled");
3157 if (IsBufferingAllowed()) {
3158 MaybeStartBuffering();
3162 void MediaDecoderStateMachine::DecodingState::HandleEndOfAudio() {
3163 AudioQueue().Finish();
3164 if (!mMaster->IsVideoDecoding()) {
3165 SetState<CompletedState>();
3166 } else {
3167 MaybeStopPrerolling();
3171 void MediaDecoderStateMachine::DecodingState::HandleEndOfVideo() {
3172 VideoQueue().Finish();
3173 if (!mMaster->IsAudioDecoding()) {
3174 SetState<CompletedState>();
3175 } else {
3176 MaybeStopPrerolling();
3180 void MediaDecoderStateMachine::DecodingState::DispatchDecodeTasksIfNeeded() {
3181 if (mMaster->IsAudioDecoding() && !mMaster->mMinimizePreroll &&
3182 !mMaster->HaveEnoughDecodedAudio()) {
3183 EnsureAudioDecodeTaskQueued();
3186 if (mMaster->IsVideoDecoding() && !mMaster->mMinimizePreroll &&
3187 !mMaster->HaveEnoughDecodedVideo()) {
3188 EnsureVideoDecodeTaskQueued();
3192 void MediaDecoderStateMachine::DecodingState::EnsureAudioDecodeTaskQueued() {
3193 if (!mMaster->IsAudioDecoding() || mMaster->IsRequestingAudioData() ||
3194 mMaster->IsWaitingAudioData()) {
3195 return;
3197 mMaster->RequestAudioData();
3200 void MediaDecoderStateMachine::DecodingState::EnsureVideoDecodeTaskQueued() {
3201 if (!mMaster->IsVideoDecoding() || mMaster->IsRequestingVideoData() ||
3202 mMaster->IsWaitingVideoData()) {
3203 return;
3205 mMaster->RequestVideoData(mMaster->GetMediaTime(),
3206 ShouldRequestNextKeyFrame());
3209 void MediaDecoderStateMachine::DecodingState::MaybeStartBuffering() {
3210 // Buffering makes senses only after decoding first frames.
3211 MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
3213 // Don't enter buffering when MediaDecoder is not playing.
3214 if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
3215 return;
3218 // Don't enter buffering while prerolling so that the decoder has a chance to
3219 // enqueue some decoded data before we give up and start buffering.
3220 if (!mMaster->IsPlaying()) {
3221 return;
3224 // Note we could have a wait promise pending when playing non-MSE EME.
3225 if (mMaster->OutOfDecodedAudio() && mMaster->IsWaitingAudioData()) {
3226 PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
3227 "OutOfDecodedAudio");
3228 SLOG("Enter buffering due to out of decoded audio");
3229 SetState<BufferingState>();
3230 return;
3232 if (mMaster->OutOfDecodedVideo() && mMaster->IsWaitingVideoData()) {
3233 PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
3234 "OutOfDecodedVideo");
3235 SLOG("Enter buffering due to out of decoded video");
3236 SetState<BufferingState>();
3237 return;
3240 if (Reader()->UseBufferingHeuristics() && mMaster->HasLowDecodedData() &&
3241 mMaster->HasLowBufferedData() && !mMaster->mCanPlayThrough) {
3242 PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
3243 "BufferingHeuristics");
3244 SLOG("Enter buffering due to buffering heruistics");
3245 SetState<BufferingState>();
3249 void MediaDecoderStateMachine::LoopingDecodingState::HandleError(
3250 const MediaResult& aError, bool aIsAudio) {
3251 SLOG("%s looping failed, aError=%s", aIsAudio ? "audio" : "video",
3252 aError.ErrorName().get());
3253 switch (aError.Code()) {
3254 case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
3255 if (aIsAudio) {
3256 HandleWaitingForAudio();
3257 } else {
3258 HandleWaitingForVideo();
3260 [[fallthrough]];
3261 case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
3262 // This could happen after either the resource has been close, or the data
3263 // hasn't been appended in MSE, so that we won't be able to get any
3264 // sample and need to fallback to normal looping.
3265 if (mIsReachingAudioEOS && mIsReachingVideoEOS) {
3266 SetState<CompletedState>();
3268 break;
3269 default:
3270 mMaster->DecodeError(aError);
3271 break;
3275 void MediaDecoderStateMachine::SeekingState::SeekCompleted() {
3276 const auto newCurrentTime = CalculateNewCurrentTime();
3278 if ((newCurrentTime == mMaster->Duration() ||
3279 newCurrentTime.EqualsAtLowestResolution(
3280 mMaster->Duration().ToBase(USECS_PER_S))) &&
3281 !mMaster->mIsLiveStream) {
3282 SLOG("Seek completed, seeked to end: %s", newCurrentTime.ToString().get());
3283 // will transition to COMPLETED immediately. Note we don't do
3284 // this when playing a live stream, since the end of media will advance
3285 // once we download more data!
3286 AudioQueue().Finish();
3287 VideoQueue().Finish();
3289 // We won't start MediaSink when paused. m{Audio,Video}Completed will
3290 // remain false and 'playbackEnded' won't be notified. Therefore we
3291 // need to set these flags explicitly when seeking to the end.
3292 mMaster->mAudioCompleted = true;
3293 mMaster->mVideoCompleted = true;
3295 // There might still be a pending audio request when doing video-only or
3296 // next-frame seek. Discard it so we won't break the invariants of the
3297 // COMPLETED state by adding audio samples to a finished queue.
3298 mMaster->mAudioDataRequest.DisconnectIfExists();
3301 // We want to resolve the seek request prior finishing the first frame
3302 // to ensure that the seeked event is fired prior loadeded.
3303 // Note: SeekJob.Resolve() resets SeekJob.mTarget. Don't use mSeekJob anymore
3304 // hereafter.
3305 mSeekJob.Resolve(__func__);
3307 // Notify FirstFrameLoaded now if we haven't since we've decoded some data
3308 // for readyState to transition to HAVE_CURRENT_DATA and fire 'loadeddata'.
3309 if (!mMaster->mSentFirstFrameLoadedEvent) {
3310 mMaster->FinishDecodeFirstFrame();
3313 // Ensure timestamps are up to date.
3314 // Suppressed visibility comes from two cases: (1) leaving dormant state,
3315 // and (2) resuming suspended video decoder. We want both cases to be
3316 // transparent to the user. So we only notify the change when the seek
3317 // request is from the user.
3318 if (mVisibility == EventVisibility::Observable) {
3319 // Don't update playback position for video-only seek.
3320 // Otherwise we might have |newCurrentTime > mMediaSink->GetPosition()|
3321 // and fail the assertion in GetClock() since we didn't stop MediaSink.
3322 mMaster->UpdatePlaybackPositionInternal(newCurrentTime);
3325 // Try to decode another frame to detect if we're at the end...
3326 SLOG("Seek completed, mCurrentPosition=%" PRId64,
3327 mMaster->mCurrentPosition.Ref().ToMicroseconds());
3329 if (mMaster->VideoQueue().PeekFront()) {
3330 mMaster->mMediaSink->Redraw(Info().mVideo);
3331 mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::Invalidate);
3334 GoToNextState();
3337 void MediaDecoderStateMachine::BufferingState::Step() {
3338 TimeStamp now = TimeStamp::Now();
3339 MOZ_ASSERT(!mBufferingStart.IsNull(), "Must know buffering start time.");
3341 if (Reader()->UseBufferingHeuristics()) {
3342 if (mMaster->IsWaitingAudioData() || mMaster->IsWaitingVideoData()) {
3343 // Can't exit buffering when we are still waiting for data.
3344 // Note we don't schedule next loop for we will do that when the wait
3345 // promise is resolved.
3346 return;
3348 // With buffering heuristics, we exit buffering state when we:
3349 // 1. can play through or
3350 // 2. time out (specified by mBufferingWait) or
3351 // 3. have enough buffered data.
3352 TimeDuration elapsed = now - mBufferingStart;
3353 TimeDuration timeout =
3354 TimeDuration::FromSeconds(mBufferingWait * mMaster->mPlaybackRate);
3355 bool stopBuffering =
3356 mMaster->mCanPlayThrough || elapsed >= timeout ||
3357 !mMaster->HasLowBufferedData(TimeUnit::FromSeconds(mBufferingWait));
3358 if (!stopBuffering) {
3359 SLOG("Buffering: wait %ds, timeout in %.3lfs", mBufferingWait,
3360 mBufferingWait - elapsed.ToSeconds());
3361 mMaster->ScheduleStateMachineIn(TimeUnit::FromMicroseconds(USECS_PER_S));
3362 return;
3364 } else if (mMaster->OutOfDecodedAudio() || mMaster->OutOfDecodedVideo()) {
3365 MOZ_ASSERT(!mMaster->OutOfDecodedAudio() ||
3366 mMaster->IsRequestingAudioData() ||
3367 mMaster->IsWaitingAudioData());
3368 MOZ_ASSERT(!mMaster->OutOfDecodedVideo() ||
3369 mMaster->IsRequestingVideoData() ||
3370 mMaster->IsWaitingVideoData());
3371 SLOG(
3372 "In buffering mode, waiting to be notified: outOfAudio: %d, "
3373 "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
3374 mMaster->OutOfDecodedAudio(), mMaster->AudioRequestStatus(),
3375 mMaster->OutOfDecodedVideo(), mMaster->VideoRequestStatus());
3376 return;
3379 SLOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
3380 mMaster->mTotalBufferingDuration += (now - mBufferingStart);
3381 SetDecodingState();
3384 void MediaDecoderStateMachine::BufferingState::HandleEndOfAudio() {
3385 AudioQueue().Finish();
3386 if (!mMaster->IsVideoDecoding()) {
3387 SetState<CompletedState>();
3388 } else {
3389 // Check if we can exit buffering.
3390 mMaster->ScheduleStateMachine();
3394 void MediaDecoderStateMachine::BufferingState::HandleEndOfVideo() {
3395 VideoQueue().Finish();
3396 if (!mMaster->IsAudioDecoding()) {
3397 SetState<CompletedState>();
3398 } else {
3399 // Check if we can exit buffering.
3400 mMaster->ScheduleStateMachine();
3404 RefPtr<ShutdownPromise> MediaDecoderStateMachine::ShutdownState::Enter() {
3405 auto* master = mMaster;
3407 master->mDelayedScheduler.Reset();
3409 // Shutdown happens while decode timer is active, we need to disconnect and
3410 // dispose of the timer.
3411 master->CancelSuspendTimer();
3413 if (master->IsPlaying()) {
3414 master->StopPlayback();
3417 master->mAudioDataRequest.DisconnectIfExists();
3418 master->mVideoDataRequest.DisconnectIfExists();
3419 master->mAudioWaitRequest.DisconnectIfExists();
3420 master->mVideoWaitRequest.DisconnectIfExists();
3422 // Resetting decode should be called after stopping media sink, which can
3423 // ensure that we have an empty media queue before seeking the demuxer.
3424 master->StopMediaSink();
3425 master->ResetDecode();
3426 master->mMediaSink->Shutdown();
3428 // Prevent dangling pointers by disconnecting the listeners.
3429 master->mAudioQueueListener.Disconnect();
3430 master->mVideoQueueListener.Disconnect();
3431 master->mMetadataManager.Disconnect();
3432 master->mOnMediaNotSeekable.Disconnect();
3433 master->mAudibleListener.DisconnectIfExists();
3435 // Disconnect canonicals and mirrors before shutting down our task queue.
3436 master->mStreamName.DisconnectIfConnected();
3437 master->mSinkDevice.DisconnectIfConnected();
3438 master->mOutputCaptureState.DisconnectIfConnected();
3439 master->mOutputDummyTrack.DisconnectIfConnected();
3440 master->mOutputTracks.DisconnectIfConnected();
3441 master->mOutputPrincipal.DisconnectIfConnected();
3443 master->mDuration.DisconnectAll();
3444 master->mCurrentPosition.DisconnectAll();
3445 master->mIsAudioDataAudible.DisconnectAll();
3447 // Shut down the watch manager to stop further notifications.
3448 master->mWatchManager.Shutdown();
3450 return Reader()->Shutdown()->Then(OwnerThread(), __func__, master,
3451 &MediaDecoderStateMachine::FinishShutdown,
3452 &MediaDecoderStateMachine::FinishShutdown);
3455 #define INIT_WATCHABLE(name, val) name(val, "MediaDecoderStateMachine::" #name)
3456 #define INIT_MIRROR(name, val) \
3457 name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Mirror)")
3458 #define INIT_CANONICAL(name, val) \
3459 name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Canonical)")
3461 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
3462 MediaFormatReader* aReader)
3463 : MediaDecoderStateMachineBase(aDecoder, aReader),
3464 mWatchManager(this, mTaskQueue),
3465 mDispatchedStateMachine(false),
3466 mDelayedScheduler(mTaskQueue, true /*aFuzzy*/),
3467 mCurrentFrameID(0),
3468 mAmpleAudioThreshold(detail::AMPLE_AUDIO_THRESHOLD),
3469 mVideoDecodeSuspended(false),
3470 mVideoDecodeSuspendTimer(mTaskQueue),
3471 mVideoDecodeMode(VideoDecodeMode::Normal),
3472 mIsMSE(aDecoder->IsMSE()),
3473 mShouldResistFingerprinting(aDecoder->ShouldResistFingerprinting()),
3474 mSeamlessLoopingAllowed(false),
3475 mTotalBufferingDuration(TimeDuration::Zero()),
3476 INIT_MIRROR(mStreamName, nsAutoString()),
3477 INIT_MIRROR(mSinkDevice, nullptr),
3478 INIT_MIRROR(mOutputCaptureState, MediaDecoder::OutputCaptureState::None),
3479 INIT_MIRROR(mOutputDummyTrack, nullptr),
3480 INIT_MIRROR(mOutputTracks, nsTArray<RefPtr<ProcessedMediaTrack>>()),
3481 INIT_MIRROR(mOutputPrincipal, PRINCIPAL_HANDLE_NONE),
3482 INIT_CANONICAL(mCanonicalOutputPrincipal, PRINCIPAL_HANDLE_NONE),
3483 mShuttingDown(false) {
3484 MOZ_COUNT_CTOR(MediaDecoderStateMachine);
3485 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
3487 InitVideoQueuePrefs();
3489 DDLINKCHILD("reader", aReader);
3492 #undef INIT_WATCHABLE
3493 #undef INIT_MIRROR
3494 #undef INIT_CANONICAL
3496 MediaDecoderStateMachine::~MediaDecoderStateMachine() {
3497 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
3498 MOZ_COUNT_DTOR(MediaDecoderStateMachine);
3501 void MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder) {
3502 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::InitializationTask",
3503 MEDIA_PLAYBACK);
3504 MOZ_ASSERT(OnTaskQueue());
3506 MediaDecoderStateMachineBase::InitializationTask(aDecoder);
3508 // Initialize watchers.
3509 mWatchManager.Watch(mStreamName,
3510 &MediaDecoderStateMachine::StreamNameChanged);
3511 mWatchManager.Watch(mOutputCaptureState,
3512 &MediaDecoderStateMachine::UpdateOutputCaptured);
3513 mWatchManager.Watch(mOutputDummyTrack,
3514 &MediaDecoderStateMachine::UpdateOutputCaptured);
3515 mWatchManager.Watch(mOutputTracks,
3516 &MediaDecoderStateMachine::UpdateOutputCaptured);
3517 mWatchManager.Watch(mOutputPrincipal,
3518 &MediaDecoderStateMachine::OutputPrincipalChanged);
3520 mMediaSink = CreateMediaSink();
3522 MOZ_ASSERT(!mStateObj);
3523 auto* s = new DecodeMetadataState(this);
3524 mStateObj.reset(s);
3525 s->Enter();
3528 void MediaDecoderStateMachine::AudioAudibleChanged(bool aAudible) {
3529 mIsAudioDataAudible = aAudible;
3532 MediaSink* MediaDecoderStateMachine::CreateAudioSink() {
3533 if (mOutputCaptureState != MediaDecoder::OutputCaptureState::None) {
3534 DecodedStream* stream = new DecodedStream(
3535 this,
3536 mOutputCaptureState == MediaDecoder::OutputCaptureState::Capture
3537 ? mOutputDummyTrack.Ref()
3538 : nullptr,
3539 mOutputTracks, mVolume, mPlaybackRate, mPreservesPitch, mAudioQueue,
3540 mVideoQueue, mSinkDevice.Ref());
3541 mAudibleListener.DisconnectIfExists();
3542 mAudibleListener = stream->AudibleEvent().Connect(
3543 OwnerThread(), this, &MediaDecoderStateMachine::AudioAudibleChanged);
3544 return stream;
3547 auto audioSinkCreator = [s = RefPtr<MediaDecoderStateMachine>(this), this]() {
3548 MOZ_ASSERT(OnTaskQueue());
3549 UniquePtr<AudioSink> audioSink{new AudioSink(
3550 mTaskQueue, mAudioQueue, Info().mAudio, mShouldResistFingerprinting)};
3551 mAudibleListener.DisconnectIfExists();
3552 mAudibleListener = audioSink->AudibleEvent().Connect(
3553 mTaskQueue, this, &MediaDecoderStateMachine::AudioAudibleChanged);
3554 return audioSink;
3556 return new AudioSinkWrapper(
3557 mTaskQueue, mAudioQueue, std::move(audioSinkCreator), mVolume,
3558 mPlaybackRate, mPreservesPitch, mSinkDevice.Ref());
3561 already_AddRefed<MediaSink> MediaDecoderStateMachine::CreateMediaSink() {
3562 MOZ_ASSERT(OnTaskQueue());
3563 RefPtr<MediaSink> audioSink = CreateAudioSink();
3564 RefPtr<MediaSink> mediaSink =
3565 new VideoSink(mTaskQueue, audioSink, mVideoQueue, mVideoFrameContainer,
3566 *mFrameStats, sVideoQueueSendToCompositorSize);
3567 if (mSecondaryVideoContainer.Ref()) {
3568 mediaSink->SetSecondaryVideoContainer(mSecondaryVideoContainer.Ref());
3570 return mediaSink.forget();
3573 TimeUnit MediaDecoderStateMachine::GetDecodedAudioDuration() const {
3574 MOZ_ASSERT(OnTaskQueue());
3575 if (mMediaSink->IsStarted()) {
3576 return mMediaSink->UnplayedDuration(TrackInfo::kAudioTrack) +
3577 TimeUnit::FromMicroseconds(AudioQueue().Duration());
3579 // MediaSink not started. All audio samples are in the queue.
3580 return TimeUnit::FromMicroseconds(AudioQueue().Duration());
3583 bool MediaDecoderStateMachine::HaveEnoughDecodedAudio() const {
3584 MOZ_ASSERT(OnTaskQueue());
3585 auto ampleAudio = mAmpleAudioThreshold.MultDouble(mPlaybackRate);
3586 return AudioQueue().GetSize() > 0 && GetDecodedAudioDuration() >= ampleAudio;
3589 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo() const {
3590 MOZ_ASSERT(OnTaskQueue());
3591 return static_cast<double>(VideoQueue().GetSize()) >=
3592 GetAmpleVideoFrames() * mPlaybackRate + 1 &&
3593 IsVideoDataEnoughComparedWithAudio();
3596 bool MediaDecoderStateMachine::IsVideoDataEnoughComparedWithAudio() const {
3597 // HW decoding is usually fast enough and we don't need to worry about its
3598 // speed.
3599 // TODO : we can consider whether we need to enable this on other HW decoding
3600 // except VAAPI. When enabling VAAPI on Linux, ffmpeg is not able to store too
3601 // many frames because it has a limitation of amount of stored video frames.
3602 // See bug1716638 and 1718309.
3603 if (mReader->VideoIsHardwareAccelerated()) {
3604 return true;
3606 // In extreme situations (e.g. 4k+ video without hardware acceleration), the
3607 // video decoding will be much slower than audio. So for 4K+ video, we want to
3608 // consider audio decoding speed as well in order to reduce frame drops. This
3609 // check tries to keep the decoded video buffered as much as audio.
3610 if (HasAudio() && Info().mVideo.mImage.width >= 3840 &&
3611 Info().mVideo.mImage.height >= 2160) {
3612 return VideoQueue().Duration() >= AudioQueue().Duration();
3614 // For non-4k video, the video decoding is usually really fast so we won't
3615 // need to consider audio decoding speed to store extra frames.
3616 return true;
3619 void MediaDecoderStateMachine::PushAudio(AudioData* aSample) {
3620 MOZ_ASSERT(OnTaskQueue());
3621 MOZ_ASSERT(aSample);
3622 AudioQueue().Push(aSample);
3623 PROFILER_MARKER("MDSM::PushAudio", MEDIA_PLAYBACK, {}, MediaSampleMarker,
3624 aSample->mTime.ToMicroseconds(),
3625 aSample->GetEndTime().ToMicroseconds(),
3626 AudioQueue().GetSize());
3629 void MediaDecoderStateMachine::PushVideo(VideoData* aSample) {
3630 MOZ_ASSERT(OnTaskQueue());
3631 MOZ_ASSERT(aSample);
3632 aSample->mFrameID = ++mCurrentFrameID;
3633 VideoQueue().Push(aSample);
3634 PROFILER_MARKER("MDSM::PushVideo", MEDIA_PLAYBACK, {}, MediaSampleMarker,
3635 aSample->mTime.ToMicroseconds(),
3636 aSample->GetEndTime().ToMicroseconds(),
3637 VideoQueue().GetSize());
3640 void MediaDecoderStateMachine::OnAudioPopped(const RefPtr<AudioData>& aSample) {
3641 MOZ_ASSERT(OnTaskQueue());
3642 mPlaybackOffset = std::max(mPlaybackOffset, aSample->mOffset);
3645 void MediaDecoderStateMachine::OnVideoPopped(const RefPtr<VideoData>& aSample) {
3646 MOZ_ASSERT(OnTaskQueue());
3647 mPlaybackOffset = std::max(mPlaybackOffset, aSample->mOffset);
3650 bool MediaDecoderStateMachine::IsAudioDecoding() {
3651 MOZ_ASSERT(OnTaskQueue());
3652 return HasAudio() && !AudioQueue().IsFinished();
3655 bool MediaDecoderStateMachine::IsVideoDecoding() {
3656 MOZ_ASSERT(OnTaskQueue());
3657 return HasVideo() && !VideoQueue().IsFinished();
3660 bool MediaDecoderStateMachine::IsPlaying() const {
3661 MOZ_ASSERT(OnTaskQueue());
3662 return mMediaSink->IsPlaying();
3665 void MediaDecoderStateMachine::SetMediaNotSeekable() { mMediaSeekable = false; }
3667 nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder) {
3668 MOZ_ASSERT(NS_IsMainThread());
3670 nsresult rv = MediaDecoderStateMachineBase::Init(aDecoder);
3671 if (NS_WARN_IF(NS_FAILED(rv))) {
3672 return rv;
3675 // Connect mirrors.
3676 aDecoder->CanonicalStreamName().ConnectMirror(&mStreamName);
3677 aDecoder->CanonicalSinkDevice().ConnectMirror(&mSinkDevice);
3678 aDecoder->CanonicalOutputCaptureState().ConnectMirror(&mOutputCaptureState);
3679 aDecoder->CanonicalOutputDummyTrack().ConnectMirror(&mOutputDummyTrack);
3680 aDecoder->CanonicalOutputTracks().ConnectMirror(&mOutputTracks);
3681 aDecoder->CanonicalOutputPrincipal().ConnectMirror(&mOutputPrincipal);
3683 mAudioQueueListener = AudioQueue().PopFrontEvent().Connect(
3684 mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
3685 mVideoQueueListener = VideoQueue().PopFrontEvent().Connect(
3686 mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
3687 mOnMediaNotSeekable = mReader->OnMediaNotSeekable().Connect(
3688 OwnerThread(), this, &MediaDecoderStateMachine::SetMediaNotSeekable);
3690 return NS_OK;
3693 void MediaDecoderStateMachine::StopPlayback() {
3694 MOZ_ASSERT(OnTaskQueue());
3695 LOG("StopPlayback()");
3697 if (IsPlaying()) {
3698 mOnPlaybackEvent.Notify(MediaPlaybackEvent{
3699 MediaPlaybackEvent::PlaybackStopped, mPlaybackOffset});
3700 mMediaSink->SetPlaying(false);
3701 MOZ_ASSERT(!IsPlaying());
3705 void MediaDecoderStateMachine::MaybeStartPlayback() {
3706 MOZ_ASSERT(OnTaskQueue());
3707 // Should try to start playback only after decoding first frames.
3708 if (!mSentFirstFrameLoadedEvent) {
3709 LOG("MaybeStartPlayback: Not starting playback before loading first frame");
3710 return;
3713 if (IsPlaying()) {
3714 // Logging this case is really spammy - don't do it.
3715 return;
3718 if (mIsMediaSinkSuspended) {
3719 LOG("MaybeStartPlayback: Not starting playback when sink is suspended");
3720 return;
3723 if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
3724 LOG("MaybeStartPlayback: Not starting playback [mPlayState=%d]",
3725 mPlayState.Ref());
3726 return;
3729 LOG("MaybeStartPlayback() starting playback");
3730 StartMediaSink();
3732 if (!IsPlaying()) {
3733 mMediaSink->SetPlaying(true);
3734 MOZ_ASSERT(IsPlaying());
3737 mOnPlaybackEvent.Notify(
3738 MediaPlaybackEvent{MediaPlaybackEvent::PlaybackStarted, mPlaybackOffset});
3741 void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(
3742 const TimeUnit& aTime) {
3743 MOZ_ASSERT(OnTaskQueue());
3744 LOGV("UpdatePlaybackPositionInternal(%" PRId64 ")", aTime.ToMicroseconds());
3746 // Ensure the position has a precision that matches other TimeUnit such as
3747 // buffering ranges and duration.
3748 mCurrentPosition = aTime.ToBase(1000000);
3749 NS_ASSERTION(mCurrentPosition.Ref() >= TimeUnit::Zero(),
3750 "CurrentTime should be positive!");
3751 if (mDuration.Ref().ref() < mCurrentPosition.Ref()) {
3752 mDuration = Some(mCurrentPosition.Ref());
3753 DDLOG(DDLogCategory::Property, "duration_us",
3754 mDuration.Ref()->ToMicroseconds());
3758 void MediaDecoderStateMachine::UpdatePlaybackPosition(const TimeUnit& aTime) {
3759 MOZ_ASSERT(OnTaskQueue());
3760 UpdatePlaybackPositionInternal(aTime);
3762 bool fragmentEnded =
3763 mFragmentEndTime.IsValid() && GetMediaTime() >= mFragmentEndTime;
3764 mMetadataManager.DispatchMetadataIfNeeded(aTime);
3766 if (fragmentEnded) {
3767 StopPlayback();
3771 /* static */ const char* MediaDecoderStateMachine::ToStateStr(State aState) {
3772 switch (aState) {
3773 case DECODER_STATE_DECODING_METADATA:
3774 return "DECODING_METADATA";
3775 case DECODER_STATE_DORMANT:
3776 return "DORMANT";
3777 case DECODER_STATE_DECODING_FIRSTFRAME:
3778 return "DECODING_FIRSTFRAME";
3779 case DECODER_STATE_DECODING:
3780 return "DECODING";
3781 case DECODER_STATE_SEEKING_ACCURATE:
3782 return "SEEKING_ACCURATE";
3783 case DECODER_STATE_SEEKING_FROMDORMANT:
3784 return "SEEKING_FROMDORMANT";
3785 case DECODER_STATE_SEEKING_NEXTFRAMESEEKING:
3786 return "DECODER_STATE_SEEKING_NEXTFRAMESEEKING";
3787 case DECODER_STATE_SEEKING_VIDEOONLY:
3788 return "SEEKING_VIDEOONLY";
3789 case DECODER_STATE_BUFFERING:
3790 return "BUFFERING";
3791 case DECODER_STATE_COMPLETED:
3792 return "COMPLETED";
3793 case DECODER_STATE_SHUTDOWN:
3794 return "SHUTDOWN";
3795 case DECODER_STATE_LOOPING_DECODING:
3796 return "LOOPING_DECODING";
3797 default:
3798 MOZ_ASSERT_UNREACHABLE("Invalid state.");
3800 return "UNKNOWN";
3803 const char* MediaDecoderStateMachine::ToStateStr() {
3804 MOZ_ASSERT(OnTaskQueue());
3805 return ToStateStr(mStateObj->GetState());
3808 void MediaDecoderStateMachine::VolumeChanged() {
3809 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::VolumeChanged",
3810 MEDIA_PLAYBACK);
3811 MOZ_ASSERT(OnTaskQueue());
3812 mMediaSink->SetVolume(mVolume);
3815 RefPtr<ShutdownPromise> MediaDecoderStateMachine::Shutdown() {
3816 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::Shutdown", MEDIA_PLAYBACK);
3817 MOZ_ASSERT(OnTaskQueue());
3818 mShuttingDown = true;
3819 return mStateObj->HandleShutdown();
3822 void MediaDecoderStateMachine::PlayStateChanged() {
3823 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::PlayStateChanged",
3824 MEDIA_PLAYBACK);
3825 MOZ_ASSERT(OnTaskQueue());
3827 if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
3828 CancelSuspendTimer();
3829 } else if (mMinimizePreroll) {
3830 // Once we start playing, we don't want to minimize our prerolling, as we
3831 // assume the user is likely to want to keep playing in future. This needs
3832 // to happen before we invoke StartDecoding().
3833 mMinimizePreroll = false;
3836 mStateObj->HandlePlayStateChanged(mPlayState);
3839 void MediaDecoderStateMachine::SetVideoDecodeMode(VideoDecodeMode aMode) {
3840 MOZ_ASSERT(NS_IsMainThread());
3841 nsCOMPtr<nsIRunnable> r = NewRunnableMethod<VideoDecodeMode>(
3842 "MediaDecoderStateMachine::SetVideoDecodeModeInternal", this,
3843 &MediaDecoderStateMachine::SetVideoDecodeModeInternal, aMode);
3844 OwnerThread()->DispatchStateChange(r.forget());
3847 void MediaDecoderStateMachine::SetVideoDecodeModeInternal(
3848 VideoDecodeMode aMode) {
3849 MOZ_ASSERT(OnTaskQueue());
3851 LOG("SetVideoDecodeModeInternal(), VideoDecodeMode=(%s->%s), "
3852 "mVideoDecodeSuspended=%c",
3853 mVideoDecodeMode == VideoDecodeMode::Normal ? "Normal" : "Suspend",
3854 aMode == VideoDecodeMode::Normal ? "Normal" : "Suspend",
3855 mVideoDecodeSuspended ? 'T' : 'F');
3857 // Should not suspend decoding if we don't turn on the pref.
3858 if (!StaticPrefs::media_suspend_background_video_enabled() &&
3859 aMode == VideoDecodeMode::Suspend) {
3860 LOG("SetVideoDecodeModeInternal(), early return because preference off and "
3861 "set to Suspend");
3862 return;
3865 if (aMode == mVideoDecodeMode) {
3866 LOG("SetVideoDecodeModeInternal(), early return because the mode does not "
3867 "change");
3868 return;
3871 // Set new video decode mode.
3872 mVideoDecodeMode = aMode;
3874 // Start timer to trigger suspended video decoding.
3875 if (mVideoDecodeMode == VideoDecodeMode::Suspend) {
3876 TimeStamp target = TimeStamp::Now() + SuspendBackgroundVideoDelay();
3878 RefPtr<MediaDecoderStateMachine> self = this;
3879 mVideoDecodeSuspendTimer.Ensure(
3880 target, [=]() { self->OnSuspendTimerResolved(); },
3881 []() { MOZ_DIAGNOSTIC_ASSERT(false); });
3882 mOnPlaybackEvent.Notify(MediaPlaybackEvent::StartVideoSuspendTimer);
3883 return;
3886 // Resuming from suspended decoding
3888 // If suspend timer exists, destroy it.
3889 CancelSuspendTimer();
3891 if (mVideoDecodeSuspended) {
3892 auto target = mMediaSink->IsStarted() ? GetClock() : GetMediaTime();
3893 AdjustByLooping(target);
3894 mStateObj->HandleResumeVideoDecoding(target + detail::RESUME_VIDEO_PREMIUM);
3898 void MediaDecoderStateMachine::BufferedRangeUpdated() {
3899 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::BufferedRangeUpdated",
3900 MEDIA_PLAYBACK);
3901 MOZ_ASSERT(OnTaskQueue());
3903 // While playing an unseekable stream of unknown duration, mDuration
3904 // is updated as we play. But if data is being downloaded
3905 // faster than played, mDuration won't reflect the end of playable data
3906 // since we haven't played the frame at the end of buffered data. So update
3907 // mDuration here as new data is downloaded to prevent such a lag.
3908 if (mBuffered.Ref().IsInvalid()) {
3909 return;
3912 bool exists;
3913 media::TimeUnit end{mBuffered.Ref().GetEnd(&exists)};
3914 if (!exists) {
3915 return;
3918 // Use estimated duration from buffer ranges when mDuration is unknown or
3919 // the estimated duration is larger.
3920 if (mDuration.Ref().isNothing() || mDuration.Ref()->IsInfinite() ||
3921 end > mDuration.Ref().ref()) {
3922 mDuration = Some(end);
3923 DDLOG(DDLogCategory::Property, "duration_us",
3924 mDuration.Ref()->ToMicroseconds());
3928 RefPtr<MediaDecoder::SeekPromise> MediaDecoderStateMachine::Seek(
3929 const SeekTarget& aTarget) {
3930 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::Seek", MEDIA_PLAYBACK);
3931 PROFILER_MARKER_UNTYPED("MDSM::Seek", MEDIA_PLAYBACK);
3932 MOZ_ASSERT(OnTaskQueue());
3934 // We need to be able to seek in some way
3935 if (!mMediaSeekable && !mMediaSeekableOnlyInBufferedRanges) {
3936 LOGW("Seek() should not be called on a non-seekable media");
3937 return MediaDecoder::SeekPromise::CreateAndReject(/* aRejectValue = */ true,
3938 __func__);
3941 if (aTarget.IsNextFrame() && !HasVideo()) {
3942 LOGW("Ignore a NextFrameSeekTask on a media file without video track.");
3943 return MediaDecoder::SeekPromise::CreateAndReject(/* aRejectValue = */ true,
3944 __func__);
3947 MOZ_ASSERT(mDuration.Ref().isSome(), "We should have got duration already");
3949 return mStateObj->HandleSeek(aTarget);
3952 void MediaDecoderStateMachine::StopMediaSink() {
3953 MOZ_ASSERT(OnTaskQueue());
3954 if (mMediaSink->IsStarted()) {
3955 LOG("Stop MediaSink");
3956 mMediaSink->Stop();
3957 mMediaSinkAudioEndedPromise.DisconnectIfExists();
3958 mMediaSinkVideoEndedPromise.DisconnectIfExists();
3962 void MediaDecoderStateMachine::RequestAudioData() {
3963 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::RequestAudioData",
3964 MEDIA_PLAYBACK);
3965 MOZ_ASSERT(OnTaskQueue());
3966 MOZ_ASSERT(IsAudioDecoding());
3967 MOZ_ASSERT(!IsRequestingAudioData());
3968 MOZ_ASSERT(!IsWaitingAudioData());
3969 LOGV("Queueing audio task - queued=%zu, decoder-queued=%zu",
3970 AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
3972 PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestData);
3973 RefPtr<MediaDecoderStateMachine> self = this;
3974 mReader->RequestAudioData()
3975 ->Then(
3976 OwnerThread(), __func__,
3977 [this, self, perfRecorder(std::move(perfRecorder))](
3978 const RefPtr<AudioData>& aAudio) mutable {
3979 perfRecorder.Record();
3980 AUTO_PROFILER_LABEL(
3981 "MediaDecoderStateMachine::RequestAudioData:Resolved",
3982 MEDIA_PLAYBACK);
3983 MOZ_ASSERT(aAudio);
3984 mAudioDataRequest.Complete();
3985 // audio->GetEndTime() is not always mono-increasing in chained
3986 // ogg.
3987 mDecodedAudioEndTime =
3988 std::max(aAudio->GetEndTime(), mDecodedAudioEndTime);
3989 LOGV("OnAudioDecoded [%" PRId64 ",%" PRId64 "]",
3990 aAudio->mTime.ToMicroseconds(),
3991 aAudio->GetEndTime().ToMicroseconds());
3992 mStateObj->HandleAudioDecoded(aAudio);
3994 [this, self](const MediaResult& aError) {
3995 AUTO_PROFILER_LABEL(
3996 "MediaDecoderStateMachine::RequestAudioData:Rejected",
3997 MEDIA_PLAYBACK);
3998 LOGV("OnAudioNotDecoded ErrorName=%s Message=%s",
3999 aError.ErrorName().get(), aError.Message().get());
4000 mAudioDataRequest.Complete();
4001 switch (aError.Code()) {
4002 case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
4003 mStateObj->HandleWaitingForAudio();
4004 break;
4005 case NS_ERROR_DOM_MEDIA_CANCELED:
4006 mStateObj->HandleAudioCanceled();
4007 break;
4008 case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
4009 mStateObj->HandleEndOfAudio();
4010 break;
4011 default:
4012 DecodeError(aError);
4015 ->Track(mAudioDataRequest);
4018 void MediaDecoderStateMachine::RequestVideoData(
4019 const media::TimeUnit& aCurrentTime, bool aRequestNextKeyFrame) {
4020 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::RequestVideoData",
4021 MEDIA_PLAYBACK);
4022 MOZ_ASSERT(OnTaskQueue());
4023 MOZ_ASSERT(IsVideoDecoding());
4024 MOZ_ASSERT(!IsRequestingVideoData());
4025 MOZ_ASSERT(!IsWaitingVideoData());
4026 LOGV(
4027 "Queueing video task - queued=%zu, decoder-queued=%zo"
4028 ", stime=%" PRId64 ", by-pass-skip=%d",
4029 VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(),
4030 aCurrentTime.ToMicroseconds(), mBypassingSkipToNextKeyFrameCheck);
4032 PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestData,
4033 Info().mVideo.mImage.height);
4034 RefPtr<MediaDecoderStateMachine> self = this;
4035 mReader
4036 ->RequestVideoData(
4037 mBypassingSkipToNextKeyFrameCheck ? media::TimeUnit() : aCurrentTime,
4038 mBypassingSkipToNextKeyFrameCheck ? false : aRequestNextKeyFrame)
4039 ->Then(
4040 OwnerThread(), __func__,
4041 [this, self, perfRecorder(std::move(perfRecorder))](
4042 const RefPtr<VideoData>& aVideo) mutable {
4043 perfRecorder.Record();
4044 AUTO_PROFILER_LABEL(
4045 "MediaDecoderStateMachine::RequestVideoData:Resolved",
4046 MEDIA_PLAYBACK);
4047 MOZ_ASSERT(aVideo);
4048 mVideoDataRequest.Complete();
4049 // Handle abnormal or negative timestamps.
4050 mDecodedVideoEndTime =
4051 std::max(mDecodedVideoEndTime, aVideo->GetEndTime());
4052 LOGV("OnVideoDecoded [%" PRId64 ",%" PRId64 "]",
4053 aVideo->mTime.ToMicroseconds(),
4054 aVideo->GetEndTime().ToMicroseconds());
4055 mStateObj->HandleVideoDecoded(aVideo);
4057 [this, self](const MediaResult& aError) {
4058 AUTO_PROFILER_LABEL(
4059 "MediaDecoderStateMachine::RequestVideoData:Rejected",
4060 MEDIA_PLAYBACK);
4061 LOGV("OnVideoNotDecoded ErrorName=%s Message=%s",
4062 aError.ErrorName().get(), aError.Message().get());
4063 mVideoDataRequest.Complete();
4064 switch (aError.Code()) {
4065 case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
4066 mStateObj->HandleWaitingForVideo();
4067 break;
4068 case NS_ERROR_DOM_MEDIA_CANCELED:
4069 mStateObj->HandleVideoCanceled();
4070 break;
4071 case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
4072 mStateObj->HandleEndOfVideo();
4073 break;
4074 default:
4075 DecodeError(aError);
4078 ->Track(mVideoDataRequest);
4081 void MediaDecoderStateMachine::WaitForData(MediaData::Type aType) {
4082 MOZ_ASSERT(OnTaskQueue());
4083 MOZ_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
4084 aType == MediaData::Type::VIDEO_DATA);
4085 RefPtr<MediaDecoderStateMachine> self = this;
4086 if (aType == MediaData::Type::AUDIO_DATA) {
4087 mReader->WaitForData(MediaData::Type::AUDIO_DATA)
4088 ->Then(
4089 OwnerThread(), __func__,
4090 [self](MediaData::Type aType) {
4091 AUTO_PROFILER_LABEL(
4092 "MediaDecoderStateMachine::WaitForData:AudioResolved",
4093 MEDIA_PLAYBACK);
4094 self->mAudioWaitRequest.Complete();
4095 MOZ_ASSERT(aType == MediaData::Type::AUDIO_DATA);
4096 self->mStateObj->HandleAudioWaited(aType);
4098 [self](const WaitForDataRejectValue& aRejection) {
4099 AUTO_PROFILER_LABEL(
4100 "MediaDecoderStateMachine::WaitForData:AudioRejected",
4101 MEDIA_PLAYBACK);
4102 self->mAudioWaitRequest.Complete();
4103 self->DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
4105 ->Track(mAudioWaitRequest);
4106 } else {
4107 mReader->WaitForData(MediaData::Type::VIDEO_DATA)
4108 ->Then(
4109 OwnerThread(), __func__,
4110 [self](MediaData::Type aType) {
4111 AUTO_PROFILER_LABEL(
4112 "MediaDecoderStateMachine::WaitForData:VideoResolved",
4113 MEDIA_PLAYBACK);
4114 self->mVideoWaitRequest.Complete();
4115 MOZ_ASSERT(aType == MediaData::Type::VIDEO_DATA);
4116 self->mStateObj->HandleVideoWaited(aType);
4118 [self](const WaitForDataRejectValue& aRejection) {
4119 AUTO_PROFILER_LABEL(
4120 "MediaDecoderStateMachine::WaitForData:VideoRejected",
4121 MEDIA_PLAYBACK);
4122 self->mVideoWaitRequest.Complete();
4123 self->DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
4125 ->Track(mVideoWaitRequest);
4129 nsresult MediaDecoderStateMachine::StartMediaSink() {
4130 MOZ_ASSERT(OnTaskQueue());
4132 if (mMediaSink->IsStarted()) {
4133 return NS_OK;
4136 mAudioCompleted = false;
4137 const auto startTime = GetMediaTime();
4138 LOG("StartMediaSink, mediaTime=%" PRId64, startTime.ToMicroseconds());
4139 nsresult rv = mMediaSink->Start(startTime, Info());
4140 StreamNameChanged();
4142 auto videoPromise = mMediaSink->OnEnded(TrackInfo::kVideoTrack);
4143 auto audioPromise = mMediaSink->OnEnded(TrackInfo::kAudioTrack);
4145 if (audioPromise) {
4146 audioPromise
4147 ->Then(OwnerThread(), __func__, this,
4148 &MediaDecoderStateMachine::OnMediaSinkAudioComplete,
4149 &MediaDecoderStateMachine::OnMediaSinkAudioError)
4150 ->Track(mMediaSinkAudioEndedPromise);
4152 if (videoPromise) {
4153 videoPromise
4154 ->Then(OwnerThread(), __func__, this,
4155 &MediaDecoderStateMachine::OnMediaSinkVideoComplete,
4156 &MediaDecoderStateMachine::OnMediaSinkVideoError)
4157 ->Track(mMediaSinkVideoEndedPromise);
4159 // Remember the initial offset when playback starts. This will be used
4160 // to calculate the rate at which bytes are consumed as playback moves on.
4161 RefPtr<MediaData> sample = mAudioQueue.PeekFront();
4162 mPlaybackOffset = sample ? sample->mOffset : 0;
4163 sample = mVideoQueue.PeekFront();
4164 if (sample && sample->mOffset > mPlaybackOffset) {
4165 mPlaybackOffset = sample->mOffset;
4167 return rv;
4170 bool MediaDecoderStateMachine::HasLowDecodedAudio() {
4171 MOZ_ASSERT(OnTaskQueue());
4172 return IsAudioDecoding() &&
4173 GetDecodedAudioDuration() <
4174 EXHAUSTED_DATA_MARGIN.MultDouble(mPlaybackRate);
4177 bool MediaDecoderStateMachine::HasLowDecodedVideo() {
4178 MOZ_ASSERT(OnTaskQueue());
4179 return IsVideoDecoding() &&
4180 VideoQueue().GetSize() <
4181 static_cast<size_t>(floorl(LOW_VIDEO_FRAMES * mPlaybackRate));
4184 bool MediaDecoderStateMachine::HasLowDecodedData() {
4185 MOZ_ASSERT(OnTaskQueue());
4186 MOZ_ASSERT(mReader->UseBufferingHeuristics());
4187 return HasLowDecodedAudio() || HasLowDecodedVideo();
4190 bool MediaDecoderStateMachine::OutOfDecodedAudio() {
4191 MOZ_ASSERT(OnTaskQueue());
4192 return IsAudioDecoding() && !AudioQueue().IsFinished() &&
4193 AudioQueue().GetSize() == 0 &&
4194 !mMediaSink->HasUnplayedFrames(TrackInfo::kAudioTrack);
4197 bool MediaDecoderStateMachine::HasLowBufferedData() {
4198 MOZ_ASSERT(OnTaskQueue());
4199 return HasLowBufferedData(detail::LOW_BUFFER_THRESHOLD);
4202 bool MediaDecoderStateMachine::HasLowBufferedData(const TimeUnit& aThreshold) {
4203 MOZ_ASSERT(OnTaskQueue());
4205 // If we don't have a duration, mBuffered is probably not going to have
4206 // a useful buffered range. Return false here so that we don't get stuck in
4207 // buffering mode for live streams.
4208 if (Duration().IsInfinite()) {
4209 return false;
4212 if (mBuffered.Ref().IsInvalid()) {
4213 return false;
4216 // We are never low in decoded data when we don't have audio/video or have
4217 // decoded all audio/video samples.
4218 TimeUnit endOfDecodedVideo = (HasVideo() && !VideoQueue().IsFinished())
4219 ? mDecodedVideoEndTime
4220 : TimeUnit::FromNegativeInfinity();
4221 TimeUnit endOfDecodedAudio = (HasAudio() && !AudioQueue().IsFinished())
4222 ? mDecodedAudioEndTime
4223 : TimeUnit::FromNegativeInfinity();
4225 auto endOfDecodedData = std::max(endOfDecodedVideo, endOfDecodedAudio);
4226 if (Duration() < endOfDecodedData) {
4227 // Our duration is not up to date. No point buffering.
4228 return false;
4231 if (endOfDecodedData.IsInfinite()) {
4232 // Have decoded all samples. No point buffering.
4233 return false;
4236 auto start = endOfDecodedData;
4237 auto end = std::min(GetMediaTime() + aThreshold, Duration());
4238 if (start >= end) {
4239 // Duration of decoded samples is greater than our threshold.
4240 return false;
4242 media::TimeInterval interval(start, end);
4243 return !mBuffered.Ref().Contains(interval);
4246 void MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent() {
4247 MOZ_ASSERT(OnTaskQueue());
4248 // Track value of mSentFirstFrameLoadedEvent from before updating it
4249 bool firstFrameBeenLoaded = mSentFirstFrameLoadedEvent;
4250 mSentFirstFrameLoadedEvent = true;
4251 MediaDecoderEventVisibility visibility =
4252 firstFrameBeenLoaded ? MediaDecoderEventVisibility::Suppressed
4253 : MediaDecoderEventVisibility::Observable;
4254 mFirstFrameLoadedEvent.Notify(UniquePtr<MediaInfo>(new MediaInfo(Info())),
4255 visibility);
4258 void MediaDecoderStateMachine::FinishDecodeFirstFrame() {
4259 MOZ_ASSERT(OnTaskQueue());
4260 MOZ_ASSERT(!mSentFirstFrameLoadedEvent);
4261 LOG("FinishDecodeFirstFrame");
4263 mMediaSink->Redraw(Info().mVideo);
4265 LOG("Media duration %" PRId64 ", mediaSeekable=%d",
4266 Duration().ToMicroseconds(), mMediaSeekable);
4268 // Get potentially updated metadata
4269 mReader->ReadUpdatedMetadata(mInfo.ptr());
4271 EnqueueFirstFrameLoadedEvent();
4274 RefPtr<ShutdownPromise> MediaDecoderStateMachine::FinishShutdown() {
4275 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::FinishShutdown",
4276 MEDIA_PLAYBACK);
4277 MOZ_ASSERT(OnTaskQueue());
4278 LOG("Shutting down state machine task queue");
4279 return OwnerThread()->BeginShutdown();
4282 void MediaDecoderStateMachine::RunStateMachine() {
4283 MOZ_ASSERT(OnTaskQueue());
4284 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::RunStateMachine",
4285 MEDIA_PLAYBACK);
4286 mDelayedScheduler.Reset(); // Must happen on state machine task queue.
4287 mDispatchedStateMachine = false;
4288 mStateObj->Step();
4291 void MediaDecoderStateMachine::ResetDecode(const TrackSet& aTracks) {
4292 MOZ_ASSERT(OnTaskQueue());
4293 LOG("MediaDecoderStateMachine::Reset");
4295 // Assert that aTracks specifies to reset the video track because we
4296 // don't currently support resetting just the audio track.
4297 MOZ_ASSERT(aTracks.contains(TrackInfo::kVideoTrack));
4299 if (aTracks.contains(TrackInfo::kVideoTrack)) {
4300 mDecodedVideoEndTime = TimeUnit::Zero();
4301 mVideoCompleted = false;
4302 VideoQueue().Reset();
4303 mVideoDataRequest.DisconnectIfExists();
4304 mVideoWaitRequest.DisconnectIfExists();
4307 if (aTracks.contains(TrackInfo::kAudioTrack)) {
4308 mDecodedAudioEndTime = TimeUnit::Zero();
4309 mAudioCompleted = false;
4310 AudioQueue().Reset();
4311 mAudioDataRequest.DisconnectIfExists();
4312 mAudioWaitRequest.DisconnectIfExists();
4315 mReader->ResetDecode(aTracks);
4318 media::TimeUnit MediaDecoderStateMachine::GetClock(
4319 TimeStamp* aTimeStamp) const {
4320 MOZ_ASSERT(OnTaskQueue());
4321 auto clockTime = mMediaSink->GetPosition(aTimeStamp);
4322 // This fails on Windows some times, see 1765563
4323 #if defined(XP_WIN)
4324 NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
4325 #else
4326 MOZ_ASSERT(GetMediaTime() <= clockTime, "Clock should go forwards.");
4327 #endif
4328 return clockTime;
4331 void MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically() {
4332 MOZ_ASSERT(OnTaskQueue());
4334 if (!IsPlaying()) {
4335 return;
4338 // Cap the current time to the larger of the audio and video end time.
4339 // This ensures that if we're running off the system clock, we don't
4340 // advance the clock to after the media end time.
4341 if (VideoEndTime() > TimeUnit::Zero() || AudioEndTime() > TimeUnit::Zero()) {
4342 auto clockTime = GetClock();
4343 // Once looping was turned on, the time is probably larger than the duration
4344 // of the media track, so the time over the end should be corrected.
4345 AdjustByLooping(clockTime);
4346 bool loopback = clockTime < GetMediaTime() && mLooping;
4347 if (loopback && mBypassingSkipToNextKeyFrameCheck) {
4348 LOG("media has looped back, no longer bypassing skip-to-next-key-frame");
4349 mBypassingSkipToNextKeyFrameCheck = false;
4352 // Skip frames up to the frame at the playback position, and figure out
4353 // the time remaining until it's time to display the next frame and drop
4354 // the current frame.
4355 NS_ASSERTION(clockTime >= TimeUnit::Zero(),
4356 "Should have positive clock time.");
4358 // These will be non -1 if we've displayed a video frame, or played an audio
4359 // frame.
4360 auto maxEndTime = std::max(VideoEndTime(), AudioEndTime());
4361 auto t = std::min(clockTime, maxEndTime);
4362 // FIXME: Bug 1091422 - chained ogg files hit this assertion.
4363 // MOZ_ASSERT(t >= GetMediaTime());
4364 if (loopback || t > GetMediaTime()) {
4365 UpdatePlaybackPosition(t);
4368 // Note we have to update playback position before releasing the monitor.
4369 // Otherwise, MediaDecoder::AddOutputTrack could kick in when we are outside
4370 // the monitor and get a staled value from GetCurrentTimeUs() which hits the
4371 // assertion in GetClock().
4373 int64_t delay = std::max<int64_t>(
4374 1, static_cast<int64_t>(AUDIO_DURATION_USECS / mPlaybackRate));
4375 ScheduleStateMachineIn(TimeUnit::FromMicroseconds(delay));
4377 // Notify the listener as we progress in the playback offset. Note it would
4378 // be too intensive to send notifications for each popped audio/video sample.
4379 // It is good enough to send 'PlaybackProgressed' events every 40us (defined
4380 // by AUDIO_DURATION_USECS), and we ensure 'PlaybackProgressed' events are
4381 // always sent after 'PlaybackStarted' and before 'PlaybackStopped'.
4382 mOnPlaybackEvent.Notify(MediaPlaybackEvent{
4383 MediaPlaybackEvent::PlaybackProgressed, mPlaybackOffset});
4386 void MediaDecoderStateMachine::ScheduleStateMachine() {
4387 MOZ_ASSERT(OnTaskQueue());
4388 if (mDispatchedStateMachine) {
4389 return;
4391 mDispatchedStateMachine = true;
4393 nsresult rv = OwnerThread()->Dispatch(
4394 NewRunnableMethod("MediaDecoderStateMachine::RunStateMachine", this,
4395 &MediaDecoderStateMachine::RunStateMachine));
4396 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
4397 Unused << rv;
4400 void MediaDecoderStateMachine::ScheduleStateMachineIn(const TimeUnit& aTime) {
4401 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::ScheduleStateMachineIn",
4402 MEDIA_PLAYBACK);
4403 MOZ_ASSERT(OnTaskQueue()); // mDelayedScheduler.Ensure() may Disconnect()
4404 // the promise, which must happen on the state
4405 // machine task queue.
4406 MOZ_ASSERT(aTime > TimeUnit::Zero());
4407 if (mDispatchedStateMachine) {
4408 return;
4411 TimeStamp target = TimeStamp::Now() + aTime.ToTimeDuration();
4413 // It is OK to capture 'this' without causing UAF because the callback
4414 // always happens before shutdown.
4415 RefPtr<MediaDecoderStateMachine> self = this;
4416 mDelayedScheduler.Ensure(
4417 target,
4418 [self]() {
4419 self->mDelayedScheduler.CompleteRequest();
4420 self->RunStateMachine();
4422 []() { MOZ_DIAGNOSTIC_ASSERT(false); });
4425 bool MediaDecoderStateMachine::IsStateMachineScheduled() const {
4426 MOZ_ASSERT(OnTaskQueue());
4427 return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
4430 void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate) {
4431 MOZ_ASSERT(OnTaskQueue());
4432 MOZ_ASSERT(aPlaybackRate != 0, "Should be handled by MediaDecoder::Pause()");
4434 mPlaybackRate = aPlaybackRate;
4435 mMediaSink->SetPlaybackRate(mPlaybackRate);
4437 // Schedule next cycle to check if we can stop prerolling.
4438 ScheduleStateMachine();
4441 void MediaDecoderStateMachine::PreservesPitchChanged() {
4442 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::PreservesPitchChanged",
4443 MEDIA_PLAYBACK);
4444 MOZ_ASSERT(OnTaskQueue());
4445 mMediaSink->SetPreservesPitch(mPreservesPitch);
4448 void MediaDecoderStateMachine::LoopingChanged() {
4449 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::LoopingChanged",
4450 MEDIA_PLAYBACK);
4451 MOZ_ASSERT(OnTaskQueue());
4452 LOGV("LoopingChanged, looping=%d", mLooping.Ref());
4453 PROFILER_MARKER_TEXT("MDSM::LoopingChanged", MEDIA_PLAYBACK, {},
4454 mLooping ? "true"_ns : "false"_ns);
4455 if (mSeamlessLoopingAllowed) {
4456 mStateObj->HandleLoopingChanged();
4460 void MediaDecoderStateMachine::StreamNameChanged() {
4461 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::StreamNameChanged",
4462 MEDIA_PLAYBACK);
4463 MOZ_ASSERT(OnTaskQueue());
4465 mMediaSink->SetStreamName(mStreamName);
4468 void MediaDecoderStateMachine::UpdateOutputCaptured() {
4469 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::UpdateOutputCaptured",
4470 MEDIA_PLAYBACK);
4471 MOZ_ASSERT(OnTaskQueue());
4472 MOZ_ASSERT_IF(
4473 mOutputCaptureState == MediaDecoder::OutputCaptureState::Capture,
4474 mOutputDummyTrack.Ref());
4476 // Reset these flags so they are consistent with the status of the sink.
4477 // TODO: Move these flags into MediaSink to improve cohesion so we don't need
4478 // to reset these flags when switching MediaSinks.
4479 mAudioCompleted = false;
4480 mVideoCompleted = false;
4482 // Don't create a new media sink if we're still suspending media sink.
4483 if (!mIsMediaSinkSuspended) {
4484 const bool wasPlaying = IsPlaying();
4485 // Stop and shut down the existing sink.
4486 StopMediaSink();
4487 mMediaSink->Shutdown();
4489 // Create a new sink according to whether output is captured.
4490 mMediaSink = CreateMediaSink();
4491 if (wasPlaying) {
4492 DebugOnly<nsresult> rv = StartMediaSink();
4493 MOZ_ASSERT(NS_SUCCEEDED(rv));
4497 // Don't buffer as much when audio is captured because we don't need to worry
4498 // about high latency audio devices.
4499 mAmpleAudioThreshold =
4500 mOutputCaptureState != MediaDecoder::OutputCaptureState::None
4501 ? detail::AMPLE_AUDIO_THRESHOLD / 2
4502 : detail::AMPLE_AUDIO_THRESHOLD;
4504 mStateObj->HandleAudioCaptured();
4507 void MediaDecoderStateMachine::OutputPrincipalChanged() {
4508 MOZ_ASSERT(OnTaskQueue());
4509 mCanonicalOutputPrincipal = mOutputPrincipal;
4512 RefPtr<GenericPromise> MediaDecoderStateMachine::InvokeSetSink(
4513 const RefPtr<AudioDeviceInfo>& aSink) {
4514 MOZ_ASSERT(NS_IsMainThread());
4515 MOZ_ASSERT(aSink);
4517 return InvokeAsync(OwnerThread(), this, __func__,
4518 &MediaDecoderStateMachine::SetSink, aSink);
4521 RefPtr<GenericPromise> MediaDecoderStateMachine::SetSink(
4522 RefPtr<AudioDeviceInfo> aDevice) {
4523 MOZ_ASSERT(OnTaskQueue());
4524 if (mIsMediaSinkSuspended) {
4525 // Don't create a new media sink when suspended.
4526 return GenericPromise::CreateAndResolve(true, __func__);
4529 return mMediaSink->SetAudioDevice(std::move(aDevice));
4532 void MediaDecoderStateMachine::InvokeSuspendMediaSink() {
4533 MOZ_ASSERT(NS_IsMainThread());
4535 nsresult rv = OwnerThread()->Dispatch(
4536 NewRunnableMethod("MediaDecoderStateMachine::SuspendMediaSink", this,
4537 &MediaDecoderStateMachine::SuspendMediaSink));
4538 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
4539 Unused << rv;
4542 void MediaDecoderStateMachine::SuspendMediaSink() {
4543 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::SuspendMediaSink",
4544 MEDIA_PLAYBACK);
4545 MOZ_ASSERT(OnTaskQueue());
4546 if (mIsMediaSinkSuspended) {
4547 return;
4549 LOG("SuspendMediaSink");
4550 mIsMediaSinkSuspended = true;
4551 StopMediaSink();
4552 mMediaSink->Shutdown();
4555 void MediaDecoderStateMachine::InvokeResumeMediaSink() {
4556 MOZ_ASSERT(NS_IsMainThread());
4558 nsresult rv = OwnerThread()->Dispatch(
4559 NewRunnableMethod("MediaDecoderStateMachine::ResumeMediaSink", this,
4560 &MediaDecoderStateMachine::ResumeMediaSink));
4561 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
4562 Unused << rv;
4565 void MediaDecoderStateMachine::ResumeMediaSink() {
4566 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::ResumeMediaSink",
4567 MEDIA_PLAYBACK);
4568 MOZ_ASSERT(OnTaskQueue());
4569 if (!mIsMediaSinkSuspended) {
4570 return;
4572 LOG("ResumeMediaSink");
4573 mIsMediaSinkSuspended = false;
4574 if (!mMediaSink->IsStarted()) {
4575 mMediaSink = CreateMediaSink();
4576 MaybeStartPlayback();
4580 void MediaDecoderStateMachine::UpdateSecondaryVideoContainer() {
4581 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::UpdateSecondaryVideoContainer",
4582 MEDIA_PLAYBACK);
4583 MOZ_ASSERT(OnTaskQueue());
4584 MOZ_DIAGNOSTIC_ASSERT(mMediaSink);
4585 mMediaSink->SetSecondaryVideoContainer(mSecondaryVideoContainer.Ref());
4586 mOnSecondaryVideoContainerInstalled.Notify(mSecondaryVideoContainer.Ref());
4589 TimeUnit MediaDecoderStateMachine::AudioEndTime() const {
4590 MOZ_ASSERT(OnTaskQueue());
4591 if (mMediaSink->IsStarted()) {
4592 return mMediaSink->GetEndTime(TrackInfo::kAudioTrack);
4594 return GetMediaTime();
4597 TimeUnit MediaDecoderStateMachine::VideoEndTime() const {
4598 MOZ_ASSERT(OnTaskQueue());
4599 if (mMediaSink->IsStarted()) {
4600 return mMediaSink->GetEndTime(TrackInfo::kVideoTrack);
4602 return GetMediaTime();
4605 void MediaDecoderStateMachine::OnMediaSinkVideoComplete() {
4606 MOZ_ASSERT(OnTaskQueue());
4607 MOZ_ASSERT(HasVideo());
4608 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnMediaSinkVideoComplete",
4609 MEDIA_PLAYBACK);
4610 LOG("[%s]", __func__);
4612 mMediaSinkVideoEndedPromise.Complete();
4613 mVideoCompleted = true;
4614 ScheduleStateMachine();
4617 void MediaDecoderStateMachine::OnMediaSinkVideoError() {
4618 MOZ_ASSERT(OnTaskQueue());
4619 MOZ_ASSERT(HasVideo());
4620 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnMediaSinkVideoError",
4621 MEDIA_PLAYBACK);
4622 LOGE("[%s]", __func__);
4624 mMediaSinkVideoEndedPromise.Complete();
4625 mVideoCompleted = true;
4626 if (HasAudio()) {
4627 return;
4629 DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__));
4632 void MediaDecoderStateMachine::OnMediaSinkAudioComplete() {
4633 MOZ_ASSERT(OnTaskQueue());
4634 MOZ_ASSERT(HasAudio());
4635 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnMediaSinkAudioComplete",
4636 MEDIA_PLAYBACK);
4637 LOG("[%s]", __func__);
4639 mMediaSinkAudioEndedPromise.Complete();
4640 mAudioCompleted = true;
4641 // To notify PlaybackEnded as soon as possible.
4642 ScheduleStateMachine();
4644 // Report OK to Decoder Doctor (to know if issue may have been resolved).
4645 mOnDecoderDoctorEvent.Notify(
4646 DecoderDoctorEvent{DecoderDoctorEvent::eAudioSinkStartup, NS_OK});
4649 void MediaDecoderStateMachine::OnMediaSinkAudioError(nsresult aResult) {
4650 MOZ_ASSERT(OnTaskQueue());
4651 MOZ_ASSERT(HasAudio());
4652 AUTO_PROFILER_LABEL("MediaDecoderStateMachine::OnMediaSinkAudioError",
4653 MEDIA_PLAYBACK);
4654 LOGE("[%s]", __func__);
4656 mMediaSinkAudioEndedPromise.Complete();
4657 mAudioCompleted = true;
4659 // Result should never be NS_OK in this *error* handler. Report to Dec-Doc.
4660 MOZ_ASSERT(NS_FAILED(aResult));
4661 mOnDecoderDoctorEvent.Notify(
4662 DecoderDoctorEvent{DecoderDoctorEvent::eAudioSinkStartup, aResult});
4664 // Make the best effort to continue playback when there is video.
4665 if (HasVideo()) {
4666 return;
4669 // Otherwise notify media decoder/element about this error for it makes
4670 // no sense to play an audio-only file without sound output.
4671 DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__));
4674 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const {
4675 MOZ_ASSERT(OnTaskQueue());
4676 return mReader->VideoIsHardwareAccelerated()
4677 ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
4678 : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
4681 void MediaDecoderStateMachine::GetDebugInfo(
4682 dom::MediaDecoderStateMachineDebugInfo& aInfo) {
4683 MOZ_ASSERT(OnTaskQueue());
4684 aInfo.mDuration =
4685 mDuration.Ref() ? mDuration.Ref().ref().ToMicroseconds() : -1;
4686 aInfo.mMediaTime = GetMediaTime().ToMicroseconds();
4687 aInfo.mClock = mMediaSink->IsStarted() ? GetClock().ToMicroseconds() : -1;
4688 aInfo.mPlayState = int32_t(mPlayState.Ref());
4689 aInfo.mSentFirstFrameLoadedEvent = mSentFirstFrameLoadedEvent;
4690 aInfo.mIsPlaying = IsPlaying();
4691 CopyUTF8toUTF16(MakeStringSpan(AudioRequestStatus()),
4692 aInfo.mAudioRequestStatus);
4693 CopyUTF8toUTF16(MakeStringSpan(VideoRequestStatus()),
4694 aInfo.mVideoRequestStatus);
4695 aInfo.mDecodedAudioEndTime = mDecodedAudioEndTime.ToMicroseconds();
4696 aInfo.mDecodedVideoEndTime = mDecodedVideoEndTime.ToMicroseconds();
4697 aInfo.mAudioCompleted = mAudioCompleted;
4698 aInfo.mVideoCompleted = mVideoCompleted;
4699 mStateObj->GetDebugInfo(aInfo.mStateObj);
4700 mMediaSink->GetDebugInfo(aInfo.mMediaSink);
4701 aInfo.mTotalBufferingTimeMs = mTotalBufferingDuration.ToMilliseconds();
4704 RefPtr<GenericPromise> MediaDecoderStateMachine::RequestDebugInfo(
4705 dom::MediaDecoderStateMachineDebugInfo& aInfo) {
4706 if (mShuttingDown) {
4707 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
4710 RefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__);
4711 RefPtr<MediaDecoderStateMachine> self = this;
4712 nsresult rv = OwnerThread()->Dispatch(
4713 NS_NewRunnableFunction("MediaDecoderStateMachine::RequestDebugInfo",
4714 [self, p, &aInfo]() {
4715 self->GetDebugInfo(aInfo);
4716 p->Resolve(true, __func__);
4718 AbstractThread::TailDispatch);
4719 MOZ_ASSERT(NS_SUCCEEDED(rv));
4720 Unused << rv;
4721 return p;
4724 class VideoQueueMemoryFunctor : public nsDequeFunctor<VideoData> {
4725 public:
4726 VideoQueueMemoryFunctor() : mSize(0) {}
4728 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
4730 virtual void operator()(VideoData* aObject) override {
4731 mSize += aObject->SizeOfIncludingThis(MallocSizeOf);
4734 size_t mSize;
4737 class AudioQueueMemoryFunctor : public nsDequeFunctor<AudioData> {
4738 public:
4739 AudioQueueMemoryFunctor() : mSize(0) {}
4741 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
4743 virtual void operator()(AudioData* aObject) override {
4744 mSize += aObject->SizeOfIncludingThis(MallocSizeOf);
4747 size_t mSize;
4750 size_t MediaDecoderStateMachine::SizeOfVideoQueue() const {
4751 VideoQueueMemoryFunctor functor;
4752 mVideoQueue.LockedForEach(functor);
4753 return functor.mSize;
4756 size_t MediaDecoderStateMachine::SizeOfAudioQueue() const {
4757 AudioQueueMemoryFunctor functor;
4758 mAudioQueue.LockedForEach(functor);
4759 return functor.mSize;
4762 const char* MediaDecoderStateMachine::AudioRequestStatus() const {
4763 MOZ_ASSERT(OnTaskQueue());
4764 if (IsRequestingAudioData()) {
4765 MOZ_DIAGNOSTIC_ASSERT(!IsWaitingAudioData());
4766 return "pending";
4769 if (IsWaitingAudioData()) {
4770 return "waiting";
4772 return "idle";
4775 const char* MediaDecoderStateMachine::VideoRequestStatus() const {
4776 MOZ_ASSERT(OnTaskQueue());
4777 if (IsRequestingVideoData()) {
4778 MOZ_DIAGNOSTIC_ASSERT(!IsWaitingVideoData());
4779 return "pending";
4782 if (IsWaitingVideoData()) {
4783 return "waiting";
4785 return "idle";
4788 void MediaDecoderStateMachine::OnSuspendTimerResolved() {
4789 LOG("OnSuspendTimerResolved");
4790 mVideoDecodeSuspendTimer.CompleteRequest();
4791 mStateObj->HandleVideoSuspendTimeout();
4794 void MediaDecoderStateMachine::CancelSuspendTimer() {
4795 LOG("CancelSuspendTimer: State: %s, Timer.IsScheduled: %c",
4796 ToStateStr(mStateObj->GetState()),
4797 mVideoDecodeSuspendTimer.IsScheduled() ? 'T' : 'F');
4798 MOZ_ASSERT(OnTaskQueue());
4799 if (mVideoDecodeSuspendTimer.IsScheduled()) {
4800 mOnPlaybackEvent.Notify(MediaPlaybackEvent::CancelVideoSuspendTimer);
4802 mVideoDecodeSuspendTimer.Reset();
4805 void MediaDecoderStateMachine::AdjustByLooping(media::TimeUnit& aTime) const {
4806 MOZ_ASSERT(OnTaskQueue());
4808 // No need to adjust time.
4809 if (mOriginalDecodedDuration == media::TimeUnit::Zero()) {
4810 return;
4813 // There are situations where we need to perform subtraction instead of modulo
4814 // to accurately adjust the clock. When we are not in a state of seamless
4815 // looping, it is usually necessary to normalize the clock time within the
4816 // range of [0, duration]. However, if the current clock time is greater than
4817 // the duration (i.e., duration+1) and not in looping, we should not adjust it
4818 // to 1 as we are not looping back to the starting position. Instead, we
4819 // should leave the clock time unchanged and trim it later to match the
4820 // maximum duration time.
4821 if (mStateObj->GetState() != DECODER_STATE_LOOPING_DECODING) {
4822 // Use the smaller offset rather than the larger one, as the larger offset
4823 // indicates the next round of looping. For example, if the duration is X
4824 // and the playback is currently in the third round of looping, both
4825 // queues will have an offset of 3X. However, if the audio decoding is
4826 // faster and the fourth round of data has already been added to the audio
4827 // queue, the audio offset will become 4X. Since playback is still in the
4828 // third round, we should use the smaller offset of 3X to adjust the time.
4829 TimeUnit offset = TimeUnit::FromInfinity();
4830 if (HasAudio()) {
4831 offset = std::min(AudioQueue().GetOffset(), offset);
4833 if (HasVideo()) {
4834 offset = std::min(VideoQueue().GetOffset(), offset);
4836 if (aTime > offset) {
4837 aTime -= offset;
4838 return;
4842 // When seamless looping happens at least once, it doesn't matter if we're
4843 // looping or not.
4844 aTime = aTime % mOriginalDecodedDuration;
4847 bool MediaDecoderStateMachine::IsInSeamlessLooping() const {
4848 return mLooping && mSeamlessLoopingAllowed;
4851 bool MediaDecoderStateMachine::HasLastDecodedData(MediaData::Type aType) {
4852 MOZ_DIAGNOSTIC_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
4853 aType == MediaData::Type::VIDEO_DATA);
4854 if (aType == MediaData::Type::AUDIO_DATA) {
4855 return mDecodedAudioEndTime != TimeUnit::Zero();
4857 return mDecodedVideoEndTime != TimeUnit::Zero();
4860 bool MediaDecoderStateMachine::IsCDMProxySupported(CDMProxy* aProxy) {
4861 #ifdef MOZ_WMF_CDM
4862 MOZ_ASSERT(aProxy);
4863 // This proxy only works with the external state machine.
4864 return !aProxy->AsWMFCDMProxy();
4865 #else
4866 return true;
4867 #endif
4870 } // namespace mozilla
4872 // avoid redefined macro in unified build
4873 #undef LOG
4874 #undef LOGV
4875 #undef LOGW
4876 #undef LOGE
4877 #undef SLOGW
4878 #undef SLOGE
4879 #undef NS_DispatchToMainThread