Bug 1261020 - part 1 - implement SeekTask; r=jwwang draft
authorKaku Kuo <tkuo@mozilla.com>
Thu, 14 Apr 2016 12:01:55 +0800
changeset 350771 171288f9c165df6e7729d9f6e749d4f37f55724a
parent 350745 922f7078062a2ad6693a9c8829ef06629d22bc03
child 350772 dd19f6e76bc344257c39a56b0991c441b1430224
push id15409
push usertkuo@mozilla.com
push dateThu, 14 Apr 2016 08:08:15 +0000
reviewersjwwang
bugs1261020
milestone48.0a1
Bug 1261020 - part 1 - implement SeekTask; r=jwwang MozReview-Commit-ID: GTrzI3ZuYeo
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
copy from dom/media/MediaDecoderStateMachine.h
copy to dom/media/SeekJob.h
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/SeekJob.h
@@ -1,1157 +1,38 @@
 /* -*- 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/. */
-/*
-
-Each media element for a media file has one thread called the "audio thread".
-
-The audio thread  writes the decoded audio data to the audio
-hardware. This is done in a separate thread to ensure that the
-audio hardware gets a constant stream of data without
-interruption due to decoding or display. At some point
-AudioStream will be refactored to have a callback interface
-where it asks for data and this thread will no longer be
-needed.
-
-The element/state machine also has a TaskQueue which runs in a
-SharedThreadPool that is shared with all other elements/decoders. The state
-machine dispatches tasks to this to call into the MediaDecoderReader to
-request decoded audio or video data. The Reader will callback with decoded
-sampled when it has them available, and the state machine places the decoded
-samples into its queues for the consuming threads to pull from.
-
-The MediaDecoderReader can choose to decode asynchronously, or synchronously
-and return requested samples synchronously inside it's Request*Data()
-functions via callback. Asynchronous decoding is preferred, and should be
-used for any new readers.
-
-Synchronisation of state between the thread is done via a monitor owned
-by MediaDecoder.
-
-The lifetime of the audio thread is controlled by the state machine when
-it runs on the shared state machine thread. When playback needs to occur
-the audio thread is created and an event dispatched to run it. The audio
-thread exits when audio playback is completed or no longer required.
-
-A/V synchronisation is handled by the state machine. It examines the audio
-playback time and compares this to the next frame in the queue of video
-frames. If it is time to play the video frame it is then displayed, otherwise
-it schedules the state machine to run again at the time of the next frame.
-
-Frame skipping is done in the following ways:
 
-  1) The state machine will skip all frames in the video queue whose
-     display time is less than the current audio time. This ensures
-     the correct frame for the current time is always displayed.
-
-  2) The decode tasks will stop decoding interframes and read to the
-     next keyframe if it determines that decoding the remaining
-     interframes will cause playback issues. It detects this by:
-       a) If the amount of audio data in the audio queue drops
-          below a threshold whereby audio may start to skip.
-       b) If the video queue drops below a threshold where it
-          will be decoding video data that won't be displayed due
-          to the decode thread dropping the frame immediately.
-     TODO: In future we should only do this when the Reader is decoding
-           synchronously.
-
-When hardware accelerated graphics is not available, YCbCr conversion
-is done on the decode task queue when video frames are decoded.
+#ifndef SEEK_JOB_H
+#define SEEK_JOB_H
 
-The decode task queue pushes decoded audio and videos frames into two
-separate queues - one for audio and one for video. These are kept
-separate to make it easy to constantly feed audio data to the audio
-hardware while allowing frame skipping of video data. These queues are
-threadsafe, and neither the decode, audio, or state machine should
-be able to monopolize them, and cause starvation of the other threads.
-
-Both queues are bounded by a maximum size. When this size is reached
-the decode tasks will no longer request video or audio depending on the
-queue that has reached the threshold. If both queues are full, no more
-decode tasks will be dispatched to the decode task queue, so other
-decoders will have an opportunity to run.
-
-During playback the audio thread will be idle (via a Wait() on the
-monitor) if the audio queue is empty. Otherwise it constantly pops
-audio data off the queue and plays it with a blocking write to the audio
-hardware (via AudioStream).
-
-*/
-#if !defined(MediaDecoderStateMachine_h__)
-#define MediaDecoderStateMachine_h__
-
-#include "mozilla/Attributes.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/StateMirroring.h"
-
-#include "nsThreadUtils.h"
+#include "mozilla/MozPromise.h"
 #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 "SeekTarget.h"
 
 namespace mozilla {
 
-namespace media {
-class MediaSink;
-}
-
-class AudioSegment;
-class DecodedStream;
-class MediaDecoderReaderWrapper;
-class OutputStreamManager;
-class TaskQueue;
-
-extern LazyLogModule gMediaDecoderLog;
-extern LazyLogModule gMediaSampleLog;
-
-enum class MediaEventType : int8_t {
-  PlaybackStarted,
-  PlaybackStopped,
-  PlaybackEnded,
-  DecodeError,
-  Invalidate
-};
-
-/*
-  The state machine class. This manages the decoding and seeking in the
-  MediaDecoderReader on the decode task queue, and A/V sync on the shared
-  state machine thread, and controls the audio "push" thread.
-
-  All internal state is synchronised via the decoder monitor. State changes
-  are propagated by scheduling the state machine to run another cycle on the
-  shared state machine thread.
-
-  See MediaDecoder.h for more details.
-*/
-class MediaDecoderStateMachine
-{
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine)
-public:
-  typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
-  typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
-  typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus;
-  typedef mozilla::layers::ImageContainer::FrameID FrameID;
-  MediaDecoderStateMachine(MediaDecoder* aDecoder,
-                           MediaDecoderReader* aReader,
-                           bool aRealTime = false);
-
-  nsresult Init(MediaDecoder* aDecoder);
-
-  // Enumeration for the valid decoding states
-  enum State {
-    DECODER_STATE_DECODING_METADATA,
-    DECODER_STATE_WAIT_FOR_CDM,
-    DECODER_STATE_DORMANT,
-    DECODER_STATE_DECODING,
-    DECODER_STATE_SEEKING,
-    DECODER_STATE_BUFFERING,
-    DECODER_STATE_COMPLETED,
-    DECODER_STATE_SHUTDOWN,
-    DECODER_STATE_ERROR
-  };
-
-  void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
-  // Remove an output stream added with AddOutputStream.
-  void RemoveOutputStream(MediaStream* aStream);
-
-  // Seeks to the decoder to aTarget asynchronously.
-  RefPtr<MediaDecoder::SeekPromise> InvokeSeek(SeekTarget aTarget);
-
-  // Set/Unset dormant state.
-  void DispatchSetDormant(bool aDormant);
-
-  RefPtr<ShutdownPromise> BeginShutdown();
-
-  // Notifies the state machine that should minimize the number of samples
-  // decoded we preroll, until playback starts. The first time playback starts
-  // the state machine is free to return to prerolling normally. Note
-  // "prerolling" in this context refers to when we decode and buffer decoded
-  // samples in advance of when they're needed for playback.
-  void DispatchMinimizePrerollUntilPlaybackStarts()
-  {
-    RefPtr<MediaDecoderStateMachine> self = this;
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
-    {
-      MOZ_ASSERT(self->OnTaskQueue());
-      self->mMinimizePreroll = true;
-
-      // Make sure that this arrives before playback starts, otherwise this won't
-      // have the intended effect.
-      MOZ_DIAGNOSTIC_ASSERT(self->mPlayState == MediaDecoder::PLAY_STATE_LOADING);
-    });
-    OwnerThread()->Dispatch(r.forget());
-  }
-
-  // Set the media fragment end time. aEndTime is in microseconds.
-  void DispatchSetFragmentEndTime(int64_t aEndTime)
-  {
-    RefPtr<MediaDecoderStateMachine> self = this;
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aEndTime] () {
-      self->mFragmentEndTime = aEndTime;
-    });
-    OwnerThread()->Dispatch(r.forget());
-  }
-
-  void DispatchAudioOffloading(bool aAudioOffloading)
-  {
-    RefPtr<MediaDecoderStateMachine> self = this;
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
-      if (self->mAudioOffloading != aAudioOffloading) {
-        self->mAudioOffloading = aAudioOffloading;
-        self->ScheduleStateMachine();
-      }
-    });
-    OwnerThread()->Dispatch(r.forget());
-  }
-
-  // Drop reference to mResource. Only called during shutdown dance.
-  void BreakCycles() {
-    MOZ_ASSERT(NS_IsMainThread());
-    mReader->BreakCycles();
-    mResource = nullptr;
-  }
-
-  TimedMetadataEventSource& TimedMetadataEvent() {
-    return mMetadataManager.TimedMetadataEvent();
-  }
-
-  MediaEventSource<void>& OnMediaNotSeekable() {
-    return mReader->OnMediaNotSeekable();
-  }
-
-  MediaEventSourceExc<nsAutoPtr<MediaInfo>,
-                      nsAutoPtr<MetadataTags>,
-                      MediaDecoderEventVisibility>&
-  MetadataLoadedEvent() { return mMetadataLoadedEvent; }
-
-  MediaEventSourceExc<nsAutoPtr<MediaInfo>,
-                      MediaDecoderEventVisibility>&
-  FirstFrameLoadedEvent() { return mFirstFrameLoadedEvent; }
-
-  MediaEventSource<MediaEventType>&
-  OnPlaybackEvent() { return mOnPlaybackEvent; }
-
-  MediaEventSource<MediaDecoderEventVisibility>&
-  OnSeekingStart() { return mOnSeekingStart; }
-
-  // Immutable after construction - may be called on any thread.
-  bool IsRealTime() const { return mRealTime; }
-
-  size_t SizeOfVideoQueue() {
-    return mReader->SizeOfVideoQueueInBytes();
-  }
-
-  size_t SizeOfAudioQueue() {
-    return mReader->SizeOfAudioQueueInBytes();
-  }
-
-private:
-  // Functions used by assertions to ensure we're calling things
-  // on the appropriate threads.
-  bool OnTaskQueue() const;
-
-  // Initialization that needs to happen on the task queue. This is the first
-  // task that gets run on the task queue, and is dispatched from the MDSM
-  // constructor immediately after the task queue is created.
-  void InitializationTask(MediaDecoder* aDecoder);
-
-  void SetDormant(bool aDormant);
-
-  void SetAudioCaptured(bool aCaptured);
-
-  void ReadMetadata();
-
-  RefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget);
-
-  RefPtr<ShutdownPromise> Shutdown();
-
-  RefPtr<ShutdownPromise> FinishShutdown();
-
-  // Update the playback position. This can result in a timeupdate event
-  // and an invalidate of the frame being dispatched asynchronously if
-  // there is no such event currently queued.
-  // Only called on the decoder thread. Must be called with
-  // the decode monitor held.
-  void UpdatePlaybackPosition(int64_t aTime);
-
-  // Causes the state machine to switch to buffering state, and to
-  // immediately stop playback and buffer downloaded data. Called on
-  // the state machine thread.
-  void StartBuffering();
-
-  bool CanPlayThrough();
-
-  MediaStatistics GetStatistics();
-
-  // This is called on the state machine thread and audio thread.
-  // The decoder monitor must be obtained before calling this.
-  bool HasAudio() const {
-    MOZ_ASSERT(OnTaskQueue());
-    return mInfo.HasAudio();
-  }
-
-  // This is called on the state machine thread and audio thread.
-  // The decoder monitor must be obtained before calling this.
-  bool HasVideo() const {
-    MOZ_ASSERT(OnTaskQueue());
-    return mInfo.HasVideo();
-  }
-
-  // Should be called by main thread.
-  bool HaveNextFrameData();
-
-  // Must be called with the decode monitor held.
-  bool IsBuffering() const {
-    MOZ_ASSERT(OnTaskQueue());
-    return mState == DECODER_STATE_BUFFERING;
-  }
-
-  // Must be called with the decode monitor held.
-  bool IsSeeking() const {
-    MOZ_ASSERT(OnTaskQueue());
-    return mState == DECODER_STATE_SEEKING;
-  }
-
-  // Returns the state machine task queue.
-  TaskQueue* OwnerThread() const { return mTaskQueue; }
-
-  // Schedules the shared state machine thread to run the state machine.
-  void ScheduleStateMachine();
-
-  // Invokes ScheduleStateMachine to run in |aMicroseconds| microseconds,
-  // unless it's already scheduled to run earlier, in which case the
-  // request is discarded.
-  void ScheduleStateMachineIn(int64_t aMicroseconds);
-
-  void OnDelayedSchedule()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    mDelayedScheduler.CompleteRequest();
-    ScheduleStateMachine();
-  }
-
-  void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); }
-
-  // Discard audio/video data that are already played by MSG.
-  void DiscardStreamData();
-  bool HaveEnoughDecodedAudio();
-  bool HaveEnoughDecodedVideo();
-
-  // Returns true if the state machine has shutdown or is in the process of
-  // shutting down. The decoder monitor must be held while calling this.
-  bool IsShutdown();
-
-  // Returns true if we're currently playing. The decoder monitor must
-  // be held.
-  bool IsPlaying() const;
+struct SeekJob {
+  SeekJob();
 
-  // TODO: Those callback function may receive demuxed-only data.
-  // Need to figure out a suitable API name for this case.
-  void OnAudioDecoded(MediaData* aAudioSample);
-  void OnVideoDecoded(MediaData* aVideoSample, TimeStamp aDecodeStartTime);
-  void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
-  void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    OnNotDecoded(MediaData::AUDIO_DATA, aReason);
-  }
-  void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    OnNotDecoded(MediaData::VIDEO_DATA, aReason);
-  }
-
-  // Resets all state related to decoding and playback, emptying all buffers
-  // and aborting all pending operations on the decode task queue.
-  void Reset();
-
-protected:
-  virtual ~MediaDecoderStateMachine();
-
-  void SetState(State aState);
-
-  void BufferedRangeUpdated();
-
-  // Inserts MediaData* samples into their respective MediaQueues.
-  // aSample must not be null.
-
-  void Push(MediaData* aSample, MediaData::Type aSampleType);
-
-  void OnAudioPopped(const RefPtr<MediaData>& aSample);
-  void OnVideoPopped(const RefPtr<MediaData>& aSample);
-
-  void CheckIsAudible(const MediaData* aSample);
-  void VolumeChanged();
-  void LogicalPlaybackRateChanged();
-  void PreservesPitchChanged();
-
-  MediaQueue<MediaData>& AudioQueue() { return mAudioQueue; }
-  MediaQueue<MediaData>& VideoQueue() { return mVideoQueue; }
-
-  // True if our buffers of decoded audio are not full, and we should
-  // decode more.
-  bool NeedToDecodeAudio();
-
-  // True if our buffers of decoded video are not full, and we should
-  // decode more.
-  bool NeedToDecodeVideo();
-
-  // Returns true if we've got less than aAudioUsecs microseconds of decoded
-  // and playable data. The decoder monitor must be held.
-  //
-  // May not be invoked when mReader->UseBufferingHeuristics() is false.
-  bool HasLowDecodedData(int64_t aAudioUsecs);
-
-  bool OutOfDecodedAudio();
-
-  bool OutOfDecodedVideo()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return IsVideoDecoding() && !VideoQueue().IsFinished() && VideoQueue().GetSize() <= 1;
-  }
-
-
-  // Returns true if we're running low on data which is not yet decoded.
-  // The decoder monitor must be held.
-  bool HasLowUndecodedData();
-
-  // Returns true if we have less than aUsecs of undecoded data available.
-  bool HasLowUndecodedData(int64_t aUsecs);
-
-  // Returns true when there's decoded audio waiting to play.
-  // The decoder monitor must be held.
-  bool HasFutureAudio();
-
-  // Returns true if we recently exited "quick buffering" mode.
-  bool JustExitedQuickBuffering();
-
-  // Recomputes mNextFrameStatus, possibly dispatching notifications to interested
-  // parties.
-  void UpdateNextFrameStatus();
-
-  // Return the current time, either the audio clock if available (if the media
-  // has audio, and the playback is possible), or a clock for the video.
-  // Called on the state machine thread.
-  // If aTimeStamp is non-null, set *aTimeStamp to the TimeStamp corresponding
-  // to the returned stream time.
-  int64_t GetClock(TimeStamp* aTimeStamp = nullptr) const;
-
-  nsresult DropAudioUpToSeekTarget(MediaData* aSample);
-  nsresult DropVideoUpToSeekTarget(MediaData* aSample);
-
-  void SetStartTime(int64_t aStartTimeUsecs);
-
-  // Update only the state machine's current playback position (and duration,
-  // if unknown).  Does not update the playback position on the decoder or
-  // media element -- use UpdatePlaybackPosition for that.  Called on the state
-  // machine thread, caller must hold the decoder lock.
-  void UpdatePlaybackPositionInternal(int64_t aTime);
-
-  // Update playback position and trigger next update by default time period.
-  // Called on the state machine thread.
-  void UpdatePlaybackPositionPeriodically();
-
-  media::MediaSink* CreateAudioSink();
-
-  // Always create mediasink which contains an AudioSink or StreamSink inside.
-  already_AddRefed<media::MediaSink> CreateMediaSink(bool aAudioCaptured);
-
-  // Stops the media sink and shut it down.
-  // The decoder monitor must be held with exactly one lock count.
-  // Called on the state machine thread.
-  void StopMediaSink();
-
-  // Create and start the media sink.
-  // The decoder monitor must be held with exactly one lock count.
-  // Called on the state machine thread.
-  void StartMediaSink();
-
-  // Notification method invoked when mPlayState changes.
-  void PlayStateChanged();
-
-  // Notification method invoked when mLogicallySeeking changes.
-  void LogicallySeekingChanged();
-
-  // Sets internal state which causes playback of media to pause.
-  // The decoder monitor must be held.
-  void StopPlayback();
+  SeekJob(SeekJob&& aOther);
 
