Bug 1111413 - Part 1: Use MediaPromises for seeking. r=bholley, a=sledru
authorMatt Woodrow <mwoodrow@mozilla.com>
Tue, 16 Dec 2014 22:52:57 +1300
changeset 244830 9eb09ba7451a443fdc5763b879f4f11b4a855895
parent 244829 a024fc4f0bd28a9c0b7d78e965ec199769247753
child 244831 7e2a075d8906fe526755f8a6d433a58d63fe6596
push id660
push userraliiev@mozilla.com
push dateWed, 18 Feb 2015 20:30:48 +0000
treeherdermozilla-release@49e493494178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, sledru
bugs1111413
milestone36.0a2
Bug 1111413 - Part 1: Use MediaPromises for seeking. r=bholley, a=sledru
dom/media/MediaDataDecodedListener.h
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaPromise.h
dom/media/android/AndroidMediaReader.cpp
dom/media/android/AndroidMediaReader.h
dom/media/apple/AppleMP3Reader.cpp
dom/media/apple/AppleMP3Reader.h
dom/media/directshow/DirectShowReader.cpp
dom/media/directshow/DirectShowReader.h
dom/media/fmp4/MP4Reader.cpp
dom/media/fmp4/MP4Reader.h
dom/media/gstreamer/GStreamerReader.cpp
dom/media/gstreamer/GStreamerReader.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/ogg/OggReader.cpp
dom/media/ogg/OggReader.h
dom/media/omx/MediaCodecReader.cpp
dom/media/omx/MediaCodecReader.h
dom/media/omx/MediaOmxReader.cpp
dom/media/omx/MediaOmxReader.h
dom/media/omx/RtspMediaCodecReader.cpp
dom/media/omx/RtspMediaCodecReader.h
dom/media/omx/RtspOmxReader.cpp
dom/media/omx/RtspOmxReader.h
dom/media/raw/RawReader.cpp
dom/media/raw/RawReader.h
dom/media/wave/WaveReader.cpp
dom/media/wave/WaveReader.h
dom/media/webm/WebMReader.cpp
dom/media/webm/WebMReader.h
dom/media/wmf/WMFReader.cpp
dom/media/wmf/WMFReader.h
--- a/dom/media/MediaDataDecodedListener.h
+++ b/dom/media/MediaDataDecodedListener.h
@@ -31,30 +31,16 @@ public:
   }
 
   void BreakCycles() {
     MonitorAutoLock lock(mMonitor);
     mTarget = nullptr;
     mTaskQueue = nullptr;
   }
 
-  virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<nsresult>(mTarget,
-                                                                   &Target::OnSeekCompleted,
-                                                                   aResult));
-    if (NS_FAILED(mTaskQueue->Dispatch(task))) {
-      NS_WARNING("Failed to dispatch OnSeekCompleted task");
-    }
-  }
-
 private:
   Monitor mMonitor;
   RefPtr<MediaTaskQueue> mTaskQueue;
   RefPtr<Target> mTarget;
 };
 
 } /* namespace mozilla */
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -35,16 +35,17 @@ public:
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
   typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason> AudioDataPromise;
   typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason> VideoDataPromise;
+  typedef MediaPromise<bool, nsresult> SeekPromise;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) = 0;
@@ -126,25 +127,22 @@ public:
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) = 0;
 
   // Fills aInfo with the latest cached data required to present the media,
   // ReadUpdatedMetadata will always be called once ReadMetadata has succeeded.
   virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
 
-  // Requests the Reader to seek and call OnSeekCompleted on the callback
-  // once completed.
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
-  virtual void Seek(int64_t aTime,
-                    int64_t aStartTime,
-                    int64_t aEndTime,
-                    int64_t aCurrentTime) = 0;
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime,
+       int64_t aEndTime, int64_t aCurrentTime) = 0;
 
   // Called to move the reader into idle state. When the reader is
   // created it is assumed to be active (i.e. not idle). When the media
   // element is paused and we don't need to decode any more data, the state
   // machine calls SetIdle() to inform the reader that its decoder won't be
   // needed for a while. The reader can use these notifications to enter
   // a low power state when the decoder isn't needed, if desired.
   // This is most useful on mobile.
@@ -307,18 +305,16 @@ private:
 // Interface that callers to MediaDecoderReader::Request{Audio,Video}Data()
 // must implement to receive the requested samples asynchronously.
 // This object is refcounted, and cycles must be broken by calling
 // BreakCycles() during shutdown.
 class RequestSampleCallback {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestSampleCallback)
 
