--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -190,32 +190,17 @@ public:
virtual ~StateObject() {}
virtual void Enter() {}; // Entry action.
virtual void Exit() {}; // Exit action.
virtual void Step() {} // Perform a 'cycle' of this state object.
virtual State GetState() const = 0;
// Event handlers for various events.
// Return true if the event is handled by this state object.
- virtual bool HandleDormant(bool aDormant)
- {
- if (!aDormant) {
- return true;
- }
- mMaster->mQueuedSeek.mTarget =
- SeekTarget(mMaster->mCurrentPosition,
- SeekTarget::Accurate,
- MediaDecoderEventVisibility::Suppressed);
- // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
- // need to create the promise even it is not used at all.
- RefPtr<MediaDecoder::SeekPromise> unused =
- mMaster->mQueuedSeek.mPromise.Ensure(__func__);
- SetState(DECODER_STATE_DORMANT);
- return true;
- }
+ virtual bool HandleDormant(bool aDormant);
virtual bool HandleCDMProxyReady() { return false; }
virtual bool HandleAudioDecoded(MediaData* aAudio) { return false; }
virtual bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart)
{
return false;
@@ -292,81 +277,17 @@ public:
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
{
MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek while decoding metadata.");
return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
}
private:
- void OnMetadataRead(MetadataHolder* aMetadata)
- {
- mMetadataRequest.Complete();
-
- // Set mode to PLAYBACK after reading metadata.
- Resource()->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
-
- mMaster->mInfo = Some(aMetadata->mInfo);
- mMaster->mMetadataTags = aMetadata->mTags.forget();
-
- if (Info().mMetadataDuration.isSome()) {
- mMaster->RecomputeDuration();
- } else if (Info().mUnadjustedMetadataEndTime.isSome()) {
- RefPtr<Master> master = mMaster;
- Reader()->AwaitStartTime()->Then(OwnerThread(), __func__,
- [master] () {
- NS_ENSURE_TRUE_VOID(!master->IsShutdown());
- auto& info = master->mInfo.ref();
- TimeUnit unadjusted = info.mUnadjustedMetadataEndTime.ref();
- TimeUnit adjustment = master->mReader->StartTime();
- info.mMetadataDuration.emplace(unadjusted - adjustment);
- master->RecomputeDuration();
- }, [master, this] () {
- SWARN("Adjusting metadata end time failed");
- }
- );
- }
-
- if (mMaster->HasVideo()) {
- SLOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
- Reader()->IsAsync(),
- Reader()->VideoIsHardwareAccelerated(),
- mMaster->GetAmpleVideoFrames());
- }
-
- // In general, we wait until we know the duration before notifying the decoder.
- // However, we notify unconditionally in this case without waiting for the start
- // time, since the caller might be waiting on metadataloaded to be fired before
- // feeding in the CDM, which we need to decode the first frame (and
- // thus get the metadata). We could fix this if we could compute the start
- // time by demuxing without necessaring decoding.
- bool waitingForCDM = Info().IsEncrypted() && !mMaster->mCDMProxy;
-
- mMaster->mNotifyMetadataBeforeFirstFrame =
- mMaster->mDuration.Ref().isSome() || waitingForCDM;
-
- if (mMaster->mNotifyMetadataBeforeFirstFrame) {
- mMaster->EnqueueLoadedMetadataEvent();
- }
-
- if (mPendingDormant) {
- // No need to store mQueuedSeek because we are at position 0.
- SetState(DECODER_STATE_DORMANT);
- return;
- }
-
- if (waitingForCDM) {
- // Metadata parsing was successful but we're still waiting for CDM caps
- // to become available so that we can build the correct decryptor/decoder.
- SetState(DECODER_STATE_WAIT_FOR_CDM);
- return;
- }
-
- SetState(DECODER_STATE_DECODING_FIRSTFRAME);
- }
+ void OnMetadataRead(MetadataHolder* aMetadata);
void OnMetadataNotRead(const MediaResult& aError)
{
mMetadataRequest.Complete();
SWARN("Decode metadata failed, shutting down decoder");
mMaster->DecodeError(aError);
}
@@ -384,30 +305,19 @@ class MediaDecoderStateMachine::WaitForC
public:
explicit WaitForCDMState(Master* aPtr) : StateObject(aPtr) {}
State GetState() const override
{
return DECODER_STATE_WAIT_FOR_CDM;
}
- bool HandleDormant(bool aDormant) override
- {
- if (aDormant) {
- // No need to store mQueuedSeek because we are at position 0.
- SetState(DECODER_STATE_DORMANT);
- }
- return true;
- }
-
- bool HandleCDMProxyReady() override
- {
- SetState(DECODER_STATE_DECODING_FIRSTFRAME);
- return true;
- }
+ bool HandleDormant(bool aDormant) override;
+
+ bool HandleCDMProxyReady() override;
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
{
SLOG("Not Enough Data to seek at this stage, queuing seek");
mMaster->mQueuedSeek.RejectIfExists(__func__);
mMaster->mQueuedSeek.mTarget = aTarget;
return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
}
@@ -428,61 +338,34 @@ public:
mMaster->mReader->ReleaseResources();
}
State GetState() const override
{
return DECODER_STATE_DORMANT;
}
- bool HandleDormant(bool aDormant) override
- {
- if (!aDormant) {
- // Exit dormant state. Check if we need the CDMProxy to start decoding.
- SetState(Info().IsEncrypted() && !mMaster->mCDMProxy
- ? DECODER_STATE_WAIT_FOR_CDM
- : DECODER_STATE_DECODING_FIRSTFRAME);
- }
- return true;
- }
+ bool HandleDormant(bool aDormant) override;
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
{
SLOG("Not Enough Data to seek at this stage, queuing seek");
mMaster->mQueuedSeek.RejectIfExists(__func__);
mMaster->mQueuedSeek.mTarget = aTarget;
return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
}
};
class MediaDecoderStateMachine::DecodingFirstFrameState
: public MediaDecoderStateMachine::StateObject
{
public:
explicit DecodingFirstFrameState(Master* aPtr) : StateObject(aPtr) {}
- void Enter() override
- {
- // Handle pending seek.
- if (mMaster->mQueuedSeek.Exists() &&
- (mMaster->mSentFirstFrameLoadedEvent ||
- Reader()->ForceZeroStartTime())) {
- mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
- return;
- }
-
- // Transition to DECODING if we've decoded first frames.
- if (mMaster->mSentFirstFrameLoadedEvent) {
- SetState(DECODER_STATE_DECODING);
- return;
- }
-
- // Dispatch tasks to decode first frames.
- mMaster->DispatchDecodeTasksIfNeeded();
- }
+ void Enter() override;
State GetState() const override
{
return DECODER_STATE_DECODING_FIRSTFRAME;
}
bool HandleAudioDecoded(MediaData* aAudio) override
{
@@ -499,90 +382,31 @@ public:
}
bool HandleEndOfStream() override
{
MaybeFinishDecodeFirstFrame();
return true;
}
- RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
- {
- // Should've transitioned to DECODING in Enter()
- // if mSentFirstFrameLoadedEvent is true.
- MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
-
- if (!Reader()->ForceZeroStartTime()) {
- SLOG("Not Enough Data to seek at this stage, queuing seek");
- mMaster->mQueuedSeek.RejectIfExists(__func__);
- mMaster->mQueuedSeek.mTarget = aTarget;
- return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
- }
-
- // Since ForceZeroStartTime() is true, we should've transitioned to SEEKING
- // in Enter() if there is any queued seek.
- MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
-
- SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
- SeekJob seekJob;
- seekJob.mTarget = aTarget;
- RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
- mMaster->InitiateSeek(Move(seekJob));
- return p.forget();
- }
+ RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
private:
// Notify FirstFrameLoaded if having decoded first frames and
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
- void MaybeFinishDecodeFirstFrame()
- {
- MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
-
- if ((mMaster->IsAudioDecoding() && mMaster->AudioQueue().GetSize() == 0) ||
- (mMaster->IsVideoDecoding() && mMaster->VideoQueue().GetSize() == 0)) {
- return;
- }
-
- mMaster->FinishDecodeFirstFrame();
-
- if (mMaster->mQueuedSeek.Exists()) {
- mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
- } else {
- SetState(DECODER_STATE_DECODING);
- }
- }
+ void MaybeFinishDecodeFirstFrame();
};
class MediaDecoderStateMachine::DecodingState
: public MediaDecoderStateMachine::StateObject
{
public:
explicit DecodingState(Master* aPtr) : StateObject(aPtr) {}
- void Enter() override
- {
- MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
- // Pending seek should've been handled by DECODING_FIRSTFRAME before
- // transitioning to DECODING.
- MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
-
- if (mMaster->CheckIfDecodeComplete()) {
- SetState(DECODER_STATE_COMPLETED);
- return;
- }
-
- mDecodeStartTime = TimeStamp::Now();
-
- MaybeStopPrerolling();
-
- // Ensure that we've got tasks enqueued to decode data if we need to.
- mMaster->DispatchDecodeTasksIfNeeded();
-
- mMaster->ScheduleStateMachine();
- }
+ void Enter() override;
void Exit() override
{
if (!mDecodeStartTime.IsNull()) {
TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
SLOG("Exiting DECODING, decoded for %.3lfs", decodeDuration.ToSeconds());
}
}
@@ -625,36 +449,19 @@ public:
bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
{
mMaster->Push(aVideo, MediaData::VIDEO_DATA);
MaybeStopPrerolling();
CheckSlowDecoding(aDecodeStart);
return true;
}
- RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
- {
- mMaster->mQueuedSeek.RejectIfExists(__func__);
- SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
- SeekJob seekJob;
- seekJob.mTarget = aTarget;
- RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
- mMaster->InitiateSeek(Move(seekJob));
- return p.forget();
- }
-
- bool HandleEndOfStream() override
- {
- if (mMaster->CheckIfDecodeComplete()) {
- SetState(DECODER_STATE_COMPLETED);
- } else {
- MaybeStopPrerolling();
- }
- return true;
- }
+ RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
+
+ bool HandleEndOfStream() override;
bool HandleWaitingForData() override
{
MaybeStopPrerolling();
return true;
}
bool HandleAudioCaptured() override
@@ -816,58 +623,31 @@ public:
mMaster->SetMediaDecoderReaderWrapperCallback();
}
State GetState() const override
{
return DECODER_STATE_SEEKING;
}
- bool HandleDormant(bool aDormant) override
- {
- if (!aDormant) {
- return true;
- }
- MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
- MOZ_ASSERT(mSeekJob.Exists());
- // Because both audio and video decoders are going to be reset in this
- // method later, we treat a VideoOnly seek task as a normal Accurate
- // seek task so that while it is resumed, both audio and video playback
- // are handled.
- if (mSeekJob.mTarget.IsVideoOnly()) {
- mSeekJob.mTarget.SetType(SeekTarget::Accurate);
- mSeekJob.mTarget.SetVideoOnly(false);
- }
- mMaster->mQueuedSeek = Move(mSeekJob);
- SetState(DECODER_STATE_DORMANT);
- return true;
- }
+ bool HandleDormant(bool aDormant) override;
bool HandleAudioDecoded(MediaData* aAudio) override
{
MOZ_ASSERT(false);
return true;
}
bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
{
MOZ_ASSERT(false);
return true;
}
- RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
- {
- mMaster->mQueuedSeek.RejectIfExists(__func__);
- SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
- SeekJob seekJob;
- seekJob.mTarget = aTarget;
- RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
- mMaster->InitiateSeek(Move(seekJob));
- return p.forget();
- }
+ RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
private:
void OnSeekTaskResolved(const SeekTaskResolveValue& aValue)
{
mSeekTaskRequest.Complete();
if (aValue.mSeekedAudioData) {
mMaster->Push(aValue.mSeekedAudioData, MediaData::AUDIO_DATA);
@@ -902,89 +682,17 @@ private:
if (aValue.mIsVideoQueueFinished) {
mMaster->VideoQueue().Finish();
}
mMaster->DecodeError(aValue.mError);
}
- void SeekCompleted()
- {
- int64_t seekTime = mSeekTask->GetSeekTarget().GetTime().ToMicroseconds();
- int64_t newCurrentTime = seekTime;
-
- // Setup timestamp state.
- RefPtr<MediaData> video = mMaster->VideoQueue().PeekFront();
- if (seekTime == mMaster->Duration().ToMicroseconds()) {
- newCurrentTime = seekTime;
- } else if (mMaster->HasAudio()) {
- RefPtr<MediaData> audio = mMaster->AudioQueue().PeekFront();
- // Though we adjust the newCurrentTime in audio-based, and supplemented
- // by video. For better UX, should NOT bind the slide position to
- // the first audio data timestamp directly.
- // While seeking to a position where there's only either audio or video, or
- // seeking to a position lies before audio or video, we need to check if
- // seekTime is bounded in suitable duration. See Bug 1112438.
- int64_t audioStart = audio ? audio->mTime : seekTime;
- // We only pin the seek time to the video start time if the video frame
- // contains the seek time.
- if (video && video->mTime <= seekTime && video->GetEndTime() > seekTime) {
- newCurrentTime = std::min(audioStart, video->mTime);
- } else {
- newCurrentTime = audioStart;
- }
- } else {
- newCurrentTime = video ? video->mTime : seekTime;
- }
-
- // Change state to DECODING or COMPLETED now.
- bool isLiveStream = Resource()->IsLiveStream();
- State nextState;
- if (newCurrentTime == mMaster->Duration().ToMicroseconds() && !isLiveStream) {
- // Seeked to end of media, move to COMPLETED state. Note we don't do
- // this when playing a live stream, since the end of media will advance
- // once we download more data!
- // Explicitly set our state so we don't decode further, and so
- // we report playback ended to the media element.
- nextState = DECODER_STATE_COMPLETED;
- } else {
- nextState = DECODER_STATE_DECODING;
- }
-
- // We want to resolve the seek request prior finishing the first frame
- // to ensure that the seeked event is fired prior loadeded.
- mSeekJob.Resolve(nextState == DECODER_STATE_COMPLETED, __func__);
-
- // Notify FirstFrameLoaded now if we haven't since we've decoded some data
- // for readyState to transition to HAVE_CURRENT_DATA and fire 'loadeddata'.
- if (!mMaster->mSentFirstFrameLoadedEvent) {
- // Only MSE can start seeking before finishing decoding first frames.
- MOZ_ASSERT(Reader()->ForceZeroStartTime());
- mMaster->FinishDecodeFirstFrame();
- }
-
- // Ensure timestamps are up to date.
- if (!mSeekJob.mTarget.IsVideoOnly()) {
- // Don't update playback position for video-only seek.
- // Otherwise we might have |newCurrentTime > mMediaSink->GetPosition()|
- // and fail the assertion in GetClock() since we didn't stop MediaSink.
- mMaster->UpdatePlaybackPositionInternal(newCurrentTime);
- }
-
- // Try to decode another frame to detect if we're at the end...
- SLOG("Seek completed, mCurrentPosition=%lld", mMaster->mCurrentPosition.Ref());
-
- if (video) {
- mMaster->mMediaSink->Redraw(Info().mVideo);
- mMaster->mOnPlaybackEvent.Notify(MediaEventType::Invalidate);
- }
-
- SetState(nextState);
- }
+ void SeekCompleted();
SeekJob mSeekJob;
MozPromiseRequestHolder<SeekTask::SeekTaskPromise> mSeekTaskRequest;
RefPtr<SeekTask> mSeekTask;
};
class MediaDecoderStateMachine::BufferingState
: public MediaDecoderStateMachine::StateObject
@@ -1003,58 +711,17 @@ public:
MediaStatistics stats = mMaster->GetStatistics();
SLOG("Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)");
mMaster->ScheduleStateMachineIn(USECS_PER_S);
}
- void Step() override
- {
- TimeStamp now = TimeStamp::Now();
- MOZ_ASSERT(!mBufferingStart.IsNull(), "Must know buffering start time.");
-
- // With buffering heuristics we will remain in the buffering state if
- // we've not decoded enough data to begin playback, or if we've not
- // downloaded a reasonable amount of data inside our buffering time.
- if (Reader()->UseBufferingHeuristics()) {
- TimeDuration elapsed = now - mBufferingStart;
- bool isLiveStream = Resource()->IsLiveStream();
- if ((isLiveStream || !mMaster->CanPlayThrough()) &&
- elapsed < TimeDuration::FromSeconds(mBufferingWait * mMaster->mPlaybackRate) &&
- mMaster->HasLowBufferedData(mBufferingWait * USECS_PER_S) &&
- Resource()->IsExpectingMoreData()) {
- SLOG("Buffering: wait %ds, timeout in %.3lfs",
- mBufferingWait, mBufferingWait - elapsed.ToSeconds());
- mMaster->ScheduleStateMachineIn(USECS_PER_S);
- return;
- }
- } else if (mMaster->OutOfDecodedAudio() || mMaster->OutOfDecodedVideo()) {
- MOZ_ASSERT(Reader()->IsWaitForDataSupported(),
- "Don't yet have a strategy for non-heuristic + non-WaitForData");
- mMaster->DispatchDecodeTasksIfNeeded();
- MOZ_ASSERT(mMaster->mMinimizePreroll ||
- !mMaster->OutOfDecodedAudio() ||
- Reader()->IsRequestingAudioData() ||
- Reader()->IsWaitingAudioData());
- MOZ_ASSERT(mMaster->mMinimizePreroll ||
- !mMaster->OutOfDecodedVideo() ||
- Reader()->IsRequestingVideoData() ||
- Reader()->IsWaitingVideoData());
- SLOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
- "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
- mMaster->OutOfDecodedAudio(), mMaster->AudioRequestStatus(),
- mMaster->OutOfDecodedVideo(), mMaster->VideoRequestStatus());
- return;
- }
-
- SLOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
- SetState(DECODER_STATE_DECODING);
- }
+ void Step() override;
State GetState() const override
{
return DECODER_STATE_BUFFERING;
}
bool HandleAudioDecoded(MediaData* aAudio) override
{
@@ -1069,37 +736,19 @@ public:
{
// This might be the sample we need to exit buffering.
// Schedule Step() to check it.
mMaster->Push(aVideo, MediaData::VIDEO_DATA);
mMaster->ScheduleStateMachine();
return true;
}
- bool HandleEndOfStream() override
- {
- if (mMaster->CheckIfDecodeComplete()) {
- SetState(DECODER_STATE_COMPLETED);
- } else {
- // Check if we can exit buffering.
- mMaster->ScheduleStateMachine();
- }
- return true;
- }
-
- RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
- {
- mMaster->mQueuedSeek.RejectIfExists(__func__);
- SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
- SeekJob seekJob;
- seekJob.mTarget = aTarget;
- RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
- mMaster->InitiateSeek(Move(seekJob));
- return p.forget();
- }
+ bool HandleEndOfStream() override;
+
+ RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
private:
TimeStamp mBufferingStart;
// The maximum number of second we spend buffering when we are short on
// unbuffered data.
const uint32_t mBufferingWait = 15;
};
@@ -1163,26 +812,17 @@ public:
}
}
State GetState() const override
{
return DECODER_STATE_COMPLETED;
}
- RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
- {
- mMaster->mQueuedSeek.RejectIfExists(__func__);
- SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
- SeekJob seekJob;
- seekJob.mTarget = aTarget;
- RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
- mMaster->InitiateSeek(Move(seekJob));
- return p.forget();
- }
+ RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
bool HandleAudioCaptured() override
{
// MediaSink is changed. Schedule Step() to check if we can start playback.
mMaster->ScheduleStateMachine();
return true;
}
@@ -1218,16 +858,448 @@ public:
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
{
MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek in shutdown state.");
return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
}
};
+bool
+MediaDecoderStateMachine::
+StateObject::HandleDormant(bool aDormant)
+{
+ if (!aDormant) {
+ return true;
+ }
+ mMaster->mQueuedSeek.mTarget =
+ SeekTarget(mMaster->mCurrentPosition,
+ SeekTarget::Accurate,
+ MediaDecoderEventVisibility::Suppressed);
+ // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
+ // need to create the promise even it is not used at all.
+ RefPtr<MediaDecoder::SeekPromise> unused =
+ mMaster->mQueuedSeek.mPromise.Ensure(__func__);
+ SetState(DECODER_STATE_DORMANT);
+ return true;
+}
+
+void
+MediaDecoderStateMachine::
+DecodeMetadataState::OnMetadataRead(MetadataHolder* aMetadata)
+{
+ mMetadataRequest.Complete();
+
+ // Set mode to PLAYBACK after reading metadata.
+ Resource()->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
+
+ mMaster->mInfo = Some(aMetadata->mInfo);
+ mMaster->mMetadataTags = aMetadata->mTags.forget();
+
+ if (Info().mMetadataDuration.isSome()) {
+ mMaster->RecomputeDuration();
+ } else if (Info().mUnadjustedMetadataEndTime.isSome()) {
+ RefPtr<Master> master = mMaster;
+ Reader()->AwaitStartTime()->Then(OwnerThread(), __func__,
+ [master] () {
+ NS_ENSURE_TRUE_VOID(!master->IsShutdown());
+ auto& info = master->mInfo.ref();
+ TimeUnit unadjusted = info.mUnadjustedMetadataEndTime.ref();
+ TimeUnit adjustment = master->mReader->StartTime();
+ info.mMetadataDuration.emplace(unadjusted - adjustment);
+ master->RecomputeDuration();
+ }, [master, this] () {
+ SWARN("Adjusting metadata end time failed");
+ }
+ );
+ }
+
+ if (mMaster->HasVideo()) {
+ SLOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
+ Reader()->IsAsync(),
+ Reader()->VideoIsHardwareAccelerated(),
+ mMaster->GetAmpleVideoFrames());
+ }
+
+ // In general, we wait until we know the duration before notifying the decoder.
+ // However, we notify unconditionally in this case without waiting for the start
+ // time, since the caller might be waiting on metadataloaded to be fired before
+ // feeding in the CDM, which we need to decode the first frame (and
+ // thus get the metadata). We could fix this if we could compute the start
+ // time by demuxing without necessaring decoding.
+ bool waitingForCDM = Info().IsEncrypted() && !mMaster->mCDMProxy;
+
+ mMaster->mNotifyMetadataBeforeFirstFrame =
+ mMaster->mDuration.Ref().isSome() || waitingForCDM;
+
+ if (mMaster->mNotifyMetadataBeforeFirstFrame) {
+ mMaster->EnqueueLoadedMetadataEvent();
+ }
+
+ if (mPendingDormant) {
+ // No need to store mQueuedSeek because we are at position 0.
+ SetState(DECODER_STATE_DORMANT);
+ return;
+ }
+
+ if (waitingForCDM) {
+ // Metadata parsing was successful but we're still waiting for CDM caps
+ // to become available so that we can build the correct decryptor/decoder.
+ SetState(DECODER_STATE_WAIT_FOR_CDM);
+ return;
+ }
+
+ SetState(DECODER_STATE_DECODING_FIRSTFRAME);
+}
+
+bool
+MediaDecoderStateMachine::
+WaitForCDMState::HandleDormant(bool aDormant)
+{
+ if (aDormant) {
+ // No need to store mQueuedSeek because we are at position 0.
+ SetState(DECODER_STATE_DORMANT);
+ }
+ return true;
+}
+
+bool
+MediaDecoderStateMachine::
+DormantState::HandleDormant(bool aDormant)
+{
+ if (!aDormant) {
+ // Exit dormant state. Check if we need the CDMProxy to start decoding.
+ SetState(Info().IsEncrypted() && !mMaster->mCDMProxy
+ ? DECODER_STATE_WAIT_FOR_CDM
+ : DECODER_STATE_DECODING_FIRSTFRAME);
+ }
+ return true;
+}
+
+bool
+MediaDecoderStateMachine::
+WaitForCDMState::HandleCDMProxyReady()
+{
+ SetState(DECODER_STATE_DECODING_FIRSTFRAME);
+ return true;
+}
+
+void
+MediaDecoderStateMachine::
+DecodingFirstFrameState::Enter()
+{
+ // Handle pending seek.
+ if (mMaster->mQueuedSeek.Exists() &&
+ (mMaster->mSentFirstFrameLoadedEvent ||
+ Reader()->ForceZeroStartTime())) {
+ mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
+ return;
+ }
+
+ // Transition to DECODING if we've decoded first frames.
+ if (mMaster->mSentFirstFrameLoadedEvent) {
+ SetState(DECODER_STATE_DECODING);
+ return;
+ }
+
+ // Dispatch tasks to decode first frames.
+ mMaster->DispatchDecodeTasksIfNeeded();
+}
+
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+DecodingFirstFrameState::HandleSeek(SeekTarget aTarget)
+{
+ // Should've transitioned to DECODING in Enter()
+ // if mSentFirstFrameLoadedEvent is true.
+ MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
+
+ if (!Reader()->ForceZeroStartTime()) {
+ SLOG("Not Enough Data to seek at this stage, queuing seek");
+ mMaster->mQueuedSeek.RejectIfExists(__func__);
+ mMaster->mQueuedSeek.mTarget = aTarget;
+ return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
+ }
+
+ // Since ForceZeroStartTime() is true, we should've transitioned to SEEKING
+ // in Enter() if there is any queued seek.
+ MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
+
+ SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+ SeekJob seekJob;
+ seekJob.mTarget = aTarget;
+ RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
+ mMaster->InitiateSeek(Move(seekJob));
+ return p.forget();
+}
+
+void
+MediaDecoderStateMachine::
+DecodingFirstFrameState::MaybeFinishDecodeFirstFrame()
+{
+ MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
+
+ if ((mMaster->IsAudioDecoding() && mMaster->AudioQueue().GetSize() == 0) ||
+ (mMaster->IsVideoDecoding() && mMaster->VideoQueue().GetSize() == 0)) {
+ return;
+ }
+
+ mMaster->FinishDecodeFirstFrame();
+
+ if (mMaster->mQueuedSeek.Exists()) {
+ mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
+ } else {
+ SetState(DECODER_STATE_DECODING);
+ }
+}
+
+void
+MediaDecoderStateMachine::
+DecodingState::Enter()
+{
+ MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
+ // Pending seek should've been handled by DECODING_FIRSTFRAME before
+ // transitioning to DECODING.
+ MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
+
+ if (mMaster->CheckIfDecodeComplete()) {
+ SetState(DECODER_STATE_COMPLETED);
+ return;
+ }
+
+ mDecodeStartTime = TimeStamp::Now();
+
+ MaybeStopPrerolling();
+
+ // Ensure that we've got tasks enqueued to decode data if we need to.
+ mMaster->DispatchDecodeTasksIfNeeded();
+
+ mMaster->ScheduleStateMachine();
+}
+
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+DecodingState::HandleSeek(SeekTarget aTarget)
+{
+ mMaster->mQueuedSeek.RejectIfExists(__func__);
+ SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+ SeekJob seekJob;
+ seekJob.mTarget = aTarget;
+ RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
+ mMaster->InitiateSeek(Move(seekJob));
+ return p.forget();
+}
+
+bool
+MediaDecoderStateMachine::
+DecodingState::HandleEndOfStream()
+{
+ if (mMaster->CheckIfDecodeComplete()) {
+ SetState(DECODER_STATE_COMPLETED);
+ } else {
+ MaybeStopPrerolling();
+ }
+ return true;
+}
+
+bool
+MediaDecoderStateMachine::
+SeekingState::HandleDormant(bool aDormant)
+{
+ if (!aDormant) {
+ return true;
+ }
+ MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
+ MOZ_ASSERT(mSeekJob.Exists());
+ // Because both audio and video decoders are going to be reset in this
+ // method later, we treat a VideoOnly seek task as a normal Accurate
+ // seek task so that while it is resumed, both audio and video playback
+ // are handled.
+ if (mSeekJob.mTarget.IsVideoOnly()) {
+ mSeekJob.mTarget.SetType(SeekTarget::Accurate);
+ mSeekJob.mTarget.SetVideoOnly(false);
+ }
+ mMaster->mQueuedSeek = Move(mSeekJob);
+ SetState(DECODER_STATE_DORMANT);
+ return true;
+}
+
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+SeekingState::HandleSeek(SeekTarget aTarget)
+{
+ mMaster->mQueuedSeek.RejectIfExists(__func__);
+ SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+ SeekJob seekJob;
+ seekJob.mTarget = aTarget;
+ RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
+ mMaster->InitiateSeek(Move(seekJob));
+ return p.forget();
+}
+
+void
+MediaDecoderStateMachine::
+SeekingState::SeekCompleted()
+{
+ int64_t seekTime = mSeekTask->GetSeekTarget().GetTime().ToMicroseconds();
+ int64_t newCurrentTime = seekTime;
+
+ // Setup timestamp state.
+ RefPtr<MediaData> video = mMaster->VideoQueue().PeekFront();
+ if (seekTime == mMaster->Duration().ToMicroseconds()) {
+ newCurrentTime = seekTime;
+ } else if (mMaster->HasAudio()) {
+ RefPtr<MediaData> audio = mMaster->AudioQueue().PeekFront();
+ // Though we adjust the newCurrentTime in audio-based, and supplemented
+ // by video. For better UX, should NOT bind the slide position to
+ // the first audio data timestamp directly.
+ // While seeking to a position where there's only either audio or video, or
+ // seeking to a position lies before audio or video, we need to check if
+ // seekTime is bounded in suitable duration. See Bug 1112438.
+ int64_t audioStart = audio ? audio->mTime : seekTime;
+ // We only pin the seek time to the video start time if the video frame
+ // contains the seek time.
+ if (video && video->mTime <= seekTime && video->GetEndTime() > seekTime) {
+ newCurrentTime = std::min(audioStart, video->mTime);
+ } else {
+ newCurrentTime = audioStart;
+ }
+ } else {
+ newCurrentTime = video ? video->mTime : seekTime;
+ }
+
+ // Change state to DECODING or COMPLETED now.
+ bool isLiveStream = Resource()->IsLiveStream();
+ State nextState;
+ if (newCurrentTime == mMaster->Duration().ToMicroseconds() && !isLiveStream) {
+ // Seeked to end of media, move to COMPLETED state. Note we don't do
+ // this when playing a live stream, since the end of media will advance
+ // once we download more data!
+ // Explicitly set our state so we don't decode further, and so
+ // we report playback ended to the media element.
+ nextState = DECODER_STATE_COMPLETED;
+ } else {
+ nextState = DECODER_STATE_DECODING;
+ }
+
+ // We want to resolve the seek request prior finishing the first frame
+ // to ensure that the seeked event is fired prior loadeded.
+ mSeekJob.Resolve(nextState == DECODER_STATE_COMPLETED, __func__);
+
+ // Notify FirstFrameLoaded now if we haven't since we've decoded some data
+ // for readyState to transition to HAVE_CURRENT_DATA and fire 'loadeddata'.
+ if (!mMaster->mSentFirstFrameLoadedEvent) {
+ // Only MSE can start seeking before finishing decoding first frames.
+ MOZ_ASSERT(Reader()->ForceZeroStartTime());
+ mMaster->FinishDecodeFirstFrame();
+ }
+
+ // Ensure timestamps are up to date.
+ if (!mSeekJob.mTarget.IsVideoOnly()) {
+ // Don't update playback position for video-only seek.
+ // Otherwise we might have |newCurrentTime > mMediaSink->GetPosition()|
+ // and fail the assertion in GetClock() since we didn't stop MediaSink.
+ mMaster->UpdatePlaybackPositionInternal(newCurrentTime);
+ }
+
+ // Try to decode another frame to detect if we're at the end...
+ SLOG("Seek completed, mCurrentPosition=%lld", mMaster->mCurrentPosition.Ref());
+
+ if (video) {
+ mMaster->mMediaSink->Redraw(Info().mVideo);
+ mMaster->mOnPlaybackEvent.Notify(MediaEventType::Invalidate);
+ }
+
+ SetState(nextState);
+}
+
+void
+MediaDecoderStateMachine::
+BufferingState::Step()
+{
+ TimeStamp now = TimeStamp::Now();
+ MOZ_ASSERT(!mBufferingStart.IsNull(), "Must know buffering start time.");
+
+ // With buffering heuristics we will remain in the buffering state if
+ // we've not decoded enough data to begin playback, or if we've not
+ // downloaded a reasonable amount of data inside our buffering time.
+ if (Reader()->UseBufferingHeuristics()) {
+ TimeDuration elapsed = now - mBufferingStart;
+ bool isLiveStream = Resource()->IsLiveStream();
+ if ((isLiveStream || !mMaster->CanPlayThrough()) &&
+ elapsed < TimeDuration::FromSeconds(mBufferingWait * mMaster->mPlaybackRate) &&
+ mMaster->HasLowBufferedData(mBufferingWait * USECS_PER_S) &&
+ Resource()->IsExpectingMoreData()) {
+ SLOG("Buffering: wait %ds, timeout in %.3lfs",
+ mBufferingWait, mBufferingWait - elapsed.ToSeconds());
+ mMaster->ScheduleStateMachineIn(USECS_PER_S);
+ return;
+ }
+ } else if (mMaster->OutOfDecodedAudio() || mMaster->OutOfDecodedVideo()) {
+ MOZ_ASSERT(Reader()->IsWaitForDataSupported(),
+ "Don't yet have a strategy for non-heuristic + non-WaitForData");
+ mMaster->DispatchDecodeTasksIfNeeded();
+ MOZ_ASSERT(mMaster->mMinimizePreroll ||
+ !mMaster->OutOfDecodedAudio() ||
+ Reader()->IsRequestingAudioData() ||
+ Reader()->IsWaitingAudioData());
+ MOZ_ASSERT(mMaster->mMinimizePreroll ||
+ !mMaster->OutOfDecodedVideo() ||
+ Reader()->IsRequestingVideoData() ||
+ Reader()->IsWaitingVideoData());
+ SLOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
+ "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
+ mMaster->OutOfDecodedAudio(), mMaster->AudioRequestStatus(),
+ mMaster->OutOfDecodedVideo(), mMaster->VideoRequestStatus());
+ return;
+ }
+
+ SLOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
+ SetState(DECODER_STATE_DECODING);
+}
+
+bool
+MediaDecoderStateMachine::
+BufferingState::HandleEndOfStream()
+{
+ if (mMaster->CheckIfDecodeComplete()) {
+ SetState(DECODER_STATE_COMPLETED);
+ } else {
+ // Check if we can exit buffering.
+ mMaster->ScheduleStateMachine();
+ }
+ return true;
+}
+
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+BufferingState::HandleSeek(SeekTarget aTarget)
+{
+ mMaster->mQueuedSeek.RejectIfExists(__func__);
+ SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+ SeekJob seekJob;
+ seekJob.mTarget = aTarget;
+ RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
+ mMaster->InitiateSeek(Move(seekJob));
+ return p.forget();
+}
+
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+CompletedState::HandleSeek(SeekTarget aTarget)
+{
+ mMaster->mQueuedSeek.RejectIfExists(__func__);
+ SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+ SeekJob seekJob;
+ seekJob.mTarget = aTarget;
+ RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
+ mMaster->InitiateSeek(Move(seekJob));
+ return p.forget();
+}
+
#define INIT_WATCHABLE(name, val) \
name(val, "MediaDecoderStateMachine::" #name)
#define INIT_MIRROR(name, val) \
name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Mirror)")
#define INIT_CANONICAL(name, val) \
name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Canonical)")
MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,