Bug 1558364 - Add MediaSpan and use it for TrackBuffersManager::mInputBuffer. r=jya
☠☠ backed out by a2930bb79701 ☠ ☠
authorChris Pearce <cpearce@mozilla.com>
Fri, 14 Jun 2019 00:31:02 +0000
changeset 478793 2e64d8db2b4b630e990903a57bc75c0bfbaca237
parent 478792 1eba4a29505b3a57006a1325fa64d8d5aecc2682
child 478794 098ce3586133fe57aa1d53fe4bdf541b26243563
push id36152
push userbtara@mozilla.com
push dateFri, 14 Jun 2019 10:00:22 +0000
treeherdermozilla-central@6c5b432d7460 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1558364
milestone69.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 1558364 - Add MediaSpan and use it for TrackBuffersManager::mInputBuffer. r=jya As seen in this profile of a Twitch replay: https://perfht.ml/2K9Ydb3 we can often end up spending time in TrackBuffersManager::CodedFrameProcessing() shaving off bytes from the front off TrackBuffersManager::mInputBuffer. This requires all the remaining bytes to be memmove'd down to the start of this array. Sometimes we have close to 1MB in that buffer, and when we're just trying to consume a few hundred bytes, that becomes high overhead. So intead of using this "slice off, shuffle down" approach change TrackBuffersManager::mInputBuffer to be a new type MediaSpan, which maintains a RefPtr to a MediaByteBuffer and a span defining the subregion of the buffer we care about. This means the RemoveElementsAt(0,N) operation becomes basically free, and we can eliminate a few other copies we were doing as well. Differential Revision: https://phabricator.services.mozilla.com/D34661
dom/media/BufferReader.h
dom/media/MediaSpan.h
dom/media/gtest/TestMediaSpan.cpp
dom/media/gtest/moz.build
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/ContainerParser.h
dom/media/mediasource/ResourceQueue.cpp
dom/media/mediasource/ResourceQueue.h
dom/media/mediasource/SourceBufferResource.cpp
dom/media/mediasource/SourceBufferResource.h
dom/media/mediasource/TrackBuffersManager.cpp
dom/media/mediasource/TrackBuffersManager.h
dom/media/mediasource/gtest/TestContainerParser.cpp
dom/media/moz.build
--- a/dom/media/BufferReader.h
+++ b/dom/media/BufferReader.h
@@ -4,16 +4,17 @@
 
 #ifndef BUFFER_READER_H_
 #define BUFFER_READER_H_
 
 #include "mozilla/EndianUtils.h"
 #include "nscore.h"
 #include "nsTArray.h"
 #include "MediaData.h"
