Bug 1015985 - Set decoder seekable members outside of ReadMetadata by StateMachine. r=cpearce
authorEric Phan <eric.phan@ensimag.grenoble-inp.fr>
Mon, 23 Jun 2014 22:08:34 +1200
changeset 212300 f14b9781345f400cec5fdbab235ee93e3c8e5177
parent 212299 080476fd705927fdc1d68a7d790ee510dc4dd0f8
child 212301 ca69915acaf38d6fe40ddf648ab086b73ffec4f1
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1015985
milestone33.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 1015985 - Set decoder seekable members outside of ReadMetadata by StateMachine. r=cpearce
content/html/content/src/HTMLMediaElement.cpp
content/media/AbstractMediaDecoder.h
content/media/BufferDecoder.cpp
content/media/BufferDecoder.h
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderReader.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/MediaResource.cpp
content/media/RtspMediaResource.cpp
content/media/apple/AppleMP3Reader.cpp
content/media/apple/AppleMP3Reader.h
content/media/directshow/DirectShowReader.cpp
content/media/directshow/DirectShowReader.h
content/media/fmp4/MP4Reader.cpp
content/media/fmp4/MP4Reader.h
content/media/gstreamer/GStreamerReader.cpp
content/media/gstreamer/GStreamerReader.h
content/media/mediasource/MediaSourceDecoder.cpp
content/media/mediasource/SourceBuffer.cpp
content/media/mediasource/SubBufferDecoder.h
content/media/ogg/OggReader.cpp
content/media/ogg/OggReader.h
content/media/omx/MediaOmxReader.cpp
content/media/omx/MediaOmxReader.h
content/media/plugins/MediaPluginReader.h
content/media/raw/RawReader.cpp
content/media/raw/RawReader.h
content/media/wave/WaveReader.cpp
content/media/wave/WaveReader.h
content/media/webm/WebMReader.cpp
content/media/webm/WebMReader.h
content/media/wmf/WMFReader.cpp
content/media/wmf/WMFReader.h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -2522,17 +2522,16 @@ nsresult HTMLMediaElement::InitializeDec
   if (!decoder->Init(this)) {
     LOG(PR_LOG_DEBUG, ("%p Failed to init cloned decoder %p", this, decoder.get()));
     return NS_ERROR_FAILURE;
   }
 
   double duration = aOriginal->GetDuration();
   if (duration >= 0) {
     decoder->SetDuration(duration);
-    decoder->SetTransportSeekable(aOriginal->IsTransportSeekable());
     decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
   }
 
   nsRefPtr<MediaResource> resource = originalResource->CloneData(decoder);
   if (!resource) {
     LOG(PR_LOG_DEBUG, ("%p Failed to cloned stream for decoder %p", this, decoder.get()));
     return NS_ERROR_FAILURE;
   }
--- a/content/media/AbstractMediaDecoder.h
+++ b/content/media/AbstractMediaDecoder.h
@@ -75,19 +75,16 @@ public:
   // Sets the duration of the media in microseconds. The MediaDecoder
   // fires a durationchange event to its owner (e.g., an HTML audio
   // tag).
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) = 0;
 
   // Set the media as being seekable or not.
   virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
 
-  // Set the transport level as being seekable or not.
-  virtual void SetTransportSeekable(bool aTransportSeekable) = 0;
-
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
   virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
 
   // Return true if the media layer supports seeking.
   virtual bool IsTransportSeekable() = 0;
 
   // Return true if the transport layer supports seeking.
   virtual bool IsMediaSeekable() = 0;
--- a/content/media/BufferDecoder.cpp
+++ b/content/media/BufferDecoder.cpp
@@ -115,22 +115,16 @@ BufferDecoder::UpdateEstimatedMediaDurat
 }
 
 void
 BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
 {
   // ignore
 }
 
-void
-BufferDecoder::SetTransportSeekable(bool aTransportSeekable)
-{
-  // ignore
-}
-
 VideoFrameContainer*
 BufferDecoder::GetVideoFrameContainer()
 {
   // no video frame
   return nullptr;
 }
 
 layers::ImageContainer*
--- a/content/media/BufferDecoder.h
+++ b/content/media/BufferDecoder.h
@@ -49,18 +49,16 @@ public:
   virtual int64_t GetMediaDuration() MOZ_OVERRIDE;
 
   virtual void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
   virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
 
-  virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_OVERRIDE;
-
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE;
 
   virtual bool IsTransportSeekable() MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_OVERRIDE;
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -412,17 +412,16 @@ bool MediaDecoder::IsInfinite()
 MediaDecoder::MediaDecoder() :
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mCurrentTime(0.0),
   mInitialVolume(0.0),
   mInitialPlaybackRate(1.0),
   mInitialPreservesPitch(true),
   mDuration(-1),
