Bug 816726 - Add seeking capabilities to DASH-WebM code r=cpearce
authorSteve Workman <sworkman@mozilla.com>
Thu, 13 Dec 2012 11:42:45 -0800
changeset 115965 5b6583b20a80
parent 115964 e7b09b636bbf
child 115966 c926d32f8ace
push id24034
push useremorley@mozilla.com
push dateFri, 14 Dec 2012 15:28:57 +0000
treeherdermozilla-central@50d8f411d305 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs816726
milestone20.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 816726 - Add seeking capabilities to DASH-WebM code r=cpearce
content/media/MediaCache.cpp
content/media/MediaCache.h
content/media/MediaResource.cpp
content/media/MediaResource.h
content/media/dash/DASHDecoder.cpp
content/media/dash/DASHDecoder.h
content/media/dash/DASHReader.cpp
content/media/dash/DASHReader.h
content/media/dash/DASHRepDecoder.cpp
content/media/dash/DASHRepDecoder.h
content/media/dash/DASHRepReader.h
content/media/webm/WebMReader.cpp
content/media/webm/WebMReader.h
--- a/content/media/MediaCache.cpp
+++ b/content/media/MediaCache.cpp
@@ -1623,16 +1623,20 @@ MediaCacheStream::NotifyDataStarted(int6
   NS_WARN_IF_FALSE(aOffset == mChannelOffset,
                    "Server is giving us unexpected offset");
   mChannelOffset = aOffset;
   if (mStreamLength >= 0) {
     // If we started reading at a certain offset, then for sure
     // the stream is at least that long.
     mStreamLength = NS_MAX(mStreamLength, mChannelOffset);
   }
+  // Ensure that |mDownloadCancelled| is set to false since we have new data.
+  if (mDownloadCancelled) {
+    mDownloadCancelled = false;
+  }
 }
 
 bool
 MediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
 {
   return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
 }
 
@@ -1792,16 +1796,34 @@ MediaCacheStream::NotifyDataEnded(nsresu
       stream->mClient->CacheClientNotifyDataEnded(aStatus);
     }
   }
 
   mChannelEnded = true;
   gMediaCache->QueueUpdate();
 }
 
+void
+MediaCacheStream::NotifyDownloadCancelled()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
+
+  MediaCache::ResourceStreamIterator iter(mResourceID);
+  while (MediaCacheStream* stream = iter.Next()) {
+    // The remainder of the download was cancelled; in order to cancel any
+    // waiting reads, assume length is equal to current channel offset.
+    stream->mDownloadCancelled = true;
+  }
+
+  // Wake up waiting streams so they will stop reading.
+  mon.NotifyAll();
+}
+
 MediaCacheStream::~MediaCacheStream()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
 
   if (gMediaCache) {
     NS_ASSERTION(mClosed, "Stream was not closed");
     gMediaCache->ReleaseStream(this);
@@ -2136,16 +2158,21 @@ MediaCacheStream::Read(char* aBuffer, ui
 
       // No data has been read yet, so block
       mon.Wait();
       if (mClosed) {
         // We may have successfully read some data, but let's just throw
         // that out.
         return NS_ERROR_FAILURE;
       }
+      // If the download was cancelled, stop reading and return silently.
+      if (mDownloadCancelled) {
+        mDownloadCancelled = false;
+        return NS_OK;
+      }
       continue;
     }
 
     gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
 
     int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
     NS_ABORT_IF_FALSE(size >= 0 && size <= INT32_MAX, "Size out of range.");
     nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, int32_t(size), &bytes);
--- a/content/media/MediaCache.h
+++ b/content/media/MediaCache.h
@@ -189,16 +189,17 @@ public:
     MODE_PLAYBACK
   };
 
   // aClient provides the underlying transport that cache will use to read
   // data for this stream.
   MediaCacheStream(ChannelMediaResource* aClient)
     : mClient(aClient), mInitialized(false),
       mHasHadUpdate(false),
+      mDownloadCancelled(false),
       mClosed(false),
       mDidNotifyDataEnded(false), mResourceID(0),
       mIsTransportSeekable(false), mCacheSuspended(false),
       mChannelEnded(false),
       mChannelOffset(0), mStreamLength(-1),  
       mStreamOffset(0), mPlaybackBytesPerSecond(10000),
       mPinCount(0), mCurrentMode(MODE_PLAYBACK),
       mMetadataInPartialBlockBuffer(false) {}
@@ -273,16 +274,23 @@ public:
   // We pass in the principal that was used to load this data.
   void NotifyDataReceived(int64_t aSize, const char* aData,
                           nsIPrincipal* aPrincipal);
   // Notifies the cache that the current bytes should be written to disk.
   // Called on the main thread.
   void FlushPartialBlock();
   // Notifies the cache that the channel has closed with the given status.
   void NotifyDataEnded(nsresult aStatus);
+  // Notifies the cache that the download was cancelled. Sets
+  // |mDownloadCancelled| to false and notifies any thread waiting on the media
+  // cache monitor. In this way, if |Read| is waiting when a download is
+  // cancelled, it will be wakened and should stop trying to read.
+  // Note: |mDownloadCancelled| is set to false when |Read| is awakened, and in
+  // |NotifyDataStarted|, when new data starts downloading.
+  void NotifyDownloadCancelled();
 
   // These methods can be called on any thread.
   // Cached blocks associated with this stream will not be evicted
   // while the stream is pinned.
   void Pin();
   void Unpin();
   // See comments above for NotifyDataLength about how the length
   // can vary over time. Returns -1 if no length is known. Returns the
@@ -435,16 +443,19 @@ private:
   // These fields are main-thread-only.
   ChannelMediaResource*  mClient;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   // Set to true when Init or InitAsClone has been called
   bool                   mInitialized;
   // Set to true when MediaCache::Update() has finished while this stream
   // was present.
   bool                   mHasHadUpdate;
+  // True if the download was cancelled. Set true in NotifyDownloadCancelled.
+  // Set false in NotifyDataStarted.
+  bool mDownloadCancelled;
   // Set to true when the stream has been closed either explicitly or
   // due to an internal cache error
   bool                   mClosed;
   // True if CacheClientNotifyDataEnded has been called for this stream.
   bool                   mDidNotifyDataEnded;
 
   // The following fields must be written holding the cache's monitor and
   // only on the main thread, thus can be read either on the main thread
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -562,16 +562,28 @@ ChannelMediaResource::OpenByteRange(nsIS
   CloseChannel();
 
   nsresult rv = RecreateChannel();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return OpenChannel(aStreamListener);
 }
 
+void
+ChannelMediaResource::CancelByteRangeOpen()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+  // Byte range download will be cancelled in |CacheClientSeek|. Here, we only
+  // need to notify the cache to in turn notify any waiting reads.
+  if (mByteRangeDownloads) {
+    mCacheStream.NotifyDownloadCancelled();
+  }
+}
+
 nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsresult rv = mCacheStream.Init();
   if (NS_FAILED(rv))
     return rv;
   NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -92,41 +92,79 @@ public:
   }
 private:
   int64_t      mAccumulatedBytes;
   TimeDuration mAccumulatedTime;
   TimeStamp    mLastStartTime;
   bool         mIsStarted;
 };
 