+#include "MediaSpan.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Result.h"
 
 namespace mozilla {
 
 extern mozilla::LazyLogModule gMP4MetadataLog;
 
 class MOZ_RAII BufferReader {
@@ -29,16 +30,20 @@ class MOZ_RAII BufferReader {
   explicit BufferReader(const nsTArray<uint8_t>& aData)
       : mPtr(aData.Elements()),
         mRemaining(aData.Length()),
         mLength(aData.Length()) {}
   explicit BufferReader(const mozilla::MediaByteBuffer* aData)
       : mPtr(aData->Elements()),
         mRemaining(aData->Length()),
         mLength(aData->Length()) {}
+  explicit BufferReader(const mozilla::MediaSpan& aData)
+      : mPtr(aData.Elements()),
+        mRemaining(aData.Length()),
+        mLength(aData.Length()) {}
 
   void SetData(const nsTArray<uint8_t>& aData) {
     MOZ_ASSERT(!mPtr && !mRemaining);
     mPtr = aData.Elements();
     mRemaining = aData.Length();
     mLength = mRemaining;
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaSpan.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#if !defined(MediaSpan_h)
+#  define MediaSpan_h
+
+#  include "MediaData.h"
+#  include "mozilla/RefPtr.h"
+#  include "mozilla/Span.h"
+
+namespace mozilla {
+
+// A MediaSpan wraps a MediaByteBuffer and exposes a slice/span, or subregion,
+// of the buffer. This allows you to slice off a logical subregion without
+// needing to reallocate a new buffer to hold it. You can also append to a
+// MediaSpan without affecting other MediaSpans referencing the same buffer
+// (the MediaSpan receiving the append will allocate a new buffer to store its
+// result if necessary, to ensure other MediaSpans referencing its original
+// buffer are unaffected). Note there are no protections here that something
+// other than MediaSpans doesn't modify the underlying MediaByteBuffer while
+// a MediaSpan is alive.
+class MediaSpan {
+ public:
+  ~MediaSpan() = default;
+
+  explicit MediaSpan(const MediaSpan& aOther) = default;
+
+  MediaSpan(MediaSpan&& aOther) noexcept = default;
+
+  explicit MediaSpan(const RefPtr<MediaByteBuffer>& aBuffer)
+      : mBuffer(aBuffer), mStart(0), mLength(aBuffer ? aBuffer->Length() : 0) {
+    MOZ_DIAGNOSTIC_ASSERT(mBuffer);
+  }
+
+  explicit MediaSpan(MediaByteBuffer* aBuffer)
+      : mBuffer(aBuffer), mStart(0), mLength(aBuffer ? aBuffer->Length() : 0) {
+    MOZ_DIAGNOSTIC_ASSERT(mBuffer);
+  }
+
+  MediaSpan& operator=(const MediaSpan& aOther) = default;
+
+  static MediaSpan WithCopyOf(const RefPtr<MediaByteBuffer>& aBuffer) {
+    RefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(aBuffer->Length());
+    buffer->AppendElements(*aBuffer);
+    return MediaSpan(buffer);
+  }
+
+  bool IsEmpty() const { return Length() == 0; }
+
+  // Note: It's unsafe to store the pointer returned by this function, as an
+  // append operation could cause the wrapped MediaByteBuffer to be
+  // reallocated, invalidating pointers previously returned by this function.
+  const uint8_t* Elements() const {
+    MOZ_DIAGNOSTIC_ASSERT(mStart < mBuffer->Length());
+    return mBuffer->Elements() + mStart;
+  }
+
+  size_t Length() const { return mLength; }
+
+  uint8_t operator[](size_t aIndex) const {
+    MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
+    return (*mBuffer)[mStart + aIndex];
+  }
+
+  bool Append(MediaByteBuffer* aBuffer) {
+    if (!aBuffer) {
+      return true;
+    }
+    if (mStart + mLength < mBuffer->Length()) {
+      // This MediaSpan finishes before the end of its buffer. The buffer
+      // could be shared with another MediaSpan. So we can't just append to
+      // the underlying buffer without risking damaging other MediaSpans' data.
+      // So we must reallocate a new buffer, copy our old data into it, and
+      // append the new data into it.
+      RefPtr<MediaByteBuffer> buffer =
+          new MediaByteBuffer(mLength + aBuffer->Length());
+      if (!buffer->AppendElements(Elements(), Length()) ||
+          !buffer->AppendElements(*aBuffer)) {
+        return false;
+      }
+      mBuffer = buffer;
+      mLength += aBuffer->Length();
+      return true;
+    }
+    if (!mBuffer->AppendElements(*aBuffer)) {
+      return false;
+    }
+    mLength += aBuffer->Length();
+    return true;
+  }
+
+  // Returns a new MediaSpan, spanning from the start of this span,
+  // up until aEnd.
+  MediaSpan To(size_t aEnd) const {
+    MOZ_DIAGNOSTIC_ASSERT(aEnd <= Length());
+    return MediaSpan(mBuffer, mStart, aEnd);
+  }
+
+  // Returns a new MediaSpan, spanning from aStart bytes offset from
+  // the start of this span, until the end of this span.
+  MediaSpan From(size_t aStart) const {
+    MOZ_DIAGNOSTIC_ASSERT(aStart <= Length());
+    return MediaSpan(mBuffer, mStart + aStart, Length() - aStart);
+  }
+
+  void RemoveFront(size_t aNumBytes) {
+    MOZ_DIAGNOSTIC_ASSERT(aNumBytes <= Length());
+    mStart += aNumBytes;
+    mLength -= aNumBytes;
+  }
+
+  MediaByteBuffer* Buffer() const { return mBuffer; }
+
+ private:
+  MediaSpan(MediaByteBuffer* aBuffer, size_t aStart, size_t aLength)
+      : mBuffer(aBuffer), mStart(aStart), mLength(aLength) {
+    MOZ_DIAGNOSTIC_ASSERT(mStart + mLength <= mBuffer->Length());
+  }
+
+  RefPtr<MediaByteBuffer> mBuffer;
+  size_t mStart = 0;
+  size_t mLength = 0;
+};
+
+}  // namespace mozilla
+
+#endif  // MediaSpan_h
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestMediaSpan.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <gtest/gtest.h>
+#include <stdint.h>
+
+#include "MediaSpan.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla;
+
+already_AddRefed<MediaByteBuffer> makeBuffer(uint8_t aStart, uint8_t aEnd) {
+  RefPtr<MediaByteBuffer> buffer(new MediaByteBuffer);
+  for (uint8_t i = aStart; i <= aEnd; i++) {
+    buffer->AppendElement(i);
+  }
+  return buffer.forget();
+}
+
+bool IsRangeAt(const MediaSpan& aSpan, uint8_t aStart, uint8_t aEnd,
+               size_t aAt) {
+  size_t length = size_t(aEnd) - size_t(aStart) + 1;
+  if (aAt + length > aSpan.Length()) {
+    return false;
+  }
+  for (size_t i = 0; i < length; i++) {
+    if (aSpan[aAt + i] != uint8_t(aStart + i)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool IsRange(const MediaSpan& aSpan, uint8_t aStart, uint8_t aEnd) {
+  return IsRangeAt(aSpan, aStart, aEnd, 0);
+}
+
+TEST(MediaSpan, AppendToFromSpan)
+{
+  RefPtr<MediaByteBuffer> buffer1 = makeBuffer(0, 9);
+  MediaSpan span1 = MediaSpan(buffer1);
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+
+  MediaSpan span2 = span1.From(5);
+
+  EXPECT_EQ(span2.Length(), size_t(5));
+  EXPECT_TRUE(IsRange(span2, 5, 9));
+  RefPtr<MediaByteBuffer> buffer2 = makeBuffer(10, 19);
+  EXPECT_EQ(buffer2->Length(), size_t(10));
+  span2.Append(buffer2);
+
+  // Span2 should be: [5...19]
+  EXPECT_EQ(span2.Length(), size_t(15));
+  EXPECT_TRUE(IsRange(span2, 5, 19));
+
+  // Span1 should not be modified by the append to span2.
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+}
+
+TEST(MediaSpan, AppendToToSpan)
+{
+  RefPtr<MediaByteBuffer> buffer1 = makeBuffer(0, 9);
+  MediaSpan span1 = MediaSpan(buffer1);
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+
+  MediaSpan span2 = span1.To(5);
+
+  // Span2 should be [0...4]
+  EXPECT_EQ(span2.Length(), size_t(5));
+  EXPECT_TRUE(IsRange(span2, 0, 4));
+  RefPtr<MediaByteBuffer> buffer2 = makeBuffer(10, 19);
+  EXPECT_EQ(buffer2->Length(), size_t(10));
+  span2.Append(buffer2);
+
+  // Span2 should be: [0...4][10...19]
+  EXPECT_EQ(span2.Length(), size_t(15));
+  EXPECT_TRUE(IsRangeAt(span2, 0, 4, 0));
+  EXPECT_TRUE(IsRangeAt(span2, 10, 19, 5));
+
+  // Span1 should not be modified by the append to span2.
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+}
+
+TEST(MediaSpan, RemoveFront)
+{
+  RefPtr<MediaByteBuffer> buffer1 = makeBuffer(0, 9);
+  MediaSpan span1 = MediaSpan(buffer1);
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+
+  MediaSpan span2(span1);
+  EXPECT_EQ(span2.Length(), size_t(10));
+
+  span2.RemoveFront(5);
+
+  // Span2 should now be [5...9]
+  EXPECT_EQ(span2.Length(), size_t(5));
+  EXPECT_TRUE(IsRange(span2, 5, 9));
+
+  // Span1 should be unaffected.
+  EXPECT_EQ(span1.Length(), size_t(10));
+  EXPECT_TRUE(IsRange(span1, 0, 9));
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -29,16 +29,17 @@ UNIFIED_SOURCES += [
     'TestDriftCompensation.cpp',
     'TestGMPUtils.cpp',
     'TestGroupId.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
     'TestMediaDataEncoder.cpp',
     'TestMediaEventSource.cpp',
     'TestMediaMIMETypes.cpp',
+    'TestMediaSpan.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
     'TestOpusParser.cpp',
     'TestRust.cpp',
     'TestTimeUnit.cpp',
     'TestVideoSegment.cpp',
     'TestVideoUtils.cpp',
     'TestVPXDecoding.cpp',
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -46,35 +46,33 @@ extern mozilla::LogModule* GetMediaSourc
 
 namespace mozilla {
 
 ContainerParser::ContainerParser(const MediaContainerType& aType)
     : mHasInitData(false), mTotalParsed(0), mGlobalOffset(0), mType(aType) {}
 
 ContainerParser::~ContainerParser() = default;
 
-MediaResult ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData) {
-  MSE_DEBUG("aLength=%zu [%x%x%x%x]", aData->Length(),
-            aData->Length() > 0 ? (*aData)[0] : 0,
-            aData->Length() > 1 ? (*aData)[1] : 0,
-            aData->Length() > 2 ? (*aData)[2] : 0,
-            aData->Length() > 3 ? (*aData)[3] : 0);
+MediaResult ContainerParser::IsInitSegmentPresent(const MediaSpan& aData) {
+  MSE_DEBUG(
+      "aLength=%zu [%x%x%x%x]", aData.Length(),
+      aData.Length() > 0 ? aData[0] : 0, aData.Length() > 1 ? aData[1] : 0,
+      aData.Length() > 2 ? aData[2] : 0, aData.Length() > 3 ? aData[3] : 0);
   return NS_ERROR_NOT_AVAILABLE;
 }
 
-MediaResult ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData) {
-  MSE_DEBUG("aLength=%zu [%x%x%x%x]", aData->Length(),
-            aData->Length() > 0 ? (*aData)[0] : 0,
-            aData->Length() > 1 ? (*aData)[1] : 0,
-            aData->Length() > 2 ? (*aData)[2] : 0,
-            aData->Length() > 3 ? (*aData)[3] : 0);
+MediaResult ContainerParser::IsMediaSegmentPresent(const MediaSpan& aData) {
+  MSE_DEBUG(
+      "aLength=%zu [%x%x%x%x]", aData.Length(),
+      aData.Length() > 0 ? aData[0] : 0, aData.Length() > 1 ? aData[1] : 0,
+      aData.Length() > 2 ? aData[2] : 0, aData.Length() > 3 ? aData[3] : 0);
   return NS_ERROR_NOT_AVAILABLE;
 }
 
-MediaResult ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+MediaResult ContainerParser::ParseStartAndEndTimestamps(const MediaSpan& aData,
                                                         int64_t& aStart,
                                                         int64_t& aEnd) {
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 bool ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs) {
   return llabs(aLhs - aRhs) <= GetRoundingError();
 }
@@ -108,54 +106,54 @@ class WebMContainerParser
     : public ContainerParser,
       public DecoderDoctorLifeLogger<WebMContainerParser> {
  public:
   explicit WebMContainerParser(const MediaContainerType& aType)
       : ContainerParser(aType), mParser(0), mOffset(0) {}
 
   static const unsigned NS_PER_USEC = 1000;
 
-  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override {
+  MediaResult IsInitSegmentPresent(const MediaSpan& aData) override {
     ContainerParser::IsInitSegmentPresent(aData);
-    if (aData->Length() < 4) {
+    if (aData.Length() < 4) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     WebMBufferedParser parser(0);
     nsTArray<WebMTimeDataOffset> mapping;
     ReentrantMonitor dummy("dummy");
     bool result =
-        parser.Append(aData->Elements(), aData->Length(), mapping, dummy);
+        parser.Append(aData.Elements(), aData.Length(), mapping, dummy);
     if (!result) {
       return MediaResult(NS_ERROR_FAILURE,
                          RESULT_DETAIL("Invalid webm content"));
     }
     return parser.mInitEndOffset > 0 ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
-  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override {
+  MediaResult IsMediaSegmentPresent(const MediaSpan& aData) override {
     ContainerParser::IsMediaSegmentPresent(aData);
-    if (aData->Length() < 4) {
+    if (aData.Length() < 4) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     WebMBufferedParser parser(0);
     nsTArray<WebMTimeDataOffset> mapping;
     ReentrantMonitor dummy("dummy");
     parser.AppendMediaSegmentOnly();
     bool result =
-        parser.Append(aData->Elements(), aData->Length(), mapping, dummy);
+        parser.Append(aData.Elements(), aData.Length(), mapping, dummy);
     if (!result) {
       return MediaResult(NS_ERROR_FAILURE,
                          RESULT_DETAIL("Invalid webm content"));
     }
     return parser.GetClusterOffset() >= 0 ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
-  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+  MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
                                          int64_t& aStart,
                                          int64_t& aEnd) override {
     bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
 
     if (mLastMapping &&
         (initSegment || NS_SUCCEEDED(IsMediaSegmentPresent(aData)))) {
       // The last data contained a complete cluster but we can only detect it
       // now that a new one is starting.
@@ -183,17 +181,17 @@ class WebMContainerParser
     }
 
     // XXX if it only adds new mappings, overlapped but not available
     // (e.g. overlap < 0) frames are "lost" from the reported mappings here.
     nsTArray<WebMTimeDataOffset> mapping;
     mapping.AppendElements(mOverlappedMapping);
     mOverlappedMapping.Clear();
     ReentrantMonitor dummy("dummy");
-    mParser.Append(aData->Elements(), aData->Length(), mapping, dummy);
+    mParser.Append(aData.Elements(), aData.Length(), mapping, dummy);
     if (mResource) {
       mResource->AppendData(aData);
     }
 
     // XXX This is a bit of a hack.  Assume if there are no timecodes
     // present and it's an init segment that it's _just_ an init segment.
     // We should be more precise.
     if (initSegment || !HasCompleteInitData()) {
@@ -209,18 +207,18 @@ class WebMContainerParser
         mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
         MSE_DEBUG("Stashed init of %" PRId64 " bytes.", mParser.mInitEndOffset);
         mResource = nullptr;
       } else {
         MSE_DEBUG("Incomplete init found.");
       }
       mHasInitData = true;
     }
-    mOffset += aData->Length();
-    mTotalParsed += aData->Length();
+    mOffset += aData.Length();
+    mTotalParsed += aData.Length();
 
     if (mapping.IsEmpty()) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     // Calculate media range for first media segment.
 
     // Check if we have a cluster finishing in the current data.
@@ -373,58 +371,58 @@ bool MP4Stream::Length(int64_t* aSize) {
 DDLoggedTypeDeclNameAndBase(MP4ContainerParser, ContainerParser);
 
 class MP4ContainerParser : public ContainerParser,
                            public DecoderDoctorLifeLogger<MP4ContainerParser> {
  public:
   explicit MP4ContainerParser(const MediaContainerType& aType)
       : ContainerParser(aType) {}
 
-  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override {
+  MediaResult IsInitSegmentPresent(const MediaSpan& aData) override {
     ContainerParser::IsInitSegmentPresent(aData);
     // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
     // file is the 'ftyp' atom followed by a file type. We just check for a
     // vaguely valid 'ftyp' atom.
-    if (aData->Length() < 8) {
+    if (aData.Length() < 8) {
       return NS_ERROR_NOT_AVAILABLE;
     }
     AtomParser parser(*this, aData, AtomParser::StopAt::eInitSegment);
     if (!parser.IsValid()) {
       return MediaResult(
           NS_ERROR_FAILURE,
           RESULT_DETAIL("Invalid Top-Level Box:%s", parser.LastInvalidBox()));
     }
     return parser.StartWithInitSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
-  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override {
-    if (aData->Length() < 8) {
+  MediaResult IsMediaSegmentPresent(const MediaSpan& aData) override {
+    if (aData.Length() < 8) {
       return NS_ERROR_NOT_AVAILABLE;
     }
     AtomParser parser(*this, aData, AtomParser::StopAt::eMediaSegment);
     if (!parser.IsValid()) {
       return MediaResult(
           NS_ERROR_FAILURE,
           RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
     }
     return parser.StartWithMediaSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
  private:
   class AtomParser {
    public:
     enum class StopAt { eInitSegment, eMediaSegment, eEnd };
 
-    AtomParser(const MP4ContainerParser& aParser, const MediaByteBuffer* aData,
+    AtomParser(const MP4ContainerParser& aParser, const MediaSpan& aData,
                StopAt aStop = StopAt::eEnd) {
       mValid = Init(aParser, aData, aStop).isOk();
     }
 
     Result<Ok, nsresult> Init(const MP4ContainerParser& aParser,
-                              const MediaByteBuffer* aData, StopAt aStop) {
+                              const MediaSpan& aData, StopAt aStop) {
       const MediaContainerType mType(
           aParser.ContainerType());  // for logging macro.
       BufferReader reader(aData);
       AtomType initAtom("moov");
       AtomType mediaAtom("moof");
       AtomType dataAtom("mdat");
 
       // Valid top-level boxes defined in ISO/IEC 14496-12 (Table 1)
@@ -514,17 +512,17 @@ class MP4ContainerParser : public Contai
     Maybe<size_t> mInitOffset;
     Maybe<size_t> mMediaOffset;
     Maybe<size_t> mDataOffset;
     bool mValid;
     char mLastInvalidBox[5];
   };
 
  public:
-  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+  MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
                                          int64_t& aStart,
                                          int64_t& aEnd) override {
     bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
     if (initSegment) {
       mResource = new SourceBufferResource();
       DDLINKCHILD("resource", mResource.get());
       mStream = new MP4Stream(mResource);
       // We use a timestampOffset of 0 for ContainerParser, and require
@@ -535,17 +533,17 @@ class MP4ContainerParser : public Contai
                                /* aIsAudio = */ false);
       DDLINKCHILD("parser", mParser.get());
       mInitData = new MediaByteBuffer();
       mCompleteInitSegmentRange = MediaByteRange();
       mCompleteMediaHeaderRange = MediaByteRange();
       mCompleteMediaSegmentRange = MediaByteRange();
       mGlobalOffset = mTotalParsed;
     } else if (!mStream || !mParser) {
-      mTotalParsed += aData->Length();
+      mTotalParsed += aData.Length();
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     mResource->AppendData(aData);
     MediaByteRangeSet byteRanges;
     byteRanges +=
         MediaByteRange(int64_t(mParser->mOffset), mResource->GetLength());
     mParser->RebuildFragmentedIndex(byteRanges);
@@ -561,33 +559,28 @@ class MP4ContainerParser : public Contai
         char* buffer = reinterpret_cast<char*>(mInitData->Elements());
         mResource->ReadFromCache(buffer, range.mStart, range.Length());
         MSE_DEBUG("Stashed init of %" PRIu64 " bytes.", range.Length());
       } else {
         MSE_DEBUG("Incomplete init found.");
       }
       mHasInitData = true;
     }
-    mTotalParsed += aData->Length();
+    mTotalParsed += aData.Length();
 
     MP4Interval<Microseconds> compositionRange =
         mParser->GetCompositionRange(byteRanges);
 
     mCompleteMediaHeaderRange =
         mParser->FirstCompleteMediaHeader() + mGlobalOffset;
     mCompleteMediaSegmentRange =
         mParser->FirstCompleteMediaSegment() + mGlobalOffset;
 
-    ErrorResult rv;
     if (HasCompleteInitData()) {
-      mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
-    }
-    if (NS_WARN_IF(rv.Failed())) {
-      rv.SuppressException();
-      return NS_ERROR_OUT_OF_MEMORY;
+      mResource->EvictData(mParser->mOffset, mParser->mOffset);
     }
 
     if (compositionRange.IsNull()) {
       return NS_ERROR_NOT_AVAILABLE;
     }
     aStart = compositionRange.start;
     aEnd = compositionRange.end;
     MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart, aEnd);
@@ -620,119 +613,116 @@ class ADTSContainerParser
     uint8_t aac_frames;    // Number of AAC frames in the ADTS frame.
     bool have_crc;
   } Header;
 
   /// Helper to parse the ADTS header, returning data we care about.
   /// Returns true if the header is parsed successfully.
   /// Returns false if the header is invalid or incomplete,
   /// without modifying the passed-in Header object.
-  bool Parse(MediaByteBuffer* aData, Header& header) {
-    MOZ_ASSERT(aData);
-
+  bool Parse(const MediaSpan& aData, Header& header) {
     // ADTS initialization segments are just the packet header.
-    if (aData->Length() < 7) {
+    if (aData.Length() < 7) {
       MSE_DEBUG("buffer too short for header.");
       return false;
     }
     // Check 0xfffx sync word plus layer 0.
-    if (((*aData)[0] != 0xff) || (((*aData)[1] & 0xf6) != 0xf0)) {
+    if ((aData[0] != 0xff) || ((aData[1] & 0xf6) != 0xf0)) {
       MSE_DEBUG("no syncword.");
       return false;
     }
-    bool have_crc = !((*aData)[1] & 0x01);
-    if (have_crc && aData->Length() < 9) {
+    bool have_crc = !(aData[1] & 0x01);
+    if (have_crc && aData.Length() < 9) {
       MSE_DEBUG("buffer too short for header with crc.");
       return false;
     }
-    uint8_t frequency_index = ((*aData)[2] & 0x3c) >> 2;
+    uint8_t frequency_index = (aData[2] & 0x3c) >> 2;
     MOZ_ASSERT(frequency_index < 16);
     if (frequency_index == 15) {
       MSE_DEBUG("explicit frequency disallowed.");
       return false;
     }
     size_t header_length = have_crc ? 9 : 7;
-    size_t data_length = (((*aData)[3] & 0x03) << 11) |
-                         (((*aData)[4] & 0xff) << 3) |
-                         (((*aData)[5] & 0xe0) >> 5);
-    uint8_t frames = ((*aData)[6] & 0x03) + 1;
+    size_t data_length = ((aData[3] & 0x03) << 11) | ((aData[4] & 0xff) << 3) |
+                         ((aData[5] & 0xe0) >> 5);
+    uint8_t frames = (aData[6] & 0x03) + 1;
     MOZ_ASSERT(frames > 0);
     MOZ_ASSERT(frames < 4);
 
     // Return successfully parsed data.
     header.header_length = header_length;
     header.frame_length = header_length + data_length;
     header.aac_frames = frames;
     header.have_crc = have_crc;
     return true;
   }
 
-  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override {
+  MediaResult IsInitSegmentPresent(const MediaSpan& aData) override {
     // Call superclass for logging.
     ContainerParser::IsInitSegmentPresent(aData);
 
     Header header;
     if (!Parse(aData, header)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     MSE_DEBUGV("%llu byte frame %d aac frames%s",
                (unsigned long long)header.frame_length, (int)header.aac_frames,
                header.have_crc ? " crc" : "");
 
     return NS_OK;
   }
 
-  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override {
+  MediaResult IsMediaSegmentPresent(const MediaSpan& aData) override {
     // Call superclass for logging.
     ContainerParser::IsMediaSegmentPresent(aData);
 
     // Make sure we have a header so we know how long the frame is.
     // NB this assumes the media segment buffer starts with an
     // initialization segment. Since every frame has an ADTS header
     // this is a normal place to divide packets, but we can re-parse
     // mInitData if we need to handle separate media segments.
     Header header;
     if (!Parse(aData, header)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
     // We're supposed to return true as long as aData contains the
     // start of a media segment, whether or not it's complete. So
     // return true if we have any data beyond the header.
-    if (aData->Length() <= header.header_length) {
+    if (aData.Length() <= header.header_length) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     // We should have at least a partial frame.
     return NS_OK;
   }
 
-  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+  MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
                                          int64_t& aStart,
                                          int64_t& aEnd) override {
     // ADTS header.
     Header header;
     if (!Parse(aData, header)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
     mHasInitData = true;
     mCompleteInitSegmentRange =
         MediaByteRange(0, int64_t(header.header_length));
 
     // Cache raw header in case the caller wants a copy.
     mInitData = new MediaByteBuffer(header.header_length);
-    mInitData->AppendElements(aData->Elements(), header.header_length);
+    mInitData->AppendElements(aData.Elements(), header.header_length);
 
     // Check that we have enough data for the frame body.
-    if (aData->Length() < header.frame_length) {
+    if (aData.Length() < header.frame_length) {
       MSE_DEBUGV(
           "Not enough data for %llu byte frame"
           " in %llu byte buffer.",
           (unsigned long long)header.frame_length,
-          (unsigned long long)(aData->Length()));
+          (unsigned long long)(aData.Length()));
       return NS_ERROR_NOT_AVAILABLE;
     }
     mCompleteMediaSegmentRange =
         MediaByteRange(header.header_length, header.frame_length);
     // The ADTS MediaSource Byte Stream Format document doesn't
     // define media header. Just treat it the same as the whole
     // media segment.
     mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -3,16 +3,17 @@
 /* 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_CONTAINERPARSER_H_
 #define MOZILLA_CONTAINERPARSER_H_
 
 #include "mozilla/RefPtr.h"
+#include "MediaSpan.h"
 #include "MediaContainerType.h"
 #include "MediaResource.h"
 #include "MediaResult.h"
 
 namespace mozilla {
 
 class MediaByteBuffer;
 class SourceBufferResource;
@@ -25,31 +26,31 @@ class ContainerParser : public DecoderDo
   virtual ~ContainerParser();
 
   // Return true if aData starts with an initialization segment.
   // The base implementation exists only for debug logging and is expected
   // to be called first from the overriding implementation.
   // Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
   // data is currently available to make a determination. Any other value
   // indicates an error.
-  virtual MediaResult IsInitSegmentPresent(MediaByteBuffer* aData);
+  virtual MediaResult IsInitSegmentPresent(const MediaSpan& aData);
 
   // Return true if aData starts with a media segment.
   // The base implementation exists only for debug logging and is expected
   // to be called first from the overriding implementation.
   // Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
   // data is currently available to make a determination. Any other value
   // indicates an error.
-  virtual MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData);
+  virtual MediaResult IsMediaSegmentPresent(const MediaSpan& aData);
 
   // Parse aData to extract the start and end frame times from the media
   // segment.  aData may not start on a parser sync boundary.  Return NS_OK
   // if aStart and aEnd have been updated and NS_ERROR_NOT_AVAILABLE otherwise
   // when no error were encountered.
-  virtual MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+  virtual MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
                                                  int64_t& aStart,
                                                  int64_t& aEnd);
 
   // Compare aLhs and rHs, considering any error that may exist in the
   // timestamps from the format's base representation.  Return true if aLhs
   // == aRhs within the error epsilon.
   bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
 
--- a/dom/media/mediasource/ResourceQueue.cpp
+++ b/dom/media/mediasource/ResourceQueue.cpp
@@ -19,27 +19,21 @@ extern mozilla::LogModule* GetSourceBuff
   MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, \
           ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #define SBR_DEBUGV(arg, ...)                                        \
   MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, \
           ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
-ResourceItem::ResourceItem(MediaByteBuffer* aData, uint64_t aOffset)
+ResourceItem::ResourceItem(const MediaSpan& aData, uint64_t aOffset)
     : mData(aData), mOffset(aOffset) {}
 
 size_t ResourceItem::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-  // size including this
-  size_t size = aMallocSizeOf(this);
-
-  // size excluding this
-  size += mData->ShallowSizeOfExcludingThis(aMallocSizeOf);
-
-  return size;
+  return aMallocSizeOf(this);
 }
 
 class ResourceQueueDeallocator : public nsDequeFunctor {
   void operator()(void* aObject) override {
     delete static_cast<ResourceItem*>(aObject);
   }
 };
 
@@ -53,119 +47,122 @@ uint64_t ResourceQueue::GetLength() { re
 const uint8_t* ResourceQueue::GetContiguousAccess(int64_t aOffset,
                                                   size_t aSize) {
   uint32_t offset = 0;
   uint32_t start = GetAtOffset(aOffset, &offset);
   if (start >= GetSize()) {
     return nullptr;
   }
   ResourceItem* item = ResourceAt(start);
-  if (offset + aSize > item->mData->Length()) {
+  if (offset + aSize > item->mData.Length()) {
     return nullptr;
   }
-  return item->mData->Elements() + offset;
+  return item->mData.Elements() + offset;
 }
 
 void ResourceQueue::CopyData(uint64_t aOffset, uint32_t aCount, char* aDest) {
   uint32_t offset = 0;
   uint32_t start = GetAtOffset(aOffset, &offset);
   size_t i = start;
   while (i < uint32_t(GetSize()) && aCount > 0) {
     ResourceItem* item = ResourceAt(i++);
-    uint32_t bytes = std::min(aCount, uint32_t(item->mData->Length() - offset));
+    uint32_t bytes = std::min(aCount, uint32_t(item->mData.Length() - offset));
     if (bytes != 0) {
-      memcpy(aDest, &(*item->mData)[offset], bytes);
+      memcpy(aDest, item->mData.Elements() + offset, bytes);
       offset = 0;
       aCount -= bytes;
       aDest += bytes;
     }
   }
 }
 
-void ResourceQueue::AppendItem(MediaByteBuffer* aData) {
+void ResourceQueue::AppendItem(const MediaSpan& aData) {
   uint64_t offset = mLogicalLength;
-  mLogicalLength += aData->Length();
+  mLogicalLength += aData.Length();
   Push(new ResourceItem(aData, offset));
 }
 
-uint32_t ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict,
-                              ErrorResult& aRv) {
+uint32_t ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict) {
   SBR_DEBUG("Evict(aOffset=%" PRIu64 ", aSizeToEvict=%u)", aOffset,
             aSizeToEvict);
-  return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict), aRv);
+  return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict));
 }
 
-uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv) {
+uint32_t ResourceQueue::EvictBefore(uint64_t aOffset) {
   SBR_DEBUG("EvictBefore(%" PRIu64 ")", aOffset);
   uint32_t evicted = 0;
   while (ResourceItem* item = ResourceAt(0)) {
-    SBR_DEBUG("item=%p length=%zu offset=%" PRIu64, item, item->mData->Length(),
+    SBR_DEBUG("item=%p length=%zu offset=%" PRIu64, item, item->mData.Length(),
               mOffset);
-    if (item->mData->Length() + mOffset >= aOffset) {
+    if (item->mData.Length() + mOffset >= aOffset) {
       if (aOffset <= mOffset) {
         break;
       }
       uint32_t offset = aOffset - mOffset;
       mOffset += offset;
       evicted += offset;
-      RefPtr<MediaByteBuffer> data = new MediaByteBuffer;
-      if (!data->AppendElements(item->mData->Elements() + offset,
-                                item->mData->Length() - offset, fallible)) {
-        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-        return 0;
-      }
+      item->mData.RemoveFront(offset);
       item->mOffset += offset;
-      item->mData = data;
       break;
     }
-    mOffset += item->mData->Length();
-    evicted += item->mData->Length();
+    mOffset += item->mData.Length();
+    evicted += item->mData.Length();
     delete PopFront();
   }
   return evicted;
 }
 
 uint32_t ResourceQueue::EvictAll() {
   SBR_DEBUG("EvictAll()");
   uint32_t evicted = 0;
   while (ResourceItem* item = ResourceAt(0)) {
-    SBR_DEBUG("item=%p length=%zu offset=%" PRIu64, item, item->mData->Length(),
+    SBR_DEBUG("item=%p length=%zu offset=%" PRIu64, item, item->mData.Length(),
               mOffset);
-    mOffset += item->mData->Length();
-    evicted += item->mData->Length();
+    mOffset += item->mData.Length();
+    evicted += item->mData.Length();
     delete PopFront();
   }
   return evicted;
 }
 
 size_t ResourceQueue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
   // Calculate the size of the internal deque.
   size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
 
-  // Sum the ResourceItems.
+  // Sum the ResourceItems. The ResourceItems's MediaSpans may share the
+  // same underlying MediaByteBuffers, so we need to de-dupe the buffers
+  // in order to report an accurate size.
+  nsTArray<MediaByteBuffer*> buffers;
   for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
     const ResourceItem* item = ResourceAt(i);
     size += item->SizeOfIncludingThis(aMallocSizeOf);
+    if (!buffers.Contains(item->mData.Buffer())) {
+      buffers.AppendElement(item->mData.Buffer());
+    }
+  }
+
+  for (MediaByteBuffer* buffer : buffers) {
+    size += buffer->ShallowSizeOfExcludingThis(aMallocSizeOf);
   }
 
   return size;
 }
 
 #if defined(DEBUG)
 void ResourceQueue::Dump(const char* aPath) {
   for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
     ResourceItem* item = ResourceAt(i);
 
     char buf[255];
     SprintfLiteral(buf, "%s/%08u.bin", aPath, i);
     FILE* fp = fopen(buf, "wb");
     if (!fp) {
       return;
     }
-    Unused << fwrite(item->mData->Elements(), item->mData->Length(), 1, fp);
+    Unused << fwrite(item->mData.Elements(), item->mData.Length(), 1, fp);
     fclose(fp);
   }
 }
 #endif
 
 ResourceItem* ResourceQueue::ResourceAt(uint32_t aIndex) const {
   return static_cast<ResourceItem*>(ObjectAt(aIndex));
 }
@@ -175,23 +172,23 @@ uint32_t ResourceQueue::GetAtOffset(uint
   MOZ_RELEASE_ASSERT(aOffset >= mOffset);
 
   size_t hi = GetSize();
   size_t lo = 0;
   while (lo < hi) {
     size_t mid = lo + (hi - lo) / 2;
     const ResourceItem* resource = ResourceAt(mid);
     if (resource->mOffset <= aOffset &&
-        aOffset < resource->mOffset + resource->mData->Length()) {
+        aOffset < resource->mOffset + resource->mData.Length()) {
       if (aResourceOffset) {
         *aResourceOffset = aOffset - resource->mOffset;
       }
       return uint32_t(mid);
     }
-    if (resource->mOffset + resource->mData->Length() <= aOffset) {
+    if (resource->mOffset + resource->mData.Length() <= aOffset) {
       lo = mid + 1;
     } else {
       hi = mid;
     }
   }
 
   return uint32_t(GetSize());
 }
--- a/dom/media/mediasource/ResourceQueue.h
+++ b/dom/media/mediasource/ResourceQueue.h
@@ -3,17 +3,17 @@
 /* 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_RESOURCEQUEUE_H_
 #define MOZILLA_RESOURCEQUEUE_H_
 
 #include "nsDeque.h"
-#include "MediaData.h"
+#include "MediaSpan.h"
 
 namespace mozilla {
 
 class ErrorResult;
 
 // 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
@@ -21,19 +21,19 @@ class ErrorResult;
 
 // 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(MediaByteBuffer* aData, uint64_t aOffset);
+  ResourceItem(const MediaSpan& aData, uint64_t aOffset);
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
-  RefPtr<MediaByteBuffer> mData;
+  MediaSpan mData;
   uint64_t mOffset;
 };
 
 class ResourceQueue : private nsDeque {
  public:
   ResourceQueue();
 
   // Returns the logical byte offset of the start of the data.
@@ -41,23 +41,23 @@ class ResourceQueue : private nsDeque {
 
   // Returns the length of all items in the queue plus the offset.
   // This is the logical length of the resource.
   uint64_t GetLength();
 
   // Copies aCount bytes from aOffset in the queue into aDest.
   void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest);
 
-  void AppendItem(MediaByteBuffer* aData);
+  void AppendItem(const MediaSpan& aData);
 
   // Tries to evict at least aSizeToEvict from the queue up until
   // aOffset. Returns amount evicted.
-  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict, ErrorResult& aRv);
+  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict);
 
-  uint32_t EvictBefore(uint64_t aOffset, ErrorResult& aRv);
+  uint32_t EvictBefore(uint64_t aOffset);
 
   uint32_t EvictAll();
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
 #if defined(DEBUG)
   void Dump(const char* aPath);
 #endif
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -74,43 +74,47 @@ nsresult SourceBufferResource::ReadFromC
   nsresult rv = ReadAtInternal(aOffset, aBuffer, aCount, &bytesRead);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // ReadFromCache return failure if not all the data is cached.
   return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
 }
 
 uint32_t SourceBufferResource::EvictData(uint64_t aPlaybackOffset,
-                                         int64_t aThreshold, ErrorResult& aRv) {
+                                         int64_t aThreshold) {
   MOZ_ASSERT(OnThread());
   SBR_DEBUG("EvictData(aPlaybackOffset=%" PRIu64
             ","
             "aThreshold=%" PRId64 ")",
             aPlaybackOffset, aThreshold);
-  uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold, aRv);
+  uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold);
   return result;
 }
 
-void SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv) {
+void SourceBufferResource::EvictBefore(uint64_t aOffset) {
   MOZ_ASSERT(OnThread());
   SBR_DEBUG("EvictBefore(aOffset=%" PRIu64 ")", aOffset);
 
-  mInputBuffer.EvictBefore(aOffset, aRv);
+  mInputBuffer.EvictBefore(aOffset);
 }
 
 uint32_t SourceBufferResource::EvictAll() {
   MOZ_ASSERT(OnThread());
   SBR_DEBUG("EvictAll()");
   return mInputBuffer.EvictAll();
 }
 
 void SourceBufferResource::AppendData(MediaByteBuffer* aData) {
+  AppendData(MediaSpan(aData));
+}
+
+void SourceBufferResource::AppendData(const MediaSpan& aData) {
   MOZ_ASSERT(OnThread());
-  SBR_DEBUG("AppendData(aData=%p, aLength=%zu)", aData->Elements(),
-            aData->Length());
+  SBR_DEBUG("AppendData(aData=%p, aLength=%zu)", aData.Elements(),
+            aData.Length());
   mInputBuffer.AppendItem(aData);
   mEnded = false;
 }
 
 void SourceBufferResource::Ended() {
   MOZ_ASSERT(OnThread());
   SBR_DEBUG("");
   mEnded = true;
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -83,28 +83,28 @@ class SourceBufferResource final
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   // Used by SourceBuffer.
   void AppendData(MediaByteBuffer* aData);
+  void AppendData(const MediaSpan& aData);
   void Ended();
   bool IsEnded() {
     MOZ_ASSERT(OnThread());
     return mEnded;
   }
   // Remove data from resource if it holds more than the threshold reduced by
   // the given number of bytes. Returns amount evicted.
-  uint32_t EvictData(uint64_t aPlaybackOffset, int64_t aThresholdReduct,
-                     ErrorResult& aRv);
+  uint32_t EvictData(uint64_t aPlaybackOffset, int64_t aThresholdReduct);
 
   // Remove data from resource before the given offset.
-  void EvictBefore(uint64_t aOffset, ErrorResult& aRv);
+  void EvictBefore(uint64_t aOffset);
 
   // Remove all data from the resource
   uint32_t EvictAll();
 
   // Returns the amount of data currently retained by this resource.
   int64_t GetSize() {
     MOZ_ASSERT(OnThread());
     return mInputBuffer.GetLength() - mInputBuffer.GetOffset();
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -88,18 +88,17 @@ class DispatchKeyNeededEvent : public Ru
  private:
   RefPtr<MediaSourceDecoder> mDecoder;
   nsTArray<uint8_t> mInitData;
   nsString mInitDataType;
 };
 
 TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
                                          const MediaContainerType& aType)
-    : mInputBuffer(new MediaByteBuffer),
-      mBufferFull(false),
+    : mBufferFull(false),
       mFirstInitializationSegmentReceived(false),
       mChangeTypeReceived(false),
       mNewMediaSegmentStarted(false),
       mActiveTrack(false),
       mType(aType),
       mParser(ContainerParser::CreateForMIMEType(aType)),
       mProcessedInput(0),
       mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(
@@ -206,20 +205,21 @@ void TrackBuffersManager::ProcessTasks()
     // nothing to do.
     return;
   }
 
   MSE_DEBUG("Process task '%s'", task->GetTypeName());
   switch (task->GetType()) {
     case Type::AppendBuffer:
       mCurrentTask = task;
-      if (!mInputBuffer) {
-        mInputBuffer = task->As<AppendBufferTask>()->mBuffer;
-      } else if (!mInputBuffer->AppendElements(
-                     *task->As<AppendBufferTask>()->mBuffer, fallible)) {
+      if (!mInputBuffer || mInputBuffer->IsEmpty()) {
+        // Note: we reset mInputBuffer here to ensure it doesn't grow unbounded.
+        mInputBuffer.reset();
+        mInputBuffer = Some(MediaSpan(task->As<AppendBufferTask>()->mBuffer));
+      } else if (!mInputBuffer->Append(task->As<AppendBufferTask>()->mBuffer)) {
         RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
         return;
       }
       mSourceBufferAttributes = MakeUnique<SourceBufferAttributes>(
           task->As<AppendBufferTask>()->mAttributes);
       mAppendWindow = TimeInterval(
           TimeUnit::FromSeconds(
               mSourceBufferAttributes->GetAppendWindowStart()),
@@ -443,17 +443,17 @@ void TrackBuffersManager::CompleteResetP
 
     // if we have been aborted, we may have pending frames that we are going
     // to discard now.
     track->mQueuedSamples.Clear();
   }
 
   // 7. Remove all bytes from the input buffer.
   mPendingInputBuffer = nullptr;
-  mInputBuffer = nullptr;
+  mInputBuffer.reset();
   if (mCurrentInputBuffer) {
     mCurrentInputBuffer->EvictAll();
     // The demuxer will be recreated during the next run of SegmentParserLoop.
     // As such we don't need to notify it that data has been removed.
     mCurrentInputBuffer = new SourceBufferResource();
   }
 
   // We could be left with a demuxer in an unusable state. It needs to be
@@ -465,18 +465,17 @@ void TrackBuffersManager::CompleteResetP
   // init segment.
   if (mFirstInitializationSegmentReceived && !mChangeTypeReceived) {
     MOZ_ASSERT(mInitData && mInitData->Length(),
                "we must have an init segment");
     // The aim here is really to destroy our current demuxer.
     CreateDemuxerforMIMEType();
     // Recreate our input buffer. We can't directly assign the initData buffer
     // to mInputBuffer as it will get modified in the Segment Parser Loop.
-    mInputBuffer = new MediaByteBuffer;
-    mInputBuffer->AppendElements(*mInitData);
+    mInputBuffer = Some(MediaSpan::WithCopyOf(mInitData));
     RecreateParser(true);
   } else {
     RecreateParser(false);
   }
 }
 
 int64_t TrackBuffersManager::EvictionThreshold() const {
   if (HasVideo()) {
@@ -700,27 +699,28 @@ void TrackBuffersManager::SegmentParserL
     // be ignored from the start of the input buffer. We do not remove bytes
     // from our input buffer. Instead we enforce that our ContainerParser is
     // able to skip over all data that is supposed to be ignored.
 
     // 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
     // steps:
     if (mSourceBufferAttributes->GetAppendState() ==
         AppendState::WAITING_FOR_SEGMENT) {
-      MediaResult haveInitSegment = mParser->IsInitSegmentPresent(mInputBuffer);
+      MediaResult haveInitSegment =
+          mParser->IsInitSegmentPresent(*mInputBuffer);
       if (NS_SUCCEEDED(haveInitSegment)) {
         SetAppendState(AppendState::PARSING_INIT_SEGMENT);
         if (mFirstInitializationSegmentReceived && !mChangeTypeReceived) {
           // This is a new initialization segment. Obsolete the old one.
           RecreateParser(false);
         }
         continue;
       }
       MediaResult haveMediaSegment =
-          mParser->IsMediaSegmentPresent(mInputBuffer);
+          mParser->IsMediaSegmentPresent(*mInputBuffer);
       if (NS_SUCCEEDED(haveMediaSegment)) {
         SetAppendState(AppendState::PARSING_MEDIA_SEGMENT);
         mNewMediaSegmentStarted = true;
         continue;
       }
       // We have neither an init segment nor a media segment.
       // Check if it was invalid data.
       if (haveInitSegment != NS_ERROR_NOT_AVAILABLE) {
@@ -735,29 +735,29 @@ void TrackBuffersManager::SegmentParserL
       }
       MSE_DEBUG("Found incomplete data.");
       NeedMoreData();
       return;
     }
 
     int64_t start, end;
     MediaResult newData =
-        mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+        mParser->ParseStartAndEndTimestamps(*mInputBuffer, start, end);
     if (!NS_SUCCEEDED(newData) && newData.Code() != NS_ERROR_NOT_AVAILABLE) {
       RejectAppend(newData, __func__);
       return;
     }
     mProcessedInput += mInputBuffer->Length();
 
     // 5. If the append state equals PARSING_INIT_SEGMENT, then run the
     // following steps:
     if (mSourceBufferAttributes->GetAppendState() ==
         AppendState::PARSING_INIT_SEGMENT) {
       if (mParser->InitSegmentRange().IsEmpty()) {
-        mInputBuffer = nullptr;
+        mInputBuffer.reset();
         NeedMoreData();
         return;
       }
       InitializationSegmentReceived();
       return;
     }
     if (mSourceBufferAttributes->GetAppendState() ==
         AppendState::PARSING_MEDIA_SEGMENT) {
@@ -792,21 +792,22 @@ void TrackBuffersManager::SegmentParserL
           }
           mNewMediaSegmentStarted = false;
         } else {
           // We don't have any data to demux yet, stash aside the data.
           // This also handles the case:
           // 2. If the input buffer does not contain a complete media segment
           // header yet, then jump to the need more data step below.
           if (!mPendingInputBuffer) {
-            mPendingInputBuffer = mInputBuffer;
-          } else {
-            mPendingInputBuffer->AppendElements(*mInputBuffer);
+            mPendingInputBuffer = new MediaByteBuffer();
           }
-          mInputBuffer = nullptr;
+          mPendingInputBuffer->AppendElements(mInputBuffer->Elements(),
+                                              mInputBuffer->Length());
+
+          mInputBuffer.reset();
           NeedMoreData();
           return;
         }
       }
 
       // 3. If the input buffer contains one or more complete coded frames, then
       // run the coded frame processing algorithm.
       RefPtr<TrackBuffersManager> self = this;
@@ -968,27 +969,35 @@ void TrackBuffersManager::OnDemuxerReset
     MOZ_ASSERT(mAudioTracks.mDemuxer);
     DDLINKCHILD("audio demuxer", mAudioTracks.mDemuxer.get());
   }
 
   if (mPendingInputBuffer) {
     // We had a partial media segment header stashed aside.
     // Reparse its content so we can continue parsing the current input buffer.
     int64_t start, end;
-    mParser->ParseStartAndEndTimestamps(mPendingInputBuffer, start, end);
+    mParser->ParseStartAndEndTimestamps(MediaSpan(mPendingInputBuffer), start,
+                                        end);
     mProcessedInput += mPendingInputBuffer->Length();
   }
 
   SegmentParserLoop();
 }
 
 void TrackBuffersManager::AppendDataToCurrentInputBuffer(
+    const MediaSpan& aData) {
+  MOZ_ASSERT(mCurrentInputBuffer);
+  mCurrentInputBuffer->AppendData(aData);
+  mInputDemuxer->NotifyDataArrived();
+}
+
+void TrackBuffersManager::AppendDataToCurrentInputBuffer(
     MediaByteBuffer* aData) {
   MOZ_ASSERT(mCurrentInputBuffer);
-  mCurrentInputBuffer->AppendData(aData);
+  mCurrentInputBuffer->AppendData(MediaSpan(aData));
   mInputDemuxer->NotifyDataArrived();
 }
 
 void TrackBuffersManager::InitializationSegmentReceived() {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mParser->HasCompleteInitData());
 
   int64_t endInit = mParser->InitSegmentRange().mEnd;
@@ -1002,22 +1011,18 @@ void TrackBuffersManager::Initialization
   }
 
   mCurrentInputBuffer = new SourceBufferResource();
   // The demuxer isn't initialized yet ; we don't want to notify it
   // that data has been appended yet ; so we simply append the init segment
   // to the resource.
   mCurrentInputBuffer->AppendData(mParser->InitData());
   uint32_t length = endInit - (mProcessedInput - mInputBuffer->Length());
-  if (mInputBuffer->Length() == length) {
-    mInputBuffer = nullptr;
-  } else {
-    MOZ_RELEASE_ASSERT(length <= mInputBuffer->Length());
-    mInputBuffer->RemoveElementsAt(0, length);
-  }
+  MOZ_RELEASE_ASSERT(length <= mInputBuffer->Length());
+  mInputBuffer->RemoveFront(length);
   CreateDemuxerforMIMEType();
   if (!mInputDemuxer) {
     NS_WARNING("TODO type not supported");
     RejectAppend(NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__);
     return;
   }
   mInputDemuxer->Init()
       ->Then(TaskQueueFromTaskQueue(), __func__, this,
@@ -1332,18 +1337,18 @@ void TrackBuffersManager::OnDemuxerInitF
 
 RefPtr<TrackBuffersManager::CodedFrameProcessingPromise>
 TrackBuffersManager::CodedFrameProcessing() {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mProcessingPromise.IsEmpty());
 
   MediaByteRange mediaRange = mParser->MediaSegmentRange();
   if (mediaRange.IsEmpty()) {
-    AppendDataToCurrentInputBuffer(mInputBuffer);
-    mInputBuffer = nullptr;
+    AppendDataToCurrentInputBuffer(*mInputBuffer);
+    mInputBuffer.reset();
   } else {
     MOZ_ASSERT(mProcessedInput >= mInputBuffer->Length());
     if (int64_t(mProcessedInput - mInputBuffer->Length()) > mediaRange.mEnd) {
       // Something is not quite right with the data appended. Refuse it.
       // This would typically happen if the previous media segment was partial
       // yet a new complete media segment was added.
       return CodedFrameProcessingPromise::CreateAndReject(NS_ERROR_FAILURE,
                                                           __func__);
@@ -1355,23 +1360,18 @@ TrackBuffersManager::CodedFrameProcessin
       // We've completed our earlier media segment and no new data is to be
       // processed. This happens with some containers that can't detect that a
       // media segment is ending until a new one starts.
       RefPtr<CodedFrameProcessingPromise> p =
           mProcessingPromise.Ensure(__func__);
       CompleteCodedFrameProcessing();
       return p;
     }
-    RefPtr<MediaByteBuffer> segment = new MediaByteBuffer;
-    if (!segment->AppendElements(mInputBuffer->Elements(), length, fallible)) {
-      return CodedFrameProcessingPromise::CreateAndReject(
-          NS_ERROR_OUT_OF_MEMORY, __func__);
-    }
-    AppendDataToCurrentInputBuffer(segment);
-    mInputBuffer->RemoveElementsAt(0, length);
+    AppendDataToCurrentInputBuffer(mInputBuffer->To(length));
+    mInputBuffer->RemoveFront(length);
   }
 
   RefPtr<CodedFrameProcessingPromise> p = mProcessingPromise.Ensure(__func__);
 
   DoDemuxVideo();
 
   return p;
 }
@@ -1538,23 +1538,17 @@ void TrackBuffersManager::CompleteCodedF
   // Clear our demuxer from any already processed data.
   int64_t safeToEvict =
       std::min(HasVideo() ? mVideoTracks.mDemuxer->GetEvictionOffset(
                                 mVideoTracks.mLastParsedEndTime)
                           : INT64_MAX,
                HasAudio() ? mAudioTracks.mDemuxer->GetEvictionOffset(
                                 mAudioTracks.mLastParsedEndTime)
                           : INT64_MAX);
-  ErrorResult rv;
-  mCurrentInputBuffer->EvictBefore(safeToEvict, rv);
-  if (rv.Failed()) {
-    rv.SuppressException();
-    RejectProcessing(NS_ERROR_OUT_OF_MEMORY, __func__);
-    return;
-  }
+  mCurrentInputBuffer->EvictBefore(safeToEvict);
 
   mInputDemuxer->NotifyDataRemoved();
   RecreateParser(true);
 
   // 7. Set append state to WAITING_FOR_SEGMENT.
   SetAppendState(AppendState::WAITING_FOR_SEGMENT);
 
   // 8. Jump to the loop top step above.
@@ -2247,17 +2241,17 @@ void TrackBuffersManager::RecreateParser
   // we can optimize this part. TODO
   if (mParser) {
     DDUNLINKCHILD(mParser.get());
   }
   mParser = ContainerParser::CreateForMIMEType(mType);
   DDLINKCHILD("parser", mParser.get());
   if (aReuseInitData && mInitData) {
     int64_t start, end;
-    mParser->ParseStartAndEndTimestamps(mInitData, start, end);
+    mParser->ParseStartAndEndTimestamps(MediaSpan(mInitData), start, end);
     mProcessedInput = mInitData->Length();
   } else {
     mProcessedInput = 0;
   }
 }
 
 nsTArray<TrackBuffersManager::TrackData*> TrackBuffersManager::GetTracksList() {
   nsTArray<TrackData*> tracks;
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -14,16 +14,17 @@
 #include "mozilla/TaskQueue.h"
 #include "mozilla/dom/MediaDebugInfoBinding.h"
 
 #include "MediaContainerType.h"
 #include "MediaData.h"
 #include "MediaDataDemuxer.h"
 #include "MediaResult.h"
 #include "MediaSourceDecoder.h"
+#include "MediaSpan.h"
 #include "SourceBufferTask.h"
 #include "TimeUnits.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 
 class AbstractThread;
@@ -195,17 +196,17 @@ class TrackBuffersManager final
   bool CodedFrameRemoval(media::TimeInterval aInterval);
   void SetAppendState(SourceBufferAttributes::AppendState aAppendState);
 
   bool HasVideo() const { return mVideoTracks.mNumTracks > 0; }
   bool HasAudio() const { return mAudioTracks.mNumTracks > 0; }
 
   // The input buffer as per
   // http://w3c.github.io/media-source/index.html#sourcebuffer-input-buffer
-  RefPtr<MediaByteBuffer> mInputBuffer;
+  Maybe<MediaSpan> mInputBuffer;
   // Buffer full flag as per
   // https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag. Accessed
   // on both the main thread and the task queue.
   Atomic<bool> mBufferFull;
   bool mFirstInitializationSegmentReceived;
   bool mChangeTypeReceived;
   // Set to true once a new segment is started.
   bool mNewMediaSegmentStarted;
@@ -216,17 +217,19 @@ class TrackBuffersManager final
   // Those are used to parse the incoming input buffer.
 
   // Recreate the ContainerParser and if aReuseInitData is true then
   // feed it with the previous init segment found.
   void RecreateParser(bool aReuseInitData);
   nsAutoPtr<ContainerParser> mParser;
 
   // Demuxer objects and methods.
+  void AppendDataToCurrentInputBuffer(const MediaSpan& aData);
   void AppendDataToCurrentInputBuffer(MediaByteBuffer* aData);
+
   RefPtr<MediaByteBuffer> mInitData;
   // Temporary input buffer to handle partial media segment header.
   // We store the current input buffer content into it should we need to
   // reinitialize the demuxer once we have some samples and a discontinuity is
   // detected.
   RefPtr<MediaByteBuffer> mPendingInputBuffer;
   RefPtr<SourceBufferResource> mCurrentInputBuffer;
   RefPtr<MediaDataDemuxer> mInputDemuxer;
--- a/dom/media/mediasource/gtest/TestContainerParser.cpp
+++ b/dom/media/mediasource/gtest/TestContainerParser.cpp
@@ -40,56 +40,56 @@ TEST(ContainerParser, ADTSHeader)
       MediaContainerType(MEDIAMIMETYPE("audio/aac")));
   ASSERT_NE(parser, nullptr);
 
   // Audio data should have no gaps.
   EXPECT_EQ(parser->GetRoundingError(), 0);
 
   // Test a valid header.
   RefPtr<MediaByteBuffer> header = make_adts_header();
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))));
 
   // Test variations.
   uint8_t save = header->ElementAt(1);
   for (uint8_t i = 1; i < 3; ++i) {
     // Set non-zero layer.
     header->ReplaceElementAt(1, (header->ElementAt(1) & 0xf9) | (i << 1));
-    EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
+    EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))))
         << "Accepted non-zero layer in header.";
   }
   header->ReplaceElementAt(1, save);
   save = header->ElementAt(2);
   header->ReplaceElementAt(2, (header->ElementAt(2) & 0x3b) | (15 << 2));
-  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))))
       << "Accepted explicit frequency in header.";
   header->ReplaceElementAt(2, save);
 
   // Test a short header.
   header->SetLength(6);
-  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))))
       << "Accepted too-short header.";
-  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
       << "Found media segment when there was just a partial header.";
 
   // Test a header with short data.
   header = make_adts_header();
   header->AppendElements(1);
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))))
       << "Rejected a valid header.";
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
       << "Rejected a one-byte media segment.";
 
   // Test parse results.
   header = make_adts_header();
-  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
       << "Found media segment when there was just a header.";
   int64_t start = 0;
   int64_t end = 0;
-  EXPECT_TRUE(
-      NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
+  EXPECT_TRUE(NS_FAILED(
+      parser->ParseStartAndEndTimestamps(MediaSpan(header), start, end)));
 
   EXPECT_TRUE(parser->HasInitData());
   EXPECT_TRUE(parser->HasCompleteInitData());
   MediaByteBuffer* init = parser->InitData();
   ASSERT_NE(init, nullptr);
   EXPECT_EQ(init->Length(), header->Length());
 
   EXPECT_EQ(parser->InitSegmentRange(),
@@ -106,32 +106,32 @@ TEST(ContainerParser, ADTSBlankMedia)
       MediaContainerType(MEDIAMIMETYPE("audio/aac")));
   ASSERT_NE(parser, nullptr);
 
   // Audio data should have no gaps.
   EXPECT_EQ(parser->GetRoundingError(), 0);
 
   // Test the header only.
   RefPtr<MediaByteBuffer> header = make_adts_header();
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))));
 
   // Test with the correct length of (invalid) frame data.
   size_t header_length = header->Length();
   size_t data_length = 24;
   size_t frame_length = header_length + data_length;
   header->AppendElements(data_length);
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(MediaSpan(header))))
       << "Rejected a valid header.";
-  EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
       << "Rejected a full (but zeroed) media segment.";
   int64_t start = 0;
   int64_t end = 0;
   // We don't report timestamps from ADTS.
-  EXPECT_TRUE(
-      NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
+  EXPECT_TRUE(NS_FAILED(
+      parser->ParseStartAndEndTimestamps(MediaSpan(header), start, end)));
   EXPECT_EQ(start, 0);
   EXPECT_EQ(end, 0);
 
   // Verify the parser calculated header and packet data boundaries.
   EXPECT_TRUE(parser->HasInitData());
   EXPECT_TRUE(parser->HasCompleteInitData());
   MediaByteBuffer* init = parser->InitData();
   ASSERT_NE(init, nullptr);
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -135,16 +135,17 @@ EXPORTS += [
     'MediaPromiseDefs.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaResourceCallback.h',
     'MediaResult.h',
     'MediaSegment.h',
     'MediaShutdownManager.h',
+    'MediaSpan.h',
     'MediaStatistics.h',
     'MediaStreamGraph.h',
     'MediaStreamListener.h',
     'MediaStreamTypes.h',
     'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
     'MemoryBlockCache.h',