Bug 976037 - Implement an eviction algorithm for media source extensions - r=kinetik
authorcajbir <cajbir@cd.pn>
Fri, 28 Feb 2014 13:54:48 +1300
changeset 171473 9b295844b85b57a2141491cf26f516a4e2e55e86
parent 171472 d0b4c5c28fb0c42fd13cb91a6fce284b8ef169e1
child 171474 416792ed8ef5377d4d521da2bfb1f0bdfc52aa1e
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerskinetik
bugs976037
milestone30.0a1
Bug 976037 - Implement an eviction algorithm for media source extensions - r=kinetik
content/media/mediasource/MediaSource.cpp
content/media/mediasource/MediaSource.h
content/media/mediasource/MediaSourceDecoder.cpp
content/media/mediasource/MediaSourceDecoder.h
content/media/mediasource/SourceBuffer.cpp
content/media/mediasource/SourceBuffer.h
content/media/mediasource/SourceBufferList.cpp
content/media/mediasource/SourceBufferList.h
content/media/mediasource/SourceBufferResource.cpp
content/media/mediasource/SourceBufferResource.h
content/media/mediasource/SubBufferDecoder.h
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -366,16 +366,24 @@ MediaSource::GetParentObject() const
 }
 
 JSObject*
 MediaSource::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return MediaSourceBinding::Wrap(aCx, aScope, this);
 }
 
+void
+MediaSource::NotifyEvicted(double aStart, double aEnd)
+{
+  // Cycle through all SourceBuffers and tell them to evict data in
+  // the given range.
+  mSourceBuffers->Evict(aStart, aEnd);
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED_2(MediaSource, nsDOMEventTargetHelper,
                                      mSourceBuffers, mActiveSourceBuffers)
 
 NS_IMPL_ADDREF_INHERITED(MediaSource, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(MediaSource, nsDOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaSource)
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::MediaSource)
--- a/content/media/mediasource/MediaSource.h
+++ b/content/media/mediasource/MediaSource.h
@@ -80,16 +80,21 @@ public:
   void SetReadyState(MediaSourceReadyState aState);
 
  // Used by SourceBuffer to call CreateSubDecoder.
   MediaSourceDecoder* GetDecoder()
   {
     return mDecoder;
   }
 
+  // Called by SourceBuffers to notify this MediaSource that data has
+  // been evicted from the buffered data. The start and end times
+  // that were evicted are provided.
+  void NotifyEvicted(double aStart, double aEnd);
+
 private:
   explicit MediaSource(nsPIDOMWindow* aWindow);
 
   friend class AsyncEventRunner<MediaSource>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   void DurationChange(double aNewDuration, ErrorResult& aRv);
--- a/content/media/mediasource/MediaSourceDecoder.cpp
+++ b/content/media/mediasource/MediaSourceDecoder.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "mozilla/mozalloc.h"
 #include "nsISupports.h"
 #include "prlog.h"
 #include "MediaSource.h"
 #include "SubBufferDecoder.h"
 #include "SourceBufferResource.h"
+#include "VideoUtils.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaSourceLog;
 #define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
@@ -233,9 +234,17 @@ MediaSourceReader::ReadMetadata(MediaInf
       decoder->SetAudioReader(reader);
     }
   }
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
+double
+MediaSourceDecoder::GetMediaSourceDuration()
+{
+  return mMediaSource ?
+           mMediaSource->Duration() :
+           mDuration / static_cast<double>(USECS_PER_S);
+}
+
 } // namespace mozilla
--- a/content/media/mediasource/MediaSourceDecoder.h
+++ b/content/media/mediasource/MediaSourceDecoder.h
@@ -74,16 +74,20 @@ public:
     return mVideoReader;
   }
 
   MediaDecoderReader* GetAudioReader()
   {
     return mAudioReader;
   }
 
+  // Returns the duration in seconds as provided by the attached MediaSource.
+  // If no MediaSource is attached, returns the duration tracked by the decoder.
+  double GetMediaSourceDuration();
+
 private:
   dom::MediaSource* mMediaSource;
 
   nsTArray<nsRefPtr<SubBufferDecoder> > mDecoders;
   nsTArray<MediaDecoderReader*> mReaders; // Readers owned by Decoders.
 
   MediaDecoderReader* mVideoReader;
   MediaDecoderReader* mAudioReader;
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -91,16 +91,33 @@ SubBufferDecoder::SetTransportSeekable(b
 }
 
 layers::ImageContainer*
 SubBufferDecoder::GetImageContainer()
 {
   return mParentDecoder->GetImageContainer();
 }
 