+// Forward declaration for use in MediaByteRange.
+class TimestampedMediaByteRange;
+
 // Represents a section of contiguous media, with a start and end offset.
 // Used to denote ranges of data which are cached.
 class MediaByteRange {
 public:
   MediaByteRange() : mStart(0), mEnd(0) {}
 
   MediaByteRange(int64_t aStart, int64_t aEnd)
     : mStart(aStart), mEnd(aEnd)
   {
     NS_ASSERTION(mStart < mEnd, "Range should end after start!");
   }
 
+  MediaByteRange(TimestampedMediaByteRange& aByteRange);
+
   bool IsNull() const {
     return mStart == 0 && mEnd == 0;
   }
 
   // Clears byte range values.
   void Clear() {
     mStart = 0;
     mEnd = 0;
   }
 
   int64_t mStart, mEnd;
 };
 
+// Represents a section of contiguous media, with a start and end offset, and
+// a timestamp representing the start time.
+class TimestampedMediaByteRange : public MediaByteRange {
+public:
+  TimestampedMediaByteRange() : MediaByteRange(), mStartTime(-1) {}
+
+  TimestampedMediaByteRange(int64_t aStart, int64_t aEnd, int64_t aStartTime)
+    : MediaByteRange(aStart, aEnd), mStartTime(aStartTime)
+  {
+    NS_ASSERTION(aStartTime >= 0, "Start time should not be negative!");
+  }
+
+  bool IsNull() const {
+    return MediaByteRange::IsNull() && mStartTime == -1;
+  }
+
+  // Clears byte range values.
+  void Clear() {
+    MediaByteRange::Clear();
+    mStartTime = -1;
+  }
+
+  // In usecs.
+  int64_t mStartTime;
+};
+
+inline MediaByteRange::MediaByteRange(TimestampedMediaByteRange& aByteRange)
+  : mStart(aByteRange.mStart), mEnd(aByteRange.mEnd)
+{
+  NS_ASSERTION(mStart < mEnd, "Range should end after start!");
+}
+
+
 /**
  * Provides a thread-safe, seek/read interface to resources
  * loaded from a URI. Uses MediaCache to cache data received over
  * Necko's async channel API, thus resolving the mismatch between clients
  * that need efficient random access to the data and protocols that do not
  * support efficient random access, such as HTTP.
  *
  * Instances of this class must be created on the main thread.
@@ -294,16 +332,22 @@ public:
    */
   virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
                                  MediaByteRange const &aByteRange)
   {
     return Open(aStreamListener);
   }
 
   /**
+   * Cancels current byte range requests previously opened via
+   * OpenByteRange.
+   */
+  virtual void CancelByteRangeOpen() { }
+
+  /**
    * Fills aRanges with MediaByteRanges representing the data which is cached
    * in the media cache. Stream should be pinned during call and while
    * aRanges is being used.
    */
   virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) = 0;
 
   // Ensure that the media cache writes any data held in its partial block.
   // Called on the main thread only.
@@ -401,16 +445,17 @@ public:
 
   // Notify that the last data byte range was loaded.
   virtual void NotifyLastByteRange() MOZ_OVERRIDE;
 
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
                                  MediaByteRange const & aByteRange);
+  virtual void     CancelByteRangeOpen();
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately);
   virtual void     Resume();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
   // Return true if the stream has been closed.
   bool IsClosed() const { return mCacheStream.IsClosed(); }
   virtual bool     CanClone();
   virtual MediaResource* CloneData(MediaDecoder* aDecoder);