-  // If the conditions are right, sets internal state which causes playback
-  // of media to begin or resume.
-  // Must be called with the decode monitor held.
-  void MaybeStartPlayback();
-
-  // Check to see if we don't have enough data to play up to the next frame.
-  // If we don't, switch to buffering mode.
-  void MaybeStartBuffering();
-
-  // Moves the decoder into decoding state. Called on the state machine
-  // thread. The decoder monitor must be held.
-  void StartDecoding();
-
-  // Moves the decoder into the shutdown state, and dispatches an error
-  // event to the media element. This begins shutting down the decoder.
-  // The decoder monitor must be held. This is only called on the
-  // decode thread.
-  void DecodeError();
-
-  // Dispatches a task to the decode task queue to begin decoding metadata.
-  // This is threadsafe and can be called on any thread.
-  // The decoder monitor must be held.
-  nsresult EnqueueDecodeMetadataTask();
-
-  // 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);
-    }
+  SeekJob& operator=(SeekJob&& aOther);
 
-    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,
-  // otherwise this dispatches a task to do the decode.
-  // This is called on the state machine or decode threads.
-  // The decoder monitor must be held.
-  nsresult EnsureAudioDecodeTaskQueued();
-  // Start a task to decode audio.
-  // The decoder monitor must be held.
-  void RequestAudioData();
-
-  nsresult DispatchVideoDecodeTaskIfNeeded();
-
-  // Ensures a task to decode video has been dispatched to the decode task queue.
-  // If a task to decode has already been dispatched, this does nothing,
-  // otherwise this dispatches a task to do the decode.
-  // The decoder monitor must be held.
-  nsresult EnsureVideoDecodeTaskQueued();
-  // Start a task to decode video.
-  // The decoder monitor must be held.
-  void RequestVideoData();
-
-  // Re-evaluates the state and determines whether we need to dispatch
-  // events to run the decode, or if not whether we should set the reader
-  // to idle mode. This is threadsafe, and can be called from any thread.
-  // The decoder monitor must be held.
-  void DispatchDecodeTasksIfNeeded();
-
-  // Returns the "media time". This is the absolute time which the media
-  // playback has reached. i.e. this returns values in the range
-  // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
-  // not start at 0. Note this is different than the "current playback position",
-  // which is in the range [0,duration].
-  int64_t GetMediaTime() const {
-    MOZ_ASSERT(OnTaskQueue());
-    return mCurrentPosition;
-  }
-
-  // Returns an upper bound on the number of microseconds of audio that is
-  // decoded and playable. This is the sum of the number of usecs of audio which
-  // is decoded and in the reader's audio queue, and the usecs of unplayed audio
-  // which has been pushed to the audio hardware for playback. Note that after
-  // calling this, the audio hardware may play some of the audio pushed to
-  // hardware, so this can only be used as a upper bound. The decoder monitor
-  // must be held when calling this. Called on the decode thread.
-  int64_t GetDecodedAudioDuration();
-
-  // Promise callbacks for metadata reading.
-  void OnMetadataRead(MetadataHolder* aMetadata);
-  void OnMetadataNotRead(ReadMetadataFailureReason aReason);
+  bool Exists();
 
