Bug 1309116. Part 1 - move some functions out of class definition because we need to see the complete definition when trying to 'new' an object. draft
authorJW Wang <jwwang@mozilla.com>
Tue, 11 Oct 2016 14:22:24 +0800
changeset 425043 6ecdcd48b954df49d142702605c2c9dd54478d8b
parent 425042 cbf9b307df18d3ec7f8068b3d8abc77703c85f0c
child 425044 ea4a14f8e64228e492000611c0e4e70c44792369
push id32322
push userjwwang@mozilla.com
push dateFri, 14 Oct 2016 02:59:40 +0000
bugs1309116
milestone52.0a1
Bug 1309116. Part 1 - move some functions out of class definition because we need to see the complete definition when trying to 'new' an object. MozReview-Commit-ID: 3pOPiFeAnPI
dom/media/MediaDecoderStateMachine.cpp
--- 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,