@@ -438,16 +483,17 @@ public:
   virtual bool    IsSuspended();
 
   class Listener MOZ_FINAL : public nsIStreamListener,
                              public nsIInterfaceRequestor,
                              public nsIChannelEventSink
   {
   public:
     Listener(ChannelMediaResource* aResource) : mResource(aResource) {}
+    ~Listener() {}
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSICHANNELEVENTSINK
     NS_DECL_NSIINTERFACEREQUESTOR
 
     void Revoke() { mResource = nullptr; }
--- a/content/media/dash/DASHDecoder.cpp
+++ b/content/media/dash/DASHDecoder.cpp
@@ -150,17 +150,18 @@ DASHDecoder::DASHDecoder() :
   mPrincipal(nullptr),
   mDASHReader(nullptr),
   mVideoAdaptSetIdx(-1),
   mAudioRepDecoderIdx(-1),
   mVideoRepDecoderIdx(-1),
   mAudioSubsegmentIdx(0),
   mVideoSubsegmentIdx(0),
   mAudioMetadataReadCount(0),
-  mVideoMetadataReadCount(0)
+  mVideoMetadataReadCount(0),
+  mSeeking(false)
 {
   MOZ_COUNT_CTOR(DASHDecoder);
 }
 
 DASHDecoder::~DASHDecoder()
 {
   MOZ_COUNT_DTOR(DASHDecoder);
 }
@@ -186,18 +187,18 @@ DASHDecoder::ReleaseStateMachine()
   }
   for (uint i = 0; i < mVideoRepDecoders.Length(); i++) {
     mVideoRepDecoders[i]->ReleaseStateMachine();
   }
 }
 
 nsresult
 DASHDecoder::Load(MediaResource* aResource,
-                    nsIStreamListener** aStreamListener,
-                    MediaDecoder* aCloneDonor)
+                  nsIStreamListener** aStreamListener,
+                  MediaDecoder* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   mDASHReader = new DASHReader(this);
 
   nsresult rv = OpenResource(aResource, aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -930,16 +931,28 @@ DASHDecoder::PossiblySwitchDecoder(DASHR
   uint32_t toDecoderIdx = mVideoRepDecoderIdx;
   if (bestRepIdx > toDecoderIdx) {
     toDecoderIdx = NS_MIN(toDecoderIdx+1, mVideoRepDecoders.Length()-1);
   } else if (toDecoderIdx < bestRepIdx) {
     // If the bitrate is too much for the current bandwidth, just use that
     // stream directly.
     toDecoderIdx = bestRepIdx;
   }
+
+  // Upgrade |toDecoderIdx| if a better subsegment was previously downloaded and
+  // is still cached.
+  if (mVideoSubsegmentIdx < mVideoSubsegmentLoads.Length() &&
+      toDecoderIdx < mVideoSubsegmentLoads[mVideoSubsegmentIdx]) {
+    // Check if the subsegment is cached.
+    uint32_t betterRepIdx = mVideoSubsegmentLoads[mVideoSubsegmentIdx];
+    if (mVideoRepDecoders[betterRepIdx]->IsSubsegmentCached(mVideoSubsegmentIdx)) {
+      toDecoderIdx = betterRepIdx;
+    }
+  }
+
   NS_ENSURE_TRUE(toDecoderIdx < mVideoRepDecoders.Length(),
                  NS_ERROR_ILLEGAL_VALUE);
 
   // Notify reader and sub decoders and do the switch.
   if (toDecoderIdx != mVideoRepDecoderIdx) {
     LOG("*** Switching video decoder from [%d] [%p] to [%d] [%p] at "
         "subsegment [%d]", mVideoRepDecoderIdx, VideoRepDecoder(),
         toDecoderIdx, mVideoRepDecoders[toDecoderIdx].get(),
@@ -953,16 +966,105 @@ DASHDecoder::PossiblySwitchDecoder(DASHR
     mVideoRepDecoders[mVideoRepDecoderIdx]->PrepareForSwitch();
     // Switch video decoders - equates to switching download source.
     mVideoRepDecoderIdx = toDecoderIdx;
   }
 
   return NS_OK;
 }
 
+nsresult
+DASHDecoder::Seek(double aTime)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
+
+  LOG("Seeking to [%.2fs]", aTime);
+
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    // We want to stop the current series of downloads and restart later with
+    // the appropriate subsegment.
+
+    // 1 - Set the seeking flag, so that when current subsegments download (if
+    // any), the next subsegment will not be downloaded.
+    mSeeking = true;
+
+    // 2 - Cancel all current downloads to reset for seeking.
+    for (uint32_t i = 0; i < mAudioRepDecoders.Length(); i++) {
+      mAudioRepDecoders[i]->CancelByteRangeLoad();
+    }
+    for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
+      mVideoRepDecoders[i]->CancelByteRangeLoad();
+    }
+  }
+
+  return MediaDecoder::Seek(aTime);
+}
+
+void
+DASHDecoder::NotifySeekInVideoSubsegment(int32_t aRepDecoderIdx,
+                                         int32_t aSubsegmentIdx)
+{
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  NS_ASSERTION(0 <= aRepDecoderIdx &&
+               aRepDecoderIdx < mVideoRepDecoders.Length(),
+               "Video decoder index is out of bounds");
+
+  // Reset current subsegment to match the one being seeked.
+  mVideoSubsegmentIdx = aSubsegmentIdx;
+  // Reset current decoder to match the one returned by
+  // |GetRepIdxForVideoSubsegmentLoad|.
+  mVideoRepDecoderIdx = aRepDecoderIdx;
+
+  mSeeking = false;
+
+  LOG("Dispatching load for video decoder [%d] [%p]: seek in subsegment [%d]",
+      mVideoRepDecoderIdx, VideoRepDecoder(), aSubsegmentIdx);
+
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(VideoRepDecoder(),
+                         &DASHRepDecoder::LoadNextByteRange);
+  nsresult rv = NS_DispatchToMainThread(event);
+  if (NS_FAILED(rv)) {
+    LOG("Error dispatching video byte range load: rv[0x%x].",
+        rv);
+    NetworkError();
+    return;
+  }
+}
+
+void
+DASHDecoder::NotifySeekInAudioSubsegment(int32_t aSubsegmentIdx)
+{
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  // Reset current subsegment to match the one being seeked.
+  mAudioSubsegmentIdx = aSubsegmentIdx;
+
+  LOG("Dispatching seeking load for audio decoder [%d] [%p]: subsegment [%d]",
+     mAudioRepDecoderIdx, AudioRepDecoder(), aSubsegmentIdx);
+
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(AudioRepDecoder(),
+                         &DASHRepDecoder::LoadNextByteRange);
+  nsresult rv = NS_DispatchToMainThread(event);
+  if (NS_FAILED(rv)) {
+    LOG("Error dispatching audio byte range load: rv[0x%x].",
+        rv);
+    NetworkError();
+    return;
+  }
+}
+
 bool
 DASHDecoder::IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder)
 {
   NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   // Only return true if |aRepDecoder| is active and metadata for all
   // representations has been downloaded.
@@ -973,26 +1075,49 @@ DASHDecoder::IsDecoderAllowedToDownloadD
 bool
 DASHDecoder::IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
                                                   int32_t const aSubsegmentIdx)
 {
   NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
+  // Forbid any downloads until we've been told what subsegment to seek to.
+  if (mSeeking) {
+    return false;
+  }
   // Return false if there is still metadata to be downloaded.
   if (mAudioMetadataReadCount != 0 || mVideoMetadataReadCount != 0) {
     return false;
   }
   // No audio switching; allow the audio decoder to download all subsegments.
   if (aRepDecoder == AudioRepDecoder()) {
     return true;
   }
 
   int32_t videoDecoderIdx = GetRepIdxForVideoSubsegmentLoad(aSubsegmentIdx);
   if (aRepDecoder == mVideoRepDecoders[videoDecoderIdx]) {
     return true;
   }
   return false;
 }
 
+void
+DASHDecoder::SetSubsegmentIndex(DASHRepDecoder* aRepDecoder,
+                                int32_t aSubsegmentIdx)
+{
+  NS_ASSERTION(0 <= aSubsegmentIdx,
+               "Subsegment index should not be negative!");
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  if (aRepDecoder == AudioRepDecoder()) {
+    mAudioSubsegmentIdx = aSubsegmentIdx;
+  } else if (aRepDecoder == VideoRepDecoder()) {
+    // If this is called in the context of a Seek, we need to cancel downloads
+    // from other rep decoders, or all rep decoders if we're not seeking in the
+    // current subsegment.
+    // Note: NotifySeekInSubsegment called from DASHReader will already have
+    // set the current decoder.
+    mVideoSubsegmentIdx = aSubsegmentIdx;
+  }
+}
+
 } // namespace mozilla
 
--- a/content/media/dash/DASHDecoder.h
+++ b/content/media/dash/DASHDecoder.h
@@ -56,27 +56,37 @@ public:
   nsresult Load(MediaResource* aResource,
                 nsIStreamListener** aListener,
                 MediaDecoder* aCloneDonor);
 
   // Notifies download of MPD file has ended.
   // Called on the main thread only.
   void NotifyDownloadEnded(nsresult aStatus);
 
+  // Notification from |DASHReader| that a seek has occurred in
+  // |aSubsegmentIdx|. Passes notification onto subdecoder which downloaded
+  // the subsegment already, if download is in the past. Otherwise, it returns.
+  void NotifySeekInVideoSubsegment(int32_t aRepDecoderIdx,
+                                   int32_t aSubsegmentIdx);
+  void NotifySeekInAudioSubsegment(int32_t aSubsegmentIdx);
+
   // Notifies that a byte range download has ended. As per the DASH spec, this
   // allows for stream switching at the boundaries of the byte ranges.
   // Called on the main thread only.
   void NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
                            nsresult aStatus,
                            int32_t const aSubsegmentIdx);
 
   // Notification from an |MediaDecoderReader| class that metadata has been
   // read. Declared here to allow overloading.
   void OnReadMetadataCompleted() MOZ_OVERRIDE { }
 
+  // Seeks to aTime in seconds
+  nsresult Seek(double aTime) MOZ_OVERRIDE;
+
   // Notification from |DASHRepDecoder| that a metadata has been read.
   // |DASHDecoder| will initiate load of data bytes for active audio/video
   // decoders. Called on the decode thread.
   void OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder);
 
   // Refers to downloading data bytes, i.e. non metadata.
   // Returns true if |aRepDecoder| is an active audio or video sub decoder AND
   // if metadata for all audio or video decoders has been read.