-  mTransportSeekable(true),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mIsDormant(false),
   mIsExitingDormant(false),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mCalledResourceLoaded(false),
@@ -546,18 +545,16 @@ nsresult MediaDecoder::InitializeStateMa
   MediaDecoder* cloneDonor = static_cast<MediaDecoder*>(aCloneDonor);
   if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ?
                                            cloneDonor->mDecoderStateMachine : nullptr))) {
     DECODER_LOG(PR_LOG_WARNING, "Failed to init state machine!");
     return NS_ERROR_FAILURE;
   }
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    mDecoderStateMachine->SetTransportSeekable(mTransportSeekable);
-    mDecoderStateMachine->SetMediaSeekable(mMediaSeekable);
     mDecoderStateMachine->SetDuration(mDuration);
     mDecoderStateMachine->SetVolume(mInitialVolume);
     mDecoderStateMachine->SetAudioCaptured(mInitialAudioCaptured);
     SetPlaybackRate(mInitialPlaybackRate);
     mDecoderStateMachine->SetPreservesPitch(mInitialPreservesPitch);
     if (mMinimizePreroll) {
       mDecoderStateMachine->SetMinimizePrerollUntilPlaybackStarts();
     }
@@ -1281,35 +1278,24 @@ void MediaDecoder::UpdateEstimatedMediaD
   NS_ENSURE_TRUE_VOID(GetStateMachine());
   GetStateMachine()->UpdateEstimatedDuration(aDuration);
 }
 
 void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
   mMediaSeekable = aMediaSeekable;
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetMediaSeekable(aMediaSeekable);
-  }
 }
 