-  virtual void OnSeekCompleted(nsresult aResult) = 0;
-
   // Called during shutdown to break any reference cycles.
   virtual void BreakCycles() = 0;
 
 protected:
   virtual ~RequestSampleCallback() {}
 };
 
 } // namespace mozilla
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2294,47 +2294,56 @@ void MediaDecoderStateMachine::DecodeSee
 
     nsresult res;
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       // We must not hold the state machine monitor while we call into
       // the reader, since it could do I/O or deadlock some other way.
       res = mReader->ResetDecode();
       if (NS_SUCCEEDED(res)) {
-        mReader->Seek(seekTime,
-                      mStartTime,
-                      mEndTime,
-                      mCurrentTimeBeforeSeek);
+        mReader->Seek(seekTime, mStartTime, mEndTime, mCurrentTimeBeforeSeek)
+               ->Then(DecodeTaskQueue(), __func__, this,
+                      &MediaDecoderStateMachine::OnSeekCompleted,
+                      &MediaDecoderStateMachine::OnSeekFailed);
       }
     }
     if (NS_FAILED(res)) {
       DecodeError();
       return;
     }
     mWaitingForDecoderSeek = true;
   }
 }
 
 void
-MediaDecoderStateMachine::OnSeekCompleted(nsresult aResult)
+MediaDecoderStateMachine::OnSeekCompleted()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mWaitingForDecoderSeek = false;
-  if (NS_FAILED(aResult)) {
-    DecodeError();
-    return;
-  }
 
   // We must decode the first samples of active streams, so we can determine
   // the new stream time. So dispatch tasks to do that.
   mDecodeToSeekTarget = true;
   DispatchDecodeTasksIfNeeded();
 }
 
 void
+MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  mWaitingForDecoderSeek = false;
+  // Sometimes we reject the promise for non-failure reasons, like
+  // when we request a second seek before the previous one has
+  // completed.
+  if (NS_FAILED(aResult)) {
+    DecodeError();
+  }
+}
+
+void
 MediaDecoderStateMachine::SeekCompleted()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // We must reset the seek target when exiting this function, but not
   // before, as if we dropped the monitor in any function called here,
   // we may begin a new seek on the state machine thread, and be in
   // an inconsistent state.
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -377,17 +377,18 @@ public:
     OnNotDecoded(MediaData::AUDIO_DATA, aReason);
   }
   void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     MOZ_ASSERT(OnDecodeThread());
     OnNotDecoded(MediaData::VIDEO_DATA, aReason);
   }
 
-  void OnSeekCompleted(nsresult aResult);
+  void OnSeekCompleted();
+  void OnSeekFailed(nsresult aResult);
 
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -60,16 +60,34 @@ public:
   explicit MediaPromise(const char* aCreationSite)
     : mCreationSite(aCreationSite)
     , mMutex("MediaPromise Mutex")
   {
     MOZ_COUNT_CTOR(MediaPromise);
     PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this);
   }
 
+  static nsRefPtr<MediaPromise<ResolveValueT, RejectValueT>>
+  CreateAndResolve(ResolveValueType aResolveValue, const char* aResolveSite)
+  {
+    nsRefPtr<MediaPromise<ResolveValueT, RejectValueT>> p =
+      new MediaPromise<ResolveValueT, RejectValueT>(aResolveSite);
+    p->Resolve(aResolveValue, aResolveSite);
+    return p;
+  }
+
+  static nsRefPtr<MediaPromise<ResolveValueT, RejectValueT>>
+  CreateAndReject(RejectValueType aRejectValue, const char* aRejectSite)
+  {
+    nsRefPtr<MediaPromise<ResolveValueT, RejectValueT>> p =
+      new MediaPromise<ResolveValueT, RejectValueT>(aRejectSite);
+    p->Reject(aRejectValue, aRejectSite);
+    return p;
+  }
+
 protected:
 
   /*
    * A ThenValue tracks a single consumer waiting on the promise. When a consumer
    * invokes promise->Then(...), a ThenValue is created. Once the Promise is
    * resolved or rejected, a {Resolve,Reject}Runnable is dispatched, which
    * invokes the resolve/reject method and then deletes the ThenValue.
    */
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -314,17 +314,18 @@ bool AndroidMediaReader::DecodeAudioData
                               source.mAudioSampleRate,
                               frames,
                               source.mAudioChannels,
                               MPCopy(static_cast<uint8_t *>(source.mData),
                                      source.mSize,
                                      source.mAudioChannels));
 }
 