-  // Checks whether we're finished decoding first audio and/or video packets.
-  // If so will trigger firing loadeddata event.
-  // If there are any queued seek, will change state to DECODER_STATE_SEEKING
-  // and return true.
-  bool MaybeFinishDecodeFirstFrame();
-  // Return true if we are currently decoding the first frames.
-  bool IsDecodingFirstFrame();
-  void FinishDecodeFirstFrame();
-
-  // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
-  // must be held with exactly one lock count.
-  void DecodeSeek();
-
-  void CheckIfSeekComplete();
-  bool IsAudioSeekComplete();
-  bool IsVideoSeekComplete();
-
-  // Completes the seek operation, moves onto the next appropriate state.
-  void SeekCompleted();
-
-  // Queries our state to see whether the decode has finished for all streams.
-  // If so, we move into DECODER_STATE_COMPLETED and schedule the state machine
-  // to run.
-  // The decoder monitor must be held.
-  void CheckIfDecodeComplete();
-
-  // Performs one "cycle" of the state machine. Polls the state, and may send
-  // a video frame to be displayed, and generally manages the decode. Called
-  // periodically via timer to ensure the video stays in sync.
-  nsresult RunStateMachine();
-
-  bool IsStateMachineScheduled() const;
-
-  // Returns true if we're not playing and the decode thread has filled its
-  // decode buffers and is waiting. We can shut the decode thread down in this
-  // case as it may not be needed again.
-  bool IsPausedAndDecoderWaiting();
-
-  // These return true if the respective stream's decode has not yet reached
-  // the end of stream.
-  bool IsAudioDecoding();
-  bool IsVideoDecoding();
-
-private:
-  // Resolved by the MediaSink to signal that all audio/video outstanding
-  // work is complete and identify which part(a/v) of the sink is shutting down.
-  void OnMediaSinkAudioComplete();
-  void OnMediaSinkVideoComplete();
-
-  // Rejected by the MediaSink to signal errors for audio/video.
-  void OnMediaSinkAudioError();
-  void OnMediaSinkVideoError();
-
-  // Return true if the video decoder's decode speed can not catch up the
-  // play time.
-  bool NeedToSkipToNextKeyframe();
-
-  void* const mDecoderID;
-  const RefPtr<FrameStatistics> mFrameStats;
-  const RefPtr<VideoFrameContainer> mVideoFrameContainer;
-  const dom::AudioChannel mAudioChannel;
-
-  // Task queue for running the state machine.
-  RefPtr<TaskQueue> mTaskQueue;
-
-  // State-watching manager.
-  WatchManager<MediaDecoderStateMachine> mWatchManager;
-
-  // True is we are decoding a realtime stream, like a camera stream.
-  const bool mRealTime;
-
-  // True if we've dispatched a task to run the state machine but the task has
-  // yet to run.
-  bool mDispatchedStateMachine;
-
-  // Used to dispatch another round schedule with specific target time.
-  DelayedScheduler mDelayedScheduler;
-
-  // Queue of audio frames. This queue is threadsafe, and is accessed from
-  // the audio, decoder, state machine, and main threads.
-  MediaQueue<MediaData> mAudioQueue;
-  // Queue of video frames. This queue is threadsafe, and is accessed from
-  // the decoder, state machine, and main threads.
-  MediaQueue<MediaData> mVideoQueue;
-
-  // The decoder monitor must be obtained before modifying this state.
-  // Accessed on state machine, audio, main, and AV thread.
-  Watchable<State> mState;
-
-  // The task queue in which we run decode tasks. This is referred to as
-  // the "decode thread", though in practise tasks can run on a different
-  // thread every time they're called.
-  TaskQueue* DecodeTaskQueue() const { return mReader->OwnerThread(); }
-
-  // Time that buffering started. Used for buffering timeout and only
-  // accessed on the state machine thread. This is null while we're not
-  // buffering.
-  TimeStamp mBufferingStart;
-
-  media::TimeUnit Duration() const { MOZ_ASSERT(OnTaskQueue()); return mDuration.Ref().ref(); }
-
-  // Recomputes the canonical duration from various sources.
-  void RecomputeDuration();
-
-
-  // FrameID which increments every time a frame is pushed to our queue.
-  FrameID mCurrentFrameID;
-
-  // The highest timestamp that our position has reached. Monotonically
-  // increasing.
-  Watchable<media::TimeUnit> mObservedDuration;
-
-  // Returns true if we're logically playing, that is, if the Play() has
-  // been called and Pause() has not or we have not yet reached the end
-  // of media. This is irrespective of the seeking state; if the owner
-  // calls Play() and then Seek(), we still count as logically playing.
-  // The decoder monitor must be held.
-  bool IsLogicallyPlaying()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return mPlayState == MediaDecoder::PLAY_STATE_PLAYING ||
-           mNextPlayState == MediaDecoder::PLAY_STATE_PLAYING;
-  }
-
-  // Queued seek - moves to mCurrentSeek when DecodeFirstFrame completes.
-  SeekJob mQueuedSeek;
-
-  // The position that we're currently seeking to.
-  SeekJob mCurrentSeek;
+  void Resolve(bool aAtEnd, const char* aCallSite);
 
