Bug 1121692 - Make seeks cancelable. r=cpearce, r=mattwoodrow, a=sledru
authorBobby Holley <bobbyholley@gmail.com>
Fri, 16 Jan 2015 10:58:00 -0800
changeset 242942 2195dc79a65f
parent 242941 3e1dd9e96598
child 242943 4f059ea15ecf
push id4343
push userryanvm@gmail.com
push date2015-01-20 16:38 +0000
treeherdermozilla-beta@c5dfa7d081f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, mattwoodrow, sledru
bugs1121692
milestone36.0
Bug 1121692 - Make seeks cancelable. r=cpearce, r=mattwoodrow, a=sledru
dom/media/MediaDecoder.cpp
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -611,20 +611,18 @@ nsresult MediaDecoder::Seek(double aTime
 
   int64_t timeUsecs = 0;
   nsresult rv = SecondsToUsecs(aTime, timeUsecs);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType);
   mCurrentTime = aTime;
 
-  // If we are already in the seeking state, then setting mRequestedSeekTarget
-  // above will result in the new seek occurring when the current seek
-  // completes.
-  if (mPlayState != PLAY_STATE_LOADING && mPlayState != PLAY_STATE_SEEKING) {
+  // If we are already in the seeking state, the new seek overrides the old one.
+  if (mPlayState != PLAY_STATE_LOADING) {
     bool paused = false;
     if (mOwner) {
       paused = mOwner->GetPaused();
     }
     mNextState = paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING;
     PinForSeek();
     ChangeState(PLAY_STATE_SEEKING);
   }
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -151,16 +151,25 @@ public:
   virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
 
   // Moves the decode head to aTime microseconds. aEndTime denotes the end
   // time of the media in usecs. This is only needed for OggReader, and should
   // probably be removed somehow.
   virtual nsRefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) = 0;
 
+  // Cancels an ongoing seek, if any. Any previously-requested seek is
+  // guaranteeed to be resolved or rejected in finite time, though no
+  // guarantees are made about precise nature of the resolve/reject, since the
+  // promise might have already dispatched a resolution or an error code before
+  // the cancel arrived.
+  //
+  // Must be called on the decode task queue.
+  virtual void CancelSeek() { };
+
   // Called to move the reader into idle state. When the reader is
   // created it is assumed to be active (i.e. not idle). When the media
   // element is paused and we don't need to decode any more data, the state
   // machine calls SetIdle() to inform the reader that its decoder won't be
   // needed for a while. The reader can use these notifications to enter
   // a low power state when the decoder isn't needed, if desired.
   // This is most useful on mobile.
   // Note: DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek should
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -215,16 +215,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mDropAudioUntilNextDiscontinuity(false),
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
   mWaitingForDecoderSeek(false),
+  mCancelingSeek(false),
   mCurrentTimeBeforeSeek(0),
   mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
   mDecodingFrozenAtStateDecoding(false),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -1658,20 +1659,16 @@ void MediaDecoderStateMachine::Seek(cons
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
   if (!mDecoder->IsMediaSeekable()) {
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return;
   }
 
-  // MediaDecoder::mPlayState should be SEEKING while we seek, and
-  // in that case MediaDecoder shouldn't be calling us.
-  NS_ASSERTION(mState != DECODER_STATE_SEEKING,
-               "We shouldn't already be seeking");
   NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
   if (mState < DECODER_STATE_DECODING) {
     DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
     mQueuedSeekTarget = aTarget;
     return;
   }
@@ -2345,22 +2342,38 @@ MediaDecoderStateMachine::FinishDecodeFi
 }
 
 void MediaDecoderStateMachine::DecodeSeek()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
   if (mState != DECODER_STATE_SEEKING ||
-      !mSeekTarget.IsValid() ||
-      mCurrentSeekTarget.IsValid()) {
+      !mSeekTarget.IsValid()) {
     DECODER_LOG("Early returning from DecodeSeek");
     return;
   }
 
+  // If there's already an existing seek in progress, we need to handle that.
+  if (mCurrentSeekTarget.IsValid()) {
+    // There are 3 states we might be in, listed in the order that they occur:
+    //   (1) Waiting for the seek to be resolved.
+    //   (2) Waiting for the seek to be resolved, having already issued a cancel.
+    //   (3) After seek resolution, waiting for SeekComplete to run.
+    //
+    // If we're in the first state, we move to the second. Otherwise, we just wait
+    // for things to sort themselves out.
+    if (mWaitingForDecoderSeek && !mCancelingSeek) {
+      mReader->CancelSeek();
+      mCancelingSeek = true;
+    }
+
+    return;
+  }
+
   mCurrentSeekTarget = mSeekTarget;
   mSeekTarget.Reset();
   mDropAudioUntilNextDiscontinuity = HasAudio();
   mDropVideoUntilNextDiscontinuity = HasVideo();
 
   // During the seek, don't have a lock on the decoder state,
   // otherwise long seek operations can block the main thread.
   // The events dispatched to the main thread are SYNC calls.
@@ -2434,33 +2447,43 @@ void MediaDecoderStateMachine::DecodeSee
   }
 }
 
 void
 MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mWaitingForDecoderSeek = false;
