Bug 1322800 part 13 - disconnect NextFrameSeekingState and SeekTask; r?jwwang draft
authorKaku Kuo <kaku@mozilla.com>
Thu, 15 Dec 2016 17:25:44 +0800
changeset 450341 1b38e6931e5c8f6acbb11dd348beddf373bc7d11
parent 450340 5d222dd4abb4382bc06e036b66e8cbce92982477
child 450342 1b951a5436d6f1c176d9f129a81b107231b82b55
push id38836
push userbmo:kaku@mozilla.com
push dateFri, 16 Dec 2016 10:40:27 +0000
reviewersjwwang
bugs1322800
milestone53.0a1
Bug 1322800 part 13 - disconnect NextFrameSeekingState and SeekTask; r?jwwang MozReview-Commit-ID: 9bcYSd2fsp1
dom/media/MediaDecoderStateMachine.cpp
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1359,77 +1359,116 @@ public:
 
   RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob aSeekJob,
                                           EventVisibility aVisibility)
   {
     MOZ_ASSERT(aSeekJob.mTarget.IsNextFrame());
     return SeekingState::Enter(Move(aSeekJob), aVisibility);
   }
 
+  void Exit() override
+  {
+    // Disconnect my async seek operation.
+    mAsyncSeekTask->Cancel();
+
+    // Disconnect MediaDecoder.
+    mSeekJob.RejectIfExists(__func__);
+  }
+
 private:
