Bug 1056441 - Make MediaSourceReader seeking asynchronous. r=kinetik
authorMatt Woodrow <mwoodrow@mozilla.com>
Wed, 05 Nov 2014 13:32:26 +1300
changeset 238615 82171e97db6e0454ffab559bca15c3b87aa5fe75
parent 238614 bb67cbef0a07ecff3afd0742af80324e88b9f2d9
child 238616 970f11f7f1774d180f330ca72e717d55e083d894
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1056441
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1056441 - Make MediaSourceReader seeking asynchronous. r=kinetik
dom/media/MediaDataDecodedListener.h
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.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/MediaSourceDecoder.cpp
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
@@ -65,16 +65,30 @@ 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:
 
   class DeliverAudioTask : public nsRunnable {
   public:
     DeliverAudioTask(AudioData* aSample, Target* aTarget)
       : mSample(aSample)
       , mTarget(aTarget)
     {
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -96,23 +96,25 @@ public:
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) = 0;
 
+  // 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 nsresult Seek(int64_t aTime,
-                        int64_t aStartTime,
-                        int64_t aEndTime,
-                        int64_t aCurrentTime) = 0;
+  virtual void 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.
@@ -270,16 +272,18 @@ public:
 
   // Receives the result of a RequestVideoData() call.
   virtual void OnVideoDecoded(VideoData* aSample) = 0;
 
   // Called when a RequestAudioData() or RequestVideoData() call can't be
   // fulfiled. The reason is passed as aReason.
   virtual void OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason) = 0;
 
+  virtual void OnSeekCompleted(nsresult aResult) = 0;
+
   // Called during shutdown to break any reference cycles.
   virtual void BreakCycles() = 0;
 
 protected:
   virtual ~RequestSampleCallback() {}
 };
 
 // A RequestSampleCallback implementation that can be passed to the
@@ -293,16 +297,17 @@ public:
   AudioDecodeRendezvous();
   ~AudioDecodeRendezvous();
 
   // RequestSampleCallback implementation. Called when decode is complete.
   // Note: aSample is null at end of stream.
   virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE;
   virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {}
   virtual void OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason) MOZ_OVERRIDE;
+  virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {};
   virtual void BreakCycles() MOZ_OVERRIDE {};
   void Reset();
 
   // Returns failure on error, or NS_OK.
   // If *aSample is null, EOS has been reached.
   nsresult Await(nsAutoPtr<AudioData>& aSample);
 
   // Interrupts a call to Wait().
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -207,16 +207,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mDropAudioUntilNextDiscontinuity(false),
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
+  mWaitingForDecoderSeek(false),
   mCurrentTimeBeforeSeek(0),
   mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
   mDecodingFrozenAtStateMetadata(false),
   mDecodingFrozenAtStateDecoding(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
@@ -1729,17 +1730,17 @@ MediaDecoderStateMachine::EnsureAudioDec
               IsAudioDecoding(), mAudioRequestPending);
 
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
 
-  if (IsAudioDecoding() && !mAudioRequestPending) {
+  if (IsAudioDecoding() && !mAudioRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mAudioRequestPending = true;
     } else {
       DECODER_WARN("Failed to dispatch task to decode audio");
     }
@@ -1774,17 +1775,17 @@ MediaDecoderStateMachine::EnsureVideoDec
                "Should be on state machine or decode thread.");
 
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
 
-  if (IsVideoDecoding() && !mVideoRequestPending) {
+  if (IsVideoDecoding() && !mVideoRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mVideoRequestPending = true;
     } else {
       DECODER_WARN("Failed to dispatch task to decode video");
     }
@@ -2131,35 +2132,47 @@ 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)) {
-        res = mReader->Seek(seekTime,
-                            mStartTime,
-                            mEndTime,
-                            mCurrentTimeBeforeSeek);
+        mReader->Seek(seekTime,
+                      mStartTime,
+                      mEndTime,
+                      mCurrentTimeBeforeSeek);
       }
     }
     if (NS_FAILED(res)) {
       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();
+    mWaitingForDecoderSeek = true;
   }
 }
 
 void
