Bug 734546: Add full byte range request ability to ChannelMediaResource r=cpearce
authorSteve Workman <sworkman@mozilla.com>
Sat, 29 Sep 2012 16:29:04 -0700
changeset 108775 6df63191884b8631984d92bef4b937ab084042cd
parent 108774 d95f2229451eb5934b73687ff1950fa3540b8ab8
child 108776 d882662a0bb6a753b34f9133c951c08def1a8851
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerscpearce
bugs734546
milestone18.0a1
Bug 734546: Add full byte range request ability to ChannelMediaResource r=cpearce
content/media/MediaResource.cpp
content/media/MediaResource.h
content/media/nsMediaDecoder.h
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -25,31 +25,52 @@
 #include "nsError.h"
 #include "nsICachingChannel.h"
 #include "nsURILoader.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Util.h" // for DebugOnly
 #include "nsContentUtils.h"
 #include "nsBlobProtocolHandler.h"
 
+#ifdef PR_LOGGING
+PRLogModuleInfo* gMediaResourceLog;
+#define LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \
+                             (msg, ##__VA_ARGS__))
+// Debug logging macro with object pointer and class name.
+#define CMLOG(msg, ...) \
+        LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
+#else
+#define LOG(msg, ...)
+#define CMLOG(msg, ...)
+#endif
+
 static const uint32_t HTTP_OK_CODE = 200;
 static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
 
 using namespace mozilla;
 
 ChannelMediaResource::ChannelMediaResource(nsMediaDecoder* aDecoder,
     nsIChannel* aChannel, nsIURI* aURI)
   : MediaResource(aDecoder, aChannel, aURI),
     mOffset(0), mSuspendCount(0),
     mReopenOnError(false), mIgnoreClose(false),
     mCacheStream(this),
     mLock("ChannelMediaResource.mLock"),
     mIgnoreResume(false),
-    mSeekingForMetadata(false)
+    mSeekingForMetadata(false),
+    mByteRangeDownloads(false),
+    mByteRangeFirstOpen(true),
+    mSeekOffsetMonitor("media.dashseekmonitor"),
+    mSeekOffset(-1)
 {
+#ifdef PR_LOGGING
+  if (!gMediaResourceLog) {
+    gMediaResourceLog = PR_NewLogModule("MediaResource");
+  }
+#endif
 }
 
 ChannelMediaResource::~ChannelMediaResource()
 {
   if (mListener) {
     // Kill its reference to us since we're going away
     mListener->Revoke();
   }
@@ -196,20 +217,67 @@ ChannelMediaResource::OnStartRequest(nsI
         if (ec == NS_OK && duration >= 0) {
           mDecoder->SetDuration(duration);
         }
       } else {
         mDecoder->SetInfinite(true);
       }
     }
 
-    if (mOffset > 0 && responseStatus == HTTP_OK_CODE) {
-      // If we get an OK response but we were seeking, we have to assume
-      // that seeking doesn't work. We also need to tell the cache that
-      // it's getting data for the start of the stream.
+    // Check response code for byte-range requests (seeking, chunk requests).
+    if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
+      // Byte range requests should get partial response codes and should
+      // accept ranges.
+      if (!acceptsRanges) {
+        CMLOG("Error! HTTP_PARTIAL_RESPONSE_CODE received but server says "
+              "range requests are not accepted! Channel[%p]", hc.get());
+        mDecoder->NetworkError();
+        CloseChannel();
+        return NS_OK;
+      }
+
+      // Parse Content-Range header.
+      int64_t rangeStart = 0;
+      int64_t rangeEnd = 0;
+      int64_t rangeTotal = 0;
+      rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
+      if (NS_FAILED(rv)) {
+        // Content-Range header text should be parse-able.
+        CMLOG("Error processing \'Content-Range' for "
+              "HTTP_PARTIAL_RESPONSE_CODE: rv[%x]channel [%p]", rv, hc.get());
+        mDecoder->NetworkError();
+        CloseChannel();
+        return NS_OK;
+      }
+
+      // Give some warnings if the ranges are unexpected.
+      // XXX These could be error conditions.
+      NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
+                       "response range start does not match request");
+      NS_WARN_IF_FALSE(mOffset == rangeStart,
+                       "response range start does not match current offset");
+      NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd,
+                       "response range end does not match request");
+      // Notify media cache about the length and start offset of data received.
+      // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
+      //       For now, tell the decoder that the stream is infinite.
+      if (rangeTotal != -1) {
+        mCacheStream.NotifyDataLength(rangeTotal);
+      } else {
+        mDecoder->SetInfinite(true);
+      }
+      mCacheStream.NotifyDataStarted(rangeStart);
+
+      mOffset = rangeStart;
+      acceptsRanges = true;
+    } else if (((mOffset > 0) || !mByteRange.IsNull())
+               && (responseStatus == HTTP_OK_CODE)) {
+      // If we get an OK response but we were seeking, or requesting a byte
+      // range, then we have to assume that seeking doesn't work. We also need
+      // to tell the cache that it's getting data for the start of the stream.
       mCacheStream.NotifyDataStarted(0);
       mOffset = 0;
 
       // The server claimed it supported range requests.  It lied.
       acceptsRanges = false;
     } else if (mOffset == 0 &&
                (responseStatus == HTTP_OK_CODE ||
                 responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
@@ -280,27 +348,82 @@ ChannelMediaResource::OnStartRequest(nsI
   // 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);
 
   return NS_OK;
 }
 
 nsresult
+ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
+                                              int64_t& aRangeStart,
+                                              int64_t& aRangeEnd,
+                                              int64_t& aRangeTotal)
+{
+  NS_ENSURE_ARG(aHttpChan);
+
+  nsAutoCString rangeStr;
+  nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
+                                             rangeStr);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
+
+  // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
+  int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
+  int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
+  int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
+
+  nsAutoCString aRangeStartText;
+  rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
+  aRangeStart = aRangeStartText.ToInteger64(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
+
+  nsAutoCString aRangeEndText;
+  rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
+  aRangeEnd = aRangeEndText.ToInteger64(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
+
+  nsAutoCString aRangeTotalText;
+  rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
+  if (aRangeTotalText[0] == '*') {
+    aRangeTotal = -1;
+  } else {
+    aRangeTotal = aRangeTotalText.ToInteger64(&rv);
+    NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  CMLOG("Received bytes [%d] to [%d] of [%d]",
+        aRangeStart, aRangeEnd, aRangeTotal);
+
+  return NS_OK;
+}
+
+nsresult
 ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   NS_ASSERTION(mSuspendCount == 0,
                "How can OnStopRequest fire while we're suspended?");
 
   {
     MutexAutoLock lock(mLock);
     mChannelStatistics.Stop(TimeStamp::Now());
   }
 
+  // If we were loading a byte range, notify decoder and return.
+  // Skip this for unterminated byte range requests, e.g. seeking for whole
+  // file downloads.
+  if (mByteRangeDownloads) {
+    mDecoder->NotifyDownloadEnded(aStatus);
+    return NS_OK;
+  }
+
   // Note that aStatus might have succeeded --- this might be a normal close
   // --- even in situations where the server cut us off because we were
   // suspended. So we need to "reopen on error" in that case too. The only
   // cases where we don't need to reopen are when *we* closed the stream.
   // But don't reopen if we need to seek and we don't think we can... that would
   // cause us to just re-read the stream, which would be really bad.
   if (mReopenOnError &&
       aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
@@ -356,16 +479,18 @@ ChannelMediaResource::CopySegmentToCache
                                          uint32_t *aWriteCount)
 {
   CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
 
   closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
 
   // Keep track of where we're up to
   closure->mResource->mOffset += aCount;
+  LOG("%p [ChannelMediaResource]: CopySegmentToCache new mOffset = %d",
+      closure->mResource, closure->mResource->mOffset);
   closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
                                                       closure->mPrincipal);
   *aWriteCount = aCount;
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
@@ -395,16 +520,47 @@ ChannelMediaResource::OnDataAvailable(ns
       return rv;
     NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
     count -= read;
   }
 
   return NS_OK;
 }
 
+/* |OpenByteRange|
+ * For terminated byte range requests, use this function.
+ * Callback is |nsBuiltinDecoder|::|NotifyByteRangeDownloaded|().
+ * See |CacheClientSeek| also.
+ */
+
+nsresult
+ChannelMediaResource::OpenByteRange(nsIStreamListener** aStreamListener,
+                                    MediaByteRange const & aByteRange)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+  mByteRangeDownloads = true;
+  mByteRange = aByteRange;
+
+  // OpenByteRange may be called multiple times; same URL, different ranges.
+  // For the first call using this URL, forward to Open for some init.
+  if (mByteRangeFirstOpen) {
+    mByteRangeFirstOpen = false;
+    return Open(aStreamListener);
+  }
+
+  // For subsequent calls, ensure channel is recreated with correct byte range.
+  CloseChannel();
+
+  nsresult rv = RecreateChannel();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return OpenChannel(aStreamListener);
+}
+
 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?");
@@ -476,19 +632,29 @@ nsresult ChannelMediaResource::OpenChann
 void ChannelMediaResource::SetupChannelHeaders()
 {
   // Always use a byte range request even if we're reading from the start
   // of the resource.
   // This enables us to detect if the stream supports byte range
   // requests, and therefore seeking, early.
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   if (hc) {
+    // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a
+    // complete file download.
     nsAutoCString rangeString("bytes=");
-    rangeString.AppendInt(mOffset);
+    if (!mByteRange.IsNull()) {
+      rangeString.AppendInt(mByteRange.mStart);
+      mOffset = mByteRange.mStart;
+    } else {
+      rangeString.AppendInt(mOffset);
+    }
     rangeString.Append("-");
+    if (!mByteRange.IsNull()) {
+      rangeString.AppendInt(mByteRange.mEnd);
+    }
     hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
 
     // Send Accept header for video and audio types only (Bug 489071)
     NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
     nsHTMLMediaElement* element = mDecoder->GetMediaElement();
     if (!element) {
       return;
     }
@@ -587,16 +753,22 @@ nsresult ChannelMediaResource::Read(char
 
   return mCacheStream.Read(aBuffer, aCount, aBytes);
 }
 
 nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
+  // Remember |aOffset|, because Media Cache may request a diff offset later.
+  if (mByteRangeDownloads) {
+    ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
+    mSeekOffset = aOffset;
+  }
+
   return mCacheStream.Seek(aWhence, aOffset);
 }
 
 void ChannelMediaResource::StartSeekingForMetadata()
 {
   mSeekingForMetadata = true;
 }
 
@@ -785,16 +957,61 @@ ChannelMediaResource::CacheClientSeek(in
   CloseChannel();
 
   if (aResume) {
     NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
     // No need to mess with the channel, since we're making a new one
     --mSuspendCount;
   }
 
+  // Note: For chunked downloads, e.g. DASH, we need to determine which chunk
+  // contains the requested offset, |mOffset|. This is either previously
+  // requested in |Seek| or updated to the most recent bytes downloaded.
+  // So the process below is:
+  //   1 - Query decoder for chunk containing desired offset, |mOffset|.
+  //       Return silently if the offset is not available; suggests decoder is
+  //         yet to get range information.
+  //       Return with NetworkError for all other errors.
+  //
+  //   2 - Adjust |mByteRange|.mStart to |aOffset|, requested by media cache.
+  //       For seeking, the media cache always requests the start of the cache
+  //       block, so we need to adjust the first chunk of a seek.
+  //       E.g. For "DASH-WebM On Demand" this means the first chunk after
+  //       seeking will most likely be larger than the subsegment (cluster).
+  //
+  //   3 - Call |OpenByteRange| requesting |mByteRange| bytes.
+
+  if (mByteRangeDownloads) {
+    // Query decoder for chunk containing desired offset.
+    // XXX Implement |nsDASHRepDecoder|::|GetByteRange| in future patch.
+    nsresult rv;
+    {
+      ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
+      // Ensure that media cache can only request an equal or smaller offset;
+      // it may be trying to include the start of a cache block.
+      NS_ENSURE_TRUE(aOffset <= mSeekOffset, NS_ERROR_ILLEGAL_VALUE);
+      rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
+      mSeekOffset = -1;
+    }
+    if (rv == NS_ERROR_NOT_AVAILABLE) {
+      // Assume decoder will request correct bytes when range information
+      // becomes available. Return silently.
+      return NS_OK;
+    } else if (NS_FAILED(rv) || mByteRange.IsNull()) {
+      // Decoder reported an error we don't want to handle here; just return.
+      mDecoder->NetworkError();
+      CloseChannel();
+      return rv;
+    }
+    // Media cache may decrease offset to start of cache data block.
+    // Adjust start of byte range accordingly.
+    mByteRange.mStart = mOffset = aOffset;
+    return OpenByteRange(nullptr, mByteRange);
+  }
+
   mOffset = aOffset;
 
   if (mSuspendCount > 0) {
     // Close the existing channel to force the channel to be recreated at
     // the correct offset upon resume.
     if (mChannel) {
       mIgnoreClose = true;
       CloseChannel();
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -3,17 +3,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(MediaResource_h_)
 #define MediaResource_h_
 
 #include "mozilla/Mutex.h"
 #include "mozilla/XPCOM.h"
+#include "mozilla/ReentrantMonitor.h"
 #include "nsIChannel.h"
+#include "nsIHttpChannel.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsMediaCache.h"
 #include "mozilla/Attributes.h"
 
@@ -106,16 +108,22 @@ public:
   {
     NS_ASSERTION(mStart < mEnd, "Range should end after start!");
   }
 
   bool IsNull() const {
     return mStart == 0 && mEnd == 0;
   }
 
+  // Clears byte range values.
+  void Clear() {
+    mStart = 0;
+    mEnd = 0;
+  }
+
   int64_t mStart, mEnd;
 };
 
 /**
  * Provides a thread-safe, seek/read interface to resources
  * loaded from a URI. Uses nsMediaCache 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
@@ -278,16 +286,27 @@ public:
 
   /**
    * Open the stream. This creates a stream listener and returns it in
    * aStreamListener; this listener needs to be notified of incoming data.
    */
   virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
 
   /**
+   * Open the stream using a specific byte range only. Creates a stream
+   * listener and returns it in aStreamListener; this listener needs to be
+   * notified of incoming data. Byte range is specified in aByteRange.
+   */
+  virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
+                                 MediaByteRange const &aByteRange)
+  {
+    return Open(aStreamListener);
+  }
+
+  /**
    * 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;
 
 protected:
   MediaResource(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
@@ -359,16 +378,18 @@ public:
   nsresult CacheClientSeek(int64_t aOffset, bool aResume);
   // Suspend the current load since data is currently not wanted
   nsresult CacheClientSuspend();
   // Resume the current load since data is wanted again
   nsresult CacheClientResume();
 
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
+  virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
+                                 MediaByteRange const & aByteRange);
   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(nsMediaDecoder* aDecoder);
@@ -430,16 +451,24 @@ protected:
   // if possible. Main thread only.
   nsresult OpenChannel(nsIStreamListener** aStreamListener);
   nsresult RecreateChannel();
   // Add headers to HTTP request. Main thread only.
   void SetupChannelHeaders();
   // Closes the channel. Main thread only.
   void CloseChannel();
 
+  // Parses 'Content-Range' header and returns results via parameters.
+  // Returns error if header is not available, values are not parse-able or
+  // values are out of range.
+  nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
+                                   int64_t& aRangeStart,
+                                   int64_t& aRangeEnd,
+                                   int64_t& aRangeTotal);
+
   void DoNotifyDataReceived();
 
   static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream,
                                       void *aClosure,
                                       const char *aFromSegment,
                                       uint32_t aToOffset,
                                       uint32_t aCount,
                                       uint32_t *aWriteCount);
@@ -475,13 +504,28 @@ protected:
 
   // True if we couldn't suspend the stream and we therefore don't want
   // to resume later. This is usually due to the channel not being in the
   // isPending state at the time of the suspend request.
   bool mIgnoreResume;
 
   // True if we are seeking to get the real duration of the file.
   bool mSeekingForMetadata;
+
+  // Start and end offset of the bytes to be requested.
+  MediaByteRange mByteRange;
+
+  // True if resource was opened with a byte rage request.
+  bool mByteRangeDownloads;
+
+  // Set to false once first byte range request has been made.
+  bool mByteRangeFirstOpen;
+
+  // For byte range requests, set to the offset requested in |Seek|.
+  // Used in |CacheClientSeek| to find the originally requested byte range.
+  // Read/Write on multiple threads; use |mSeekMonitor|.
+  ReentrantMonitor mSeekOffsetMonitor;
+  int64_t mSeekOffset;
 };
 
 }
 
 #endif
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -15,16 +15,17 @@ class nsHTMLMediaElement;
 class nsIStreamListener;
 class nsTimeRanges;
 class nsIMemoryReporter;
 class nsIPrincipal;
 class nsITimer;
 
 namespace mozilla {
 class MediaResource;
+class MediaByteRange;
 }
 
 // The size to use for audio data frames in MozAudioAvailable events.
 // This value is per channel, and is chosen to give ~43 fps of events,
 // for example, 44100 with 2 channels, 2*1024 = 2048.
 static const uint32_t FRAMEBUFFER_LENGTH_PER_CHANNEL = 1024;
 
 // The total size of the framebuffer used for MozAudioAvailable events
@@ -34,16 +35,17 @@ static const uint32_t FRAMEBUFFER_LENGTH
 
 // All methods of nsMediaDecoder must be called from the main thread only
 // with the exception of GetVideoFrameContainer and GetStatistics,
 // which can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
 public:
   typedef mozilla::MediaResource MediaResource;
+  typedef mozilla::MediaByteRange MediaByteRange;
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
   typedef mozilla::SourceMediaStream SourceMediaStream;
   typedef mozilla::ProcessedMediaStream ProcessedMediaStream;
   typedef mozilla::MediaInputPort MediaInputPort;
   typedef mozilla::MainThreadMediaStreamListener MainThreadMediaStreamListener;
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
@@ -68,16 +70,24 @@ public:
 
   // Return the time position in the video stream being
   // played measured in seconds.
   virtual double GetCurrentTime() = 0;
 
   // Seek to the time position in (seconds) from the start of the video.
   virtual nsresult Seek(double aTime) = 0;
 
+  // Enables decoders to supply an enclosing byte range for a seek offset.
+  // E.g. used by ChannelMediaResource to download a whole cluster for
+  // DASH-WebM.
+  virtual nsresult GetByteRangeForSeek(int64_t const aOffset,
+                                       MediaByteRange &aByteRange) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // Called by the element when the playback rate has been changed.
   // Adjust the speed of the playback, optionally with pitch correction,
   // when this is called.
   virtual nsresult PlaybackRateChanged() = 0;
 
   // Return the duration of the video in seconds.
   virtual double GetDuration() = 0;