+int64_t
+SubBufferDecoder::ConvertToByteOffset(double aTime)
+{
+  // Uses a conversion based on (aTime/duration) * length.  For the
+  // purposes of eviction this should be adequate since we have the
+  // byte threshold as well to ensure data actually gets evicted and
+  // we ensure we don't evict before the current playable point.
+  double duration = mParentDecoder->GetMediaSourceDuration();
+  if (duration <= 0.0 || IsNaN(duration)) {
+    return -1;
+  }
+  int64_t length = GetResource()->GetLength();
+  MOZ_ASSERT(length > 0);
+  int64_t offset = (aTime / duration) * length;
+  return offset;
+}
+
 namespace dom {
 
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
@@ -325,17 +342,66 @@ SourceBuffer::AppendData(const uint8_t* 
   LOG(PR_LOG_DEBUG, ("%p Append(ArrayBuffer=%u)", this, aLength));
   StartUpdating();
   // XXX: For future reference: NDA call must run on the main thread.
   mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
                               aLength,
                               mDecoder->GetResource()->GetLength());
   // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
   mDecoder->GetResource()->AppendData(aData, aLength);
+
+  // Eviction uses a byte threshold. If the buffer is greater than the
+  // number of bytes then data is evicted. The time range for this
+  // eviction is reported back to the media source. It will then
+  // evict data before that range across all SourceBuffer's it knows
+  // about.
+  const int evict_threshold = 1000000;
+  bool evicted = mDecoder->GetResource()->EvictData(evict_threshold);
+  if (evicted) {
+    double start = 0.0;
+    double end = 0.0;
+    GetBufferedStartEndTime(&start, &end);
+
+    // We notify that we've evicted from the time range 0 through to
+    // the current start point.
+    mMediaSource->NotifyEvicted(0.0, start);
+  }
   StopUpdating();
+
+  // Schedule the state machine thread to ensure playback starts
+  // if required when data is appended.
+  mMediaSource->GetDecoder()->ScheduleStateMachineThread();
+}
+
+void
+SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd)
+{
+  nsRefPtr<TimeRanges> ranges = new TimeRanges();
+  mDecoder->GetBuffered(ranges);
+  ranges->Normalize();
+  int length = ranges->Length();
+  ErrorResult rv;
+
+  if (aStart) {
+    *aStart = length > 0 ? ranges->Start(0, rv) : 0.0;
+  }
+
+  if (aEnd) {
+    *aEnd = length > 0 ? ranges->End(length - 1, rv) : 0.0;
+  }
+}
+
+void
+SourceBuffer::Evict(double aStart, double aEnd)
+{
+  // Need to map time to byte offset then evict
+  int64_t end = mDecoder->ConvertToByteOffset(aEnd);
+  if (end > 0) {
+    mDecoder->GetResource()->EvictBefore(end);
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_1(SourceBuffer, nsDOMEventTargetHelper, mMediaSource)
 
 NS_IMPL_ADDREF_INHERITED(SourceBuffer, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SourceBuffer, nsDOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer)
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -100,29 +100,36 @@ public:
   void Detach();
   bool IsAttached() const
   {
     return mMediaSource != nullptr;
   }
 
   void Ended();
 
+  // Evict data in the source buffer in the given time range.
+  void Evict(double aStart, double aEnd);
+
 private:
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Update mUpdating and fire the appropriate events.
   void StartUpdating();
   void StopUpdating();
   void AbortUpdating();
 
   // Shared implementation of AppendBuffer overloads.
   void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
 
+  // Provide the minimum start time and maximum end time that is available
+  // in the data buffered by this SourceBuffer.
+  void GetBufferedStartEndTime(double* aStart, double* aEnd);
+
   nsRefPtr<MediaSource> mMediaSource;
 
   nsRefPtr<SubBufferDecoder> mDecoder;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;
 
   double mTimestampOffset;
--- a/content/media/mediasource/SourceBufferList.cpp
+++ b/content/media/mediasource/SourceBufferList.cpp
@@ -99,16 +99,24 @@ SourceBufferList::Remove(double aStart, 
     mSourceBuffers[i]->Remove(aStart, aEnd, aRv);
     if (aRv.Failed()) {
       return;
     }
   }
 }
 
 void
+SourceBufferList::Evict(double aStart, double aEnd)
+{
+  for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
+    mSourceBuffers[i]->Evict(aStart, aEnd);
+  }
+}
+
+void
 SourceBufferList::Ended()
 {
   for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
     mSourceBuffers[i]->Ended();
   }
 }
 
 void