+MediaDecoderStateMachine::OnSeekCompleted(nsresult aResult)
+{
+  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::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
@@ -337,16 +337,17 @@ public:
   // 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 SetMinimizePrerollUntilPlaybackStarts();
 
   void OnAudioDecoded(AudioData* aSample);
   void OnVideoDecoded(VideoData* aSample);
   void OnNotDecoded(MediaData::Type aType, RequestSampleCallback::NotDecodedReason aReason);
+  void OnSeekCompleted(nsresult aResult);
 
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
@@ -910,16 +911,21 @@ protected:
   // a "discontinuity".
   bool mDropAudioUntilNextDiscontinuity;
   bool mDropVideoUntilNextDiscontinuity;
 
   // True if we need to decode forwards to the seek target inside
   // mCurrentSeekTarget.
   bool mDecodeToSeekTarget;
 
+  // True if we've issued Seek() to the reader, but haven't yet received
+  // OnSeekCompleted. We should avoid trying to decode more audio/video
+  // until this completes.
+  bool mWaitingForDecoderSeek;
+
   // We record the playback position before we seek in order to
   // determine where the seek terminated relative to the playback position
   // we were at before the seek.
   int64_t mCurrentTimeBeforeSeek;
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -311,17 +311,17 @@ bool AndroidMediaReader::DecodeAudioData
                               source.mAudioSampleRate,
                               frames,
                               source.mAudioChannels,
                               MPCopy(static_cast<uint8_t *>(source.mData),
                                      source.mSize,
                                      source.mAudioChannels));
 }
 
-nsresult AndroidMediaReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+void 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
@@ -331,17 +331,17 @@ nsresult AndroidMediaReader::Seek(int64_
     // 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;
   }
 
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 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,17 @@ public:
   virtual bool IsMediaSeekable()
   {
     // not used
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
   virtual void 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;
   }
 }
 
 