@@ -97,25 +107,17 @@ public:
   // Called on the main thread.
   nsresult PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder);
 
   // Sets the byte range index for audio|video downloads. Will only increment
   // for current active decoders. Could be called from any thread.
   // Requires monitor because of write to |mAudioSubsegmentIdx| or
   // |mVideoSubsegmentIdx|.
   void SetSubsegmentIndex(DASHRepDecoder* aRepDecoder,
-                          uint32_t aSubsegmentIdx)
-  {
-    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    if (aRepDecoder == AudioRepDecoder()) {
-      mAudioSubsegmentIdx = aSubsegmentIdx;
-    } else if (aRepDecoder == VideoRepDecoder()) {
-      mVideoSubsegmentIdx = aSubsegmentIdx;
-    }
-  }
+                          int32_t aSubsegmentIdx);
 private:
   // Increments the byte range index for audio|video downloads. Will only
   // increment for current active decoders. Could be called from any thread.
   // Requires monitor because of write to |mAudioSubsegmentIdx| or
   // |mVideoSubsegmentIdx|.
   void IncrementSubsegmentIndex(DASHRepDecoder* aRepDecoder)
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
@@ -140,27 +142,48 @@ public:
     }
     return (-1);
   }
 
   // Returns the index of the rep decoder used to load a subsegment. Will enter
   // monitor for read access off the decode thread.
   int32_t GetRepIdxForVideoSubsegmentLoad(int32_t aSubsegmentIdx)
   {
-    NS_ASSERTION(0 < aSubsegmentIdx, "Subsegment index should not be negative.");
+    NS_ASSERTION(0 <= aSubsegmentIdx, "Subsegment index should not be negative.");
     ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
                                            GetReentrantMonitor());
     if ((uint32_t)aSubsegmentIdx < mVideoSubsegmentLoads.Length()) {
       return mVideoSubsegmentLoads[aSubsegmentIdx];
     } else {
       // If it hasn't been downloaded yet, use the lowest bitrate decoder.
       return 0;
     }
   }
 
+  int32_t GetSwitchCountAtVideoSubsegment(int32_t aSubsegmentIdx)
+  {
+    ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
+                                           GetReentrantMonitor());
+    NS_ASSERTION(0 <= aSubsegmentIdx, "Subsegment index should not be negative.");
+    if (aSubsegmentIdx == 0) {
+      // Do the zeroeth switch next.
+      return 0;
+    }
+    int32_t switchCount = 0;
+    for (uint32_t i = 1;
+         i < mVideoSubsegmentLoads.Length() &&
+         i <= (uint32_t)aSubsegmentIdx;
+         i++) {
+      if (mVideoSubsegmentLoads[i-1] != mVideoSubsegmentLoads[i]) {
+        switchCount++;
+      }
+    }
+    return switchCount;
+  }
+
   // Drop reference to state machine and tell sub-decoders to do the same.
   // Only called during shutdown dance, on main thread only.
   void ReleaseStateMachine();
 
   // Overridden to forward |Shutdown| to sub-decoders.
   // Called on the main thread only.
   void Shutdown();
 
@@ -287,13 +310,18 @@ private:
   // zero again, all metadata has been read for audio or video, and data bytes
   // can be downloaded.
   uint32_t mAudioMetadataReadCount;
   uint32_t mVideoMetadataReadCount;
 
   // Array records the index of the decoder/Representation which loaded each
   // subsegment.
   nsTArray<int32_t> mVideoSubsegmentLoads;
+
+  // True when Seek is called; will block any downloads until
+  // |NotifySeekInSubsegment| is called, which will set it to false, and will
+  // start a new series of downloads from the seeked subsegment.
+  bool mSeeking;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/dash/DASHReader.cpp
+++ b/content/media/dash/DASHReader.cpp
@@ -52,16 +52,32 @@ DASHReader::DASHReader(AbstractMediaDeco
 }
 
 DASHReader::~DASHReader()
 {
   MOZ_COUNT_DTOR(DASHReader);
 }
 
 nsresult
+DASHReader::ResetDecode()
+{
+  MediaDecoderReader::ResetDecode();
+  nsresult rv;
+  for (uint i = 0; i < mAudioReaders.Length(); i++) {
+    rv = mAudioReaders[i]->ResetDecode();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  for (uint i = 0; i < mVideoReaders.Length(); i++) {
+    rv = mVideoReaders[i]->ResetDecode();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  return NS_OK;
+}
+
+nsresult
 DASHReader::Init(MediaDecoderReader* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(mAudioReaders.Length() != 0 && mVideoReaders.Length() != 0,
                "Audio and video readers should exist already.");
 
@@ -132,17 +148,17 @@ DASHReader::AudioQueueMemoryInUse()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
   return AudioQueueMemoryInUse();
 }
 
 bool
 DASHReader::DecodeVideoFrame(bool &aKeyframeSkip,
-                               int64_t aTimeThreshold)
+                             int64_t aTimeThreshold)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (mVideoReader) {
    return mVideoReader->DecodeVideoFrame(aKeyframeSkip, aTimeThreshold);
   } else {
    return false;
   }
 }
@@ -202,38 +218,102 @@ DASHReader::ReadMetadata(VideoInfo* aInf
 
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
 nsresult
 DASHReader::Seek(int64_t aTime,
-                   int64_t aStartTime,
-                   int64_t aEndTime,
-                   int64_t aCurrentTime)
+                 int64_t aStartTime,
+                 int64_t aEndTime,
+                 int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
+  NS_ENSURE_SUCCESS(ResetDecode(), NS_ERROR_FAILURE);
+
+  LOG("Seeking to [%.2fs]", aTime/1000000.0);
+
   nsresult rv;
+  DASHDecoder* dashDecoder = static_cast<DASHDecoder*>(mDecoder);
 
   if (mAudioReader) {
+    int64_t subsegmentIdx = -1;
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      subsegmentIdx = mAudioReader->GetSubsegmentForSeekTime(aTime);
+      NS_ENSURE_TRUE(0 <= subsegmentIdx, NS_ERROR_ILLEGAL_VALUE);
+    }
+    dashDecoder->NotifySeekInAudioSubsegment(subsegmentIdx);
+
     rv = mAudioReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+
   if (mVideoReader) {
-    rv = mVideoReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
-    NS_ENSURE_SUCCESS(rv, rv);
+    // Determine the video subsegment we're seeking to.
+    int32_t subsegmentIdx = -1;
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      subsegmentIdx = mVideoReader->GetSubsegmentForSeekTime(aTime);
+      NS_ENSURE_TRUE(0 <= subsegmentIdx, NS_ERROR_ILLEGAL_VALUE);
+    }
+
+    LOG("Seek to [%.2fs] found in video subsegment [%d]",
+        aTime/1000000.0, subsegmentIdx);
+
+    // Determine if/which video reader previously downloaded this subsegment.
+    int32_t readerIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(subsegmentIdx);
+
+    dashDecoder->NotifySeekInVideoSubsegment(readerIdx, subsegmentIdx);
+
+    if (0 <= readerIdx) {
+      NS_ENSURE_TRUE(readerIdx < mVideoReaders.Length(),
+                     NS_ERROR_ILLEGAL_VALUE);
+      // Switch to this reader and do the Seek.
+      DASHRepReader* fromReader = mVideoReader;
+      DASHRepReader* toReader = mVideoReaders[readerIdx];
+
+      {
+        ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+        if (fromReader != toReader) {
+          LOG("Switching video readers now from [%p] to [%p] for a seek to "
+              "[%.2fs] in subsegment [%d]",
+              fromReader, toReader, aTime/1000000.0, subsegmentIdx);
+
+          mVideoReader = toReader;
+        }
+      }
+
+      rv = mVideoReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
+      if (NS_FAILED(rv)) {
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Go back to the appropriate count in the switching history, and setup
+      // this main reader and the sub readers for the next switch (if any).
+      {
+        ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+        mSwitchCount = dashDecoder->GetSwitchCountAtVideoSubsegment(subsegmentIdx);
+        LOG("After mVideoReader->Seek() mSwitchCount %d", mSwitchCount);
+        NS_ENSURE_TRUE(0 <= mSwitchCount, NS_ERROR_ILLEGAL_VALUE);
+        NS_ENSURE_TRUE(mSwitchCount <= subsegmentIdx, NS_ERROR_ILLEGAL_VALUE);
+      }
+    } else {
+      LOG("Error getting rep idx for video subsegment [%d]",
+          subsegmentIdx);
+    }
   }
   return NS_OK;
 }
 
 nsresult
 DASHReader::GetBuffered(nsTimeRanges* aBuffered,
-                          int64_t aStartTime)
+                        int64_t aStartTime)
 {
   NS_ENSURE_ARG(aBuffered);
 
   MediaResource* resource = nullptr;
   AbstractMediaDecoder* decoder = nullptr;
 
   // Need to find intersect of |nsTimeRanges| for audio and video.
   nsTimeRanges audioBuffered, videoBuffered;
@@ -364,18 +444,18 @@ DASHReader::VideoQueue()
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
   NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
   return mVideoQueue;
 }
 
 void
 DASHReader::RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