--- a/content/media/mediasource/SourceBufferList.h
+++ b/content/media/mediasource/SourceBufferList.h
@@ -68,16 +68,19 @@ public:
 
   // Calls Remove(aStart, aEnd) on each SourceBuffer in the list.  Aborts on
   // first error, with result returned in aRv.
   void Remove(double aStart, double aEnd, ErrorResult& aRv);
 
   // Mark all SourceBuffers input buffers as ended.
   void Ended();
 
+  // Evicts data for the given time range from each SourceBuffer in the list.
+  void Evict(double aStart, double aEnd);
+
 private:
   friend class AsyncEventRunner<SourceBufferList>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   nsRefPtr<MediaSource> mMediaSource;
   nsTArray<nsRefPtr<SourceBuffer> > mSourceBuffers;
 };
--- a/content/media/mediasource/SourceBufferResource.cpp
+++ b/content/media/mediasource/SourceBufferResource.cpp
@@ -41,17 +41,19 @@ SourceBufferResource::Close()
 }
 
 nsresult
 SourceBufferResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
   ReentrantMonitorAutoEnter mon(mMonitor);
   bool blockingRead = !!aBytes;
 
-  while (blockingRead && !mEnded && mOffset + aCount > GetLength()) {
+  while (blockingRead &&
+         !mEnded &&
+         mOffset + aCount > static_cast<uint64_t>(GetLength())) {
     LOG(PR_LOG_DEBUG, ("%p SBR::Read waiting for data", this));
     mon.Wait();
   }
 
   uint32_t available = GetLength() - mOffset;
   uint32_t count = std::min(aCount, available);
   if (!PR_GetEnv("MOZ_QUIET")) {
     LOG(PR_LOG_DEBUG, ("%p SBR::Read aCount=%u length=%u offset=%u "
@@ -60,17 +62,17 @@ SourceBufferResource::Read(char* aBuffer
                        blockingRead, mEnded));
   }
   if (available == 0) {
     LOG(PR_LOG_DEBUG, ("%p SBR::Read EOF", this));
     *aBytes = 0;
     return NS_OK;
   }
 
-  memcpy(aBuffer, &mInputBuffer[mOffset], count);
+  mInputBuffer.CopyData(mOffset, count, aBuffer);
   *aBytes = count;
   mOffset += count;
   return NS_OK;
 }
 
 nsresult
 SourceBufferResource::ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
@@ -119,21 +121,36 @@ SourceBufferResource::ReadFromCache(char
   ReentrantMonitorAutoEnter mon(mMonitor);
   nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   if (NS_FAILED(rv)) {
     return rv;
   }
   return Read(aBuffer, aCount, nullptr);
 }
 
+bool
+SourceBufferResource::EvictData(uint32_t aThreshold)
+{
+  return mInputBuffer.Evict(mOffset, aThreshold);
+}
+
+void
+SourceBufferResource::EvictBefore(uint64_t aOffset)
+{
+  // If aOffset is past the current playback offset we don't evict.
+  if (aOffset < mOffset) {
+    mInputBuffer.Evict(aOffset, 0);
+  }
+}
+
 void
 SourceBufferResource::AppendData(const uint8_t* aData, uint32_t aLength)
 {
   ReentrantMonitorAutoEnter mon(mMonitor);
-  mInputBuffer.AppendElements(aData, aLength);
+  mInputBuffer.PushBack(new ResourceItem(aData, aLength));
   mon.NotifyAll();
 }
 
 void
 SourceBufferResource::Ended()
 {
   ReentrantMonitorAutoEnter mon(mMonitor);
   mEnded = true;
--- a/content/media/mediasource/SourceBufferResource.h
+++ b/content/media/mediasource/SourceBufferResource.h
@@ -2,41 +2,186 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #ifndef MOZILLA_SOURCEBUFFERRESOURCE_H_
 #define MOZILLA_SOURCEBUFFERRESOURCE_H_
 
+#include <algorithm>
 #include "MediaCache.h"
 #include "MediaResource.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsIPrincipal.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
+#include "nsDeque.h"
 #include "nscore.h"
 
 class nsIStreamListener;
 
 namespace mozilla {
 
 class MediaDecoder;
 
 namespace dom {
 
 class SourceBuffer;
 
 }  // namespace dom
 
 class SourceBufferResource MOZ_FINAL : public MediaResource
 {
+private:
+  // A SourceBufferResource has a queue containing the data
+  // that is appended to it. The queue holds instances of
+  // ResourceItem which is an array of the bytes. Appending
+  // data to the SourceBufferResource pushes this onto the
+  // queue. As items are played they are taken off the front
+  // of the queue.
+  // Data is evicted once it reaches a size threshold. This
+  // pops the items off the front of the queue and deletes it.
+  // If an eviction happens then the MediaSource is notified
+  // (done in SourceBuffer::AppendData) which then requests
+  // all SourceBuffers to evict data up to approximately
+  // the same timepoint.
+  struct ResourceItem {
+    ResourceItem(uint8_t const* aData, uint32_t aSize) {
+      mData.AppendElements(aData, aSize);
+    }
+    nsTArray<uint8_t> mData;
+  };
+
+  class ResourceQueueDeallocator : public nsDequeFunctor {
+    virtual void* operator() (void* anObject) {
+      delete static_cast<ResourceItem*>(anObject);
+      return nullptr;
+    }
+  };
+
+  class ResourceQueue : private nsDeque {
+  private:
+    // Logical offset into the resource of the first element
+    // in the queue.
+    uint64_t mOffset;
+
+  public:
+    ResourceQueue() :
+      nsDeque(new ResourceQueueDeallocator()),
+      mOffset(0)
+    {
+    }
+
+    // Clears all items from the queue
+    inline void Clear() {
+      return nsDeque::Erase();
+    }
+
+    // Returns the number of items in the queue
+    inline uint32_t GetSize() {
+      return nsDeque::GetSize();
+    }
+
+    // Returns the logical byte offset of the start of the data.
+    inline uint64_t GetOffset() {
+      return mOffset;
+    }
+
+    inline ResourceItem* ResourceAt(uint32_t aIndex) {
+      return static_cast<ResourceItem*>(nsDeque::ObjectAt(aIndex));
+    }
+
+    // Returns the length of all items in the queue plus the offset.
+    // This is the logical length of the resource.
+    inline uint64_t GetLength() {
+      uint64_t s = mOffset;
+      for (uint32_t i = 0; i < GetSize(); ++i) {
+        ResourceItem* item = ResourceAt(i);
+        s += item->mData.Length();
+      }
+      return s;
+    }
+
+    // Returns the index of the resource that contains the given
+    // logical offset. aResourceOffset will contain the offset into
+    // the resource at the given index returned if it is not null.  If
+    // no such resource exists, returns GetSize() and aOffset is
+    // untouched.
+    inline uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset) {
+      uint64_t offset = mOffset;
+      for (uint32_t i = 0; i < GetSize(); ++i) {
+        ResourceItem* item = ResourceAt(i);
+        // If the item contains the start of the offset we want to
+        // break out of the loop.
+        if (item->mData.Length() + offset > aOffset) {
+          if (aResourceOffset) {
+            *aResourceOffset = aOffset - offset;
+          }
+          return i;
+        }
+        offset += item->mData.Length();
+      }
+      return GetSize();
+    }
+
+    // Copies aCount bytes from aOffset in the queue into aDest.
+    inline void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest) {
+      uint32_t offset = 0;
+      uint32_t start = GetAtOffset(aOffset, &offset);
+      uint32_t end = std::min(GetAtOffset(aOffset + aCount, nullptr) + 1, GetSize());
+      for (uint32_t i = start; i < end; ++i) {
+        ResourceItem* item = ResourceAt(i);
+        uint32_t bytes = std::min(aCount, item->mData.Length() - offset);
+        if (bytes != 0) {
+          memcpy(aDest, &item->mData[offset], bytes);
+          offset = 0;
+          aCount -= bytes;
+          aDest += bytes;
+        }
+      }
+    }
+
+    inline void PushBack(ResourceItem* aItem) {
+      nsDeque::Push(aItem);
+    }
+
+    inline void PushFront(ResourceItem* aItem) {
+      nsDeque::PushFront(aItem);
+    }
+
+    inline ResourceItem* PopBack() {
+      return static_cast<ResourceItem*>(nsDeque::Pop());
+    }
+
+    inline ResourceItem* PopFront() {
+      return static_cast<ResourceItem*>(nsDeque::PopFront());
+    }
+
+    // Evict data in queue if the total queue size is greater than
+    // aThreshold past the offset. Returns true if some data was
+    // actually evicted.
+    inline bool Evict(uint64_t aOffset, uint32_t aThreshold) {
+      bool evicted = false;
+      while (GetLength() - mOffset > aThreshold) {
+        ResourceItem* item = ResourceAt(0);
+        if (item->mData.Length() + mOffset > aOffset) {
+          break;
+        }
+        mOffset += item->mData.Length();
+        delete PopFront();
+        evicted = true;
+      }
+      return evicted;
+    }
+  };
+
 public:
   SourceBufferResource(nsIPrincipal* aPrincipal,
                        const nsACString& aType);
   ~SourceBufferResource();
 
   virtual nsresult Close() MOZ_OVERRIDE;
   virtual void Suspend(bool aCloseImmediately) MOZ_OVERRIDE {}
   virtual void Resume() MOZ_OVERRIDE {}
@@ -57,48 +202,57 @@ public:
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE;
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE;
   virtual void StartSeekingForMetadata() MOZ_OVERRIDE { }
   virtual void EndSeekingForMetadata() MOZ_OVERRIDE {}
   virtual int64_t Tell() MOZ_OVERRIDE { return mOffset; }
   virtual void Pin() MOZ_OVERRIDE {}
   virtual void Unpin() MOZ_OVERRIDE {}
   virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { return 0; }
-  virtual int64_t GetLength() MOZ_OVERRIDE { return mInputBuffer.Length(); }
+  virtual int64_t GetLength() MOZ_OVERRIDE { return mInputBuffer.GetLength(); }
   virtual int64_t GetNextCachedData(int64_t aOffset) MOZ_OVERRIDE { return aOffset; }
   virtual int64_t GetCachedDataEnd(int64_t aOffset) MOZ_OVERRIDE { return GetLength(); }
-  virtual bool IsDataCachedToEndOfResource(int64_t aOffset) MOZ_OVERRIDE { return true; }
+  virtual bool IsDataCachedToEndOfResource(int64_t aOffset) MOZ_OVERRIDE { return false; }
   virtual bool IsSuspendedByCache() MOZ_OVERRIDE { return false; }
   virtual bool IsSuspended() MOZ_OVERRIDE { return false; }
   virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) MOZ_OVERRIDE;
   virtual bool IsTransportSeekable() MOZ_OVERRIDE { return true; }
   virtual nsresult Open(nsIStreamListener** aStreamListener) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
   virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) MOZ_OVERRIDE
   {
-    aRanges.AppendElement(MediaByteRange(0, GetLength()));
+    aRanges.AppendElement(MediaByteRange(mInputBuffer.GetOffset(),
+                                         mInputBuffer.GetLength()));
     return NS_OK;
   }
 
   virtual const nsCString& GetContentType() const MOZ_OVERRIDE { return mType; }
 
   // Used by SourceBuffer.
   void AppendData(const uint8_t* aData, uint32_t aLength);
   void Ended();