-nsresult
+void
 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,28 +513,29 @@ AppleMP3Reader::Seek(int64_t aTime,
 
   OSStatus rv = AudioFileStreamSeek(mAudioFileStream,
                                     packet,
                                     &byteOffset,
                                     &flags);
 
   if (rv) {
     LOGE("Couldn't seek demuxer. Error code %x\n", rv);
-    return NS_ERROR_FAILURE;
+    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+    return;
   }
 
   LOGD("computed byte offset = %lld; estimated = %s\n",
        byteOffset,
        (flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO");
 
   mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
 
   ResetDecode();
 
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 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,20 @@ 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 nsresult Seek(int64_t aTime,
-                        int64_t aStartTime,
-                        int64_t aEndTime,
-                        int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual void 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,22 +362,29 @@ DirectShowReader::HasAudio()
 
 bool
 DirectShowReader::HasVideo()
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   return false;
 }
 
-nsresult
+void
 DirectShowReader::Seek(int64_t aTargetUs,
                        int64_t aStartTime,
                        int64_t aEndTime,
                        int64_t aCurrentTime)
 {
+  nsresult res = SeekInternal(aTargetUs);
+  GetCallback()->OnSeekCompleted(res);
+}
+
+nsresult
+DirectShowReader::SeekInternal(int64_t aTargetUs)
+{
   HRESULT hr;
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");\
 
   LOG("DirectShowReader::Seek() target=%lld", aTargetUs);
 
   hr = mControl->Pause();
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -55,34 +55,36 @@ public:
                         int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   bool HasAudio() MOZ_OVERRIDE;
   bool HasVideo() MOZ_OVERRIDE;
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
-  nsresult Seek(int64_t aTime,
-                int64_t aStartTime,
-                int64_t aEndTime,
-                int64_t aCurrentTime) MOZ_OVERRIDE;
+  void 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:
 
   // Notifies the filter graph that playback is complete. aStatus is
   // the code to send to the filter graph. Always returns false, so
   // that we can just "return Finish()" from DecodeAudioData().
   bool Finish(HRESULT aStatus);
 
+  nsresult SeekInternal(int64_t aTime);
+
   // DirectShow filter graph, and associated playback and seeking
   // control interfaces.
   RefPtr<IGraphBuilder> mGraph;
   RefPtr<IMediaControl> mControl;
   RefPtr<IMediaSeeking> mMediaSeeking;
 
   // Wraps the MediaResource, and feeds undecoded data into the filter graph.
   RefPtr<SourceFilter> mSourceFilter;
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -750,37 +750,38 @@ MP4Reader::DecodeVideoFrame(bool &aKeyfr
     MonitorAutoLock mon(mVideo.mMonitor);
     uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
     decoded = static_cast<uint32_t>(delta);
     mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
   }
   return rv;
 }
 
-nsresult
+void
 MP4Reader::Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime)
 {
   if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) {
-    return NS_ERROR_FAILURE;
+    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+    return;
   }
 
   mQueuedVideoSample = nullptr;
   if (mDemuxer->HasValidVideo()) {
     mDemuxer->SeekVideo(aTime);
     mQueuedVideoSample = PopSample(kVideo);
   }
   if (mDemuxer->HasValidAudio()) {
     mDemuxer->SeekAudio(
       mQueuedVideoSample ? mQueuedVideoSample->composition_timestamp : aTime);
   }
 
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 void
 MP4Reader::NotifyDataArrived(const char* aBuffer, uint32_t aLength,
                              int64_t aOffset)
 {
   if (NS_IsMainThread()) {
     if (GetTaskQueue()) {
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -40,20 +40,20 @@ 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 nsresult Seek(int64_t aTime,
-                        int64_t aStartTime,
-                        int64_t aEndTime,
-                        int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual void Seek(int64_t aTime,
+                    int64_t aStartTime,
+                    int64_t aEndTime,
+                    int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength,
                                  int64_t aOffset) MOZ_OVERRIDE;
 
   virtual int64_t GetEvictionOffset(double aTime) MOZ_OVERRIDE;
 
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -782,42 +782,43 @@ bool GStreamerReader::DecodeVideoFrame(b
                                                 isKeyframe, -1, mPicture);
   mVideoQueue.Push(video);
 
   gst_buffer_unref(buffer);
 
   return true;
 }
 
-nsresult GStreamerReader::Seek(int64_t aTarget,
-                                 int64_t aStartTime,
-                                 int64_t aEndTime,
-                                 int64_t aCurrentTime)
+void 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");
-    return NS_ERROR_FAILURE;
+    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+    return;
   }
   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");
 
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 nsresult GStreamerReader::GetBuffered(dom::TimeRanges* aBuffered,
                                       int64_t aStartTime)
 {
   if (!mInfo.HasValidMedia()) {
     return NS_OK;
   }
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -43,20 +43,20 @@ 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 nsresult Seek(int64_t aTime,
-                        int64_t aStartTime,
-                        int64_t aEndTime,
-                        int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime,
+                    int64_t aStartTime,
+                    int64_t aEndTime,
+                    int64_t aCurrentTime);
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
   virtual void NotifyDataArrived(const char *aBuffer,
                                  uint32_t aLength,
                                  int64_t aOffset) MOZ_OVERRIDE;
 
   virtual bool HasAudio() {
     return mInfo.HasAudio();
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -173,18 +173,18 @@ MediaSourceDecoder::SetMediaSourceDurati
   }
   ErrorResult dummy;
   mMediaSource->DurationChange(aDuration, dummy);
 }
 
 void
 MediaSourceDecoder::NotifyTimeRangesChanged()
 {
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  mon.NotifyAll();
+  MOZ_ASSERT(mReader);
+  mReader->NotifyTimeRangesChanged();
 }
 
 void
 MediaSourceDecoder::PrepareReaderInitialization()
 {
   MOZ_ASSERT(mReader);
   mReader->PrepareInitialization();
 }
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -34,16 +34,23 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #endif
 
 namespace mozilla {
 
 MediaSourceReader::MediaSourceReader(MediaSourceDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
   , mLastAudioTime(-1)
   , mLastVideoTime(-1)
+  , mPendingSeekTime(-1)
+  , mPendingStartTime(-1)
+  , mPendingEndTime(-1)
+  , mPendingCurrentTime(-1)
+  , mWaitingForSeekData(false)
+  , mPendingSeeks(0)
+  , mSeekResult(NS_OK)
   , mTimeThreshold(-1)
   , mDropAudioBeforeThreshold(false)
   , mDropVideoBeforeThreshold(false)
   , mEnded(false)
   , mAudioIsSeeking(false)
   , mVideoIsSeeking(false)
   , mHasEssentialTrackBuffers(false)
 {
@@ -361,85 +368,139 @@ MediaSourceReader::OnTrackBufferConfigur
   }
   if (aInfo.HasVideo() && !mVideoTrack) {
     MSE_DEBUG("MediaSourceReader(%p)::OnTrackBufferConfigured %p video", this, aTrackBuffer);
     mVideoTrack = aTrackBuffer;
   }
   mDecoder->NotifyWaitingForResourcesStatusChanged();
 }
 
-void
-MediaSourceReader::WaitForTimeRange(int64_t aTime)
-{
-  MSE_DEBUG("MediaSourceReader(%p)::WaitForTimeRange(%lld)", this, aTime);
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  // Loop until we have the requested time range in the active TrackBuffers.
-  // Ideally, this wait loop would use an async request and callback
-  // instead.  Bug 1056441 covers that change.
-  while (!TrackBuffersContainTime(aTime) && !IsShutdown() && !IsEnded()) {
-    MSE_DEBUG("MediaSourceReader(%p)::WaitForTimeRange(%lld) waiting", this, aTime);
-    mon.Wait();
-  }
-}
-
 bool
 MediaSourceReader::TrackBuffersContainTime(int64_t aTime)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mAudioTrack && !mAudioTrack->ContainsTime(aTime)) {
     return false;
   }
   if (mVideoTrack && !mVideoTrack->ContainsTime(aTime)) {
     return false;
   }
   return true;
 }
 
-nsresult
+void
+MediaSourceReader::NotifyTimeRangesChanged()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  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
 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);
 
+  if (IsShutdown()) {
+    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // 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;
+
+  // Only increment the number of expected OnSeekCompleted
+  // notifications if we weren't already waiting for AttemptSeek
+  // to complete (and they would have been accounted for already).
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+    if (!mWaitingForSeekData) {
+      mWaitingForSeekData = true;
+      if (mAudioTrack) {
+        mPendingSeeks++;
+      }
+      if (mVideoTrack) {
+        mPendingSeeks++;
+      }
+    }
+  }
+
+  AttemptSeek();
+}
+
+void
+MediaSourceReader::OnSeekCompleted(nsresult aResult)
+{
+  mPendingSeeks--;
+  // Keep the most recent failed result (if any)
+  if (NS_FAILED(aResult)) {
+    mSeekResult = aResult;
+  }
+  // Only dispatch the final event onto the state machine
+  // since it's only expecting one response.
+  if (!mPendingSeeks) {
+    GetCallback()->OnSeekCompleted(mSeekResult);
+    mSeekResult = NS_OK;
+  }
+}
+
+void
+MediaSourceReader::AttemptSeek()
+{
+  // Make sure we don't hold the monitor while calling into the reader
+  // Seek methods since it can deadlock.
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    if (!mWaitingForSeekData || !TrackBuffersContainTime(mPendingSeekTime)) {
+      return;
+    }
+  }
+
   ResetDecode();
   for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
     mTrackBuffers[i]->ResetDecode();
   }
 
   // Decoding discontinuity upon seek, reset last times to seek target.