-  // Media Fragment end time in microseconds. Access controlled by decoder monitor.
-  int64_t mFragmentEndTime;
-
-  // The media sink resource.  Used on the state machine thread.
-  RefPtr<media::MediaSink> mMediaSink;
-
-  // The reader, don't call its methods with the decoder monitor held.
-  // This is created in the state machine's constructor.
-  const RefPtr<MediaDecoderReader> mReader;
-
-  const RefPtr<MediaDecoderReaderWrapper> mReaderWrapper;
-
-  // The end time of the last audio frame that's been pushed onto the media sink
-  // in microseconds. This will approximately be the end time
-  // of the audio stream, unless another frame is pushed to the hardware.
-  int64_t AudioEndTime() const;
-
-  // The end time of the last rendered video frame that's been sent to
-  // compositor.
-  int64_t VideoEndTime() const;
-
-  // The end time of the last decoded audio frame. This signifies the end of
-  // decoded audio data. Used to check if we are low in decoded data.
-  int64_t mDecodedAudioEndTime;
-
-  // The end time of the last decoded video frame. Used to check if we are low
-  // on decoded video data.
-  int64_t mDecodedVideoEndTime;
-
-  // Playback rate. 1.0 : normal speed, 0.5 : two times slower.
-  double mPlaybackRate;
-
-  // Time at which we started decoding. Synchronised via decoder monitor.
-  TimeStamp mDecodeStartTime;
-
-  // The maximum number of second we spend buffering when we are short on
-  // unbuffered data.
-  uint32_t mBufferingWait;
-  int64_t  mLowDataThresholdUsecs;
-
-  // If we've got more than this number of decoded video frames waiting in
-  // the video queue, we will not decode any more video frames until some have
-  // been consumed by the play state machine thread.
-  // Must hold monitor.
-  uint32_t GetAmpleVideoFrames() const;
-
-  // Low audio threshold. If we've decoded less than this much audio we
-  // consider our audio decode "behind", and we may skip video decoding
-  // in order to allow our audio decoding to catch up. We favour audio
-  // decoding over video. We increase this threshold if we're slow to
-  // decode video frames, in order to reduce the chance of audio underruns.
-  // Note that we don't ever reset this threshold, it only ever grows as
-  // we detect that the decode can't keep up with rendering.
-  int64_t mLowAudioThresholdUsecs;
-
-  // Our "ample" audio threshold. Once we've this much audio decoded, we
-  // pause decoding. If we increase mLowAudioThresholdUsecs, we'll also
-  // increase this too appropriately (we don't want mLowAudioThresholdUsecs
-  // to be greater than ampleAudioThreshold, else we'd stop decoding!).
-  // Note that we don't ever reset this threshold, it only ever grows as
-  // we detect that the decode can't keep up with rendering.
-  int64_t mAmpleAudioThresholdUsecs;
-
-  // If we're quick buffering, we'll remain in buffering mode while we have less than
-  // QUICK_BUFFERING_LOW_DATA_USECS of decoded data available.
-  int64_t mQuickBufferingLowDataThresholdUsecs;
-
-  // At the start of decoding we want to "preroll" the decode until we've
-  // got a few frames decoded before we consider whether decode is falling
-  // behind. Otherwise our "we're falling behind" logic will trigger
-  // unneccessarily if we start playing as soon as the first sample is
-  // decoded. These two fields store how many video frames and audio
-  // samples we must consume before are considered to be finished prerolling.
-  uint32_t AudioPrerollUsecs() const
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return IsRealTime() ? 0 : mAmpleAudioThresholdUsecs / 2;
-  }
-
-  uint32_t VideoPrerollFrames() const
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
-  }
-
-  bool DonePrerollingAudio()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return !IsAudioDecoding() ||
-        GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
-  }
-
-  bool DonePrerollingVideo()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return !IsVideoDecoding() ||
-        static_cast<uint32_t>(VideoQueue().GetSize()) >=
-            VideoPrerollFrames() * mPlaybackRate + 1;
-  }
-
-  void StopPrerollingAudio()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    if (mIsAudioPrerolling) {
-      mIsAudioPrerolling = false;
-      ScheduleStateMachine();
-    }
-  }
-
-  void StopPrerollingVideo()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    if (mIsVideoPrerolling) {
-      mIsVideoPrerolling = false;
-      ScheduleStateMachine();
-    }
-  }
-
-  // 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;
-
-  // When we start decoding (either for the first time, or after a pause)
-  // we may be low on decoded data. We don't want our "low data" logic to
-  // kick in and decide that we're low on decoded data because the download
-  // can't keep up with the decode, and cause us to pause playback. So we
-  // have a "preroll" stage, where we ignore the results of our "low data"
-  // logic during the first few frames of our decode. This occurs during
-  // playback. The flags below are true when the corresponding stream is
-  // being "prerolled".
-  bool mIsAudioPrerolling;
-  bool mIsVideoPrerolling;
+  void RejectIfExists(const char* aCallSite);
 
