Bug 1261020 - part 1 - implement SeekTask; r?jwwang draft
authorKaku Kuo <tkuo@mozilla.com>
Fri, 08 Apr 2016 12:09:57 +0800
changeset 348792 4d5a0252ce9b827b161f0c7338be0117ee3fcf13
parent 348788 2c6fafe4002aac97199a4d6f8dcf07558ef0dd18
child 348793 46cac463768a085d97c3e150240d929158dbc4a2
child 348795 0523a66fe66a6acfa80bdb72b3b05e37f249fbb8
child 348851 972d0533c68caba12af81570d3cba27c29cd5003
push id14925
push usertkuo@mozilla.com
push dateFri, 08 Apr 2016 04:11:06 +0000
reviewersjwwang
bugs1261020
milestone48.0a1
Bug 1261020 - part 1 - implement SeekTask; r?jwwang MozReview-Commit-ID: 57ERVuxJq2h
dom/media/MediaDecoderStateMachine.h
dom/media/SeekJob.cpp
dom/media/SeekJob.h
dom/media/SeekTask.cpp
dom/media/SeekTask.h
dom/media/moz.build
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -90,16 +90,17 @@ hardware (via AudioStream).
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaStatistics.h"
 #include "MediaTimer.h"
 #include "ImageContainer.h"
+#include "SeekTask.h"
 
 namespace mozilla {
 
 namespace media {
 class MediaSink;
 }
 
 class AudioSegment;
@@ -509,63 +510,16 @@ protected:
 
   // Dispatches a LoadedMetadataEvent.
   // This is threadsafe and can be called on any thread.
   // The decoder monitor must be held.
   void EnqueueLoadedMetadataEvent();
 
   void EnqueueFirstFrameLoadedEvent();
 
-  struct SeekJob {
-    SeekJob() {}
-
-    SeekJob(SeekJob&& aOther) : mTarget(aOther.mTarget)
-    {
-      aOther.mTarget.Reset();
-      mPromise = Move(aOther.mPromise);
-    }
-
-    SeekJob& operator=(SeekJob&& aOther)
-    {
-      MOZ_DIAGNOSTIC_ASSERT(!Exists());
-      mTarget = aOther.mTarget;
-      aOther.mTarget.Reset();
-      mPromise = Move(aOther.mPromise);
-      return *this;
-    }
-
-    bool Exists()
-    {
-      MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty());
-      return mTarget.IsValid();
-    }
-
-    void Resolve(bool aAtEnd, const char* aCallSite)
-    {
-      mTarget.Reset();
-      MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility);
-      mPromise.Resolve(val, aCallSite);
-    }
-
-    void RejectIfExists(const char* aCallSite)
-    {
-      mTarget.Reset();
-      mPromise.RejectIfExists(true, aCallSite);
-    }
-
-    ~SeekJob()
-    {
-      MOZ_DIAGNOSTIC_ASSERT(!mTarget.IsValid());
-      MOZ_DIAGNOSTIC_ASSERT(mPromise.IsEmpty());
-    }
-
-    SeekTarget mTarget;
-    MozPromiseHolder<MediaDecoder::SeekPromise> mPromise;
-  };
-
   // Clears any previous seeking state and initiates a new see on the decoder.
   // The decoder monitor must be held.
   void InitiateSeek(SeekJob aSeekJob);
 
   nsresult DispatchAudioDecodeTaskIfNeeded();
 
   // Ensures a task to decode audio has been dispatched to the decode task queue.
   // If a task to decode has already been dispatched, this does nothing,