-  mLastAudioTime = aTime;
-  mLastVideoTime = aTime;
-
-  WaitForTimeRange(aTime);
-
-  if (IsShutdown()) {
-    return NS_ERROR_FAILURE;
-  }
+  mLastAudioTime = mPendingSeekTime;
+  mLastVideoTime = mPendingSeekTime;
 
   if (mAudioTrack) {
     mAudioIsSeeking = true;
-    SwitchAudioReader(aTime);
-    nsresult rv = mAudioReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
-    MSE_DEBUG("MediaSourceReader(%p)::Seek audio reader=%p rv=%x", this, mAudioReader.get(), rv);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
+    SwitchAudioReader(mPendingSeekTime);
+    mAudioReader->Seek(mPendingSeekTime,
+                       mPendingStartTime,
+                       mPendingEndTime,
+                       mPendingCurrentTime);
+    MSE_DEBUG("MediaSourceReader(%p)::Seek audio reader=%p", this, mAudioReader.get());
   }
   if (mVideoTrack) {
     mVideoIsSeeking = true;
-    SwitchVideoReader(aTime);
-    nsresult rv = mVideoReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
-    MSE_DEBUG("MediaSourceReader(%p)::Seek video reader=%p rv=%x", this, mVideoReader.get(), rv);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
+    SwitchVideoReader(mPendingSeekTime);
+    mVideoReader->Seek(mPendingSeekTime,
+                       mPendingStartTime,
+                       mPendingEndTime,
+                       mPendingCurrentTime);
+    MSE_DEBUG("MediaSourceReader(%p)::Seek video reader=%p", this, mVideoReader.get());
   }