-void MediaDecoder::SetTransportSeekable(bool aTransportSeekable)
+bool
+MediaDecoder::IsTransportSeekable()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
-  mTransportSeekable = aTransportSeekable;
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetTransportSeekable(aTransportSeekable);
-  }
-}
-
-bool MediaDecoder::IsTransportSeekable()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mTransportSeekable;
+  MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
+  return GetResource()->IsTransportSeekable();
 }
 
 bool MediaDecoder::IsMediaSeekable()
 {
   NS_ENSURE_TRUE(GetStateMachine(), false);
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
   return mMediaSeekable;
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -603,17 +603,17 @@ public:
   // different from the existing duration, the change request is ignored.
   // If the incoming duration is significantly different, the duration is
   // changed, this causes a durationchanged event to fire to the media
   // element.
   void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
 
   // Set a flag indicating whether seeking is supported
   virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
-  virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_FINAL MOZ_OVERRIDE;
+
   // Returns true if this media supports seeking. False for example for WebM
   // files without an index and chained ogg files.
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
   // Returns true if seeking is supported on a transport level (e.g. the server
   // supports range requests, we are playing a file, etc.).
   virtual bool IsTransportSeekable();
 
   // Return the time ranges that can be seeked into.
@@ -1032,20 +1032,16 @@ protected:
   // Duration of the media resource. Set to -1 if unknown.
   // Set when the metadata is loaded. Accessed on the main thread
   // only.
   int64_t mDuration;
 
   // True when playback should start with audio captured (not playing).
   bool mInitialAudioCaptured;
 
-  // True if the resource is seekable at a transport level (server supports byte
-  // range requests, local file, etc.).
-  bool mTransportSeekable;
-
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable;
 
   // True if the media is same-origin with the element. Data can only be
   // passed to MediaStreams when this is true.
   bool mSameOriginMedia;
 
   /******
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -168,16 +168,20 @@ public:
     return mDecoder;
   }
 
   AudioData* DecodeToFirstAudioData();
   VideoData* DecodeToFirstVideoData();
 
   MediaInfo GetMediaInfo() { return mInfo; }
 
+  // Indicates if the media is seekable.
+  // ReadMetada should be called before calling this method.
+  virtual bool IsMediaSeekable() = 0;
+  
 protected:
   virtual ~MediaDecoderReader();
 
   // Overrides of this function should decodes an unspecified amount of
   // audio data, enqueuing the audio data in mAudioQueue. Returns true
   // when there's more audio to decode, false if the audio is finished,
   // end of file has been reached, or an un-recoverable read error has
   // occured. This function blocks until the decode is complete.
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -197,18 +197,16 @@ MediaDecoderStateMachine::MediaDecoderSt
   mPreservesPitch(true),
   mBasePosition(0),
   mAmpleVideoFrames(2),
   mLowAudioThresholdUsecs(LOW_AUDIO_USECS),
   mAmpleAudioThresholdUsecs(AMPLE_AUDIO_USECS),
   mAudioRequestPending(false),
   mVideoRequestPending(false),
   mAudioCaptured(false),
-  mTransportSeekable(true),
-  mMediaSeekable(true),
   mPositionChangeQueued(false),
   mAudioCompleted(false),
   mGotDurationFromMetaData(false),
   mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
@@ -1597,33 +1595,16 @@ void MediaDecoderStateMachine::SetMediaE
 
 void MediaDecoderStateMachine::SetFragmentEndTime(int64_t aEndTime)
 {
   AssertCurrentThreadInMonitor();
 
   mFragmentEndTime = aEndTime < 0 ? aEndTime : aEndTime + mStartTime;
 }
 
-void MediaDecoderStateMachine::SetTransportSeekable(bool aTransportSeekable)
-{
-  NS_ASSERTION(NS_IsMainThread() || OnDecodeThread(),
-      "Should be on main thread or the decoder thread.");
-  AssertCurrentThreadInMonitor();
-
-  mTransportSeekable = aTransportSeekable;
-}
-
-void MediaDecoderStateMachine::SetMediaSeekable(bool aMediaSeekable)
-{
-  NS_ASSERTION(NS_IsMainThread() || OnDecodeThread(),
-      "Should be on main thread or the decoder thread.");
-
-  mMediaSeekable = aMediaSeekable;
-}
-
 bool MediaDecoderStateMachine::IsDormantNeeded()
 {
   return mReader->IsDormantNeeded();
 }
 
 void MediaDecoderStateMachine::SetDormant(bool aDormant)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -1770,17 +1751,18 @@ void MediaDecoderStateMachine::NotifyDat
 
 void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
-  if (!mMediaSeekable) {
+  if (!mDecoder->IsMediaSeekable()) {
+    NS_WARNING("Seek() function should not be called on a non-seekable state machine");
     return;
   }
   // MediaDecoder::mPlayState should be SEEKING while we seek, and
   // in that case MediaDecoder shouldn't be calling us.
   NS_ASSERTION(mState != DECODER_STATE_SEEKING,
                "We shouldn't already be seeking");
   NS_ASSERTION(mState >= DECODER_STATE_DECODING,
                "We should have loaded metadata");
@@ -2181,25 +2163,31 @@ nsresult MediaDecoderStateMachine::Decod
   }
 
   nsresult res;
   MediaInfo info;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     res = mReader->ReadMetadata(&info, getter_Transfers(mMetadataTags));
   }
+
   if (NS_SUCCEEDED(res)) {
     if (mState == DECODER_STATE_DECODING_METADATA &&
         mReader->IsWaitingMediaResources()) {
       // change state to DECODER_STATE_WAIT_FOR_RESOURCES
       StartWaitForResources();
+      // affect values only if ReadMetadata succeeds
       return NS_OK;
     }
   }
 
+  if (NS_SUCCEEDED(res)) {
+    mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
+  }
+
   mInfo = info;
 
   if (NS_FAILED(res) || (!info.HasValidMedia())) {
     return NS_ERROR_FAILURE;
   }
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1);
 
@@ -2261,23 +2249,26 @@ MediaDecoderStateMachine::FinishDecodeMe
     if (VideoQueue().GetSize()) {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       RenderVideoFrame(VideoQueue().PeekFront(), TimeStamp::Now());
     }
   }
 
   NS_ASSERTION(mStartTime != -1, "Must have start time");
   MOZ_ASSERT((!HasVideo() && !HasAudio()) ||
-              !(mMediaSeekable && mTransportSeekable) || mEndTime != -1,
-              "Active seekable media should have end time");
-  MOZ_ASSERT(!(mMediaSeekable && mTransportSeekable) ||
-             GetDuration() != -1, "Seekable media should have duration");
+               !(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
+               mEndTime != -1,
+             "Active seekable media should have end time");
+  MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
+               GetDuration() != -1,
+             "Seekable media should have duration");
   DECODER_LOG(PR_LOG_DEBUG, "Media goes from %lld to %lld (duration %lld) "
-              "transportSeekable=%d, mediaSeekable=%d",
-              mStartTime, mEndTime, GetDuration(), mTransportSeekable, mMediaSeekable);
+                            "transportSeekable=%d, mediaSeekable=%d",
+              mStartTime, mEndTime, GetDuration(),
+              mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
 
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
   }
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -204,27 +204,16 @@ public:
   // monitor must be obtained before calling this.
   double GetCurrentTime() const;
 
   // Clear the flag indicating that a playback position change event
   // is currently queued. This is called from the main thread and must
   // be called with the decode monitor held.
   void ClearPositionChangeFlag();
 
-  // Called from the main thread or the decoder thread to set whether the media
-  // resource can seek into unbuffered ranges. The decoder monitor must be
-  // obtained before calling this.
-  void SetTransportSeekable(bool aSeekable);
-
-  // Called from the main thread or the decoder thread to set whether the media
-  // can seek to random location. This is not true for chained ogg and WebM
-  // media without index. The decoder monitor must be obtained before calling
-  // this.
-  void SetMediaSeekable(bool aSeekable);
-
   // Update the playback position. This can result in a timeupdate event
   // and an invalidate of the frame being dispatched asynchronously if
   // there is no such event currently queued.
   // Only called on the decoder thread. Must be called with
   // the decode monitor held.
   void UpdatePlaybackPosition(int64_t aTime);
 
   // Causes the state machine to switch to buffering state, and to
@@ -285,26 +274,16 @@ public:
 
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   int64_t GetEndMediaTime() const {
     AssertCurrentThreadInMonitor();
     return mEndTime;
   }
 
-  bool IsTransportSeekable() {
-    AssertCurrentThreadInMonitor();
-    return mTransportSeekable;
-  }
-
-  bool IsMediaSeekable() {
-    AssertCurrentThreadInMonitor();
-    return mMediaSeekable;
-  }
-
   // Returns the shared state machine thread.
   nsIEventTarget* GetStateMachineThread();
 
   // Calls ScheduleStateMachine() after taking the decoder lock. Also
   // notifies the decoder thread in case it's waiting on the decoder lock.
   void ScheduleStateMachineWithLockAndWakeDecoder();
 
   // Schedules the shared state machine thread to run the state machine
@@ -903,24 +882,16 @@ protected:
   bool mAudioRequestPending;
   bool mVideoRequestPending;
 
   // True if we shouldn't play our audio (but still write it to any capturing
   // streams). When this is true, mStopAudioThread is always true and
   // the audio thread will never start again after it has stopped.
   bool mAudioCaptured;
 
-  // True if the media resource can be seeked on a transport level. Accessed
-  // from the state machine and main threads. Synchronised via decoder monitor.
-  bool mTransportSeekable;
-
-  // True if the media can be seeked. Accessed from the state machine and main
-  // threads. Synchronised via decoder monitor.
-  bool mMediaSeekable;
-
   // True if an event to notify about a change in the playback
   // position has been queued, but not yet run. It is set to false when
   // the event is run. This allows coalescing of these events as they can be
   // produced many times per second. Synchronised via decoder monitor.
   // Accessed on main and state machine threads.
   bool mPositionChangeQueued;
 
   // True if the audio playback thread has finished. It is finished
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -343,17 +343,16 @@ ChannelMediaResource::OnStartRequest(nsI
     if (seekable && boundedSeekLimit) {
       // If range requests are supported, and we did not see an unbounded
       // upper range limit, we assume the resource is bounded.
       dataIsBounded = true;
     }
 
     mDecoder->SetInfinite(!dataIsBounded);
   }
-  mDecoder->SetTransportSeekable(seekable);
   mCacheStream.SetTransportSeekable(seekable);
 
   {
     MutexAutoLock lock(mLock);
     mIsTransportSeekable = seekable;
     mChannelStatistics->Start();
   }
 
--- a/content/media/RtspMediaResource.cpp
+++ b/content/media/RtspMediaResource.cpp
@@ -602,34 +602,31 @@ RtspMediaResource::OnConnected(uint8_t a
   if (!mDecoder) {
     return NS_ERROR_FAILURE;
   }
 
   // If the duration is 0, imply the stream is live stream.
   if (duration) {
     // Not live stream.
     mRealTime = false;
-    bool seekable = true;
     mDecoder->SetInfinite(false);
-    mDecoder->SetTransportSeekable(seekable);
     mDecoder->SetDuration(duration);
   } else {
     // Live stream.
     // Check the preference "media.realtime_decoder.enabled".
     if (!Preferences::GetBool("media.realtime_decoder.enabled", false)) {
       // Give up, report error to media element.
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
       NS_DispatchToMainThread(event);
       return NS_ERROR_FAILURE;
     } else {
       mRealTime = true;
       bool seekable = false;
       mDecoder->SetInfinite(true);
-      mDecoder->SetTransportSeekable(seekable);
       mDecoder->SetMediaSeekable(seekable);
     }
   }
   // Fires an initial progress event and sets up the stall counter so stall events
   // fire if no download occurs within the required time frame.
   mDecoder->Progress(false);
 
   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
--- a/content/media/apple/AppleMP3Reader.cpp
+++ b/content/media/apple/AppleMP3Reader.cpp
@@ -324,16 +324,22 @@ AppleMP3Reader::HasAudio()
 
 bool
 AppleMP3Reader::HasVideo()
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread");
   return false;
 }
 
+bool
+AppleMP3Reader::IsMediaSeekable()
+{
+  // not used
+  return true;
+}
 
 /*
  * Query the MP3 parser for a piece of metadata.
  */
 static nsresult
 GetProperty(AudioFileStreamID aAudioFileStream,
             AudioFileStreamPropertyID aPropertyID, void *aData)
 {
--- a/content/media/apple/AppleMP3Reader.h
+++ b/content/media/apple/AppleMP3Reader.h
@@ -46,16 +46,18 @@ public:
   void AudioMetadataCallback(AudioFileStreamID aFileStream,
                              AudioFileStreamPropertyID aPropertyID,
                              UInt32 *aFlags);
 
   virtual void NotifyDataArrived(const char* aBuffer,
                                  uint32_t aLength,
                                  int64_t aOffset) MOZ_OVERRIDE;
 
+  virtual bool IsMediaSeekable() MOZ_OVERRIDE;
+
 private:
   void SetupDecoder();
   nsresult Read(uint32_t *aNumBytes, char *aData);
 
   static OSStatus PassthroughInputDataCallback(AudioConverterRef aAudioConverter,
                                                UInt32 *aNumDataPackets,
                                                AudioBufferList *aData,
                                                AudioStreamPacketDescription **aPacketDesc,
--- a/content/media/directshow/DirectShowReader.cpp
+++ b/content/media/directshow/DirectShowReader.cpp
@@ -206,20 +206,16 @@ DirectShowReader::ReadMetadata(MediaInfo
   *aTags = nullptr;
 
   // Begin decoding!
   hr = mControl->Run();
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   DWORD seekCaps = 0;
   hr = mMediaSeeking->GetCapabilities(&seekCaps);
-  bool canSeek = ((AM_SEEKING_CanSeekAbsolute & seekCaps) == AM_SEEKING_CanSeekAbsolute);
-  if (!canSeek) {
-    mDecoder->SetMediaSeekable(false);
-  }
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (SUCCEEDED(hr)) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(duration);
   }
 
   LOG("Successfully initialized DirectShow MP3 decoder.");
@@ -227,16 +223,25 @@ DirectShowReader::ReadMetadata(MediaInfo
       mInfo.mAudio.mChannels,
       mInfo.mAudio.mRate,
       RefTimeToUsecs(duration),
       mBytesPerSample);
 
   return NS_OK;
 }
 
+bool
+DirectShowReader::IsMediaSeekable()
+{
+  DWORD seekCaps = 0;
+  HRESULT hr = mMediaSeeking->GetCapabilities(&seekCaps);
+  return ((AM_SEEKING_CanSeekAbsolute & seekCaps) ==
+          AM_SEEKING_CanSeekAbsolute);
+}
+
 inline float
 UnsignedByteToAudioSample(uint8_t aValue)
 {
   return aValue * (2.0f / UINT8_MAX) - 1.0f;
 }
 
 bool
 DirectShowReader::Finish(HRESULT aStatus)
--- a/content/media/directshow/DirectShowReader.h
+++ b/content/media/directshow/DirectShowReader.h
@@ -64,16 +64,18 @@ public:
                 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);
 
   // DirectShow filter graph, and associated playback and seeking
--- a/content/media/fmp4/MP4Reader.cpp
+++ b/content/media/fmp4/MP4Reader.cpp
@@ -238,29 +238,32 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
   }
 
   // Get the duration, and report it to the decoder if we have it.
   Microseconds duration = mDemuxer->Duration();
   if (duration != -1) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(duration);
   }