-                                       uint32_t aToReaderIdx,
-                                       uint32_t aSubsegmentIdx)
+                                     uint32_t aToReaderIdx,
+                                     uint32_t aSubsegmentIdx)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(aFromReaderIdx < mVideoReaders.Length(),
                "From index is greater than number of video readers!");
   NS_ASSERTION(aToReaderIdx < mVideoReaders.Length(),
                "To index is greater than number of video readers!");
   NS_ASSERTION(aToReaderIdx != aFromReaderIdx,
                "Don't request switches to same reader!");
@@ -388,16 +468,24 @@ DASHReader::RequestVideoReaderSwitch(uin
   DASHRepReader* fromReader = mVideoReaders[aFromReaderIdx];
   DASHRepReader* toReader = mVideoReaders[aToReaderIdx];
 
   LOG("Switch requested from reader [%d] [%p] to reader [%d] [%p] "
       "at subsegment[%d].",
       aFromReaderIdx, fromReader, aToReaderIdx, toReader, aSubsegmentIdx);
 
   // Append the subsegment index to the list of pending switches.
+  for (uint32_t i = 0; i < mSwitchToVideoSubsegmentIndexes.Length(); i++) {
+    if (mSwitchToVideoSubsegmentIndexes[i] == aSubsegmentIdx) {
+      // A backwards |Seek| has changed the switching history; delete from
+      // this point on.
+      mSwitchToVideoSubsegmentIndexes.TruncateLength(i);
+      break;
+    }
+  }
   mSwitchToVideoSubsegmentIndexes.AppendElement(aSubsegmentIdx);
 
   // Tell the SWITCH FROM reader when it should stop reading.
   fromReader->RequestSwitchAtSubsegment(aSubsegmentIdx, toReader);
 
   // Tell the SWITCH TO reader to seek to the correct offset.
   toReader->RequestSeekToSubsegment(aSubsegmentIdx);
 
--- a/content/media/dash/DASHReader.h
+++ b/content/media/dash/DASHReader.h
@@ -24,26 +24,27 @@ namespace mozilla {
 
 class DASHRepReader;
 
 class DASHReader : public MediaDecoderReader
 {
 public:
   DASHReader(AbstractMediaDecoder* aDecoder);
   ~DASHReader();
+  nsresult ResetDecode() MOZ_OVERRIDE;
 
   // Adds a pointer to a audio/video reader for a media |Representation|.
   // Called on the main thread only.
   void AddAudioReader(DASHRepReader* aAudioReader);
   void AddVideoReader(DASHRepReader* aVideoReader);
 
   // Waits for metadata bytes to be downloaded, then reads and parses them.
   // Called on the decode thread only.
   nsresult ReadMetadata(VideoInfo* aInfo,
-                        MetadataTags** aTags);
+                        MetadataTags** aTags) MOZ_OVERRIDE;
 
   // Waits for |ReadyToReadMetadata| or |NotifyDecoderShuttingDown|
   // notification, whichever comes first. Ensures no attempt to read metadata
   // during |DASHDecoder|::|Shutdown|. Called on decode thread only.
   nsresult WaitForMetadata() {
     NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     ReentrantMonitorAutoEnter mon(mReadMetadataMonitor);
     while (true) {
@@ -74,51 +75,51 @@ public:
     ReentrantMonitorAutoEnter metadataMon(mReadMetadataMonitor);
     mDecoderIsShuttingDown = true;
     // Notify |ReadMetadata| of the shutdown if it's waiting.
     metadataMon.NotifyAll();
   }
 
   // Audio/video status are dependent on the presence of audio/video readers.
   // Call on decode thread only.
-  bool HasAudio();
-  bool HasVideo();
+  bool HasAudio() MOZ_OVERRIDE;
+  bool HasVideo() MOZ_OVERRIDE;
 
   // Returns references to the audio/video queues of sub-readers. Called on
   // decode, state machine and audio threads.
   MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE;
   MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE;
 
   // Called from MediaDecoderStateMachine on the main thread.
-  nsresult Init(MediaDecoderReader* aCloneDonor);
+  nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
 
   // Used by |MediaMemoryReporter|.
-  int64_t VideoQueueMemoryInUse();
-  int64_t AudioQueueMemoryInUse();
+  int64_t VideoQueueMemoryInUse() MOZ_OVERRIDE;
+  int64_t AudioQueueMemoryInUse() MOZ_OVERRIDE;
 
   // Called on the decode thread, at the start of the decode loop, before
   // |DecodeVideoFrame|.  Carries out video reader switch if previously
   // requested, and tells sub-readers to |PrepareToDecode|.
   void PrepareToDecode() MOZ_OVERRIDE;
 
   // Called on the decode thread.
-  bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
-  bool DecodeAudioData();
+  bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) MOZ_OVERRIDE;
+  bool DecodeAudioData() MOZ_OVERRIDE;
 
   // Converts seek time to byte offset. Called on the decode thread only.
   nsresult Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
-                int64_t aCurrentTime);
+                int64_t aCurrentTime) MOZ_OVERRIDE;
 
   // Called by state machine on multiple threads.
-  nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
+  nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime) MOZ_OVERRIDE;
 
   // Called on the state machine or decode threads.
-  VideoData* FindStartTime(int64_t& aOutStartTime);
+  VideoData* FindStartTime(int64_t& aOutStartTime) MOZ_OVERRIDE;
 
   // Prepares for an upcoming switch of video readers. Called by
   // |DASHDecoder| when it has switched download streams. Sets the index of
   // the reader to switch TO and the index of the subsegment to switch AT
   // (start offset). (Note: Subsegment boundaries are switch access points for
   // DASH-WebM). Called on the main thread. Must be in the decode monitor.
   void RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
                                 uint32_t aToReaderIdx,