-  return NS_OK;
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mWaitingForSeekData = false;
+  }
 }
 
 nsresult
 MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata tracks=%u/%u audio=%p video=%p",
             this, mEssentialTrackBuffers.Length(), mTrackBuffers.Length(),
             mAudioTrack.get(), mVideoTrack.get());
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -51,36 +51,40 @@ public:
   void OnAudioDecoded(AudioData* aSample);
 
   void RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   void OnVideoDecoded(VideoData* aSample);
 
   void OnNotDecoded(MediaData::Type aType, RequestSampleCallback::NotDecodedReason aReason);
 
+  void OnSeekCompleted(nsresult aResult);
+
   bool HasVideo() MOZ_OVERRIDE
   {
     return mInfo.HasVideo();
   }
 
   bool HasAudio() MOZ_OVERRIDE
   {
     return mInfo.HasAudio();
   }
 
+  void NotifyTimeRangesChanged();
+
   // We can't compute a proper start time since we won't necessarily
   // have the first frame of the resource available. This does the same
   // as chrome/blink and assumes that we always start at t=0.
   virtual int64_t ComputeStartTime(const VideoData* aVideo, const AudioData* aAudio) MOZ_OVERRIDE { return 0; }
 
   bool IsMediaSeekable() { return true; }
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE;
-  nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                int64_t aCurrentTime) MOZ_OVERRIDE;
+  void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
+            int64_t aCurrentTime) MOZ_OVERRIDE;
 
   already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType);
 
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
   void OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo);
 
   void Shutdown();
@@ -110,20 +114,17 @@ private:
   bool SwitchAudioReader(int64_t aTarget);
   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);
 
-  // Waits on the decoder monitor for aTime to become available in the active
-  // TrackBuffers.  Used to block a Seek call until the necessary data has been
-  // provided to the relevant SourceBuffers.
-  void WaitForTimeRange(int64_t aTime);
+  void AttemptSeek();
 
   nsRefPtr<MediaDecoderReader> mAudioReader;
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
   nsRefPtr<TrackBuffer> mVideoTrack;
@@ -131,16 +132,30 @@ private:
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #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.
+  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
+  // result we're going to forward onto our callback.
+  uint32_t mPendingSeeks;
+  nsresult mSeekResult;
+
   int64_t mTimeThreshold;
   bool mDropAudioBeforeThreshold;
   bool mDropVideoBeforeThreshold;
 
   bool mEnded;
 
   // For a seek to complete we need to send a sample with
   // the mDiscontinuity field set to true once we have the
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -1449,20 +1449,29 @@ 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);
 }
 
-nsresult OggReader::Seek(int64_t aTarget,
-                         int64_t aStartTime,
-                         int64_t aEndTime,
-                         int64_t aCurrentTime)
+void OggReader::Seek(int64_t aTarget,
+                     int64_t aStartTime,
+                     int64_t aEndTime,
+                     int64_t aCurrentTime)
+{
+  nsresult res = SeekInternal(aTarget, aStartTime, aEndTime, aCurrentTime);
+  GetCallback()->OnSeekCompleted(res);
+}
+
+nsresult OggReader::SeekInternal(int64_t aTarget,
+                                 int64_t aStartTime,
+                                 int64_t aEndTime,
+                                 int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (mIsChained)
     return NS_ERROR_FAILURE;
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
   nsresult res;
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE);
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -73,17 +73,17 @@ public:
   }
 
   virtual bool HasVideo() {
     return mTheoraState != 0 && mTheoraState->mActive;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
   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
@@ -93,16 +93,18 @@ private:
 
   // This monitor should be taken when reading or writing to mIsChained.
   ReentrantMonitor mMonitor;
 
   // Specialized Reset() method to signal if the seek is
   // to the start of the stream.
   nsresult ResetDecode(bool start);
 
+  nsresult SeekInternal(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+
   bool HasSkeleton() {
     return mSkeletonState != 0 && mSkeletonState->mActive;
   }
 
   // Seeks to the keyframe preceeding the target time using available
   // keyframe indexes.
   enum IndexedSeekResult {
     SEEK_OK,          // Success.
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -877,17 +877,17 @@ MediaCodecReader::DecodeVideoFrameSync(i
       (status == ERROR_END_OF_STREAM)) {
     VideoQueue().Finish();
   }
   mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
 
   return result;
 }
 
-nsresult
+void
 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;
@@ -909,17 +909,18 @@ 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) {
-      return NS_ERROR_FAILURE;
+      GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+      return;
     }
     sp<MetaData> format = source_buffer->meta_data();
     if (format != nullptr) {
       if (format->findInt64(kKeyTime, &timestamp) &&
           IsValidTimestampUs(timestamp)) {
         mVideoTrack.mSeekTimeUs = timestamp;
         mAudioTrack.mSeekTimeUs = timestamp;
       }
@@ -933,17 +934,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();
   }
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 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
@@ -84,20 +84,20 @@ 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 nsresult Seek(int64_t aTime,
-                        int64_t aStartTime,
-                        int64_t aEndTime,
-                        int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime,
+                    int64_t aStartTime,
+                    int64_t aEndTime,
+                    int64_t aCurrentTime);
 
   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
@@ -534,17 +534,17 @@ bool MediaOmxReader::DecodeAudioData()
                               source.mAudioSampleRate,
                               frames,
                               source.mAudioChannels,
                               OmxCopy(static_cast<uint8_t *>(source.mData),
                                       source.mSize,
                                       source.mAudioChannels));
 }
 