new file mode 100644
--- /dev/null
+++ b/dom/media/SeekJob.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SeekJob.h"
+
+namespace mozilla {
+
+SeekJob::SeekJob()
+{
+}
+
+SeekJob::SeekJob(SeekJob&& aOther) : mTarget(aOther.mTarget)
+{
+  aOther.mTarget.Reset();
+  mPromise = Move(aOther.mPromise);
+}
+
+SeekJob::~SeekJob()
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mTarget.IsValid());
+  MOZ_DIAGNOSTIC_ASSERT(mPromise.IsEmpty());
+}
+
+SeekJob& SeekJob::operator=(SeekJob&& aOther)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!Exists());
+  mTarget = aOther.mTarget;
+  aOther.mTarget.Reset();
+  mPromise = Move(aOther.mPromise);
+  return *this;
+}
+
+bool SeekJob::Exists()
+{
+  MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty());
+  return mTarget.IsValid();
+}
+
+void SeekJob::Resolve(bool aAtEnd, const char* aCallSite)
+{
+  MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility);
+  mPromise.Resolve(val, aCallSite);
+  mTarget.Reset();
+}
+
+void SeekJob::RejectIfExists(const char* aCallSite)
+{
+  mTarget.Reset();
+  mPromise.RejectIfExists(true, aCallSite);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/SeekJob.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SEEK_JOB_H
+#define SEEK_JOB_H
+
+#include "mozilla/MozPromise.h"
+#include "MediaDecoderReader.h"
+#include "SeekTarget.h"
+
+namespace mozilla {
+
+struct SeekJob {
+
+  SeekJob();
+
+  SeekJob(SeekJob&& aOther);
+
+  ~SeekJob();
+
+  SeekJob& operator=(SeekJob&& aOther);
+
+  bool Exists();
+
+  void Resolve(bool aAtEnd, const char* aCallSite);
+
+  void RejectIfExists(const char* aCallSite);
+
+  SeekTarget mTarget;
+  MozPromiseHolder<MediaDecoder::SeekPromise> mPromise;
+};
+
+} // namespace mozilla
+
+#endif /* SEEK_JOB_H */
new file mode 100644
--- /dev/null
+++ b/dom/media/SeekTask.cpp
@@ -0,0 +1,718 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SeekTask.h"
+#include "MediaDecoderReaderWrapper.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+extern LazyLogModule gMediaSampleLog;
+
+// avoid redefined macro in unified build
+#undef LOG
+#undef DECODER_LOG
+#undef VERBOSE_LOG
+
+#define LOG(m, l, x, ...) \
+  MOZ_LOG(m, l, ("[SeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
+#define DECODER_LOG(x, ...) \
+  LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
+#define VERBOSE_LOG(x, ...) \
+  LOG(gMediaDecoderLog, LogLevel::Verbose, x, ##__VA_ARGS__)
+#define SAMPLE_LOG(x, ...) \
+  LOG(gMediaSampleLog, LogLevel::Debug, x, ##__VA_ARGS__)
+
+// Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
+// when __VA_ARGS__ expands to nothing. This is a workaround for it.
+#define DECODER_WARN_HELPER(a, b) NS_WARNING b
+#define DECODER_WARN(x, ...) \
+  DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get()))
+
+namespace media {
+
+/*static*/ SeekTask*
+SeekTask::CreateSeekTask(const void* aDecoderID,
+                         AbstractThread* aThread,
+                         MediaDecoderReader* aReader,
+                         MediaDecoderReaderWrapper* aReaderWrapper,
+                         SeekJob&& aSeekJob,
+                         const MediaInfo& aInfo,
+                         const media::TimeUnit& aDuration,
+                         int64_t aCurrentMediaTime)
+{
+  return new SeekTask(aDecoderID, aThread, aReader, aReaderWrapper,
+                      Forward<SeekJob>(aSeekJob), aInfo, aDuration,
+                      aCurrentMediaTime);
+}
+
+SeekTask::SeekTask(const void* aDecoderID,
+                   AbstractThread* aThread,
+                   MediaDecoderReader* aReader,
+                   MediaDecoderReaderWrapper* aReaderWrapper,
+                   SeekJob&& aSeekJob,
+                   const MediaInfo& aInfo,
+                   const media::TimeUnit& aDuration,
+                   int64_t aCurrentMediaTime)
+  : mDecoderID(aDecoderID)
+  , mOwnerThread(aThread)
+  , mReader(aReader)
+  , mReaderWrapper(aReaderWrapper)
+  , mAudioRate(aInfo.mAudio.mRate)
+  , mHasAudio(aInfo.HasAudio())
+  , mHasVideo(aInfo.HasVideo())
+  , mSeekJob(Forward<SeekJob>(aSeekJob))
+  , mCurrentTimeBeforeSeek(aCurrentMediaTime)
+  , mDropAudioUntilNextDiscontinuity(false)
+  , mDropVideoUntilNextDiscontinuity(false)
+  , mIsAudioQueueFinished(false)
+  , mIsVideoQueueFinished(false)
+  , mNeedToStopPrerollingAudio(false)
+  , mNeedToStopPrerollingVideo(false)
+{
+  // Bound the seek time to be inside the media range.
+  int64_t end = aDuration.ToMicroseconds();
+  NS_ASSERTION(end != -1, "Should know end time by now");
+  int64_t seekTime = mSeekJob.mTarget.GetTime().ToMicroseconds();
+  seekTime = std::min(seekTime, end);
+  seekTime = std::max(int64_t(0), seekTime);
+  NS_ASSERTION(seekTime >= 0 && seekTime <= end,
+               "Can only seek in range [0,duration]");
+  mSeekJob.mTarget.SetTime(media::TimeUnit::FromMicroseconds(seekTime));
+
+  mDropAudioUntilNextDiscontinuity = HasAudio();
+  mDropVideoUntilNextDiscontinuity = HasVideo();
+}
+
+SeekTask::~SeekTask()
+{
+  Discard();
+}
+
+void
+SeekTask::Resolve(const char* aCallSite)
+{
+  SeekTaskResolveValue val;
+  val.mSeekedAudioData = mSeekedAudioData;
+  val.mSeekedVideoData = mSeekedVideoData;
+  val.mIsAudioQueueFinished = mIsAudioQueueFinished;
+  val.mIsVideoQueueFinished = mIsVideoQueueFinished;
+  val.mNeedToStopPrerollingAudio = mNeedToStopPrerollingAudio;
+  val.mNeedToStopPrerollingVideo = mNeedToStopPrerollingVideo;
+
+  mSeekTaskPromise.Resolve(val, aCallSite);
+}
+
+void
+SeekTask::RejectIfExist(const char* aCallSite)
+{
+  SeekTaskRejectValue val;
+  val.mIsAudioQueueFinished = mIsAudioQueueFinished;
+  val.mIsVideoQueueFinished = mIsVideoQueueFinished;
+  val.mNeedToStopPrerollingAudio = mNeedToStopPrerollingAudio;
+  val.mNeedToStopPrerollingVideo = mNeedToStopPrerollingVideo;
+
+  mSeekTaskPromise.RejectIfExists(val, aCallSite);
+}
+
+void
+SeekTask::AssertOwnerThread() const
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+}
+
+bool
+SeekTask::HasAudio() const
+{
+  return mHasAudio;
+}
+
+bool
+SeekTask::HasVideo() const
+{
+  return mHasVideo;
+}
+
+TaskQueue*
+SeekTask::DecodeTaskQueue() const
+{
+  return mReader->OwnerThread();
+}
+
+AbstractThread*
+SeekTask::OwnerThread() const
+{
+  return mOwnerThread;
+}
+
+void
+SeekTask::Discard()
+{
+  // Disconnect MediaDecoder.
+  mSeekJob.RejectIfExists(__func__);
+
+  // Disconnect MDSM.
+  RejectIfExist(__func__);
+
+  // Disconnect MediaDecoderReader.
+  mSeekRequest.DisconnectIfExists();
+  mAudioDataRequest.DisconnectIfExists();
+  mVideoDataRequest.DisconnectIfExists();
+  mAudioWaitRequest.DisconnectIfExists();
+  mVideoWaitRequest.DisconnectIfExists();
+}
+
+bool
+SeekTask::NeedToResetMDSM() const
+{
+  return true;
+}
+
+SeekJob&
+SeekTask::GetSeekJob()
+{
+  return mSeekJob;
+}
+
+bool
+SeekTask::Exists()
+{
+  return mSeekJob.Exists();
+}
+
+RefPtr<SeekTask::SeekTaskPromise>
+SeekTask::Seek(const media::TimeUnit& aDuration)
+{
+  AssertOwnerThread();
+
+  // Do the seek.
+  mSeekRequest.Begin(mReaderWrapper->Seek(mSeekJob.mTarget, aDuration)
+    ->Then(OwnerThread(), __func__, this,
+           &SeekTask::OnSeekResolved, &SeekTask::OnSeekRejected));
+
+  return mSeekTaskPromise.Ensure(__func__);
+}
+
+bool
+SeekTask::IsVideoDecoding() const
+{
+  AssertOwnerThread();
+  return HasVideo() && !mIsVideoQueueFinished;
+}
+
+bool
+SeekTask::IsAudioDecoding() const
+{
+  AssertOwnerThread();
+  return HasAudio() && !mIsAudioQueueFinished;
+}
+
+nsresult
+SeekTask::EnsureAudioDecodeTaskQueued()
+{
+  AssertOwnerThread();
+
+  SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%s",
+              IsAudioDecoding(), AudioRequestStatus());
+
+  if (!IsAudioDecoding() ||
+      mAudioDataRequest.Exists() ||
+      mAudioWaitRequest.Exists() ||
+      mSeekRequest.Exists()) {
+    return NS_OK;
+  }
+
+  RequestAudioData();
+  return NS_OK;
+}
+
+nsresult
+SeekTask::EnsureVideoDecodeTaskQueued()
+{
+  AssertOwnerThread();
+
+  SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s",
+             IsVideoDecoding(), VideoRequestStatus());
+
+  if (!IsVideoDecoding() ||
+      mVideoDataRequest.Exists() ||
+      mVideoWaitRequest.Exists() ||
+      mSeekRequest.Exists()) {
+    return NS_OK;
+  }
+
+  RequestVideoData();
+  return NS_OK;
+}
+
+const char*
+SeekTask::AudioRequestStatus()
+{
+  AssertOwnerThread();
+  if (mAudioDataRequest.Exists()) {
+    MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists());
+    return "pending";
+  } else if (mAudioWaitRequest.Exists()) {
+    return "waiting";
+  }
+  return "idle";
+}
+
+const char*
+SeekTask::VideoRequestStatus()
+{
+  AssertOwnerThread();
+  if (mVideoDataRequest.Exists()) {
+    MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists());
+    return "pending";
+  } else if (mVideoWaitRequest.Exists()) {
+    return "waiting";
+  }
+  return "idle";
+}
+
+void
+SeekTask::RequestAudioData()
+{
+  AssertOwnerThread();
+
+  SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
+             !!mSeekedAudioData, mReader->SizeOfAudioQueueInFrames());
+
+  mAudioDataRequest.Begin(mReaderWrapper->RequestAudioData()
+    ->Then(OwnerThread(), __func__, this,
+           &SeekTask::OnAudioDecoded, &SeekTask::OnAudioNotDecoded));
+}
+
+void
+SeekTask::RequestVideoData()
+{
+  AssertOwnerThread();
+  //These two variables are not used in the SEEKING state.
+  const bool skipToNextKeyFrame = false;
+  const media::TimeUnit currentTime = media::TimeUnit::FromMicroseconds(0);
+
+  SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
+               !!mSeekedVideoData, mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
+               currentTime);
+
+  mVideoDataRequest.Begin(
+    mReaderWrapper->RequestVideoData(skipToNextKeyFrame, currentTime)
+    ->Then(OwnerThread(), __func__, this,
+           &SeekTask::OnVideoDecoded, &SeekTask::OnVideoNotDecoded));
+}
+
+nsresult
+SeekTask::DropAudioUpToSeekTarget(MediaData* aSample)
+{
+  AssertOwnerThread();
+  RefPtr<AudioData> audio(aSample->As<AudioData>());
+  MOZ_ASSERT(audio && mSeekJob.Exists() && mSeekJob.mTarget.IsAccurate());
+
+  CheckedInt64 sampleDuration = FramesToUsecs(audio->mFrames, mAudioRate);
+  if (!sampleDuration.isValid()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (audio->mTime + sampleDuration.value() <= mSeekJob.mTarget.GetTime().ToMicroseconds()) {
+    // Our seek target lies after the frames in this AudioData. Don't
+    // push it onto the audio queue, and keep decoding forwards.
+    return NS_OK;
+  }
+
+  if (audio->mTime > mSeekJob.mTarget.GetTime().ToMicroseconds()) {
+    // The seek target doesn't lie in the audio block just after the last
+    // audio frames we've seen which were before the seek target. This
+    // could have been the first audio data we've seen after seek, i.e. the
+    // seek terminated after the seek target in the audio stream. Just
+    // abort the audio decode-to-target, the state machine will play
+    // silence to cover the gap. Typically this happens in poorly muxed
+    // files.
+    DECODER_WARN("Audio not synced after seek, maybe a poorly muxed file?");
+    mSeekedAudioData = audio;
+    return NS_OK;
+  }
+
+  // The seek target lies somewhere in this AudioData's frames, strip off
+  // any frames which lie before the seek target, so we'll begin playback
+  // exactly at the seek target.
+  NS_ASSERTION(mSeekJob.mTarget.GetTime().ToMicroseconds() >= audio->mTime,
+               "Target must at or be after data start.");
+  NS_ASSERTION(mSeekJob.mTarget.GetTime().ToMicroseconds() < audio->mTime + sampleDuration.value(),
+               "Data must end after target.");
+
+  CheckedInt64 framesToPrune =
+    UsecsToFrames(mSeekJob.mTarget.GetTime().ToMicroseconds() - audio->mTime, mAudioRate);
+  if (!framesToPrune.isValid()) {
+    return NS_ERROR_FAILURE;
+  }
+  if (framesToPrune.value() > audio->mFrames) {
+    // We've messed up somehow. Don't try to trim frames, the |frames|
+    // variable below will overflow.
+    DECODER_WARN("Can't prune more frames that we have!");
+    return NS_ERROR_FAILURE;
+  }
+  uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune.value());
+  uint32_t channels = audio->mChannels;
+  auto audioData = MakeUnique<AudioDataValue[]>(frames * channels);
+  memcpy(audioData.get(),
+         audio->mAudioData.get() + (framesToPrune.value() * channels),
+         frames * channels * sizeof(AudioDataValue));
+  CheckedInt64 duration = FramesToUsecs(frames, mAudioRate);
+  if (!duration.isValid()) {
+    return NS_ERROR_FAILURE;
+  }
+  RefPtr<AudioData> data(new AudioData(audio->mOffset,
+                                       mSeekJob.mTarget.GetTime().ToMicroseconds(),
+                                       duration.value(),
+                                       frames,
+                                       Move(audioData),
+                                       channels,
+                                       audio->mRate));
+  MOZ_ASSERT(!mSeekedAudioData, "Should be the 1st sample after seeking");
+  mSeekedAudioData = audio;
+
+  return NS_OK;
+}
+
+nsresult
+SeekTask::DropVideoUpToSeekTarget(MediaData* aSample)
+{
+  AssertOwnerThread();
+  RefPtr<VideoData> video(aSample->As<VideoData>());
+  MOZ_ASSERT(video);
+  DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld]",
+              video->mTime, video->GetEndTime());
+  MOZ_ASSERT(mSeekJob.Exists());
+  const int64_t target = mSeekJob.mTarget.GetTime().ToMicroseconds();
+
+  // If the frame end time is less than the seek target, we won't want
+  // to display this frame after the seek, so discard it.
+  if (target >= video->GetEndTime()) {
+    DECODER_LOG("DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
+                video->mTime, video->GetEndTime(), target);
+    mFirstVideoFrameAfterSeek = video;
+  } else {
+    if (target >= video->mTime && video->GetEndTime() >= target) {
+      // The seek target lies inside this frame's time slice. Adjust the frame's
+      // start time to match the seek target. We do this by replacing the
+      // first frame with a shallow copy which has the new timestamp.
+      RefPtr<VideoData> temp = VideoData::ShallowCopyUpdateTimestamp(video.get(), target);
+      video = temp;
+    }
+    mFirstVideoFrameAfterSeek = nullptr;
+
+    DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
+                video->mTime, video->GetEndTime(), target);
+
+    MOZ_ASSERT(!mSeekedVideoData, "Should be the 1st sample after seeking");
+    mSeekedVideoData = video;
+  }
+
+  return NS_OK;
+}
+
+bool
+SeekTask::IsAudioSeekComplete()
+{
+  AssertOwnerThread();
+
+  SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
+      mSeekJob.Exists(), mDropAudioUntilNextDiscontinuity, mIsAudioQueueFinished, !!mSeekedAudioData);
+  return
+    !HasAudio() ||
+    (Exists() && !mDropAudioUntilNextDiscontinuity &&
+     (mIsAudioQueueFinished || mSeekedAudioData));
+}
+
+bool
+SeekTask::IsVideoSeekComplete()
+{
+  AssertOwnerThread();
+
+  SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d mVidDis=%d vqFin=%d vqSz=%d",
+      mSeekJob.Exists(), mDropVideoUntilNextDiscontinuity, mIsVideoQueueFinished, !!mSeekedVideoData);
+
+  return
+    !HasVideo() ||
+    (Exists() && !mDropVideoUntilNextDiscontinuity &&
+     (mIsVideoQueueFinished || mSeekedVideoData));
+}
+
+void
+SeekTask::CheckIfSeekComplete()
+{
+  AssertOwnerThread();
+
+  const bool videoSeekComplete = IsVideoSeekComplete();
+  if (HasVideo() && !videoSeekComplete) {
+    // We haven't reached the target. Ensure we have requested another sample.
+    if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
+      DECODER_WARN("Failed to request video during seek");
+      RejectIfExist(__func__);
+    }
+  }
+
+  const bool audioSeekComplete = IsAudioSeekComplete();
+  if (HasAudio() && !audioSeekComplete) {
+    // We haven't reached the target. Ensure we have requested another sample.
+    if (NS_FAILED(EnsureAudioDecodeTaskQueued())) {
+      DECODER_WARN("Failed to request audio during seek");
+      RejectIfExist(__func__);
+    }
+  }
+
+  SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
+             audioSeekComplete, videoSeekComplete);
+
+  if (audioSeekComplete && videoSeekComplete) {
+    Resolve(__func__); // Call to MDSM::SeekCompleted();
+  }
+}
+
+void
+SeekTask::OnSeekResolved(media::TimeUnit)
+{
+  AssertOwnerThread();
+  mSeekRequest.Complete();
+  // We must decode the first samples of active streams, so we can determine
+  // the new stream time. So dispatch tasks to do that.
+  EnsureAudioDecodeTaskQueued();
+  EnsureVideoDecodeTaskQueued();
+}
+
+void
+SeekTask::OnSeekRejected(nsresult aResult)
+{
+  AssertOwnerThread();
+  mSeekRequest.Complete();
+  MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
+  RejectIfExist(__func__);
+}
+
+void
+SeekTask::OnAudioDecoded(MediaData* aAudioSample)
+{
+  AssertOwnerThread();
+  RefPtr<MediaData> audio(aAudioSample);
+  MOZ_ASSERT(audio);
+  mAudioDataRequest.Complete();
+
+  // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
+  // resolved.
+
+  SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d",
+             (audio ? audio->mTime : -1),
+             (audio ? audio->GetEndTime() : -1),
+             (audio ? audio->mDiscontinuity : 0));
+
+  if (!Exists()) {
+    // We've received a sample from a previous decode. Discard it.
+    return;
+  }
+
+  if (audio->mDiscontinuity) {
+    mDropAudioUntilNextDiscontinuity = false;
+  }
+
+  if (!mDropAudioUntilNextDiscontinuity) {
+    // We must be after the discontinuity; we're receiving samples
+    // at or after the seek target.
+    if (mSeekJob.mTarget.IsFast() &&
+        mSeekJob.mTarget.GetTime().ToMicroseconds() > mCurrentTimeBeforeSeek &&
+        audio->mTime < mCurrentTimeBeforeSeek) {
+      // We are doing a fastSeek, but we ended up *before* the previous
+      // playback position. This is surprising UX, so switch to an accurate
+      // seek and decode to the seek target. This is not conformant to the
+      // spec, fastSeek should always be fast, but until we get the time to
+      // change all Readers to seek to the keyframe after the currentTime
+      // in this case, we'll just decode forward. Bug 1026330.
+      mSeekJob.mTarget.SetType(SeekTarget::Accurate);
+    }
+    if (mSeekJob.mTarget.IsFast()) {
+      // Non-precise seek; we can stop the seek at the first sample.
+      mSeekedAudioData = audio;
+    } else {
+      // We're doing an accurate seek. We must discard
+      // MediaData up to the one containing exact seek target.
+      if (NS_FAILED(DropAudioUpToSeekTarget(audio.get()))) {
+        RejectIfExist(__func__);
+        return;
+      }
+    }
+  }
+  CheckIfSeekComplete();
+}
+
+void
+SeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+{
+  AssertOwnerThread();
+  SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", MediaData::AUDIO_DATA, aReason);
+  mAudioDataRequest.Complete();
+
+  if (aReason == MediaDecoderReader::DECODE_ERROR) {
+    // If this is a decode error, delegate to the generic error path.
+    RejectIfExist(__func__);
+    return;
+  }
+
+  // If the decoder is waiting for data, we tell it to call us back when the
+  // data arrives.
+  if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
+    MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+               "Readers that send WAITING_FOR_DATA need to implement WaitForData");
+    RefPtr<SeekTask> self = this;
+    mAudioWaitRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
+                                        &MediaDecoderReader::WaitForData,
+                                        MediaData::AUDIO_DATA)
+      ->Then(OwnerThread(), __func__,
+             [self] (MediaData::Type aType) -> void {
+               self->mAudioWaitRequest.Complete();
+               self->EnsureAudioDecodeTaskQueued();
+             },
+             [self] (WaitForDataRejectValue aRejection) -> void {
+               self->mAudioWaitRequest.Complete();
+             }));
+
+    // We are out of data to decode and will enter buffering mode soon.
+    // We want to play the frames we have already decoded, so we stop pre-rolling
+    // and ensure that loadeddata is fired as required.
+    mNeedToStopPrerollingAudio = true;
+    return;
+  }
+
+  if (aReason == MediaDecoderReader::CANCELED) {
+    EnsureAudioDecodeTaskQueued();
+    return;
+  }
+
+  if (aReason == MediaDecoderReader::END_OF_STREAM) {
+    mIsAudioQueueFinished = true;
+    mDropAudioUntilNextDiscontinuity = false; // To make IsAudioSeekComplete() return TRUE.
+    CheckIfSeekComplete();
+  }
+}
+
+void
+SeekTask::OnVideoDecoded(MediaData* aVideoSample)
+{
+  AssertOwnerThread();
+  RefPtr<MediaData> video(aVideoSample);
+  MOZ_ASSERT(video);
+  mVideoDataRequest.Complete();
+
+  // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
+  // resolved.
+
+  SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
+             (video ? video->mTime : -1),
+             (video ? video->GetEndTime() : -1),
+             (video ? video->mDiscontinuity : 0));
+
+  if (!Exists()) {
+    // We've received a sample from a previous decode. Discard it.
+    return;
+  }
+
+  if (mDropVideoUntilNextDiscontinuity) {
+    if (video->mDiscontinuity) {
+      mDropVideoUntilNextDiscontinuity = false;
+    }
+  }
+
+  if (!mDropVideoUntilNextDiscontinuity) {
+    // We must be after the discontinuity; we're receiving samples
+    // at or after the seek target.
+    if (mSeekJob.mTarget.IsFast() &&
+        mSeekJob.mTarget.GetTime().ToMicroseconds() > mCurrentTimeBeforeSeek &&
+        video->mTime < mCurrentTimeBeforeSeek) {
+      // We are doing a fastSeek, but we ended up *before* the previous
+      // playback position. This is surprising UX, so switch to an accurate
+      // seek and decode to the seek target. This is not conformant to the
+      // spec, fastSeek should always be fast, but until we get the time to
+      // change all Readers to seek to the keyframe after the currentTime
+      // in this case, we'll just decode forward. Bug 1026330.
+      mSeekJob.mTarget.SetType(SeekTarget::Accurate);
+    }
+    if (mSeekJob.mTarget.IsFast()) {
+      // Non-precise seek. We can stop the seek at the first sample.
+      mSeekedVideoData = video;
+    } else {
+      // We're doing an accurate seek. We still need to discard
+      // MediaData up to the one containing exact seek target.
+      if (NS_FAILED(DropVideoUpToSeekTarget(video.get()))) {
+        RejectIfExist(__func__);
+        return;
+      }
+    }
+  }
+  CheckIfSeekComplete();
+}
+
+void
+SeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+{
+  AssertOwnerThread();
+  SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", MediaData::VIDEO_DATA, aReason);
+  mVideoDataRequest.Complete();
+
+  if (aReason == MediaDecoderReader::DECODE_ERROR) {
+    // If this is a decode error, delegate to the generic error path.
+    RejectIfExist(__func__);
+    return;
+  }
+
+  // If the decoder is waiting for data, we tell it to call us back when the
+  // data arrives.
+  if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
+    MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+               "Readers that send WAITING_FOR_DATA need to implement WaitForData");
+    RefPtr<SeekTask> self = this;
+    mVideoWaitRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
+                                        &MediaDecoderReader::WaitForData,
+                                        MediaData::VIDEO_DATA)
+      ->Then(OwnerThread(), __func__,
+             [self] (MediaData::Type aType) -> void {
+               self->mVideoWaitRequest.Complete();
+               self->EnsureVideoDecodeTaskQueued();
+             },
+             [self] (WaitForDataRejectValue aRejection) -> void {
+               self->mVideoWaitRequest.Complete();
+             }));
+
+    // We are out of data to decode and will enter buffering mode soon.
+    // We want to play the frames we have already decoded, so we stop pre-rolling
+    // and ensure that loadeddata is fired as required.
+    mNeedToStopPrerollingVideo = true;
+    return;
+  }
+
+  if (aReason == MediaDecoderReader::CANCELED) {
+    EnsureVideoDecodeTaskQueued();
+    return;
+  }
+
+  if (aReason == MediaDecoderReader::END_OF_STREAM) {
+
+    if (Exists() && mFirstVideoFrameAfterSeek) {
+      // Null sample. Hit end of stream. If we have decoded a frame,
+      // insert it into the queue so that we have something to display.
+      // We make sure to do this before invoking VideoQueue().Finish()
+      // below.
+      mSeekedVideoData = mFirstVideoFrameAfterSeek;
+      mFirstVideoFrameAfterSeek = nullptr;
+    }
+
+    mIsVideoQueueFinished = true;
+    mDropVideoUntilNextDiscontinuity = false; // To make IsVideoSeekComplete() return TRUE.
+    CheckIfSeekComplete();
+  }
+}
+
+} // namespace media
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/SeekTask.h
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SEEK_TASK_H
+#define SEEK_TASK_H
+
+#include "mozilla/MozPromise.h"
+#include "MediaDecoderReader.h"
+#include "SeekJob.h"
+
+namespace mozilla {
+
+class AbstractThread;
+class MediaData;
+class MediaDecoderReaderWrapper;
+
+namespace media {
+
+struct SeekTaskResolveValue
+{
+  RefPtr<MediaData> mSeekedAudioData;
+  RefPtr<MediaData> mSeekedVideoData;
+  bool mIsAudioQueueFinished;
+  bool mIsVideoQueueFinished;
+  bool mNeedToStopPrerollingAudio;
+  bool mNeedToStopPrerollingVideo;
+};
+
+struct SeekTaskRejectValue
+{
+  bool mIsAudioQueueFinished;
+  bool mIsVideoQueueFinished;
+  bool mNeedToStopPrerollingAudio;
+  bool mNeedToStopPrerollingVideo;
+};
+
+class SeekTask {
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SeekTask)
+
+public:
+  static const bool IsExclusive = true;
+
+  using SeekTaskPromise =
+    MozPromise<SeekTaskResolveValue, SeekTaskRejectValue, IsExclusive>;
+
+  static SeekTask* CreateSeekTask(const void* aDecoderID,
+                                  AbstractThread* aThread,
+                                  MediaDecoderReader* aReader,
+                                  MediaDecoderReaderWrapper* aReaderWrapper,
+                                  SeekJob&& aSeekJob,
+                                  const MediaInfo& aInfo,
+                                  const media::TimeUnit& aDuration,
+                                  int64_t aCurrentMediaTime);
+
+  virtual void Discard();
+
+  virtual RefPtr<SeekTaskPromise> Seek(const media::TimeUnit& aDuration);
+
+  virtual bool NeedToResetMDSM() const;
+
+  SeekJob& GetSeekJob();
+
+  bool Exists();
+
+protected:
+  SeekTask(const void* aDecoderID,
+           AbstractThread* aThread,
+           MediaDecoderReader* aReader,
+           MediaDecoderReaderWrapper* aReaderWrapper,
+           SeekJob&& aSeekJob,
+           const MediaInfo& aInfo,
+           const media::TimeUnit& aDuration,
+           int64_t aCurrentMediaTime);
+
+  virtual ~SeekTask();
+
+  void Resolve(const char* aCallSite);
+
+  void RejectIfExist(const char* aCallSite);
+
+  void AssertOwnerThread() const;
+
+  bool HasAudio() const;
+
+  bool HasVideo() const;
+
+  TaskQueue* DecodeTaskQueue() const;
+
+  AbstractThread* OwnerThread() const;
+
+  bool IsVideoDecoding() const;
+
+  bool IsAudioDecoding() const;
+
+  nsresult EnsureVideoDecodeTaskQueued();
+
+  nsresult EnsureAudioDecodeTaskQueued();
+
+  const char* AudioRequestStatus();
+
+  const char* VideoRequestStatus();
+
+  void RequestVideoData();
+
+  void RequestAudioData();
+
+  nsresult DropAudioUpToSeekTarget(MediaData* aSample);
+
+  nsresult DropVideoUpToSeekTarget(MediaData* aSample);
+
+  bool IsAudioSeekComplete();
+
+  bool IsVideoSeekComplete();
+
+  void CheckIfSeekComplete();
+
+  virtual void OnSeekResolved(media::TimeUnit);
+
+  virtual void OnSeekRejected(nsresult aResult);
+
+  virtual void OnAudioDecoded(MediaData* aAudioSample);
+
+  virtual void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+
+  virtual void OnVideoDecoded(MediaData* aVideoSample);
+
+  virtual void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+
+  // Data shared with MDSM.
+  const void* mDecoderID; // For logging.
+  const RefPtr<AbstractThread> mOwnerThread;
+  const RefPtr<MediaDecoderReader> mReader;
+  const RefPtr<MediaDecoderReaderWrapper> mReaderWrapper;
+  const uint32_t mAudioRate;  // Audio sample rate.
+  const bool mHasAudio;
+  const bool mHasVideo;
+
+  SeekJob mSeekJob;
+  MozPromiseHolder<SeekTaskPromise> mSeekTaskPromise;
+  int64_t mCurrentTimeBeforeSeek;
+  bool mDropAudioUntilNextDiscontinuity;
+  bool mDropVideoUntilNextDiscontinuity;
+
+  // This temporarily stores the first frame we decode after we seek.
+  // This is so that if we hit end of stream while we're decoding to reach
+  // the seek target, we will still have a frame that we can display as the
+  // last frame in the media.
+  RefPtr<MediaData> mFirstVideoFrameAfterSeek;
+
+  // Track the current seek promise made by the reader.
+  MozPromiseRequestHolder<MediaDecoderReader::SeekPromise> mSeekRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::AudioDataPromise> mAudioDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mVideoDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest;
+
+  /*
+   * Information which are going to be returned to MDSM.
+   */
+  RefPtr<MediaData> mSeekedAudioData;
+  RefPtr<MediaData> mSeekedVideoData;
+  bool mIsAudioQueueFinished;
+  bool mIsVideoQueueFinished;
+  bool mNeedToStopPrerollingAudio;
+  bool mNeedToStopPrerollingVideo;
+};
+
+} // namespace media
+} // namespace mozilla
+
+#endif /* SEEK_TASK_H */
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -128,17 +128,19 @@ EXPORTS += [
     'MediaTrackList.h',
     'MP3Decoder.h',
     'MP3Demuxer.h',
     'MP3FrameParser.h',
     'nsIDocumentActivity.h',
     'PrincipalChangeObserver.h',
     'QueueObject.h',
     'RtspMediaResource.h',
+    'SeekJob.h',
     'SeekTarget.h',
+    'SeekTask.h',
     'SelfRef.h',
     'SharedBuffer.h',
     'StreamBuffer.h',
     'ThreadPoolCOMListener.h',
     'TimeUnits.h',
     'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoSegment.h',
@@ -230,16 +232,18 @@ UNIFIED_SOURCES += [
     'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
     'MP3Decoder.cpp',
     'MP3Demuxer.cpp',
     'MP3FrameParser.cpp',
     'QueueObject.cpp',
     'RtspMediaResource.cpp',
+    'SeekJob.cpp',
+    'SeekTask.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
     'TrackUnionStream.cpp',
     'VideoFrameContainer.cpp',