+  mCancelingSeek = false;
 
   // We must decode the first samples of active streams, so we can determine
   // the new stream time. So dispatch tasks to do that.
   mDecodeToSeekTarget = true;
   DispatchDecodeTasksIfNeeded();
 }
 
 void
 MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  bool wasCanceled = mCancelingSeek;
   mWaitingForDecoderSeek = false;
-  // Sometimes we reject the promise for non-failure reasons, like
-  // when we request a second seek before the previous one has
-  // completed.
+  mCancelingSeek = false;
+
   if (NS_FAILED(aResult)) {
     DecodeError();
+  } else if (wasCanceled && mSeekTarget.IsValid() && mState == DECODER_STATE_SEEKING) {
+    // Try again.
+    mCurrentSeekTarget = mSeekTarget;
+    mSeekTarget.Reset();
+    mReader->Seek(mCurrentSeekTarget.mTime, mEndTime)
+           ->Then(DecodeTaskQueue(), __func__, this,
+                  &MediaDecoderStateMachine::OnSeekCompleted,
+                  &MediaDecoderStateMachine::OnSeekFailed);
+    mWaitingForDecoderSeek = true;
   }
 }
 
 void
 MediaDecoderStateMachine::SeekCompleted()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
@@ -2513,17 +2536,22 @@ MediaDecoderStateMachine::SeekCompleted(
   }
 
   // Change state to DECODING or COMPLETED now. SeekingStopped will
   // call MediaDecoderStateMachine::Seek to reset our state to SEEKING
   // if we need to seek again.
 
   nsCOMPtr<nsIRunnable> stopEvent;
   bool isLiveStream = mDecoder->GetResource()->GetLength() == -1;
-  if (GetMediaTime() == mEndTime && !isLiveStream) {
+  if (mSeekTarget.IsValid()) {
+    // A new seek target came in while we were processing the old one. No rest
+    // for the seeking.
+    DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING");
+    SetState(DECODER_STATE_SEEKING);
+  } else if (GetMediaTime() == mEndTime && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
     DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     SetState(DECODER_STATE_COMPLETED);
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1048,16 +1048,20 @@ protected:
   // mCurrentSeekTarget.
   bool mDecodeToSeekTarget;
 
   // True if we've issued Seek() to the reader, but haven't yet received
   // OnSeekCompleted. We should avoid trying to decode more audio/video
   // until this completes.
   bool mWaitingForDecoderSeek;
 
+  // True if we're in the process of canceling a seek. This allows us to avoid
+  // invoking CancelSeek() multiple times.
+  bool mCancelingSeek;
+
   // We record the playback position before we seek in order to
   // determine where the seek terminated relative to the playback position
   // we were at before the seek.
   int64_t mCurrentTimeBeforeSeek;
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -602,41 +602,58 @@ MediaSourceReader::NotifyTimeRangesChang
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg which is non-MSE */)
 {
   MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aEnd=%lld, aCurrent=%lld)",
             this, aTime);
 
-  mSeekPromise.RejectIfExists(NS_OK, __func__);
+  MOZ_ASSERT(mSeekPromise.IsEmpty());
   nsRefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
 
   if (IsShutdown()) {
     mSeekPromise.Reject(NS_ERROR_FAILURE, __func__);
     return p;
   }
 
   // Store pending seek target in case the track buffers don't contain
   // the desired time and we delay doing the seek.
   mPendingSeekTime = aTime;
 
-  // Only increment the number of expected OnSeekCompleted
-  // notifications if we weren't already waiting for AttemptSeek
-  // to complete (and they would have been accounted for already).
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mWaitingForSeekData = true;
   }
 
   AttemptSeek();
   return p;
 }
 
 void
+MediaSourceReader::CancelSeek()
+{
+  MOZ_ASSERT(OnDecodeThread());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  if (mWaitingForSeekData) {
+    mSeekPromise.Reject(NS_OK, __func__);
+    mWaitingForSeekData = false;
+    mPendingSeekTime = -1;
+  } else if (mVideoIsSeeking) {
+    // NB: Currently all readers have sync Seeks(), so this is a no-op.
+    mVideoReader->CancelSeek();
+  } else if (mAudioIsSeeking) {
+    // NB: Currently all readers have sync Seeks(), so this is a no-op.
+    mAudioReader->CancelSeek();
+  } else {
+    MOZ_ASSERT(mSeekPromise.IsEmpty());
+  }
+}
+
+void
 MediaSourceReader::OnVideoSeekCompleted(int64_t aTime)
 {
   mPendingSeekTime = aTime;
   MOZ_ASSERT(mVideoIsSeeking);
   MOZ_ASSERT(!mAudioIsSeeking);
   mVideoIsSeeking = false;
 
   if (mAudioTrack) {
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -91,16 +91,18 @@ public:
 
   bool IsMediaSeekable() MOZ_OVERRIDE { return true; }
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE;
   void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
   nsRefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE;
 
+  void CancelSeek() MOZ_OVERRIDE;
+
   // Acquires the decoder monitor, and is thus callable on any thread.
   nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;
 
   already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType,
                                                          int64_t aTimestampOffset /* microseconds */);
 
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);