-void AndroidMediaReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+AndroidMediaReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   if (mHasAudio && mHasVideo) {
     // The decoder seeks/demuxes audio and video streams separately. So if
     // we seek both audio and video to aTarget, the audio stream can typically
     // seek closer to the seek target, since typically every audio block is
     // a sync point, whereas for video there are only keyframes once every few
@@ -334,17 +335,17 @@ void AndroidMediaReader::Seek(int64_t aT
     // audio and video streams won't be in sync after the seek.
     mVideoSeekTimeUs = aTarget;
     const VideoData* v = DecodeToFirstVideoData();
     mAudioSeekTimeUs = v ? v->mTime : aTarget;
   } else {
     mAudioSeekTimeUs = mVideoSeekTimeUs = aTarget;
   }
 
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 AndroidMediaReader::ImageBufferCallback::ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer) :
   mImageContainer(aImageContainer)
 {
 }
 
 void *
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -64,17 +64,18 @@ public:
   virtual bool IsMediaSeekable()
   {
     // not used
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   class ImageBufferCallback : public MPAPI::BufferCallback {
     typedef mozilla::layers::Image Image;
 
   public:
     ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer);
--- a/dom/media/apple/AppleMP3Reader.cpp
+++ b/dom/media/apple/AppleMP3Reader.cpp
@@ -487,17 +487,17 @@ AppleMP3Reader::SetupDecoder()
   if (rv) {
     LOGE("Error constructing audio format converter: %x\n", rv);
     mAudioConverter = nullptr;
     return;
   }
 }
 
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 AppleMP3Reader::Seek(int64_t aTime,
                      int64_t aStartTime,
                      int64_t aEndTime,
                      int64_t aCurrentTime)
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread");
   NS_ASSERTION(aStartTime < aEndTime,
                "Seeking should happen over a positive range");