-  // We can seek if we get a duration *and* the reader reports that it's
-  // seekable.
-  if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) {
-    mDecoder->SetMediaSeekable(false);
-  }
 
   *aInfo = mInfo;
   *aTags = nullptr;
 
   return NS_OK;
 }
 
 bool
+MP4Reader::IsMediaSeekable()
+{
+  // We can seek if we get a duration *and* the reader reports that it's
+  // seekable.
+  return mDecoder->GetResource()->IsTransportSeekable() && mDemuxer->CanSeek();
+}
+
+bool
 MP4Reader::HasAudio()
 {
   return mAudio.mActive;
 }
 
 bool
 MP4Reader::HasVideo()
 {
--- a/content/media/fmp4/MP4Reader.h
+++ b/content/media/fmp4/MP4Reader.h
@@ -44,16 +44,19 @@ public:
 
   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 bool IsMediaSeekable() MOZ_OVERRIDE;
+
 private:
 
   // Destroys all decoder resources.
   void Shutdown();
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
--- a/content/media/gstreamer/GStreamerReader.cpp
+++ b/content/media/gstreamer/GStreamerReader.cpp
@@ -432,18 +432,16 @@ nsresult GStreamerReader::ReadMetadata(M
     GstFormat format = GST_FORMAT_TIME;
     if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
       &format, &duration) && format == GST_FORMAT_TIME) {
 #endif
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       LOG(PR_LOG_DEBUG, "have duration %" GST_TIME_FORMAT, GST_TIME_ARGS(duration));
       duration = GST_TIME_AS_USECONDS (duration);
       mDecoder->SetMediaDuration(duration);
-    } else {
-      mDecoder->SetMediaSeekable(false);
     }
   }
 
   int n_video = 0, n_audio = 0;
   g_object_get(mPlayBin, "n-video", &n_video, "n-audio", &n_audio, nullptr);
   mInfo.mVideo.mHasVideo = n_video != 0;
   mInfo.mAudio.mHasAudio = n_audio != 0;
 