-  // Only one of a given pair of ({Audio,Video}DataPromise, WaitForDataPromise)
-  // should exist at any given moment.
-
-  MozPromiseRequestHolder<MediaDecoderReader::AudioDataPromise> mAudioDataRequest;
-  MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest;
-  const char* AudioRequestStatus()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    if (mAudioDataRequest.Exists()) {
-      MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists());
-      return "pending";
-    } else if (mAudioWaitRequest.Exists()) {
-      return "waiting";
-    }
-    return "idle";
-  }
-
-  MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest;
-  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mVideoDataRequest;
-  const char* VideoRequestStatus()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    if (mVideoDataRequest.Exists()) {
-      MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists());
-      return "pending";
-    } else if (mVideoWaitRequest.Exists()) {
-      return "waiting";
-    }
-    return "idle";
-  }
-
-  MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>& WaitRequestRef(MediaData::Type aType)
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest;
-  }
-
-  // True if we shouldn't play our audio (but still write it to any capturing
-  // streams). When this is true, the audio thread will never start again after
-  // it has stopped.
-  bool mAudioCaptured;
-
-  // True if the audio playback thread has finished. It is finished
-  // when either all the audio frames have completed playing, or we've moved
-  // into shutdown state, and the threads are to be
-  // destroyed. Written by the audio playback thread and read and written by
-  // the state machine thread. Synchronised via decoder monitor.
-  // When data is being sent to a MediaStream, this is true when all data has
-  // been written to the MediaStream.
-  Watchable<bool> mAudioCompleted;
-
-  // True if all video frames are already rendered.
-  Watchable<bool> mVideoCompleted;
-
-  // Set if MDSM receives dormant request during reading metadata.
-  Maybe<bool> mPendingDormant;
-
-  // Flag whether we notify metadata before decoding the first frame or after.
-  //
-  // Note that the odd semantics here are designed to replicate the current
-  // behavior where we notify the decoder each time we come out of dormant, but
-  // send suppressed event visibility for those cases. This code can probably be
-  // simplified.
-  bool mNotifyMetadataBeforeFirstFrame;
-
-  // True if we've dispatched an event to the decode task queue to call
-  // DecodeThreadRun(). We use this flag to prevent us from dispatching
-  // unneccessary runnables, since the decode thread runs in a loop.
-  bool mDispatchedEventToDecode;
-
-  // If this is true while we're in buffering mode, we can exit early,
-  // as it's likely we may be able to playback. This happens when we enter
-  // buffering mode soon after the decode starts, because the decode-ahead
-  // ran fast enough to exhaust all data while the download is starting up.
-  // Synchronised via decoder monitor.
-  bool mQuickBuffering;
-
-  // True if we should not decode/preroll unnecessary samples, unless we're
-  // played. "Prerolling" in this context refers to when we decode and
-  // buffer decoded samples in advance of when they're needed for playback.
-  // This flag is set for preload=metadata media, and means we won't
-  // decode more than the first video frame and first block of audio samples
-  // for that media when we startup, or after a seek. When Play() is called,
-  // we reset this flag, as we assume the user is playing the media, so
-  // prerolling is appropriate then. This flag is used to reduce the overhead
-  // of prerolling samples for media elements that may not play, both
-  // memory and CPU overhead.
-  bool mMinimizePreroll;
-
-  // True if the decode thread has gone filled its buffers and is now
-  // waiting to be awakened before it continues decoding. Synchronized
-  // by the decoder monitor.
-  bool mDecodeThreadWaiting;
-
-  // These two flags are true when we need to drop decoded samples that
-  // we receive up to the next discontinuity. We do this when we seek;
-  // the first sample in each stream after the seek is marked as being
-  // a "discontinuity".
-  bool mDropAudioUntilNextDiscontinuity;
-  bool mDropVideoUntilNextDiscontinuity;
-
-  // Track the current seek promise made by the reader.
-  MozPromiseRequestHolder<MediaDecoderReader::SeekPromise> mSeekRequest;
-
-  // 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;
-
-  // Track our request for metadata from the reader.
-  MozPromiseRequestHolder<MediaDecoderReader::MetadataPromise> mMetadataRequest;
-
-  // Stores presentation info required for playback. The decoder monitor
-  // must be held when accessing this.
-  MediaInfo mInfo;
-
-  nsAutoPtr<MetadataTags> mMetadataTags;
-
-  mozilla::MediaMetadataManager mMetadataManager;
-
-  // Track our request to update the buffered ranges
-  MozPromiseRequestHolder<MediaDecoderReader::BufferedUpdatePromise> mBufferedUpdateRequest;
-
-  // True if we need to call FinishDecodeFirstFrame() upon frame decoding
-  // successeeding.
-  bool mDecodingFirstFrame;
+  ~SeekJob();
 