@@ -513,29 +513,28 @@ AppleMP3Reader::Seek(int64_t aTime,
 
   OSStatus rv = AudioFileStreamSeek(mAudioFileStream,
                                     packet,
                                     &byteOffset,
                                     &flags);
 
   if (rv) {
     LOGE("Couldn't seek demuxer. Error code %x\n", rv);
-    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-    return;
+    return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   LOGD("computed byte offset = %lld; estimated = %s\n",
        byteOffset,
        (flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO");
 
   mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
 
   ResetDecode();
 
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 void
 AppleMP3Reader::NotifyDataArrived(const char* aBuffer,
                                   uint32_t aLength,
                                   int64_t aOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/apple/AppleMP3Reader.h
+++ b/dom/media/apple/AppleMP3Reader.h
@@ -28,20 +28,21 @@ public:
                                 int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   virtual bool HasAudio() MOZ_OVERRIDE;
   virtual bool HasVideo() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
-  virtual void Seek(int64_t aTime,
-                    int64_t aStartTime,
-                    int64_t aEndTime,
-                    int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   void AudioSampleCallback(UInt32 aNumBytes,
                            UInt32 aNumPackets,
                            const void *aData,
                            AudioStreamPacketDescription *aPackets);
 
   void AudioMetadataCallback(AudioFileStreamID aFileStream,
                              AudioFileStreamPropertyID aPropertyID,
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -362,24 +362,28 @@ DirectShowReader::HasAudio()
 
 bool
 DirectShowReader::HasVideo()
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   return false;
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 DirectShowReader::Seek(int64_t aTargetUs,
                        int64_t aStartTime,
                        int64_t aEndTime,
                        int64_t aCurrentTime)
 {
   nsresult res = SeekInternal(aTargetUs);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 nsresult
 DirectShowReader::SeekInternal(int64_t aTargetUs)
 {
   HRESULT hr;
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");\
 
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -55,20 +55,21 @@ public:
                         int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   bool HasAudio() MOZ_OVERRIDE;
   bool HasVideo() MOZ_OVERRIDE;
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
-  void Seek(int64_t aTime,
-            int64_t aStartTime,
-            int64_t aEndTime,
-            int64_t aCurrentTime) MOZ_OVERRIDE;
+  nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   void NotifyDataArrived(const char* aBuffer,
                          uint32_t aLength,
                          int64_t aOffset) MOZ_OVERRIDE;
 
   bool IsMediaSeekable() MOZ_OVERRIDE;
 
 private:
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -778,41 +778,40 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(
     }
     mQueuedVideoSample = compressed;
     break;
   }
 
   return true;
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 MP4Reader::Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime)
 {
   LOG("MP4Reader::Seek(%lld)", aTime);
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) {
     VLOG("Seek() END (Unseekable)");
-    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-    return;
+    return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   mQueuedVideoSample = nullptr;
   if (mDemuxer->HasValidVideo()) {
     mDemuxer->SeekVideo(aTime);
     mQueuedVideoSample = PopSample(kVideo);
   }
   if (mDemuxer->HasValidAudio()) {
     mDemuxer->SeekAudio(
       mQueuedVideoSample ? mQueuedVideoSample->composition_timestamp : aTime);
   }
   LOG("MP4Reader::Seek(%lld) exit", aTime);
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 void
 MP4Reader::UpdateIndex()
 {
   if (!mIndexReady) {
     return;
   }
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -50,20 +50,21 @@ public:
   // MediaDecoderStateMachine could go to DORMANT state if the hardware codec is
   // not available.
   virtual void PreReadMetadata() MOZ_OVERRIDE;
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
   virtual void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
 
-  virtual void Seek(int64_t aTime,
-                    int64_t aStartTime,
-                    int64_t aEndTime,
-                    int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual int64_t GetEvictionOffset(double aTime) MOZ_OVERRIDE;
 
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;
 
   // For Media Resource Management
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -783,43 +783,43 @@ bool GStreamerReader::DecodeVideoFrame(b
                                                          isKeyframe, -1, mPicture);
   mVideoQueue.Push(video);
 
   gst_buffer_unref(buffer);
 
   return true;
 }
 
-void GStreamerReader::Seek(int64_t aTarget,
-                           int64_t aStartTime,
-                           int64_t aEndTime,
-                           int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+GStreamerReader::Seek(int64_t aTarget,
+                      int64_t aStartTime,
+                      int64_t aEndTime,
+                      int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   gint64 seekPos = aTarget * GST_USECOND;
   LOG(PR_LOG_DEBUG, "%p About to seek to %" GST_TIME_FORMAT,
         mDecoder, GST_TIME_ARGS(seekPos));
 
   int flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT;
   if (!gst_element_seek_simple(mPlayBin,
                                GST_FORMAT_TIME,
                                static_cast<GstSeekFlags>(flags),
                                seekPos)) {
     LOG(PR_LOG_ERROR, "seek failed");
-    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-    return;
+    return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
   LOG(PR_LOG_DEBUG, "seek succeeded");
   GstMessage* message = gst_bus_timed_pop_filtered(mBus, GST_CLOCK_TIME_NONE,
                (GstMessageType)(GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR));
   gst_message_unref(message);
   LOG(PR_LOG_DEBUG, "seek completed");
 
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 nsresult GStreamerReader::GetBuffered(dom::TimeRanges* aBuffered)
 {
   if (!mInfo.HasValidMedia()) {
     return NS_OK;
   }
 
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -43,20 +43,21 @@ public:
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual bool DecodeAudioData();
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold);
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime,
-                    int64_t aStartTime,
-                    int64_t aEndTime,
-                    int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered);
 
   virtual void NotifyDataArrived(const char *aBuffer,
                                  uint32_t aLength,
                                  int64_t aOffset) MOZ_OVERRIDE;
 
   virtual bool HasAudio() {
     return mInfo.HasAudio();
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -291,16 +291,18 @@ MediaSourceReader::OnVideoNotDecoded(Not
   // We don't have the data the caller wants. Tell that we're waiting for JS to
   // give us more data.
   mVideoPromise.Reject(WAITING_FOR_DATA, __func__);
 }
 
 nsRefPtr<ShutdownPromise>
 MediaSourceReader::Shutdown()
 {
+  mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+
   MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
   nsRefPtr<ShutdownPromise> p = mMediaSourceShutdownPromise.Ensure(__func__);
 
   ContinueShutdown();
   return p;
 }
 
 void
@@ -533,26 +535,29 @@ MediaSourceReader::NotifyTimeRangesChang
   if (mWaitingForSeekData) {
     //post a task to the state machine thread to call seek.
     RefPtr<nsIRunnable> task(NS_NewRunnableMethod(
         this, &MediaSourceReader::AttemptSeek));
     GetTaskQueue()->Dispatch(task.forget());
   }
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaSourceReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
                         int64_t aCurrentTime)
 {
   MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aStart=%lld, aEnd=%lld, aCurrent=%lld)",
             this, aTime, aStartTime, aEndTime, aCurrentTime);
 
+  mSeekPromise.RejectIfExists(NS_OK, __func__);
+  nsRefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
+
   if (IsShutdown()) {
-    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-    return;
+    mSeekPromise.Reject(NS_ERROR_FAILURE, __func__);
+    return p;
   }
 
   // Store pending seek target in case the track buffers don't contain
   // the desired time and we delay doing the seek.
   mPendingSeekTime = aTime;
   mPendingStartTime = aStartTime;
   mPendingEndTime = aEndTime;
   mPendingCurrentTime = aCurrentTime;
@@ -570,30 +575,48 @@ MediaSourceReader::Seek(int64_t aTime, i
       }
       if (mVideoTrack) {
         mPendingSeeks++;
       }
     }
   }
 
   AttemptSeek();
+  return p;
 }
 
 void
-MediaSourceReader::OnSeekCompleted(nsresult aResult)
+MediaSourceReader::OnSeekCompleted()
+{
+  mPendingSeeks--;
+  FinalizeSeek();
+}
+
+void
+MediaSourceReader::OnSeekFailed(nsresult aResult)
 {
   mPendingSeeks--;
   // Keep the most recent failed result (if any)
   if (NS_FAILED(aResult)) {
     mSeekResult = aResult;
   }
+  FinalizeSeek();
+}
+
+void
+MediaSourceReader::FinalizeSeek()
+{
   // Only dispatch the final event onto the state machine
   // since it's only expecting one response.
   if (!mPendingSeeks) {
-    GetCallback()->OnSeekCompleted(mSeekResult);
+    if (NS_FAILED(mSeekResult)) {
+      mSeekPromise.Reject(mSeekResult, __func__);
+    } else {
+      mSeekPromise.Resolve(true, __func__);
+    }
     mSeekResult = NS_OK;
   }
 }
 
 void
 MediaSourceReader::AttemptSeek()
 {
   // Make sure we don't hold the monitor while calling into the reader
@@ -615,26 +638,32 @@ MediaSourceReader::AttemptSeek()
   mLastVideoTime = mPendingSeekTime;
 
   if (mAudioTrack) {
     mAudioIsSeeking = true;
     SwitchAudioReader(mPendingSeekTime);
     mAudioReader->Seek(mPendingSeekTime,
                        mPendingStartTime,
                        mPendingEndTime,
-                       mPendingCurrentTime);
+                       mPendingCurrentTime)
+                ->Then(GetTaskQueue(), __func__, this,
+                       &MediaSourceReader::OnSeekCompleted,
+                       &MediaSourceReader::OnSeekFailed);
     MSE_DEBUG("MediaSourceReader(%p)::Seek audio reader=%p", this, mAudioReader.get());
   }
   if (mVideoTrack) {
     mVideoIsSeeking = true;
     SwitchVideoReader(mPendingSeekTime);
     mVideoReader->Seek(mPendingSeekTime,
                        mPendingStartTime,
                        mPendingEndTime,
-                       mPendingCurrentTime);
+                       mPendingCurrentTime)
+                ->Then(GetTaskQueue(), __func__, this,
+                       &MediaSourceReader::OnSeekCompleted,
+                       &MediaSourceReader::OnSeekFailed);
     MSE_DEBUG("MediaSourceReader(%p)::Seek video reader=%p", this, mVideoReader.get());
   }
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mWaitingForSeekData = false;
   }
 }
 
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -50,17 +50,18 @@ public:
   nsRefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   void OnAudioDecoded(AudioData* aSample);
   void OnAudioNotDecoded(NotDecodedReason aReason);
   void OnVideoDecoded(VideoData* aSample);
   void OnVideoNotDecoded(NotDecodedReason aReason);
 
-  void OnSeekCompleted(nsresult aResult);
+  void OnSeekCompleted();
+  void OnSeekFailed(nsresult aResult);
 
   bool HasVideo() MOZ_OVERRIDE
   {
     return mInfo.HasVideo();
   }
 
   bool HasAudio() MOZ_OVERRIDE
   {
@@ -79,18 +80,19 @@ public:
   // essentially a streaming heuristic, but JS is supposed to take care of that
   // in the MSE world. Avoid injecting inexplicable delays.
   virtual uint32_t GetBufferingWait() { return 0; }
 
   bool IsMediaSeekable() { return true; }
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE;
   void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
-  void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-            int64_t aCurrentTime) MOZ_OVERRIDE;
+  nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   // Acquires the decoder monitor, and is thus callable on any thread.
   nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;
 
   already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType);
 
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
@@ -124,16 +126,17 @@ private:
   bool SwitchVideoReader(int64_t aTarget);
 
   // Return a reader from the set available in aTrackDecoders that has data
   // available in the range requested by aTarget.
   already_AddRefed<MediaDecoderReader> SelectReader(int64_t aTarget,
                                                     const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
 
   void AttemptSeek();
+  void FinalizeSeek();
 
   nsRefPtr<MediaDecoderReader> mAudioReader;
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
@@ -147,16 +150,17 @@ private:
 #endif
 
   // These are read and written on the decode task queue threads.
   int64_t mLastAudioTime;
   int64_t mLastVideoTime;
 
   // Temporary seek information while we wait for the data
   // to be added to the track buffer.
+  MediaPromiseHolder<SeekPromise> mSeekPromise;
   int64_t mPendingSeekTime;
   int64_t mPendingStartTime;
   int64_t mPendingEndTime;
   int64_t mPendingCurrentTime;
   bool mWaitingForSeekData;
 
   // Number of outstanding OnSeekCompleted notifications
   // we're expecting to get from child decoders, and the
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -1449,23 +1449,28 @@ nsresult OggReader::SeekInUnbuffered(int
 #endif /* MOZ_OPUS */
   int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
   // Minimize the bisection search space using the known timestamps from the
   // buffered ranges.
   SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false);
   return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
 }
 
-void OggReader::Seek(int64_t aTarget,
-                     int64_t aStartTime,
-                     int64_t aEndTime,
-                     int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+OggReader::Seek(int64_t aTarget,
+                int64_t aStartTime,
+                int64_t aEndTime,
+                int64_t aCurrentTime)
 {
   nsresult res = SeekInternal(aTarget, aStartTime, aEndTime, aCurrentTime);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 nsresult OggReader::SeekInternal(int64_t aTarget,
                                  int64_t aStartTime,
                                  int64_t aEndTime,
                                  int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -73,17 +73,18 @@ public:
   }
 
   virtual bool HasVideo() {
     return mTheoraState != 0 && mTheoraState->mActive;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
 private:
   // TODO: DEPRECATED. This uses synchronous decoding.
   // Stores the presentation time of the first frame we'd be able to play if
   // we started playback at the current position. Returns the first video
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -994,17 +994,17 @@ MediaCodecReader::DecodeVideoFrameSync(i
     textureClient->SetRecycleCallback(MediaCodecReader::TextureClientRecycleCallback, this);
   } else {
     mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
   }
 
   return result;
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaCodecReader::Seek(int64_t aTime,
                        int64_t aStartTime,
                        int64_t aEndTime,
                        int64_t aCurrentTime)
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   mVideoTrack.mSeekTimeUs = aTime;
@@ -1026,18 +1026,17 @@ MediaCodecReader::Seek(int64_t aTime,
     }
 
     MediaBuffer* source_buffer = nullptr;
     MediaSource::ReadOptions options;
     int64_t timestamp = sInvalidTimestampUs;
     options.setSeekTo(aTime, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
     if (mVideoTrack.mSource->read(&source_buffer, &options) != OK ||
         source_buffer == nullptr) {
-      GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-      return;
+      return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
     }
     sp<MetaData> format = source_buffer->meta_data();
     if (format != nullptr) {
       if (format->findInt64(kKeyTime, &timestamp) &&
           IsValidTimestampUs(timestamp)) {
         mVideoTrack.mSeekTimeUs = timestamp;
         mAudioTrack.mSeekTimeUs = timestamp;
       }
@@ -1051,17 +1050,17 @@ MediaCodecReader::Seek(int64_t aTime,
     if (CheckAudioResources()) {
       MOZ_ASSERT(mAudioTrack.mTaskQueue->IsEmpty());
       DispatchAudioTask();
     }
   } else if (CheckAudioResources()) {// Audio only
     MOZ_ASSERT(mAudioTrack.mTaskQueue->IsEmpty());
     DispatchAudioTask();
   }
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 bool
 MediaCodecReader::IsMediaSeekable()
 {
   // Check the MediaExtract flag if the source is seekable.
   return (mExtractor != nullptr) &&
          (mExtractor->flags() & MediaExtractor::CAN_SEEK);
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -95,20 +95,21 @@ public:
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
 
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
-  virtual void Seek(int64_t aTime,
-                    int64_t aStartTime,
-                    int64_t aEndTime,
-                    int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual android::sp<android::MediaSource> GetAudioOffloadTrack();
 
 protected:
   struct TrackInputCopier
   {
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -538,17 +538,18 @@ bool MediaOmxReader::DecodeAudioData()
                               source.mAudioSampleRate,
                               frames,
                               source.mAudioChannels,
                               OmxCopy(static_cast<uint8_t *>(source.mData),
                                       source.mSize,
                                       source.mAudioChannels));
 }
 
-void MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   EnsureActive();
 
   VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
   if (container && container->GetImageContainer()) {
     container->GetImageContainer()->ClearAllImagesExceptFront();
   }
@@ -564,17 +565,17 @@ void MediaOmxReader::Seek(int64_t aTarge
     // audio and video streams won't be in sync after the seek.
     mVideoSeekTimeUs = aTarget;
     const VideoData* v = DecodeToFirstVideoData();
     mAudioSeekTimeUs = v ? v->mTime : aTarget;
   } else {
     mAudioSeekTimeUs = mVideoSeekTimeUs = aTarget;
   }
 
-  GetCallback()->OnSeekCompleted(NS_OK);
+  return SeekPromise::CreateAndResolve(true, __func__);
 }
 
 void MediaOmxReader::SetIdle() {
   if (!mOmxDecoder.get()) {
     return;
   }
   mOmxDecoder->Pause();
 }
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -93,17 +93,18 @@ public:
   virtual bool IsWaitingMediaResources() MOZ_OVERRIDE;
 
   virtual bool IsDormantNeeded();
   virtual void ReleaseMediaResources();
 
   virtual void PreReadMetadata() MOZ_OVERRIDE;
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
   virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   bool IsShutdown() {
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -33,27 +33,27 @@ RtspMediaCodecReader::CreateExtractor()
     return true;
   }
 
   mExtractor = new RtspExtractor(mRtspResource);
 
   return mExtractor != nullptr;
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 RtspMediaCodecReader::Seek(int64_t aTime, int64_t aStartTime,
                            int64_t aEndTime, int64_t aCurrentTime)
 {
   // The seek function of Rtsp is time-based, we call the SeekTime function in
   // RtspMediaResource. The SeekTime function finally send a seek command to
   // Rtsp stream server through network and also clear the buffer data in
   // RtspMediaResource.
   mRtspResource->SeekTime(aTime);
 
-  MediaCodecReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
+  return MediaCodecReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
 }
 
 void
 RtspMediaCodecReader::SetIdle()
 {
   nsIStreamingProtocolController* controller =
     mRtspResource->GetMediaStreamController();
   if (controller) {
--- a/dom/media/omx/RtspMediaCodecReader.h
+++ b/dom/media/omx/RtspMediaCodecReader.h
@@ -31,18 +31,19 @@ protected:
   void EnsureActive();
 
 public:
   RtspMediaCodecReader(AbstractMediaDecoder* aDecoder);
 
   virtual ~RtspMediaCodecReader();
 
   // Implement a time-based seek instead of byte-based.
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                    int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   // Override GetBuffered() to do nothing for below reasons:
   // 1. Because the Rtsp stream is a/v separated. The buffered data in a/v
   // tracks are not consistent with time stamp.
   // For example: audio buffer: 1~2s, video buffer: 1.5~2.5s
   // 2. Since the Rtsp is a realtime streaming, the buffer we made for
   // RtspMediaResource is quite small. The small buffer implies the time ranges
   // we returned are not useful for the MediaDecodeStateMachine. Unlike the
--- a/dom/media/omx/RtspOmxReader.cpp
+++ b/dom/media/omx/RtspOmxReader.cpp
@@ -27,33 +27,34 @@ nsresult RtspOmxReader::InitOmxDecoder()
     mOmxDecoder = new OmxDecoder(mDecoder->GetResource(), mDecoder);
     if (!mOmxDecoder->Init(mExtractor)) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
-void RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime,
-                         int64_t aEndTime, int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime,
+                    int64_t aEndTime, int64_t aCurrentTime)
 {
   // The seek function of Rtsp is time-based, we call the SeekTime function in
   // RtspMediaResource. The SeekTime function finally send a seek command to
   // Rtsp stream server through network and also clear the buffer data in
   // RtspMediaResource.
   if (mRtspResource) {
     mRtspResource->SeekTime(aTime);
     mRtspResource->EnablePlayoutDelay();
   }
 
   // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
   // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
   // that store the decoded data and also call the |DecodeToTarget| to pass
   // the seek time to OMX a/v decoders.
-  MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
+  return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
 }
 
 void RtspOmxReader::SetIdle() {
   // Call parent class to set OMXCodec idle.
   MediaOmxReader::SetIdle();
 
   // Need to pause RTSP streaming OMXCodec decoding.
   if (mRtspResource) {
--- a/dom/media/omx/RtspOmxReader.h
+++ b/dom/media/omx/RtspOmxReader.h
@@ -41,18 +41,19 @@ public:
     MOZ_ASSERT(mRtspResource);
   }
 
   virtual ~RtspOmxReader() MOZ_OVERRIDE {
     MOZ_COUNT_DTOR(RtspOmxReader);
   }
 
   // Implement a time-based seek instead of byte-based..
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                    int64_t aCurrentTime) MOZ_FINAL MOZ_OVERRIDE;
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_FINAL MOZ_OVERRIDE;
 
   // Override GetBuffered() to do nothing for below reasons:
   // 1. Because the Rtsp stream is a/v separated. The buffered data in a/v
   // tracks are not consistent with time stamp.
   // For example: audio buffer: 1~2s, video buffer: 1.5~2.5s
   // 2. Since the Rtsp is a realtime streaming, the buffer we made for
   // RtspMediaResource is quite small. The small buffer implies the time ranges
   // we returned are not useful for the MediaDecodeStateMachine. Unlike the
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -230,20 +230,25 @@ bool RawReader::DecodeVideoFrame(bool &a
   mVideoQueue.Push(v);
   mCurrentFrame++;
   decoded++;
   currentFrameTime += USECS_PER_S / mFrameRate;
 
   return true;
 }
 
-void RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
 {
   nsresult res = SeekInternal(aTime);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 nsresult RawReader::SeekInternal(int64_t aTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(),
                "Should be on decode thread.");
 
   MediaResource *resource = mDecoder->GetResource();
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -34,17 +34,19 @@ public:
 
   virtual bool HasVideo()
   {
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
+
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
 private:
   bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
 
   nsresult SeekInternal(int64_t aTime);
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -252,33 +252,38 @@ bool WaveReader::DecodeAudioData()
 bool WaveReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                       int64_t aTimeThreshold)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   return false;
 }
 
-void WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+nsRefPtr<MediaDecoderReader::SeekPromise>
+WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
+
   if (NS_FAILED(ResetDecode())) {
-    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
-    return;
+    return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
   double d = BytesToTime(GetDataLength());
   NS_ASSERTION(d < INT64_MAX / USECS_PER_S, "Duration overflow");
   int64_t duration = static_cast<int64_t>(d * USECS_PER_S);
   double seekTime = std::min(aTarget, duration) / static_cast<double>(USECS_PER_S);
   int64_t position = RoundDownToFrame(static_cast<int64_t>(TimeToBytes(seekTime)));
   NS_ASSERTION(INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek");
   position += mWavePCMOffset;
   nsresult res = mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 static double RoundToUsecs(double aSeconds) {
   return floor(aSeconds * USECS_PER_S) / USECS_PER_S;
 }
 
 nsresult WaveReader::GetBuffered(dom::TimeRanges* aBuffered)
 {
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -38,17 +38,19 @@ public:
 
   virtual bool HasVideo()
   {
     return false;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
+
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered);
 
   // To seek in a buffered range, we just have to seek the stream.
   virtual bool IsSeekableInBufferedRanges() {
     return true;
   }
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -961,21 +961,26 @@ bool WebMReader::DecodeVideoFrame(bool &
   return mVideoDecoder->DecodeVideoFrame(aKeyframeSkip, aTimeThreshold);
 }
 
 void WebMReader::PushVideoPacket(NesteggPacketHolder* aItem)
 {
     mVideoPackets.PushFront(aItem);
 }
 
-void WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
+nsRefPtr<MediaDecoderReader::SeekPromise>
+WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
                       int64_t aCurrentTime)
 {
   nsresult res = SeekInternal(aTarget, aStartTime);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 nsresult WebMReader::SeekInternal(int64_t aTarget, int64_t aStartTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (mVideoDecoder) {
     nsresult rv = mVideoDecoder->Flush();
     NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -152,18 +152,19 @@ public:
   virtual bool HasVideo()
   {
     NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     return mHasVideo;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                    int64_t aCurrentTime);
+  virtual nsRefPtr<SeekPromise>
+  Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE;
+
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered);
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength,
                                  int64_t aOffset);
   virtual int64_t GetEvictionOffset(double aTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   // Value passed to NextPacket to determine if we are reading a video or an
--- a/dom/media/wmf/WMFReader.cpp
+++ b/dom/media/wmf/WMFReader.cpp
@@ -883,24 +883,28 @@ WMFReader::DecodeVideoFrame(bool &aKeyfr
     // End of stream.
     DECODER_LOG("End of video stream");
     return false;
   }
 
   return true;
 }
 
-void
+nsRefPtr<MediaDecoderReader::SeekPromise>
 WMFReader::Seek(int64_t aTargetUs,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime)
 {
   nsresult res = SeekInternal(aTargetUs);
-  GetCallback()->OnSeekCompleted(res);
+  if (NS_FAILED(res)) {
+    return SeekPromise::CreateAndReject(res, __func__);
+  } else {
+    return SeekPromise::CreateAndResolve(true, __func__);
+  }
 }
 
 nsresult
 WMFReader::SeekInternal(int64_t aTargetUs)
 {
   DECODER_LOG("WMFReader::Seek() %lld", aTargetUs);
 
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
--- a/dom/media/wmf/WMFReader.h
+++ b/dom/media/wmf/WMFReader.h
@@ -38,20 +38,21 @@ public:
                         int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   bool HasAudio() MOZ_OVERRIDE;
   bool HasVideo() MOZ_OVERRIDE;
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
-  void Seek(int64_t aTime,
-            int64_t aStartTime,
-            int64_t aEndTime,
-            int64_t aCurrentTime) MOZ_OVERRIDE;
+  nsRefPtr<SeekPromise>
+  Seek(int64_t aTime,
+       int64_t aStartTime,
+       int64_t aEndTime,
+       int64_t aCurrentTime) MOZ_OVERRIDE;
 
   bool IsMediaSeekable() MOZ_OVERRIDE;
   
 private:
 
   HRESULT CreateSourceReader();
   HRESULT ConfigureAudioDecoder();
   HRESULT ConfigureVideoDecoder();