@@ -460,16 +458,38 @@ nsresult GStreamerReader::ReadMetadata(M
 
   /* set the pipeline to PLAYING so that it starts decoding and queueing data in
    * the appsinks */
   gst_element_set_state(mPlayBin, GST_STATE_PLAYING);
 
   return NS_OK;
 }
 
+bool
+GStreamerReader::IsMediaSeekable()
+{
+  if (mUseParserDuration) {
+    return true;
+  }
+
+  gint64 duration;
+#if GST_VERSION_MAJOR >= 1
+  if (gst_element_query_duration(GST_ELEMENT(mPlayBin), GST_FORMAT_TIME,
+                                 &duration)) {
+#else
+  GstFormat format = GST_FORMAT_TIME;
+  if (gst_element_query_duration(GST_ELEMENT(mPlayBin), &format, &duration) &&
+      format == GST_FORMAT_TIME) {
+#endif
+    return true;
+  }
+
+  return false;
+}
+
 nsresult GStreamerReader::CheckSupportedFormats()
 {
   bool done = false;
   bool unsupported = false;
 
   GstIterator* it = gst_bin_iterate_recurse(GST_BIN(mPlayBin));
   while (!done) {
     GstIteratorResult res;
--- a/content/media/gstreamer/GStreamerReader.h
+++ b/content/media/gstreamer/GStreamerReader.h
@@ -63,16 +63,18 @@ public:
   }
 
   virtual bool HasVideo() {
     return mInfo.HasVideo();
   }
 
   layers::ImageContainer* GetImageContainer() { return mDecoder->GetImageContainer(); }
 
+  virtual bool IsMediaSeekable() MOZ_OVERRIDE;
+
 private:
 
   void ReadAndPushData(guint aLength);
   nsRefPtr<layers::PlanarYCbCrImage> GetImageFromBuffer(GstBuffer* aBuffer);
   void CopyIntoImageBuffer(GstBuffer *aBuffer, GstBuffer** aOutBuffer, nsRefPtr<layers::PlanarYCbCrImage> &image);
   GstCaps* BuildAudioSinkCaps();
   void InstallPadCallbacks();
 
--- a/content/media/mediasource/MediaSourceDecoder.cpp
+++ b/content/media/mediasource/MediaSourceDecoder.cpp
@@ -138,16 +138,18 @@ public:
     return mInfo.HasVideo();
   }
 
   bool HasAudio() MOZ_OVERRIDE
   {
     return mInfo.HasAudio();
   }
 
+  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;
   nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) MOZ_OVERRIDE;
   already_AddRefed<SubBufferDecoder> CreateSubDecoder(const nsACString& aType,
                                                       MediaSourceDecoder* aParentDecoder,
                                                       MediaTaskQueue* aTaskQueue);
 
