Bug 1314219. Part 1 - align next frame status updates with state changes of MDSM.
We want entering/exiting dormant to be transparent to the media element. So we don't
change next frame status when entering/exiting dormant.
MozReview-Commit-ID: DCWxAGZ9sVw
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -352,16 +352,17 @@ class MediaDecoderStateMachine::WaitForC
: public MediaDecoderStateMachine::StateObject
{
public:
explicit WaitForCDMState(Master* aPtr) : StateObject(aPtr) {}
void Enter()
{
MOZ_ASSERT(!mMaster->mVideoDecodeSuspended);
+ mMaster->UpdateNextFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
}
void Exit() override
{
// mPendingSeek is either moved in HandleCDMProxyReady() or should be
// rejected here before transition to SHUTDOWN.
mPendingSeek.RejectIfExists(__func__);
}
@@ -563,16 +564,20 @@ public:
// Start playback if necessary so that the clock can be properly queried.
if (!mIsPrerolling) {
mMaster->MaybeStartPlayback();
}
mMaster->UpdatePlaybackPositionPeriodically();
+ // Ensure currentTime is up to date prior updating mNextFrameStatus so that
+ // the MediaDecoderOwner fire events at correct currentTime.
+ mMaster->UpdateNextFrameStatus();
+
MOZ_ASSERT(!mMaster->IsPlaying() ||
mMaster->IsStateMachineScheduled(),
"Must have timer scheduled");
MaybeStartBuffering();
}
State GetState() const override
@@ -753,16 +758,17 @@ class MediaDecoderStateMachine::SeekingS
{
public:
explicit SeekingState(Master* aPtr) : StateObject(aPtr) {}
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob aSeekJob,
EventVisibility aVisibility)
{
mSeekJob = Move(aSeekJob);
+ mVisibility = aVisibility;
// Always switch off the blank decoder otherwise we might become visible
// in the middle of seeking and won't have a valid video frame to show
// when seek is done.
if (mMaster->mVideoDecodeSuspended) {
mMaster->mVideoDecodeSuspended = false;
mMaster->mOnPlaybackEvent.Notify(MediaEventType::ExitVideoSuspend);
Reader()->SetVideoBlankDecode(false);
@@ -793,18 +799,21 @@ public:
// mSeekJob.mTarget.mTime might be different from
// mSeekTask->GetSeekTarget().mTime because the seek task might clamp the
// seek target to [0, duration]. We want to update the playback position to
// the clamped value.
mMaster->UpdatePlaybackPositionInternal(
mSeekTask->GetSeekTarget().GetTime().ToMicroseconds());
- if (aVisibility == EventVisibility::Observable) {
+ if (mVisibility == EventVisibility::Observable) {
mMaster->mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
+ // We want dormant actions to be transparent to the user.
+ // So we only notify the change when the seek request is from the user.
+ mMaster->UpdateNextFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
}
// Reset our state machine and decoding pipeline before seeking.
if (mSeekTask->NeedToResetMDSM()) {
if (mSeekJob.mTarget.IsVideoOnly()) {
mMaster->Reset(TrackInfo::kVideoTrack);
} else {
mMaster->Reset();
@@ -901,16 +910,17 @@ private:
}
mMaster->DecodeError(aValue.mError);
}
void SeekCompleted();
SeekJob mSeekJob;
+ EventVisibility mVisibility = EventVisibility::Observable;
MozPromiseRequestHolder<SeekTask::SeekTaskPromise> mSeekTaskRequest;
RefPtr<SeekTask> mSeekTask;
};
/**
* Purpose: stop playback until enough data is decoded to continue playback.
*
* Transition to:
@@ -934,16 +944,18 @@ public:
mBufferingStart = TimeStamp::Now();
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);
+
+ mMaster->UpdateNextFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING);
}
void Step() override;
State GetState() const override
{
return DECODER_STATE_BUFFERING;
}
@@ -1318,16 +1330,18 @@ DecodingFirstFrameState::Enter(SeekJob a
}
MOZ_ASSERT(!mMaster->mVideoDecodeSuspended);
mPendingSeek = Move(aPendingSeek);
// Dispatch tasks to decode first frames.
mMaster->DispatchDecodeTasksIfNeeded();
+
+ mMaster->UpdateNextFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
}
RefPtr<MediaDecoder::SeekPromise>
MediaDecoderStateMachine::
DecodingFirstFrameState::HandleSeek(SeekTarget aTarget)
{
// Should've transitioned to DECODING in Enter()
// if mSentFirstFrameLoadedEvent is true.
@@ -1514,16 +1528,20 @@ SeekingState::SeekCompleted()
// 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);
}
+ if (mVisibility == EventVisibility::Observable) {
+ mMaster->UpdateNextFrameStatus();
+ }
+
if (nextState == DECODER_STATE_COMPLETED) {
SetState<CompletedState>();
} else {
SetState<DecodingState>();
}
}
void
@@ -1753,19 +1771,16 @@ MediaDecoderStateMachine::Initialization
mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
mMediaPrincipalHandle.Connect(aDecoder->CanonicalMediaPrincipalHandle());
mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
// Initialize watchers.
mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
- mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
- mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
- mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
if (MediaPrefs::MDSMSuspendBackgroundVideoEnabled()) {
@@ -1992,36 +2007,33 @@ MediaDecoderStateMachine::Push(MediaData
// TODO: Send aSample to MSG and recalculate readystate before pushing,
// otherwise AdvanceFrame may pop the sample before we have a chance
// to reach playing.
aSample->As<VideoData>()->mFrameID = ++mCurrentFrameID;
VideoQueue().Push(aSample);
} else {
// TODO: Handle MediaRawData, determine which queue should be pushed.
}
- UpdateNextFrameStatus();
DispatchDecodeTasksIfNeeded();
}
void
MediaDecoderStateMachine::OnAudioPopped(const RefPtr<MediaData>& aSample)
{
MOZ_ASSERT(OnTaskQueue());
mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
- UpdateNextFrameStatus();
DispatchAudioDecodeTaskIfNeeded();
}
void
MediaDecoderStateMachine::OnVideoPopped(const RefPtr<MediaData>& aSample)
{
MOZ_ASSERT(OnTaskQueue());
mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
- UpdateNextFrameStatus();
DispatchVideoDecodeTaskIfNeeded();
}
void
MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
const MediaResult& aError)
{
MOZ_ASSERT(OnTaskQueue());
@@ -2923,51 +2935,46 @@ MediaDecoderStateMachine::UpdatePlayback
// Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
// the monitor and get a staled value from GetCurrentTimeUs() which hits the
// assertion in GetClock().
int64_t delay = std::max<int64_t>(1, AUDIO_DURATION_USECS / mPlaybackRate);
ScheduleStateMachineIn(delay);
}
-void MediaDecoderStateMachine::UpdateNextFrameStatus()
+/* static */ const char*
+MediaDecoderStateMachine::ToStr(NextFrameStatus aStatus)
+{
+ switch (aStatus) {
+ case MediaDecoderOwner::NEXT_FRAME_AVAILABLE: return "NEXT_FRAME_AVAILABLE";
+ case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE: return "NEXT_FRAME_UNAVAILABLE";
+ case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING: return "NEXT_FRAME_UNAVAILABLE_BUFFERING";
+ case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING: return "NEXT_FRAME_UNAVAILABLE_SEEKING";
+ case MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED: return "NEXT_FRAME_UNINITIALIZED";
+ }
+ return "UNKNOWN";
+}
+
+void
+MediaDecoderStateMachine::UpdateNextFrameStatus(NextFrameStatus aStatus)
{
MOZ_ASSERT(OnTaskQueue());
-
- MediaDecoderOwner::NextFrameStatus status;
- const char* statusString;
-
- switch (mState.Ref()) {
- case DECODER_STATE_BUFFERING:
- status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING;
- statusString = "NEXT_FRAME_UNAVAILABLE_BUFFERING";
- break;
- case DECODER_STATE_SEEKING:
- status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING;
- statusString = "NEXT_FRAME_UNAVAILABLE_SEEKING";
- break;
- default:
- bool b = HaveNextFrameData();
- status = b ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE :
- MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
- statusString = b ? "NEXT_FRAME_AVAILABLE" : "NEXT_FRAME_UNAVAILABLE";
- break;
+ if (aStatus != mNextFrameStatus) {
+ DECODER_LOG("Changed mNextFrameStatus to %s", ToStr(aStatus));
+ mNextFrameStatus = aStatus;
}
-
- if (status != mNextFrameStatus) {
- DECODER_LOG("Changed mNextFrameStatus to %s", statusString);
- if(status == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING ||
- status == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) {
- // Ensure currentTime is up to date prior updating mNextFrameStatus so that
- // the MediaDecoderOwner fire events at correct currentTime.
- UpdatePlaybackPositionPeriodically();
- }
- }
-
- mNextFrameStatus = status;
+}
+
+void
+MediaDecoderStateMachine::UpdateNextFrameStatus()
+{
+ MOZ_ASSERT(OnTaskQueue());
+ UpdateNextFrameStatus(HaveNextFrameData()
+ ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
+ : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
}
bool
MediaDecoderStateMachine::CanPlayThrough()
{
MOZ_ASSERT(OnTaskQueue());
return GetStatistics().CanPlayThrough();
}
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -265,16 +265,17 @@ private:
class DecodingFirstFrameState;
class DecodingState;
class SeekingState;
class BufferingState;
class CompletedState;
class ShutdownState;
static const char* ToStateStr(State aState);
+ static const char* ToStr(NextFrameStatus aStatus);
const char* ToStateStr();
// Functions used by assertions to ensure we're calling things
// on the appropriate threads.
bool OnTaskQueue() const;
// Initialization that needs to happen on the task queue. This is the first
// task that gets run on the task queue, and is dispatched from the MDSM
@@ -396,16 +397,17 @@ protected:
// Returns true when there's decoded audio waiting to play.
// The decoder monitor must be held.
bool HasFutureAudio();
// Recomputes mNextFrameStatus, possibly dispatching notifications to interested
// parties.
void UpdateNextFrameStatus();
+ void UpdateNextFrameStatus(NextFrameStatus aStatus);
// Return the current time, either the audio clock if available (if the media
// has audio, and the playback is possible), or a clock for the video.
// Called on the state machine thread.
// If aTimeStamp is non-null, set *aTimeStamp to the TimeStamp corresponding
// to the returned stream time.
int64_t GetClock(TimeStamp* aTimeStamp = nullptr) const;