+  // Remove data from resource if it holds more than the threshold
+  // number of bytes. Returns true if some data was evicted.
+  bool EvictData(uint32_t aThreshold);
+
+  // Remove data from resource before the given offset.
+  void EvictBefore(uint64_t aOffset);
 
 private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   const nsAutoCString mType;
 
   // Provides synchronization between SourceBuffers and InputAdapters.
   // Protects all of the member variables below.  Read() will await a
   // Notify() (from Seek, AppendData, Ended, or Close) when insufficient
   // data is available in mData.
   ReentrantMonitor mMonitor;
-  nsTArray<uint8_t> mInputBuffer;
 
-  int64_t mOffset;
+  // The buffer holding resource data is a queue of ResourceItem's.
+  ResourceQueue mInputBuffer;
+
+  uint64_t mOffset;
   bool mClosed;
   bool mEnded;
 };
 
 } // namespace mozilla
 #endif /* MOZILLA_SOURCEBUFFERRESOURCE_H_ */
--- a/content/media/mediasource/SubBufferDecoder.h
+++ b/content/media/mediasource/SubBufferDecoder.h
@@ -49,16 +49,20 @@ public:
   }
 
   nsresult GetBuffered(dom::TimeRanges* aBuffered)
   {
     // XXX: Need mStartTime (from StateMachine) instead of passing 0.
     return mReader->GetBuffered(aBuffered, 0);
   }
 
+  // Given a time convert it into an approximate byte offset from the
+  // cached data. Returns -1 if no such value is computable.
+  int64_t ConvertToByteOffset(double aTime);
+
 private:
   MediaSourceDecoder* mParentDecoder;
   nsAutoPtr<MediaDecoderReader> mReader;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_SUBBUFFERDECODER_H_ */