@@ -508,19 +510,16 @@ MediaSourceReader::GetBuffered(dom::Time
   }
   aBuffered->Normalize();
   return NS_OK;
 }
 
 nsresult
 MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
-  mDecoder->SetMediaSeekable(true);
-  mDecoder->SetTransportSeekable(false);
-
   MSE_DEBUG("%p: MSR::ReadMetadata pending=%u", this, mPendingDecoders.Length());
 
   InitializePendingDecoders();
 
   MSE_DEBUG("%p: MSR::ReadMetadata decoders=%u", this, mDecoders.Length());
 
   // XXX: Make subdecoder setup async, so that use cases like bug 989888 can
   // work.  This will require teaching the state machine about dynamic track
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -85,22 +85,16 @@ SubBufferDecoder::UpdateEstimatedMediaDu
 }
 
 void
 SubBufferDecoder::SetMediaSeekable(bool aMediaSeekable)
 {
   //mParentDecoder->SetMediaSeekable(aMediaSeekable);
 }
 
-void
-SubBufferDecoder::SetTransportSeekable(bool aTransportSeekable)
-{
-  //mParentDecoder->SetTransportSeekable(aTransportSeekable);
-}
-
 layers::ImageContainer*
 SubBufferDecoder::GetImageContainer()
 {
   return mParentDecoder->GetImageContainer();
 }
 
 MediaDecoderOwner*
 SubBufferDecoder::GetOwner()