--- a/content/media/dash/DASHRepDecoder.cpp
+++ b/content/media/dash/DASHRepDecoder.cpp
@@ -68,18 +68,18 @@ void
 DASHRepDecoder::SetReader(WebMReader* aReader)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mReader = aReader;
 }
 
 nsresult
 DASHRepDecoder::Load(MediaResource* aResource,
-                       nsIStreamListener** aListener,
-                       MediaDecoder* aCloneDonor)
+                     nsIStreamListener** aListener,
+                     MediaDecoder* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ENSURE_TRUE(mMPDRepresentation, NS_ERROR_NOT_INITIALIZED);
 
   // Get init range and index range from MPD.
   SegmentBase const * segmentBase = mMPDRepresentation->GetSegmentBase();
   NS_ENSURE_TRUE(segmentBase, NS_ERROR_NULL_POINTER);
 
@@ -126,16 +126,17 @@ DASHRepDecoder::NotifyDownloadEnded(nsre
   if (!mMainDecoder) {
     LOG("Error! Main Decoder is reported as null: mMainDecoder [%p]",
         mMainDecoder.get());
     DecodeError();
     return;
   }
 
   if (NS_SUCCEEDED(aStatus)) {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     // Decrement counter as metadata chunks are downloaded.
     // Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|.
     if (mMetadataChunkCount > 0) {
       LOG("Metadata chunk [%d] downloaded: range requested [%lld - %lld] "
           "subsegmentIdx [%d]",
           mMetadataChunkCount,
           mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx);
       mMetadataChunkCount--;
@@ -143,19 +144,16 @@ DASHRepDecoder::NotifyDownloadEnded(nsre
       LOG("Byte range downloaded: status [%x] range requested [%lld - %lld] "
           "subsegmentIdx [%d]",
           aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd,
           mSubsegmentIdx);
       if ((uint32_t)mSubsegmentIdx == mByteRanges.Length()-1) {
         mResource->NotifyLastByteRange();
       }
       // Notify main decoder that a DATA byte range is downloaded.
-      // Only notify IF this decoder is allowed to download data.
-      NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
-                   "This decoder should not have downloaded data.");
       mMainDecoder->NotifyDownloadEnded(this, aStatus, mSubsegmentIdx);
     }
   } else if (aStatus == NS_BINDING_ABORTED) {
     LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
     if (mMainDecoder) {
       mMainDecoder->LoadAborted();
     }
     return;
@@ -192,16 +190,17 @@ DASHRepDecoder::OnReadMetadataCompleted(
 nsresult
 DASHRepDecoder::PopulateByteRanges()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
   // Should not be called during shutdown.
   NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
 
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if (!mByteRanges.IsEmpty()) {
     return NS_OK;
   }
   NS_ENSURE_TRUE(mReader, NS_ERROR_NULL_POINTER);
   LOG1("Populating byte range array.");
   return mReader->GetSubsegmentByteRanges(mByteRanges);
 }
 
@@ -212,16 +211,17 @@ DASHRepDecoder::LoadNextByteRange()
   NS_ASSERTION(mResource, "Error: resource is reported as null!");
 
   // Return silently if shutting down.
   if (mShuttingDown) {
     LOG1("Shutting down! Ignoring LoadNextByteRange().");
     return;
   }
 
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   NS_ASSERTION(mMainDecoder, "Error: main decoder is null!");
   NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
                "Should not be called on non-active decoders!");
 
   // Cannot have empty byte ranges.
   if (mByteRanges.IsEmpty()) {
     LOG1("Error getting list of subsegment byte ranges.");
     DecodeError();
@@ -245,53 +245,104 @@ DASHRepDecoder::LoadNextByteRange()
   // Request a seek for the first reader. Required so that the reader is
   // primed to start here, and will block subsequent subsegment seeks unless
   // the subsegment has been read.
   if (subsegmentIdx == 0) {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mReader->RequestSeekToSubsegment(0);
   }
 
+  // Query resource for cached ranges; only download if it's not there.
+  if (IsSubsegmentCached(mSubsegmentIdx)) {
+    LOG("Subsegment [%d] bytes [%lld] to [%lld] already cached. No need to "
+        "download.", mSubsegmentIdx,
+        mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &DASHRepDecoder::DoNotifyDownloadEnded);
+    nsresult rv = NS_DispatchToMainThread(event);
+    if (NS_FAILED(rv)) {
+      LOG("Error notifying subsegment [%d] cached: rv[0x%x].",
+          mSubsegmentIdx, rv);
+      NetworkError();
+    }
+    return;
+  }
+
   // Open byte range corresponding to subsegment.
   nsresult rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
   if (NS_FAILED(rv)) {
     LOG("Error opening byte range [%lld - %lld]: subsegmentIdx [%d] rv [%x].",
         mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx, rv);
     NetworkError();
     return;
   }
 }
 
+void
+DASHRepDecoder::CancelByteRangeLoad()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  NS_ASSERTION(mResource, "Error: resource is reported as null!");
+
+  if (mCurrentByteRange.IsNull() || mSubsegmentIdx < 0) {
+    LOG1("Canceling current byte range load: none to cancel.");
+    return;
+  }
+  LOG("Canceling current byte range load: [%lld] to [%lld] subsegment "
+      "[%lld]", mCurrentByteRange.mStart, mCurrentByteRange.mEnd,
+      mSubsegmentIdx);
+
+  mResource->CancelByteRangeOpen();
+}
+
+bool
+DASHRepDecoder::IsSubsegmentCached(int32_t aSubsegmentIdx)
+{
+  GetReentrantMonitor().AssertCurrentThreadIn();
+
+  MediaByteRange byteRange = mByteRanges[aSubsegmentIdx];
+  int64_t start = mResource->GetNextCachedData(byteRange.mStart);
+  int64_t end = mResource->GetCachedDataEnd(byteRange.mStart);
+  return (start == byteRange.mStart &&
+          end >= byteRange.mEnd);
+}
+
+void
+DASHRepDecoder::DoNotifyDownloadEnded()
+{
+  NotifyDownloadEnded(NS_OK);
+}
+
 nsresult
 DASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
-                                      MediaByteRange& aByteRange)
+                                    MediaByteRange& aByteRange)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Only check data ranges if they're available and if this decoder is active,
   // i.e. inactive rep decoders should only load metadata.