-  // True if we are back from DECODER_STATE_DORMANT state and
-  // LoadedMetadataEvent was already sent.
-  bool mSentLoadedMetadataEvent;
-  // True if we are back from DECODER_STATE_DORMANT state and
-  // FirstFrameLoadedEvent was already sent, then we can skip
-  // SetStartTime because the mStartTime already set before. Also we don't need
-  // to decode any audio/video since the MediaDecoder will trigger a seek
-  // operation soon.
-  bool mSentFirstFrameLoadedEvent;
-
-  bool mSentPlaybackEndedEvent;
-
-  // Data about MediaStreams that are being fed by the decoder.
-  const RefPtr<OutputStreamManager> mOutputStreamManager;
-
-  // Media data resource from the decoder.
-  RefPtr<MediaResource> mResource;
-
-  // Track the complete & error for audio/video separately
-  MozPromiseRequestHolder<GenericPromise> mMediaSinkAudioPromise;
-  MozPromiseRequestHolder<GenericPromise> mMediaSinkVideoPromise;
-
-  MediaEventListener mAudioQueueListener;
-  MediaEventListener mVideoQueueListener;
-
-  MediaEventProducerExc<nsAutoPtr<MediaInfo>,
-                        nsAutoPtr<MetadataTags>,
-                        MediaDecoderEventVisibility> mMetadataLoadedEvent;
-  MediaEventProducerExc<nsAutoPtr<MediaInfo>,
-                        MediaDecoderEventVisibility> mFirstFrameLoadedEvent;
-
-  MediaEventProducer<MediaEventType> mOnPlaybackEvent;
-  MediaEventProducer<MediaDecoderEventVisibility> mOnSeekingStart;
-
-  // True if audio is offloading.
-  // Playback will not start when audio is offloading.
-  bool mAudioOffloading;
-
-  // Duration of the continuous silent data.
-  uint32_t mSilentDataDuration;
-
-#ifdef MOZ_EME
-  void OnCDMProxyReady(RefPtr<CDMProxy> aProxy);
-  void OnCDMProxyNotReady();
-  RefPtr<CDMProxy> mCDMProxy;
-  MozPromiseRequestHolder<MediaDecoder::CDMProxyPromise> mCDMProxyPromise;
-#endif
-
-private:
-  // The buffered range. Mirrored from the decoder thread.
-  Mirror<media::TimeIntervals> mBuffered;
-
-  // The duration according to the demuxer's current estimate, mirrored from the main thread.
-  Mirror<media::NullableTimeUnit> mEstimatedDuration;
-
-  // The duration explicitly set by JS, mirrored from the main thread.
-  Mirror<Maybe<double>> mExplicitDuration;
-
-  // The current play state and next play state, mirrored from the main thread.
-  Mirror<MediaDecoder::PlayState> mPlayState;
-  Mirror<MediaDecoder::PlayState> mNextPlayState;
-  Mirror<bool> mLogicallySeeking;
-
-  // Volume of playback. 0.0 = muted. 1.0 = full volume.
-  Mirror<double> mVolume;
-
-  // TODO: The separation between mPlaybackRate and mLogicalPlaybackRate is a
-  // kludge to preserve existing fragile logic while converting this setup to
-  // state-mirroring. Some hero should clean this up.
-  Mirror<double> mLogicalPlaybackRate;
-
-  // Pitch preservation for the playback rate.
-  Mirror<bool> mPreservesPitch;
-
-  // True if the media is same-origin with the element. Data can only be
-  // passed to MediaStreams when this is true.
-  Mirror<bool> mSameOriginMedia;
-
-  // An identifier for the principal of the media. Used to track when
-  // main-thread induced principal changes get reflected on MSG thread.
-  Mirror<PrincipalHandle> mMediaPrincipalHandle;
-
-  // Estimate of the current playback rate (bytes/second).
-  Mirror<double> mPlaybackBytesPerSecond;
-
-  // True if mPlaybackBytesPerSecond is a reliable estimate.
-  Mirror<bool> mPlaybackRateReliable;
-
-  // Current decoding position in the stream.
-  Mirror<int64_t> mDecoderPosition;
-
-  // True if the media is seekable (i.e. supports random access).
-  Mirror<bool> mMediaSeekable;
-
-  // True if the media is seekable only in buffered ranges.
-  Mirror<bool> mMediaSeekableOnlyInBufferedRanges;
-
-  // Duration of the media. This is guaranteed to be non-null after we finish
-  // decoding the first frame.
-  Canonical<media::NullableTimeUnit> mDuration;
-
-  // Whether we're currently in or transitioning to shutdown state.
-  Canonical<bool> mIsShutdown;
-
-  // The status of our next frame. Mirrored on the main thread and used to
-  // compute ready state.
-  Canonical<NextFrameStatus> mNextFrameStatus;
-
-  // The time of the current frame in microseconds, corresponding to the "current
-  // playback position" in HTML5. This is referenced from 0, which is the initial
-  // playback position.
-  Canonical<int64_t> mCurrentPosition;
-
-  // Current playback position in the stream in bytes.
-  Canonical<int64_t> mPlaybackOffset;
-
-  // Used to distiguish whether the audio is producing sound.
-  Canonical<bool> mIsAudioDataAudible;
-
-public:
-  AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() {
-    return mReader->CanonicalBuffered();
-  }
-  AbstractCanonical<media::NullableTimeUnit>* CanonicalDuration() {
-    return &mDuration;
-  }
-  AbstractCanonical<bool>* CanonicalIsShutdown() {
-    return &mIsShutdown;
-  }
-  AbstractCanonical<NextFrameStatus>* CanonicalNextFrameStatus() {
-    return &mNextFrameStatus;
-  }
-  AbstractCanonical<int64_t>* CanonicalCurrentPosition() {
-    return &mCurrentPosition;
-  }
-  AbstractCanonical<int64_t>* CanonicalPlaybackOffset() {
-    return &mPlaybackOffset;
-  }
-  AbstractCanonical<bool>* CanonicalIsAudioDataAudible() {
-    return &mIsAudioDataAudible;
-  }
+  SeekTarget mTarget;
+  MozPromiseHolder<MediaDecoder::SeekPromise> mPromise;
 };
 
 } // namespace mozilla
 