-nsresult MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+void 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();
   }
@@ -560,17 +560,17 @@ nsresult MediaOmxReader::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;
   }
 
-  return NS_OK;
+  GetCallback()->OnSeekCompleted(NS_OK);
 }
 
 void MediaOmxReader::SetIdle() {
   if (!mOmxDecoder.get()) {
     return;
   }
   mOmxDecoder->Pause();
 }
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -93,17 +93,17 @@ 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 nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
   virtual void 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;
 }
 
-nsresult
+void
 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);
 
-  return MediaCodecReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
+  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,18 @@ protected:
   void EnsureActive();
 
 public:
   RtspMediaCodecReader(AbstractMediaDecoder* aDecoder);
 
   virtual ~RtspMediaCodecReader();
 
   // Implement a time-based seek instead of byte-based.
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                        int64_t aCurrentTime) MOZ_OVERRIDE;
+  virtual void 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,33 @@ nsresult RtspOmxReader::InitOmxDecoder()
     mOmxDecoder = new OmxDecoder(mDecoder->GetResource(), mDecoder);
     if (!mOmxDecoder->Init(mExtractor)) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
-nsresult RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime,
-                             int64_t aEndTime, int64_t aCurrentTime)
+void 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.
-  return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
+  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,18 @@ public:
     MOZ_ASSERT(mRtspResource);
   }
 
   virtual ~RtspOmxReader() MOZ_OVERRIDE {
     MOZ_COUNT_DTOR(RtspOmxReader);
   }
 
   // Implement a time-based seek instead of byte-based..
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                        int64_t aCurrentTime) MOZ_FINAL MOZ_OVERRIDE;
+  virtual void 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,17 +230,23 @@ bool RawReader::DecodeVideoFrame(bool &a
   mVideoQueue.Push(v);
   mCurrentFrame++;
   decoded++;
   currentFrameTime += USECS_PER_S / mFrameRate;
 
   return true;
 }
 
-nsresult RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+void RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+{
+  nsresult res = SeekInternal(aTime);
+  GetCallback()->OnSeekCompleted(res);
+}
+
+nsresult RawReader::SeekInternal(int64_t aTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(),
                "Should be on decode thread.");
 
   MediaResource *resource = mDecoder->GetResource();
   NS_ASSERTION(resource, "Decoder has no media resource");
 
   uint32_t frame = mCurrentFrame;
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -34,24 +34,26 @@ public:
 
   virtual bool HasVideo()
   {
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
 private:
   bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
 
+  nsresult SeekInternal(int64_t aTime);
+
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
 };
 
 } // namespace mozilla
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -252,31 +252,33 @@ bool WaveReader::DecodeAudioData()
 bool WaveReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                       int64_t aTimeThreshold)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   return false;
 }
 
-nsresult WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
+void 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())) {
-    return NS_ERROR_FAILURE;
+    GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
+    return;
   }
   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;