--- a/content/media/mediasource/SubBufferDecoder.h
+++ b/content/media/mediasource/SubBufferDecoder.h
@@ -39,17 +39,16 @@ public:
   virtual ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE;
   virtual bool OnStateMachineThread() const MOZ_OVERRIDE;
   virtual bool OnDecodeThread() const MOZ_OVERRIDE;
   virtual SourceBufferResource* GetResource() const MOZ_OVERRIDE;
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE;
   virtual void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
   virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
-  virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE;
   virtual MediaDecoderOwner* GetOwner() MOZ_OVERRIDE;
 
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
   {
     mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
 
     // XXX: Params make no sense to parent decoder as it relates to a
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -373,31 +373,34 @@ nsresult OggReader::ReadMetadata(MediaIn
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         endTime = RangeEndTime(length);
       }
       if (endTime != -1) {
         mDecoder->SetMediaEndTime(endTime);
         LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
       }
       mDecoder->GetResource()->EndSeekingForMetadata();
-    } else if (mDecoder->GetMediaDuration() == -1) {
-      // We don't have a duration, and we don't know enough about the resource
-      // to try a seek. Abort trying to get a duration. This happens for example
-      // when the server says it accepts range requests, but does not give us a
-      // Content-Length.
-      mDecoder->SetTransportSeekable(false);
     }
   } else {
     return NS_ERROR_FAILURE;
   }
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
+bool
+OggReader::IsMediaSeekable()
+{
+  if (mIsChained) {
+    return false;
+  }
+  return true;
+}
+
 nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) {
   NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
 
   if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
     return NS_ERROR_FAILURE;
   }
   if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
                                &mVorbisState->mBlock) != 0)
--- a/content/media/ogg/OggReader.h
+++ b/content/media/ogg/OggReader.h
@@ -73,16 +73,18 @@ public:
     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 nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
+  virtual bool IsMediaSeekable() MOZ_OVERRIDE;
+
 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);
 
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -156,19 +156,16 @@ nsresult MediaOmxReader::ReadMetadata(Me
   // Set the total duration (the max of the audio and video track).
   int64_t durationUs;
   mOmxDecoder->GetDuration(&durationUs);
   if (durationUs) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(durationUs);
   }
 
-  // Check the MediaExtract flag if the source is seekable.
-  mDecoder->SetMediaSeekable(mExtractor->flags() & MediaExtractor::CAN_SEEK);
-
   if (mOmxDecoder->HasVideo()) {
     int32_t displayWidth, displayHeight, width, height;
     mOmxDecoder->GetVideoParameters(&displayWidth, &displayHeight,
                                     &width, &height);
     nsIntRect pictureRect(0, 0, width, height);
 
     // Validate the container-reported frame and pictureRect sizes. This ensures
     // that our video frame creation code doesn't overflow.
@@ -199,16 +196,23 @@ nsresult MediaOmxReader::ReadMetadata(Me
     mInfo.mAudio.mRate = sampleRate;
   }
 
  *aInfo = mInfo;
 
   return NS_OK;
 }
 
+bool
+MediaOmxReader::IsMediaSeekable()
+{
+  // Check the MediaExtract flag if the source is seekable.
+  return (mExtractor->flags() & MediaExtractor::CAN_SEEK);
+}
+
 bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                       int64_t aTimeThreshold)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   EnsureActive();
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
--- a/content/media/omx/MediaOmxReader.h
+++ b/content/media/omx/MediaOmxReader.h
@@ -79,16 +79,18 @@ public:
 
   virtual bool IsDormantNeeded();
   virtual void ReleaseMediaResources();
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult 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;
 
   void SetAudioChannel(dom::AudioChannel aAudioChannel) {
     mAudioChannel = aAudioChannel;
   }
 
--- a/content/media/plugins/MediaPluginReader.h
+++ b/content/media/plugins/MediaPluginReader.h
@@ -56,16 +56,22 @@ public:
     return mHasAudio;
   }
 
   virtual bool HasVideo()
   {
     return mHasVideo;
   }
 
+  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 Shutdown() MOZ_OVERRIDE;
 
   class ImageBufferCallback : public MPAPI::BufferCallback {
     typedef mozilla::layers::Image Image;
--- a/content/media/raw/RawReader.cpp
+++ b/content/media/raw/RawReader.cpp
@@ -107,16 +107,23 @@ nsresult RawReader::ReadMetadata(MediaIn
 
   *aInfo = mInfo;
 
   *aTags = nullptr;
 
   return NS_OK;
 }
 
+bool
+RawReader::IsMediaSeekable()
+{
+  // not used
+  return true;
+}
+
  bool RawReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on state machine thread or decode thread.");
   return false;
 }
 
 // Helper method that either reads until it gets aLength bytes 
