Bug 1405025. P1 - ensure 'seeking' is fired before 'waiting'.
Use MediaEventSource instead of state-mirroring to notify nextFrameStatus
changes so we have more control over the order of events.
MozReview-Commit-ID: 3DGtMbghEQm
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -369,17 +369,16 @@ MediaDecoder::MediaDecoder(MediaDecoderI
, mFiredMetadataLoaded(false)
, mIsDocumentVisible(false)
, mElementVisibility(Visibility::UNTRACKED)
, mIsElementInTree(false)
, mForcedHidden(false)
, mHasSuspendTaint(aInit.mHasSuspendTaint)
, mPlaybackRate(aInit.mPlaybackRate)
, INIT_MIRROR(mBuffered, TimeIntervals())
- , INIT_MIRROR(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE)
, INIT_MIRROR(mCurrentPosition, TimeUnit::Zero())
, INIT_MIRROR(mStateMachineDuration, NullableTimeUnit())
, INIT_MIRROR(mPlaybackPosition, 0)
, INIT_MIRROR(mIsAudioDataAudible, false)
, INIT_CANONICAL(mVolume, aInit.mVolume)
, INIT_CANONICAL(mPreservesPitch, aInit.mPreservesPitch)
, INIT_CANONICAL(mLooping, aInit.mLooping)
, INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
@@ -399,17 +398,16 @@ MediaDecoder::MediaDecoder(MediaDecoderI
// Initialize watchers.
//
// mDuration
mWatchManager.Watch(mStateMachineDuration, &MediaDecoder::DurationChanged);
// readyState
mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
- mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
// ReadyState computation depends on MediaDecoder::CanPlayThrough, which
// depends on the download rate.
mWatchManager.Watch(mBuffered, &MediaDecoder::UpdateReadyState);
// mLogicalPosition
mWatchManager.Watch(mCurrentPosition, &MediaDecoder::UpdateLogicalPosition);
mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateLogicalPosition);
mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::UpdateLogicalPosition);
@@ -447,16 +445,17 @@ MediaDecoder::Shutdown()
mFirstFrameLoadedListener.Disconnect();
mOnPlaybackEvent.Disconnect();
mOnPlaybackErrorEvent.Disconnect();
mOnDecoderDoctorEvent.Disconnect();
mOnMediaNotSeekable.Disconnect();
mOnEncrypted.Disconnect();
mOnWaitingForKey.Disconnect();
mOnDecodeWarning.Disconnect();
+ mOnNextFrameStatus.Disconnect();
mDecoderStateMachine->BeginShutdown()
->Then(mAbstractMainThread, __func__, this,
&MediaDecoder::FinishShutdown,
&MediaDecoder::FinishShutdown);
} else {
// Ensure we always unregister asynchronously in order not to disrupt
// the hashtable iterating in MediaShutdownManager::Shutdown().
@@ -552,16 +551,46 @@ MediaDecoder::OnDecoderDoctorEvent(Decod
nsIDocument* doc = GetOwner()->GetDocument();
if (!doc) {
return;
}
DecoderDoctorDiagnostics diags;
diags.StoreEvent(doc, aEvent, __func__);
}
+static const char*
+NextFrameStatusToStr(MediaDecoderOwner::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
+MediaDecoder::OnNextFrameStatus(MediaDecoderOwner::NextFrameStatus aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
+ if (mNextFrameStatus != aStatus) {
+ LOG("Changed mNextFrameStatus to %s", NextFrameStatusToStr(aStatus));
+ mNextFrameStatus = aStatus;
+ UpdateReadyState();
+ }
+}
+
void
MediaDecoder::FinishShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
SetStateMachine(nullptr);
mVideoFrameContainer = nullptr;
MediaShutdownManager::Instance().Unregister(this);
}
@@ -601,16 +630,18 @@ MediaDecoder::SetStateMachineParameters(
mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
mAbstractMainThread, this, &MediaDecoder::OnPlaybackEvent);
mOnPlaybackErrorEvent = mDecoderStateMachine->OnPlaybackErrorEvent().Connect(
mAbstractMainThread, this, &MediaDecoder::OnPlaybackErrorEvent);
mOnDecoderDoctorEvent = mDecoderStateMachine->OnDecoderDoctorEvent().Connect(
mAbstractMainThread, this, &MediaDecoder::OnDecoderDoctorEvent);
mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
mAbstractMainThread, this, &MediaDecoder::OnMediaNotSeekable);
+ mOnNextFrameStatus = mDecoderStateMachine->OnNextFrameStatus().Connect(
+ mAbstractMainThread, this, &MediaDecoder::OnNextFrameStatus);
mOnEncrypted = mReader->OnEncrypted().Connect(
mAbstractMainThread, GetOwner(), &MediaDecoderOwner::DispatchEncrypted);
mOnWaitingForKey = mReader->OnWaitingForKey().Connect(
mAbstractMainThread, GetOwner(), &MediaDecoderOwner::NotifyWaitingForKey);
mOnDecodeWarning = mReader->OnDecodeWarning().Connect(
mAbstractMainThread, GetOwner(), &MediaDecoderOwner::DecodeWarning);
}
@@ -1242,29 +1273,27 @@ MediaDecoder::SetLooping(bool aLooping)
void
MediaDecoder::ConnectMirrors(MediaDecoderStateMachine* aObject)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aObject);
mStateMachineDuration.Connect(aObject->CanonicalDuration());
mBuffered.Connect(aObject->CanonicalBuffered());
- mNextFrameStatus.Connect(aObject->CanonicalNextFrameStatus());
mCurrentPosition.Connect(aObject->CanonicalCurrentPosition());
mPlaybackPosition.Connect(aObject->CanonicalPlaybackOffset());
mIsAudioDataAudible.Connect(aObject->CanonicalIsAudioDataAudible());
}
void
MediaDecoder::DisconnectMirrors()
{
MOZ_ASSERT(NS_IsMainThread());
mStateMachineDuration.DisconnectIfConnected();
mBuffered.DisconnectIfConnected();
- mNextFrameStatus.DisconnectIfConnected();
mCurrentPosition.DisconnectIfConnected();
mPlaybackPosition.DisconnectIfConnected();
mIsAudioDataAudible.DisconnectIfConnected();
}
void
MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
{
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -489,16 +489,18 @@ private:
void OnDecoderDoctorEvent(DecoderDoctorEvent aEvent);
void OnMediaNotSeekable()
{
mMediaSeekable = false;
}
+ void OnNextFrameStatus(MediaDecoderOwner::NextFrameStatus);
+
void FinishShutdown();
void ConnectMirrors(MediaDecoderStateMachine* aObject);
void DisconnectMirrors();
virtual bool CanPlayThroughImpl() = 0;
virtual bool IsLiveStream() = 0;
@@ -574,40 +576,41 @@ protected:
// If true, forces the decoder to be considered hidden.
bool mForcedHidden;
// True if the decoder has a suspend taint - meaning suspend-video-decoder is
// disabled.
bool mHasSuspendTaint;
+ MediaDecoderOwner::NextFrameStatus mNextFrameStatus =
+ MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
+
// A listener to receive metadata updates from MDSM.
MediaEventListener mTimedMetadataListener;
MediaEventListener mMetadataLoadedListener;
MediaEventListener mFirstFrameLoadedListener;
MediaEventListener mOnPlaybackEvent;
MediaEventListener mOnPlaybackErrorEvent;
MediaEventListener mOnDecoderDoctorEvent;
MediaEventListener mOnMediaNotSeekable;
MediaEventListener mOnEncrypted;
MediaEventListener mOnWaitingForKey;
MediaEventListener mOnDecodeWarning;
+ MediaEventListener mOnNextFrameStatus;
protected:
// PlaybackRate and pitch preservation status we should start at.
double mPlaybackRate;
// Buffered range, mirrored from the reader.
Mirror<media::TimeIntervals> mBuffered;
- // NextFrameStatus, mirrored from the state machine.
- Mirror<MediaDecoderOwner::NextFrameStatus> mNextFrameStatus;
-
// NB: Don't use mCurrentPosition directly, but rather CurrentPosition().
Mirror<media::TimeUnit> mCurrentPosition;
// Duration of the media resource according to the state machine.
Mirror<media::NullableTimeUnit> mStateMachineDuration;
// Current playback position in the stream. This is (approximately)
// where we're up to playing back the stream. This is not adjusted
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -864,17 +864,17 @@ public:
// request is from the user.
if (mVisibility == EventVisibility::Observable) {
// Don't stop playback for a video-only seek since we want to keep playing
// audio and we don't need to stop playback while leaving dormant for the
// playback should has been stopped.
mMaster->StopPlayback();
mMaster->UpdatePlaybackPositionInternal(mSeekJob.mTarget->GetTime());
mMaster->mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
- mMaster->UpdateNextFrameStatus(
+ mMaster->mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
}
RefPtr<MediaDecoder::SeekPromise> p = mSeekJob.mPromise.Ensure(__func__);
DoSeek();
return p;
@@ -1139,18 +1139,19 @@ protected:
if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
SLOG("OnSeekRejected reason=WAITING_FOR_DATA type=%d", aReject.mType);
MOZ_ASSERT_IF(aReject.mType == MediaData::AUDIO_DATA, !mMaster->IsRequestingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::VIDEO_DATA, !mMaster->IsRequestingVideoData());
MOZ_ASSERT_IF(aReject.mType == MediaData::AUDIO_DATA, !mMaster->IsWaitingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::VIDEO_DATA, !mMaster->IsWaitingVideoData());
// Fire 'waiting' to notify the player that we are waiting for data.
- mMaster->UpdateNextFrameStatus(
+ mMaster->mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
+
Reader()
->WaitForData(aReject.mType)
->Then(OwnerThread(), __func__,
[this](MediaData::Type aType) {
SLOG("OnSeekRejected wait promise resolved");
mWaitRequest.Complete();
DemuxerSeek();
},
@@ -1790,17 +1791,17 @@ public:
void Enter()
{
if (mMaster->IsPlaying()) {
mMaster->StopPlayback();
}
mBufferingStart = TimeStamp::Now();
mMaster->ScheduleStateMachineIn(TimeUnit::FromMicroseconds(USECS_PER_S));
- mMaster->UpdateNextFrameStatus(
+ mMaster->mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING);
}
void Step() override;
State GetState() const override { return DECODER_STATE_BUFFERING; }
void HandleAudioDecoded(AudioData* aAudio) override
@@ -1896,17 +1897,17 @@ public:
// We've decoded all samples.
// We don't need decoders anymore if not looping.
Reader()->ReleaseResources();
}
#endif
bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted) &&
(!mMaster->HasVideo() || !mMaster->mVideoCompleted);
- mMaster->UpdateNextFrameStatus(
+ mMaster->mOnNextFrameStatus.Notify(
hasNextFrame ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
: MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
Step();
}
void Exit() override
{
@@ -2276,17 +2277,17 @@ DecodingState::Enter()
});
mOnVideoPopped = VideoQueue().PopEvent().Connect(
OwnerThread(), [this] () {
if (mMaster->IsVideoDecoding() && !mMaster->HaveEnoughDecodedVideo()) {
EnsureVideoDecodeTaskQueued();
}
});
- mMaster->UpdateNextFrameStatus(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
+ mMaster->mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
mDecodeStartTime = TimeStamp::Now();
MaybeStopPrerolling();
// Ensure that we've got tasks enqueued to decode data if we need to.
DispatchDecodeTasksIfNeeded();
@@ -2568,17 +2569,16 @@ ShutdownState::Enter()
master->mPlayState.DisconnectIfConnected();
master->mVolume.DisconnectIfConnected();
master->mPreservesPitch.DisconnectIfConnected();
master->mLooping.DisconnectIfConnected();
master->mSameOriginMedia.DisconnectIfConnected();
master->mMediaPrincipalHandle.DisconnectIfConnected();
master->mDuration.DisconnectAll();
- master->mNextFrameStatus.DisconnectAll();
master->mCurrentPosition.DisconnectAll();
master->mPlaybackOffset.DisconnectAll();
master->mIsAudioDataAudible.DisconnectAll();
// Shut down the watch manager to stop further notifications.
master->mWatchManager.Shutdown();
return Reader()->Shutdown()->Then(
@@ -2621,17 +2621,16 @@ MediaDecoderStateMachine::MediaDecoderSt
INIT_MIRROR(mBuffered, TimeIntervals()),
INIT_MIRROR(mPlayState, MediaDecoder::PLAY_STATE_LOADING),
INIT_MIRROR(mVolume, 1.0),
INIT_MIRROR(mPreservesPitch, true),
INIT_MIRROR(mLooping, false),
INIT_MIRROR(mSameOriginMedia, false),
INIT_MIRROR(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE),
INIT_CANONICAL(mDuration, NullableTimeUnit()),
- INIT_CANONICAL(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE),
INIT_CANONICAL(mCurrentPosition, TimeUnit::Zero()),
INIT_CANONICAL(mPlaybackOffset, 0),
INIT_CANONICAL(mIsAudioDataAudible, false)
#ifdef XP_WIN
, mShouldUseHiResTimers(Preferences::GetBool("media.hi-res-timers.enabled", true))
#endif
{
MOZ_COUNT_CTOR(MediaDecoderStateMachine);
@@ -3482,44 +3481,16 @@ 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(TimeUnit::FromMicroseconds(delay));
}
-/* 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());
- if (aStatus != mNextFrameStatus) {
- LOG("Changed mNextFrameStatus to %s", ToStr(aStatus));
- mNextFrameStatus = aStatus;
- }
-}
-
void
MediaDecoderStateMachine::ScheduleStateMachine()
{
MOZ_ASSERT(OnTaskQueue());
if (mDispatchedStateMachine) {
return;
}
mDispatchedStateMachine = true;
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -247,16 +247,19 @@ public:
MediaEventSource<MediaEventType>&
OnPlaybackEvent() { return mOnPlaybackEvent; }
MediaEventSource<MediaResult>&
OnPlaybackErrorEvent() { return mOnPlaybackErrorEvent; }
MediaEventSource<DecoderDoctorEvent>&
OnDecoderDoctorEvent() { return mOnDecoderDoctorEvent; }
+ MediaEventSource<NextFrameStatus>&
+ OnNextFrameStatus() { return mOnNextFrameStatus; }
+
size_t SizeOfVideoQueue() const;
size_t SizeOfAudioQueue() const;
// Sets the video decode mode. Used by the suspend-video-decoder feature.
void SetVideoDecodeMode(VideoDecodeMode aMode);
private:
@@ -270,17 +273,16 @@ private:
class NextFrameSeekingState;
class NextFrameSeekingFromDormantState;
class VideoOnlySeekingState;
class BufferingState;
class CompletedState;
class ShutdownState;
static const char* ToStateStr(State aState);
- static const char* ToStr(NextFrameStatus aStatus);
const char* ToStateStr();
nsCString GetDebugInfo();
// Functions used by assertions to ensure we're calling things
// on the appropriate threads.
bool OnTaskQueue() const;
@@ -378,18 +380,16 @@ protected:
// Returns true if we're running low on buffered data.
bool HasLowBufferedData();
// Returns true if we have less than aThreshold of buffered data available.
bool HasLowBufferedData(const media::TimeUnit& aThreshold);
- 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.
media::TimeUnit GetClock(TimeStamp* aTimeStamp = nullptr) const;
// Update only the state machine's current playback position (and duration,
@@ -656,16 +656,18 @@ private:
MediaEventProducerExc<nsAutoPtr<MediaInfo>,
MediaDecoderEventVisibility> mFirstFrameLoadedEvent;
MediaEventProducer<MediaEventType> mOnPlaybackEvent;
MediaEventProducer<MediaResult> mOnPlaybackErrorEvent;
MediaEventProducer<DecoderDoctorEvent> mOnDecoderDoctorEvent;
+ MediaEventProducer<NextFrameStatus> mOnNextFrameStatus;
+
const bool mIsMSE;
private:
// The buffered range. Mirrored from the decoder thread.
Mirror<media::TimeIntervals> mBuffered;
// The current play state, mirrored from the main thread.
Mirror<MediaDecoder::PlayState> mPlayState;
@@ -687,20 +689,16 @@ private:
// An identifier for the principal of the media. Used to track when
// main-thread induced principal changes get reflected on MSG thread.
Mirror<PrincipalHandle> mMediaPrincipalHandle;
// Duration of the media. This is guaranteed to be non-null after we finish
// decoding the first frame.
Canonical<media::NullableTimeUnit> mDuration;
- // The status of our next frame. Mirrored on the main thread and used to
- // compute ready state.
- Canonical<NextFrameStatus> mNextFrameStatus;
-
// The time of the current frame, corresponding to the "current
// playback position" in HTML5. This is referenced from 0, which is the initial
// playback position.
Canonical<media::TimeUnit> mCurrentPosition;
// Current playback position in the stream in bytes.
Canonical<int64_t> mPlaybackOffset;
@@ -709,20 +707,16 @@ private:
public:
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() const;
AbstractCanonical<media::NullableTimeUnit>* CanonicalDuration()
{
return &mDuration;
}
- AbstractCanonical<NextFrameStatus>* CanonicalNextFrameStatus()
- {
- return &mNextFrameStatus;
- }
AbstractCanonical<media::TimeUnit>* CanonicalCurrentPosition()
{
return &mCurrentPosition;
}
AbstractCanonical<int64_t>* CanonicalPlaybackOffset()
{
return &mPlaybackOffset;
}