-  return mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
+  nsresult res = mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
+  GetCallback()->OnSeekCompleted(res);
 }
 
 static double RoundToUsecs(double aSeconds) {
   return floor(aSeconds * USECS_PER_S) / USECS_PER_S;
 }
 
 nsresult WaveReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
 {
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -38,17 +38,17 @@ public:
 
   virtual bool HasVideo()
   {
     return false;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
   // 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
@@ -1041,18 +1041,24 @@ bool WebMReader::DecodeVideoFrame(bool &
 }
 
 void
 WebMReader::PushVideoPacket(NesteggPacketHolder* aItem)
 {
     mVideoPackets.PushFront(aItem);
 }
 
-nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
-                          int64_t aCurrentTime)
+void WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
+                      int64_t aCurrentTime)
+{
+  nsresult res = SeekInternal(aTarget, aStartTime);
+  GetCallback()->OnSeekCompleted(res);
+}
+
+nsresult WebMReader::SeekInternal(int64_t aTarget, int64_t aStartTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
                      this, mDecoder, double(aTarget) / USECS_PER_S));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -129,18 +129,18 @@ public:
   virtual bool HasVideo()
   {
     NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     return mHasVideo;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
-  virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
-                        int64_t aCurrentTime);
+  virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
+                    int64_t aCurrentTime);
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength,
                                  int64_t aOffset);
   virtual int64_t GetEvictionOffset(double aTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
 protected:
@@ -179,16 +179,18 @@ protected:
                   int64_t aOffset, uint64_t aTstampUsecs,
                   nestegg_packet* aPacket);
 #endif
 
   // Release context and set to null. Called when an error occurs during
   // reading metadata or destruction of the reader itself.
   void Cleanup();
 
+  virtual nsresult SeekInternal(int64_t aTime, int64_t aStartTime);
+
 private:
   // libnestegg context for webm container. Access on state machine thread
   // or decoder thread only.
   nestegg* mContext;
 
   // VP8 decoder state
   vpx_codec_ctx_t mVPX;
 
--- a/dom/media/wmf/WMFReader.cpp
+++ b/dom/media/wmf/WMFReader.cpp
@@ -884,22 +884,29 @@ WMFReader::DecodeVideoFrame(bool &aKeyfr
     // End of stream.
     DECODER_LOG("End of video stream");
     return false;
   }
 
   return true;
 }
 
-nsresult
+void
 WMFReader::Seek(int64_t aTargetUs,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime)
 {
+  nsresult res = SeekInternal(aTargetUs);
+  GetCallback()->OnSeekCompleted(res);
+}
+
+nsresult
+WMFReader::SeekInternal(int64_t aTargetUs)
+{
   DECODER_LOG("WMFReader::Seek() %lld", aTargetUs);
 
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 #ifdef DEBUG
   bool canSeek = false;
   GetSourceReaderCanSeek(mSourceReader, canSeek);
   NS_ASSERTION(canSeek, "WMFReader::Seek() should only be called if we can seek!");
 #endif
--- a/dom/media/wmf/WMFReader.h
+++ b/dom/media/wmf/WMFReader.h
@@ -38,20 +38,20 @@ public:
                         int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   bool HasAudio() MOZ_OVERRIDE;
   bool HasVideo() MOZ_OVERRIDE;
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
-  nsresult Seek(int64_t aTime,
-                int64_t aStartTime,
-                int64_t aEndTime,
-                int64_t aCurrentTime) MOZ_OVERRIDE;
+  void 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();
@@ -68,16 +68,18 @@ private:
                               int64_t aTimestampUsecs,
                               int64_t aDurationUsecs,
                               int64_t aOffsetBytes,
                               VideoData** aOutVideoData);
 
   // Attempt to initialize DXVA. Returns true on success.
   bool InitializeDXVA();  
 
+  nsresult SeekInternal(int64_t aTime);
+
   RefPtr<IMFSourceReader> mSourceReader;
   RefPtr<WMFByteStream> mByteStream;
   RefPtr<WMFSourceReaderCallback> mSourceReaderCallback;
   nsAutoPtr<DXVA2Manager> mDXVA2Manager;
 
   // Region inside the video frame that makes up the picture. Pixels outside
   // of this region should not be rendered.
   nsIntRect mPictureRegion;