-#endif
+#endif /* SEEK_JOB_H */
new file mode 100644
--- /dev/null
+++ b/dom/media/SeekTask.cpp
@@ -0,0 +1,729 @@
+/* -*- 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"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+extern LazyLogModule gMediaDecoderLog;
+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*/ already_AddRefed<SeekTask>
+SeekTask::CreateSeekTask(const void* aDecoderID,
+                         AbstractThread* aThread,
+                         MediaDecoderReader* aReader,
+                         MediaDecoderReaderWrapper* aReaderWrapper,
+                         SeekJob&& aSeekJob,
+                         const MediaInfo& aInfo,
+                         const media::TimeUnit& aDuration,
+                         int64_t aCurrentMediaTime)
+{
+  RefPtr<SeekTask> task(new SeekTask(aDecoderID, aThread, aReader,
+                                     aReaderWrapper, Move(aSeekJob), aInfo,
+                                     aDuration, aCurrentMediaTime));
+
+  return task.forget();
+}
+
+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)
+  , mSeekJob(Move(aSeekJob))
+  , mCurrentTimeBeforeSeek(aCurrentMediaTime)
+  , mAudioRate(aInfo.mAudio.mRate)
+  , mHasAudio(aInfo.HasAudio())
+  , mHasVideo(aInfo.HasVideo())
+  , mDropAudioUntilNextDiscontinuity(false)
+  , mDropVideoUntilNextDiscontinuity(false)
+  , mIsDiscarded(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()
+{
+  MOZ_ASSERT(mIsDiscarded);
+}
+
+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();
+
+  mIsDiscarded = true;
+}
+
+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.ToMicroseconds());
+
+  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;
+  AlignedAudioBuffer audioData(frames * channels);
+  if (!audioData) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  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 = data;
+
+  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("OnAduioNotDecoded (aReason=%u)", 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("OnVideoNotDecoded (aReason=%u)", 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,184 @@
+/* -*- 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 already_AddRefed<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;
+
+  /*
+   * Internal state.
+   */
+  SeekJob mSeekJob;
+  MozPromiseHolder<SeekTaskPromise> mSeekTaskPromise;
+  int64_t mCurrentTimeBeforeSeek;
+  const uint32_t mAudioRate;  // Audio sample rate.
+  const bool mHasAudio;
+  const bool mHasVideo;
+  bool mDropAudioUntilNextDiscontinuity;
+  bool mDropVideoUntilNextDiscontinuity;
+  bool mIsDiscarded;
+
+  // 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
@@ -129,17 +129,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',
@@ -233,16 +235,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',