Bug 1405025. P1 - ensure 'seeking' is fired before 'waiting'. draft
authorJW Wang <jwwang@mozilla.com>
Fri, 06 Oct 2017 15:48:38 +0800
changeset 676690 3c4a43e298a3d7b31e8b7cde40547225eef76a85
parent 676409 d721710e6de941ed44c7f0cca974da6ebf705582
child 676691 9e40bce811cd136bddcdc9540889b20d4c461870
push id83586
push userjwwang@mozilla.com
push dateMon, 09 Oct 2017 09:49:11 +0000
bugs1405025
milestone58.0a1
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
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
--- 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;
   }