--- a/content/media/raw/RawReader.h
+++ b/content/media/raw/RawReader.h
@@ -34,16 +34,18 @@ public:
     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 nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime);
 
+  virtual bool IsMediaSeekable() MOZ_OVERRIDE;
+
 private:
   bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
 
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
--- a/content/media/wave/WaveReader.cpp
+++ b/content/media/wave/WaveReader.cpp
@@ -153,16 +153,23 @@ nsresult WaveReader::ReadMetadata(MediaI
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mDecoder->SetMediaDuration(
     static_cast<int64_t>(BytesToTime(GetDataLength()) * USECS_PER_S));
 
   return NS_OK;
 }
 
+bool
+WaveReader::IsMediaSeekable()
+{
+  // not used
+  return true;
+}
+
 template <typename T> T UnsignedByteToAudioSample(uint8_t aValue);
 template <typename T> T SignedShortToAudioSample(int16_t aValue);
 
 template <> inline float
 UnsignedByteToAudioSample<float>(uint8_t aValue)
 {
   return aValue * (2.0f / UINT8_MAX) - 1.0f;
 }
--- a/content/media/wave/WaveReader.h
+++ b/content/media/wave/WaveReader.h
@@ -43,16 +43,18 @@ public:
   virtual nsresult 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;
+
 private:
   bool ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead = nullptr);
   bool LoadRIFFChunk();
   bool GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize);
   bool LoadFormatChunk(uint32_t aChunkSize);
   bool FindDataOffset(uint32_t aChunkSize);
   bool LoadListChunk(uint32_t aChunkSize, nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
   bool LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -459,26 +459,29 @@ nsresult WebMReader::ReadMetadata(MediaI
 #endif
       } else {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
     }
   }
 
-  // We can't seek in buffered regions if we have no cues.
-  mDecoder->SetMediaSeekable(nestegg_has_cues(mContext) == 1);
-
   *aInfo = mInfo;
 
   *aTags = nullptr;
 
   return NS_OK;
 }
 
+bool
+WebMReader::IsMediaSeekable()
+{
+  return mContext && nestegg_has_cues(mContext);
+}
+
 #ifdef MOZ_OPUS
 bool WebMReader::InitOpusDecoder()
 {
   int r;
 
   NS_ASSERTION(mOpusDecoder == nullptr, "leaking OpusDecoder");
 
   mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
--- a/content/media/webm/WebMReader.h
+++ b/content/media/webm/WebMReader.h
@@ -130,16 +130,18 @@ public:
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult 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 bool IsMediaSeekable() MOZ_OVERRIDE;
+
 protected:
   // Value passed to NextPacket to determine if we are reading a video or an
   // audio packet.
   enum TrackType {
     VIDEO = 0,
     AUDIO = 1
   };
 
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -536,34 +536,42 @@ WMFReader::ReadMetadata(MediaInfo* aInfo
 
   // Get the duration, and report it to the decoder if we have it.
   int64_t duration = 0;
   hr = GetSourceReaderDuration(mSourceReader, duration);
   if (SUCCEEDED(hr)) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaEndTime(duration);
   }
-  // We can seek if we get a duration *and* the reader reports that it's
-  // seekable.
-  bool canSeek = false;
-  if (FAILED(hr) ||
-      FAILED(GetSourceReaderCanSeek(mSourceReader, canSeek)) ||
-      !canSeek) {
-    mDecoder->SetMediaSeekable(false);
-  }
 
   *aInfo = mInfo;
   *aTags = nullptr;
   // aTags can be retrieved using techniques like used here:
   // http://blogs.msdn.com/b/mf/archive/2010/01/12/mfmediapropdump.aspx
 
   return NS_OK;
 }
 
 bool
+WMFReader::IsMediaSeekable()
+{
+  // Get the duration
+  int64_t duration = 0;
+  HRESULT hr = GetSourceReaderDuration(mSourceReader, duration);
+  // We can seek if we get a duration *and* the reader reports that it's
+  // seekable.
+  bool canSeek = false;
+  if (FAILED(hr) || FAILED(GetSourceReaderCanSeek(mSourceReader, canSeek)) ||
+      !canSeek) {
+    return false;
+  }
+  return true;
+}
+
+bool
 WMFReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   HRESULT hr;
   hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,
                                  0, // control flags
                                  0, // read stream index
--- a/content/media/wmf/WMFReader.h
+++ b/content/media/wmf/WMFReader.h
@@ -42,16 +42,19 @@ public:
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
   nsresult 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();
   HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
   void GetSupportedAudioCodecs(const GUID** aCodecs, uint32_t* aNumCodecs);