+  // VisualStudio does not allow inner class to access protected member of the
+  // enclosing class' parent class. Redefine AudioQueue()/VideoQueue() here so
+  // that AysncNextFrameSeekTask could use these version instead of
+  // StateObject::{Audio,Video}Queue().
+  MediaQueue<MediaData>& AudioQueue() const { return mMaster->mAudioQueue; }
+  MediaQueue<MediaData>& VideoQueue() const { return mMaster->mVideoQueue; }
+
+  class AysncNextFrameSeekTask : public Runnable
+  {
+  public:
+    explicit AysncNextFrameSeekTask(NextFrameSeekingState* aStateObject)
+      : mStateObj(aStateObject)
+    {
+    }
+
+    ~AysncNextFrameSeekTask() {}
+
+    void Cancel() { mIsCancelled = true; }
+
+    NS_IMETHOD Run()
+    {
+      if (!mIsCancelled) {
+        auto currentTime = mStateObj->mCurrentTime;
+        DiscardFrames(mStateObj->VideoQueue(), [currentTime] (int64_t aSampleTime) {
+          return aSampleTime <= currentTime;
+        });
+
+        if (!mStateObj->IsVideoRequestPending() && mStateObj->NeedMoreVideo()) {
+          mStateObj->RequestVideoData();
+        }
+
+        mStateObj->MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue.
+      }
+
+      return NS_OK;
+    }
+
+  private:
+    bool mIsCancelled = false;
+    NextFrameSeekingState* mStateObj;
+  };
+
   void CreateSeekTask() override
   {
     mCurrentTime = mMaster->GetMediaTime();
     mDuration = mMaster->Duration();
-
-    mSeekTask = new NextFrameSeekTask(
-      mMaster->mDecoderID, OwnerThread(), Reader(), mSeekJob.mTarget,
-      Info(), mMaster->Duration(),mMaster->GetMediaTime(),
-      AudioQueue(), VideoQueue());
-
-    mTask = static_cast<NextFrameSeekTask*>(mSeekTask.get());
   }
 
   void ResetMDSM() override
   {
     // Do nothing.
   }
 
   void DoSeek() override
   {
-    auto currentTime = mCurrentTime;
-    DiscardFrames(VideoQueue(), [currentTime] (int64_t aSampleTime) {
-      return aSampleTime <= currentTime;
-    });
-
-    mSeekTaskRequest.Begin(mSeekTask->Seek(mMaster->Duration())
-      ->Then(OwnerThread(), __func__,
-             [this] (const SeekTaskResolveValue& aValue) {
-               OnSeekTaskResolved(aValue);
-             },
-             [this] (const SeekTaskRejectValue& aValue) {
-               OnSeekTaskRejected(aValue);
-             }));
-
-    if (!IsVideoRequestPending() && NeedMoreVideo()) {
-      RequestVideoData();
-    }
-    MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue.
+    // We need to do the seek operation asynchronously. Because for a special
+    // case (bug504613.ogv) which has no data at all, the 1st seekToNextFrame()
+    // operation reaches to the end of the media. If we did the seek operation
+    // synchronously, we immediately resolve the SeekPromise in mSeekJob and
+    // then switch to the CompletedState which dispatches an "ended" event.
+    // However, the ThenValue of the SeekPromise has not yet been set, so the
+    // promise resolving is postponed and then the JS developer receives the
+    // "ended" event before the seek promise is resolved.
+    // An asynchronous seek operation helps to solve this issue since while the
+    // seek is actually performed, the ThenValue of SeekPromise has already
+    // been set so that it won't be postponed.
+    RefPtr<Runnable> r = mAsyncSeekTask = new AysncNextFrameSeekTask(this);
+    OwnerThread()->Dispatch(r.forget());
   }
 
   void HandleAudioDecoded(MediaData* aAudio) override
   {
     MOZ_ASSERT(aAudio);
-    MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
 
     // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
     // resolved.
 
     SSAMPLELOG("OnAudioDecoded [%lld,%lld]", aAudio->mTime, aAudio->GetEndTime());
 
     // We accept any audio data here.
     mSeekedAudioData = aAudio;
 
     MaybeFinishSeek();
   }
 
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     MOZ_ASSERT(aVideo);
-    MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
 
     // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
     // resolved.
 
     SSAMPLELOG("OnVideoDecoded [%lld,%lld]", aVideo->mTime, aVideo->GetEndTime());
 
     if (aVideo->mTime > mCurrentTime) {
       mSeekedVideoData = aVideo;
@@ -1440,34 +1479,32 @@ private:
       return;
     }
 
     MaybeFinishSeek();
   }
 
   void HandleNotDecoded(MediaData::Type aType, const MediaResult& aError) override
   {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+
     switch (aType) {
     case MediaData::AUDIO_DATA:
     {
-      MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
-
       SSAMPLELOG("OnAudioNotDecoded (aError=%u)", aError.Code());
 
       // We don't really handle audio deocde error here. Let MDSM to trigger further
       // audio decoding tasks if it needs to play audio, and MDSM will then receive
       // the decoding state from MediaDecoderReader.
 
       MaybeFinishSeek();
       break;
     }
     case MediaData::VIDEO_DATA:
     {
-      MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
-
       SSAMPLELOG("OnVideoNotDecoded (aError=%u)", aError.Code());
 
       if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
         mIsVideoQueueFinished = true;
       }
 
       // Video seek not finished.
       if (NeedMoreVideo()) {
@@ -1478,67 +1515,67 @@ private:
           case NS_ERROR_DOM_MEDIA_CANCELED:
             RequestVideoData();
             break;
           case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
             MOZ_ASSERT(false, "Shouldn't want more data for ended video.");
             break;
           default:
             // Reject the promise since we can't finish video seek anyway.
-            mTask->RejectIfExist(aError, __func__);
+            OnSeekTaskRejected(aError);
             break;
         }
         return;
       }
 
       MaybeFinishSeek();
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("We cannot handle RAW_DATA or NULL_DATA here.");
     }
   }
 
   void HandleAudioWaited(MediaData::Type aType) override
   {
-    MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
 
     // We don't make an audio decode request here, instead, let MDSM to
     // trigger further audio decode tasks if MDSM itself needs to play audio.
     MaybeFinishSeek();
   }
 
   void HandleVideoWaited(MediaData::Type aType) override
   {
-    MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
 
     if (NeedMoreVideo()) {
       RequestVideoData();
       return;
     }
     MaybeFinishSeek();
   }
 
   void HandleNotWaited(const WaitForDataRejectValue& aRejection) override
   {
-    MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
 
     switch(aRejection.mType) {
     case MediaData::AUDIO_DATA:
     {
       // We don't make an audio decode request here, instead, let MDSM to
       // trigger further audio decode tasks if MDSM itself needs to play audio.
       MaybeFinishSeek();
       break;
     }
     case MediaData::VIDEO_DATA:
     {
       if (NeedMoreVideo()) {
         // Reject if we can't finish video seeking.
-        mTask->RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+        OnSeekTaskRejected(NS_ERROR_DOM_MEDIA_CANCELED);
         return;
       }
       MaybeFinishSeek();
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("We cannot handle RAW_DATA or NULL_DATA here.");
     }
@@ -1546,56 +1583,52 @@ private:
 
   int64_t CalculateNewCurrentTime() const override
   {
     // The HTMLMediaElement.currentTime should be updated to the seek target
     // which has been updated to the next frame's time.
     return mSeekJob.mTarget.GetTime().ToMicroseconds();
   }
 
-  void OnSeekTaskResolved(const SeekTaskResolveValue& aValue)
+  void OnSeekTaskResolved()
   {
-    mSeekTaskRequest.Complete();
-
-    if (aValue.mSeekedAudioData) {
-      mMaster->Push(aValue.mSeekedAudioData);
+    if (mSeekedAudioData) {
+      mMaster->Push(mSeekedAudioData);
       mMaster->mDecodedAudioEndTime = std::max(
-        aValue.mSeekedAudioData->GetEndTime(), mMaster->mDecodedAudioEndTime);
+        mSeekedAudioData->GetEndTime(), mMaster->mDecodedAudioEndTime);
     }
 
-    if (aValue.mSeekedVideoData) {
-      mMaster->Push(aValue.mSeekedVideoData);
+    if (mSeekedVideoData) {
+      mMaster->Push(mSeekedVideoData);
       mMaster->mDecodedVideoEndTime = std::max(
-        aValue.mSeekedVideoData->GetEndTime(), mMaster->mDecodedVideoEndTime);
+        mSeekedVideoData->GetEndTime(), mMaster->mDecodedVideoEndTime);
     }
 
-    if (aValue.mIsAudioQueueFinished) {
+    if (mIsAudioQueueFinished) {
       AudioQueue().Finish();
     }
 
-    if (aValue.mIsVideoQueueFinished) {
+    if (mIsVideoQueueFinished) {
       VideoQueue().Finish();
     }
 
     SeekCompleted();
   }
 
-  void OnSeekTaskRejected(const SeekTaskRejectValue& aValue)
+  void OnSeekTaskRejected(MediaResult aError)
   {
-    mSeekTaskRequest.Complete();
-
-    if (aValue.mIsAudioQueueFinished) {
+    if (mIsAudioQueueFinished) {
       AudioQueue().Finish();
     }
 
-    if (aValue.mIsVideoQueueFinished) {
+    if (mIsVideoQueueFinished) {
       VideoQueue().Finish();
     }
 
-    mMaster->DecodeError(aValue.mError);
+    mMaster->DecodeError(aError);
   }
 
   void RequestVideoData()
   {
     Reader()->RequestVideoData(false, media::TimeUnit());
   }
 
   bool NeedMoreVideo() const
@@ -1647,37 +1680,34 @@ private:
     if (IsAudioSeekComplete() && IsVideoSeekComplete()) {
       UpdateSeekTargetTime();
 
       auto time = mSeekJob.mTarget.GetTime().ToMicroseconds();
       DiscardFrames(AudioQueue(), [time] (int64_t aSampleTime) {
         return aSampleTime < time;
       });
 
-      mTask->Resolve(__func__); // Call to MDSM::SeekCompleted();
+      OnSeekTaskResolved();
     }
   }
 
   /*
    * Internal state.
    */
   int64_t mCurrentTime;
   media::TimeUnit mDuration;
+  RefPtr<AysncNextFrameSeekTask> mAsyncSeekTask;
 
   /*
    * Information which are going to be returned to MDSM.
    */
   RefPtr<MediaData> mSeekedAudioData;
   RefPtr<MediaData> mSeekedVideoData;
   bool mIsAudioQueueFinished = false;
   bool mIsVideoQueueFinished = false;
-
-  // For refactoring only, will be removed later.
-  RefPtr<NextFrameSeekTask> mTask;
-
 };
 
 /**
  * Purpose: stop playback until enough data is decoded to continue playback.
  *
  * Transition to:
  *   SEEKING if any seek request.
  *   SHUTDOWN if any decode error.