-  bool canDownloadData = mMainDecoder->IsDecoderAllowedToDownloadData(this);
-  if (canDownloadData) {
-    for (int i = 0; i < mByteRanges.Length(); i++) {
-      NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
-      // Check if |aOffset| lies within the current data range.
-      if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  for (int i = 0; i < mByteRanges.Length(); i++) {
+    NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
+    // Check if |aOffset| lies within the current data range.
+    if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
+      if (mMainDecoder->IsDecoderAllowedToDownloadSubsegment(this, i)) {
         mCurrentByteRange = aByteRange = mByteRanges[i];
         mSubsegmentIdx = i;
         // XXX Hack: should be setting subsegment outside this function, but
         // need to review seeking for multiple switches anyhow.
         mMainDecoder->SetSubsegmentIndex(this, i);
         LOG("Getting DATA range [%d] for seek offset [%lld]: "
             "bytes [%lld] to [%lld]",
             i, aOffset, aByteRange.mStart, aByteRange.mEnd);
         return NS_OK;
       }
+      break;
     }
-  } else {
-    LOG1("Restricting seekable byte ranges to metadata for this decoder.");
   }
   // Don't allow metadata downloads once they're loaded and byte ranges have
   // been populated.
   bool canDownloadMetadata = mByteRanges.IsEmpty();
   if (canDownloadMetadata) {
     // Check metadata ranges; init range.
     if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
       mCurrentByteRange = aByteRange = mInitByteRange;
@@ -309,22 +360,21 @@ DASHRepDecoder::GetByteRangeForSeek(int6
       return NS_OK;
     }
   } else {
     LOG1("Metadata should be read; inhibiting further metadata downloads.");
   }
 
   // If no byte range is found by this stage, clear the parameter and return.
   aByteRange.Clear();
-  if (mByteRanges.IsEmpty() || !canDownloadData || !canDownloadMetadata) {
+  if (mByteRanges.IsEmpty() || !canDownloadMetadata) {
     // Assume mByteRanges will be populated after metadata is read.
-    LOG("Data ranges not populated [%s]; data download restricted [%s]; "
-        "metadata download restricted [%s]: offset[%lld].",
+    LOG("Data ranges not populated [%s]; metadata download restricted [%s]: "
+        "offset[%lld].",
         (mByteRanges.IsEmpty() ? "yes" : "no"),
-        (canDownloadData ? "no" : "yes"),
         (canDownloadMetadata ? "no" : "yes"), aOffset);
     return NS_ERROR_NOT_AVAILABLE;
   } else {
     // Cannot seek to an unknown offset.
     // XXX Revisit this for dynamic MPD profiles if MPD is regularly updated.
     LOG("Error! Offset [%lld] is in an unknown range!", aOffset);
     return NS_ERROR_ILLEGAL_VALUE;
   }
@@ -369,18 +419,18 @@ DASHRepDecoder::SetMediaSeekable(bool aM
 void
 DASHRepDecoder::Progress(bool aTimer)
 {
   if (mMainDecoder) { mMainDecoder->Progress(aTimer); }
 }
 
 void
 DASHRepDecoder::NotifyDataArrived(const char* aBuffer,
-                                    uint32_t aLength,
-                                    int64_t aOffset)
+                                  uint32_t aLength,
+                                  int64_t aOffset)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   LOG("Data bytes [%lld - %lld] arrived via buffer [%p].",
       aOffset, aOffset+aLength, aBuffer);
   // Notify reader directly, since call to |MediaDecoderStateMachine|::
   // |NotifyDataArrived| will go to |DASHReader|::|NotifyDataArrived|, which
   // has no way to forward the notification to the correct sub-reader.
@@ -417,24 +467,23 @@ bool
 DASHRepDecoder::OnDecodeThread() const
 {
   return (mMainDecoder ? mMainDecoder->OnDecodeThread() : false);
 }
 
 ReentrantMonitor&
 DASHRepDecoder::GetReentrantMonitor()
 {
+  NS_ASSERTION(mMainDecoder, "Can't get monitor if main decoder is null!");
   return mMainDecoder->GetReentrantMonitor();
 }
 
 mozilla::layers::ImageContainer*
 DASHRepDecoder::GetImageContainer()
 {
-  NS_ASSERTION(mMainDecoder && mMainDecoder->OnDecodeThread(),
-               "Should be on decode thread.");
   return (mMainDecoder ? mMainDecoder->GetImageContainer() : nullptr);
 }
 
 void
 DASHRepDecoder::DecodeError()
 {
   if (NS_IsMainThread()) {
     MediaDecoder::DecodeError();
--- a/content/media/dash/DASHRepDecoder.h
+++ b/content/media/dash/DASHRepDecoder.h
@@ -76,16 +76,22 @@ public:
   nsresult Load(MediaResource* aResource = nullptr,
                 nsIStreamListener** aListener = nullptr,
                 MediaDecoder* aCloneDonor = nullptr);
 
   // Loads the next byte range (or first one on first call). Called on the main
   // thread only.
   void LoadNextByteRange();
 
+  // Cancels current byte range loads. Called on the main thread only.
+  void CancelByteRangeLoad();
+
+  // Returns true if the subsegment is already in the media cache.
+  bool IsSubsegmentCached(int32_t aSubsegmentIdx);
+
   // Calls from DASHRepDecoder. Called on the main thread only.
   void SetReader(WebMReader* aReader);
 
   // Called if the media file encounters a network error. Call on the main
   // thread only.
   void NetworkError();
 
   // Set the duration of the media resource in units of seconds.
@@ -114,16 +120,21 @@ public:
   // Called by MediaResource when some data has been received.
   // Call on the main thread only.
   void NotifyBytesDownloaded();
 
   // Notify that a byte range request has been completed by the media resource.
   // Called on the main thread only.
   void NotifyDownloadEnded(nsresult aStatus);
 
+  // Called asynchronously by |LoadNextByteRange| if the data is already in the
+  // media cache. This will call NotifyDownloadEnded on the main thread with
+  // |aStatus| of NS_OK.
+  void DoNotifyDownloadEnded();
+
   // Called by MediaResource when the "cache suspended" status changes.
   // If MediaResource::IsSuspendedByCache returns true, then the decoder
   // should stop buffering or otherwise waiting for download progress and
   // start consuming data, if possible, because the cache is full.
   void NotifySuspendedStatusChanged();
 
   // Gets a byte range containing the byte offset. Call on main thread only.
   nsresult GetByteRangeForSeek(int64_t const aOffset,
@@ -151,16 +162,19 @@ public:
 
   // Called when Metadata has been read; notifies that index data is read.
   // Called on the decode thread only.
   void OnReadMetadataCompleted() MOZ_OVERRIDE;
 
   // Overridden to cleanup ref to |DASHDecoder|. Called on main thread only.
   void Shutdown() {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+    // Remove ref to state machine before |MediaDecoder|::|Shutdown|, since
+    // |DASHDecoder| is responsible for its shutdown.
+    mDecoderStateMachine = nullptr;
     // Call parent class shutdown.
     MediaDecoder::Shutdown();
     NS_ENSURE_TRUE(mShuttingDown, );
     // Cleanup ref to main decoder.
     mMainDecoder = nullptr;
   }
 
   // Drop reference to state machine and mReader (owned by state machine).
--- a/content/media/dash/DASHRepReader.h
+++ b/content/media/dash/DASHRepReader.h
@@ -36,16 +36,19 @@ public:
   virtual void SetMainReader(DASHReader *aMainReader) = 0;
 
   // Sets range for initialization bytes; used by DASH.
   virtual void SetInitByteRange(MediaByteRange &aByteRange) = 0;
 
   // Sets range for index frame bytes; used by DASH.
   virtual void SetIndexByteRange(MediaByteRange &aByteRange) = 0;
 
+  // Returns the index of the subsegment which contains the seek time (usecs).
+  virtual int64_t GetSubsegmentForSeekTime(int64_t aSeekToTime) = 0;
+
   // Returns list of ranges for index frame start/end offsets. Used by DASH.
   virtual nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges) = 0;
 
   // Returns true if the reader has reached a DASH switch access point.
   virtual bool HasReachedSubsegment(uint32_t aSubsegmentIndex) = 0;
 
   // Requests a seek to the start of a particular DASH subsegment.
   virtual void RequestSeekToSubsegment(uint32_t aIdx) = 0;
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -186,16 +186,26 @@ nsresult WebMReader::ResetDecode()
   // Ignore failed results from vorbis_synthesis_restart. They
   // aren't fatal and it fails when ResetDecode is called at a
   // time when no vorbis data has been read.
   vorbis_synthesis_restart(&mVorbisDsp);
 
   mVideoPackets.Reset();
   mAudioPackets.Reset();
 
+#ifdef MOZ_DASH
+  LOG(PR_LOG_DEBUG, ("Resetting DASH seek vars"));
+  mSwitchingCluster = -1;
+  mNextReader = nullptr;
+  mSeekToCluster = -1;
+  mCurrentOffset = -1;
+  mPushVideoPacketToNextReader = false;
+  mReachedSwitchAccessPoint = false;
+#endif
+
   return res;
 }
 
 void WebMReader::Cleanup()
 {
   if (mContext) {
     nestegg_destroy(mContext);
     mContext = nullptr;
@@ -376,30 +386,34 @@ nsresult WebMReader::ReadMetadata(VideoI
   if (!mCuesByteRange.IsNull()) {
     maxOffset = mCuesByteRange.mEnd;
 
     // Iterate through cluster ranges until nestegg returns the last one
     NS_ENSURE_TRUE(mClusterByteRanges.IsEmpty(),
                    NS_ERROR_ALREADY_INITIALIZED);
     int clusterNum = 0;
     bool done = false;
+    uint64_t timestamp;
     do {
       mClusterByteRanges.AppendElement();
       r = nestegg_get_cue_point(mContext, clusterNum, maxOffset,
                                 &(mClusterByteRanges[clusterNum].mStart),
-                                &(mClusterByteRanges[clusterNum].mEnd));
+                                &(mClusterByteRanges[clusterNum].mEnd),
+                                &timestamp);
       if (r != 0) {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
       LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: Cluster [%d]: "
-                         "start [%d] end [%d]",
+                         "start [%lld] end [%lld], timestamp [%.2llfs]",
                          this, mDecoder, clusterNum,
                          mClusterByteRanges[clusterNum].mStart,
-                         mClusterByteRanges[clusterNum].mEnd));
+                         mClusterByteRanges[clusterNum].mEnd,
+                         timestamp/NS_PER_S));
+      mClusterByteRanges[clusterNum].mStartTime = timestamp/NS_PER_USEC;
       // Last cluster will have '-1' as end value
       if (mClusterByteRanges[clusterNum].mEnd == -1) {
         mClusterByteRanges[clusterNum].mEnd = (mCuesByteRange.mStart-1);
         done = true;
       } else {
         clusterNum++;
       }
     } while (!done);
@@ -918,25 +932,47 @@ nsresult WebMReader::GetBuffered(nsTimeR
 }
 
 void WebMReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
 {
   mBufferedState->NotifyDataArrived(aBuffer, aLength, aOffset);
 }
 
 #ifdef MOZ_DASH
+int64_t
+WebMReader::GetSubsegmentForSeekTime(int64_t aSeekToTime)
+{
+  NS_ENSURE_TRUE(0 <= aSeekToTime, -1);
+  // Check the first n-1 subsegments. End time is the start time of the next
+  // subsegment.
+  for (uint32_t i = 1; i < (mClusterByteRanges.Length()); i++) {
+    if (aSeekToTime < mClusterByteRanges[i].mStartTime) {
+      return i-1;
+    }
+  }
+  // Check the last subsegment. End time is the end time of the file.
+  NS_ASSERTION(mDecoder, "Decoder should not be null!");
+  if (aSeekToTime <= mDecoder->GetMediaDuration()) {
+    return mClusterByteRanges.Length()-1;
+  }
+
+  return (-1);
+}
 nsresult
 WebMReader::GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
 {
   NS_ENSURE_TRUE(mContext, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aByteRanges.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
   NS_ENSURE_FALSE(mClusterByteRanges.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_FALSE(mCuesByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
 
-  aByteRanges = mClusterByteRanges;
+  for (uint32_t i = 0; i < mClusterByteRanges.Length(); i++) {
+    aByteRanges.AppendElement();
+    aByteRanges[i] = mClusterByteRanges[i];
+  }
 
   return NS_OK;
 }
 
 void
 WebMReader::RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
                                       MediaDecoderReader* aNextReader)
 {
@@ -945,16 +981,17 @@ WebMReader::RequestSwitchAtSubsegment(in
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   // Only allow one switch at a time; ignore if one is already requested.
   if (mSwitchingCluster != -1) {
     return;
   }
   NS_ENSURE_TRUE((uint32_t)aSubsegmentIdx < mClusterByteRanges.Length(), );
   mSwitchingCluster = aSubsegmentIdx;
+  NS_ENSURE_TRUE(aNextReader, );
   NS_ENSURE_TRUE(aNextReader != this, );
   mNextReader = static_cast<WebMReader*>(aNextReader);
 }
 
 void
 WebMReader::RequestSeekToSubsegment(uint32_t aIdx)
 {
   NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
@@ -1023,10 +1060,8 @@ WebMReader::HasReachedSubsegment(uint32_
     mReachedSwitchAccessPoint = false;
     return true;
   }
   return false;
 }
 #endif /* MOZ_DASH */
 
 } // namespace mozilla
-
-
--- a/content/media/webm/WebMReader.h
+++ b/content/media/webm/WebMReader.h
@@ -176,16 +176,19 @@ public:
     mInitByteRange = aByteRange;
   }
 
   // Sets byte range for cue points, i.e. cluster offsets; used by DASH.
   void SetIndexByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
     mCuesByteRange = aByteRange;
   }
 
+  // Returns the index of the subsegment which contains the seek time.
+  int64_t GetSubsegmentForSeekTime(int64_t aSeekToTime) MOZ_OVERRIDE;
+
   // Returns list of ranges for cluster start and end offsets.
   nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
                                                                   MOZ_OVERRIDE;
 
   // Called by |DASHReader|::|PossiblySwitchVideoReaders| to check if this
   // reader has reached a switch access point and it's ok to switch readers.
   // Called on the decode thread.
   bool HasReachedSubsegment(uint32_t aSubsegmentIndex) MOZ_OVERRIDE;
@@ -299,17 +302,17 @@ private:
 #ifdef MOZ_DASH
   // Byte range for initialisation data; e.g. specified in DASH manifest.
   MediaByteRange mInitByteRange;
 
   // Byte range for cues; e.g. specified in DASH manifest.
   MediaByteRange mCuesByteRange;
 
   // Byte ranges for clusters; set internally, derived from cues.
-  nsTArray<MediaByteRange> mClusterByteRanges;
+  nsTArray<TimestampedMediaByteRange> mClusterByteRanges;
 
   // Pointer to the main |DASHReader|. Set in the constructor.
   DASHReader* mMainReader;
 
   // Index of the cluster to switch to. Monitor must be entered for write
   // access on all threads, read access off the decode thread.
   int32_t mSwitchingCluster;