Bug 1171330: P10. Add TrackBuffersManager object. r=cajbir
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 11 Jun 2015 15:55:20 +1000
changeset 248274 34ec7493164c7a7b10cd7e6ad263e41c662dce43
parent 248273 3b9bd01428c3c9c2caaaf7cc08dcceea688f61d4
child 248275 b4d64177e02feb19194eaf99abc9b6ffffad96a8
push id28893
push userkwierso@gmail.com
push dateFri, 12 Jun 2015 00:02:58 +0000
treeherderautoland@8cf9d3e497f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscajbir
bugs1171330
milestone41.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 1171330: P10. Add TrackBuffersManager object. r=cajbir This implements MSE's SourceBuffer exactly per spec. No memory or speed optimisations of any kind were added for the purpose of being 100% W3C spec compliant.
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBuffer.h
dom/media/mediasource/SourceBufferContentManager.cpp
dom/media/mediasource/SourceBufferContentManager.h
dom/media/mediasource/TrackBuffersManager.cpp
dom/media/mediasource/TrackBuffersManager.h
dom/media/mediasource/moz.build
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -56,51 +56,85 @@ public:
 private:
   nsRefPtr<SourceBuffer> mSourceBuffer;
   uint32_t mUpdateID;
 };
 
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
+  typedef mozilla::SourceBufferContentManager::AppendState AppendState;
+
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetMode(aMode=%d)", aMode);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
-  if (aMode == SourceBufferAppendMode::Sequence) {
+  if (!mIsUsingFormatReader && aMode == SourceBufferAppendMode::Sequence) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
+  if (mIsUsingFormatReader && mGenerateTimestamp &&
+      aMode == SourceBufferAppendMode::Segments) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return;
+  }
   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
-  // TODO: Test append state.
-  // TODO: If aMode is "sequence", set sequence start time.
+  if (mIsUsingFormatReader &&
+      mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  if (mIsUsingFormatReader && aMode == SourceBufferAppendMode::Sequence) {
+    // Will set GroupStartTimestamp to GroupEndTimestamp.
+    mContentManager->RestartGroupStartTimestamp();
+  }
+
   mAppendMode = aMode;
 }
 
 void
 SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
 {
+  typedef mozilla::SourceBufferContentManager::AppendState AppendState;
+
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetTimestampOffset(aTimestampOffset=%f)", aTimestampOffset);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
-  // TODO: Test append state.
-  // TODO: If aMode is "sequence", set sequence start time.
+  if (mIsUsingFormatReader &&
+      mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+  mApparentTimestampOffset = aTimestampOffset;
+  mTimestampOffset = TimeUnit::FromSeconds(aTimestampOffset);
+  if (mIsUsingFormatReader && mAppendMode == SourceBufferAppendMode::Sequence) {
+    mContentManager->SetGroupStartTimestamp(mTimestampOffset);
+  }
+}
+
+void
+SourceBuffer::SetTimestampOffset(const TimeUnit& aTimestampOffset)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
   mTimestampOffset = aTimestampOffset;
+  mApparentTimestampOffset = aTimestampOffset.ToSeconds();
 }
 
 already_AddRefed<TimeRanges>
 SourceBuffer::GetBuffered(ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!IsAttached()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@@ -182,17 +216,17 @@ SourceBuffer::Abort(ErrorResult& aRv)
   mAppendWindowEnd = PositiveInfinity<double>();
 }
 
 void
 SourceBuffer::AbortBufferAppend()
 {
   if (mUpdating) {
     mPendingAppend.DisconnectIfExists();
-    // TODO: Abort segment parser loop, and stream append loop algorithms.
+    // TODO: Abort stream append loop algorithms.
     // cancel any pending buffer append.
     mContentManager->AbortAppendData();
     AbortUpdating();
   }
 }
 
 void
 SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
@@ -255,30 +289,47 @@ SourceBuffer::Ended()
   mContentManager->Ended();
 }
 
 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
   , mAppendWindowStart(0)
   , mAppendWindowEnd(PositiveInfinity<double>())
-  , mTimestampOffset(0)
+  , mApparentTimestampOffset(0)
   , mAppendMode(SourceBufferAppendMode::Segments)
   , mUpdating(false)
   , mActive(false)
   , mUpdateID(0)
   , mType(aType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aMediaSource);
   mEvictionThreshold = Preferences::GetUint("media.mediasource.eviction_threshold",
                                             75 * (1 << 20));
-  mContentManager = SourceBufferContentManager::CreateManager(aMediaSource->GetDecoder(), aType);
+  mContentManager =
+    SourceBufferContentManager::CreateManager(this,
+                                              aMediaSource->GetDecoder(),
+                                              aType);
   MSE_DEBUG("Create mContentManager=%p",
             mContentManager.get());
+  if (aType.LowerCaseEqualsLiteral("audio/mpeg") ||
+      aType.LowerCaseEqualsLiteral("audio/aac")) {
+    mGenerateTimestamp = true;
+  } else {
+    mGenerateTimestamp = false;
+  }
+  mIsUsingFormatReader =
+    Preferences::GetBool("media.mediasource.format-reader", false);
+  ErrorResult dummy;
+  if (mGenerateTimestamp) {
+    SetMode(SourceBufferAppendMode::Sequence, dummy);
+  } else {
+    SetMode(SourceBufferAppendMode::Segments, dummy);
+  }
 }
 
 SourceBuffer::~SourceBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mMediaSource);
   MSE_DEBUG("");
 }
@@ -365,17 +416,17 @@ void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   MSE_DEBUG("AppendData(aLength=%u)", aLength);
 
   nsRefPtr<MediaLargeByteBuffer> data = PrepareAppend(aData, aLength, aRv);
   if (!data) {
     return;
   }
-  mContentManager->AppendData(data, TimeUnit::FromSeconds(mTimestampOffset));
+  mContentManager->AppendData(data, mTimestampOffset);
 
   StartUpdating();
 
   MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments,
              "We don't handle timestampOffset for sequence mode yet");
   nsCOMPtr<nsIRunnable> task = new BufferAppendRunnable(this, mUpdateID);
   NS_DispatchToMainThread(task);
 }
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_SourceBuffer_h_
 #define mozilla_dom_SourceBuffer_h_
 
 #include "MediaPromise.h"
 #include "MediaSource.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/SourceBufferBinding.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/mozalloc.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionNoteChild.h"
@@ -27,18 +28,18 @@
 
 class JSObject;
 struct JSContext;
 
 namespace mozilla {
 
 class ErrorResult;
 class MediaLargeByteBuffer;
-class TrackBuffer;
 template <typename T> class AsyncEventRunner;
+class TrackBuffersManager;
 
 namespace dom {
 
 using media::TimeUnit;
 using media::TimeIntervals;
 
 class TimeRanges;
 
@@ -57,17 +58,17 @@ public:
   {
     return mUpdating;
   }
 
   already_AddRefed<TimeRanges> GetBuffered(ErrorResult& aRv);
 
   double TimestampOffset() const
   {
-    return mTimestampOffset;
+    return mApparentTimestampOffset;
   }
 
   void SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv);
 
   double AppendWindowStart() const
   {
     return mAppendWindowStart;
   }
@@ -127,16 +128,17 @@ public:
   void Dump(const char* aPath);
 #endif
 
 private:
   ~SourceBuffer();
 
   friend class AsyncEventRunner<SourceBuffer>;
   friend class BufferAppendRunnable;
+  friend class mozilla::TrackBuffersManager;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Update mUpdating and fire the appropriate events.
   void StartUpdating();
   void StopUpdating();
   void AbortUpdating();
 
@@ -159,31 +161,37 @@ private:
   // on success or nullptr (with aRv set) on error.
   already_AddRefed<MediaLargeByteBuffer> PrepareAppend(const uint8_t* aData,
                                                        uint32_t aLength,
                                                        ErrorResult& aRv);
 
   void AppendDataCompletedWithSuccess(bool aHasActiveTracks);
   void AppendDataErrored(nsresult aError);
 
+  // Set timestampOffset, must be called on the main thread.
+  void SetTimestampOffset(const TimeUnit& aTimestampOffset);
+
   nsRefPtr<MediaSource> mMediaSource;
 
   uint32_t mEvictionThreshold;
 
   nsRefPtr<SourceBufferContentManager> mContentManager;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;
 
-  double mTimestampOffset;
+  double mApparentTimestampOffset;
+  TimeUnit mTimestampOffset;
 
   SourceBufferAppendMode mAppendMode;
   bool mUpdating;
+  bool mGenerateTimestamp;
+  bool mIsUsingFormatReader;
 
-  bool mActive;
+  mozilla::Atomic<bool> mActive;
 
   // Each time mUpdating is set to true, mUpdateID will be incremented.
   // This allows for a queued AppendData task to identify if it was earlier
   // aborted and another AppendData queued.
   uint32_t mUpdateID;
 
   MediaPromiseRequestHolder<SourceBufferContentManager::AppendPromise> mPendingAppend;
   const nsCString mType;
--- a/dom/media/mediasource/SourceBufferContentManager.cpp
+++ b/dom/media/mediasource/SourceBufferContentManager.cpp
@@ -1,20 +1,29 @@
 /* -*- 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/. */
 
 #include "SourceBufferContentManager.h"
+#include "TrackBuffer.h"
+#include "TrackBuffersManager.h"
 
 namespace mozilla {
 
 already_AddRefed<SourceBufferContentManager>
-SourceBufferContentManager::CreateManager(MediaSourceDecoder* aParentDecoder,
+SourceBufferContentManager::CreateManager(dom::SourceBuffer* aParent,
+                                          MediaSourceDecoder* aParentDecoder,
                                           const nsACString &aType)
 {
   nsRefPtr<SourceBufferContentManager> manager;
-  manager = new TrackBuffer(aParentDecoder, aType);
+  bool useFormatReader =
+    Preferences::GetBool("media.mediasource.format-reader", false);
+  if (useFormatReader) {
+    manager = new TrackBuffersManager(aParent, aParentDecoder, aType);
+  } else {
+    manager = new TrackBuffer(aParentDecoder, aType);
+  }
   return  manager.forget();
 }
 
 }
--- a/dom/media/mediasource/SourceBufferContentManager.h
+++ b/dom/media/mediasource/SourceBufferContentManager.h
@@ -22,17 +22,18 @@ using media::TimeIntervals;
 class SourceBufferContentManager {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferContentManager);
 
   typedef MediaPromise<bool, nsresult, /* IsExclusive = */ true> AppendPromise;
   typedef AppendPromise RangeRemovalPromise;
 
   static already_AddRefed<SourceBufferContentManager>
-  CreateManager(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
+  CreateManager(dom::SourceBuffer* aParent, MediaSourceDecoder* aParentDecoder,
+                const nsACString& aType);
 
   // Add data to the end of the input buffer.
   // Returns false if the append failed.
   virtual bool
   AppendData(MediaLargeByteBuffer* aData, TimeUnit aTimestampOffset) = 0;
 
   // Run MSE Buffer Append Algorithm
   // 3.5.5 Buffer Append Algorithm.
@@ -77,16 +78,33 @@ public:
   virtual int64_t GetSize() = 0;
 
   // Indicate that the MediaSource parent object got into "ended" state.
   virtual void Ended() = 0;
 
   // The parent SourceBuffer is about to be destroyed.
   virtual void Detach() = 0;
 
+  // Current state as per Segment Parser Loop Algorithm
+  // http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
+  enum class AppendState : int32_t
+  {
+    WAITING_FOR_SEGMENT,
+    PARSING_INIT_SEGMENT,
+    PARSING_MEDIA_SEGMENT,
+  };
+
+  virtual AppendState GetAppendState()
+  {
+    return AppendState::WAITING_FOR_SEGMENT;
+  }
+
+  virtual void SetGroupStartTimestamp(const TimeUnit& aGroupStartTimestamp) {}
+  virtual void RestartGroupStartTimestamp() {}
+
 #if defined(DEBUG)
   virtual void Dump(const char* aPath) { }
 #endif
 
 protected:
   virtual ~SourceBufferContentManager() { }
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -0,0 +1,1231 @@
+/* -*- 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/. */
+
+#include "TrackBuffersManager.h"
+#include "SourceBufferResource.h"
+#include "SourceBuffer.h"
+
+#ifdef MOZ_FMP4
+#include "MP4Demuxer.h"
+#endif
+
+#include <limits>
+
+extern PRLogModuleInfo* GetMediaSourceLog();
+
+#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+static const char*
+AppendStateToStr(TrackBuffersManager::AppendState aState)
+{
+  switch (aState) {
+    case TrackBuffersManager::AppendState::WAITING_FOR_SEGMENT:
+      return "WAITING_FOR_SEGMENT";
+    case TrackBuffersManager::AppendState::PARSING_INIT_SEGMENT:
+      return "PARSING_INIT_SEGMENT";
+    case TrackBuffersManager::AppendState::PARSING_MEDIA_SEGMENT:
+      return "PARSING_MEDIA_SEGMENT";
+    default:
+      return "IMPOSSIBLE";
+  }
+}
+
+TrackBuffersManager::TrackBuffersManager(dom::SourceBuffer* aParent, MediaSourceDecoder* aParentDecoder, const nsACString& aType)
+  : mInputBuffer(new MediaLargeByteBuffer)
+  , mAppendState(AppendState::WAITING_FOR_SEGMENT)
+  , mBufferFull(false)
+  , mFirstInitializationSegmentReceived(false)
+  , mActiveTrack(false)
+  , mType(aType)
+  , mParser(ContainerParser::CreateForMIMEType(aType))
+  , mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
+  , mParent(new nsMainThreadPtrHolder<dom::SourceBuffer>(aParent, false /* strict */))
+  , mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(aParentDecoder, false /* strict */))
+  , mAbort(false)
+  , mMonitor("TrackBuffersManager")
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Must be instanciated on the main thread");
+}
+
+bool
+TrackBuffersManager::AppendData(MediaLargeByteBuffer* aData,
+                                TimeUnit aTimestampOffset)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MonitorAutoLock mon(mMonitor);
+  MSE_DEBUG("Appending %lld bytes", aData->Length());
+  mIncomingBuffers.AppendElement(IncomingBuffer(aData, aTimestampOffset));
+  mEnded = false;
+  return true;
+}
+
+nsRefPtr<TrackBuffersManager::AppendPromise>
+TrackBuffersManager::BufferAppend()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mAppendPromise.IsEmpty());
+  nsRefPtr<AppendPromise> p = mAppendPromise.Ensure(__func__);
+
+  nsCOMPtr<nsIRunnable> task =
+    NS_NewRunnableMethod(this, &TrackBuffersManager::InitSegmentParserLoop);
+  GetTaskQueue()->Dispatch(task.forget());
+
+  return p;
+}
+
+// Abort any pending AppendData.
+// We don't really care about really aborting our inner loop as by spec the
+// process is happening asynchronously, as such where and when we would abort is
+// non-deterministic. The SourceBuffer also makes sure BufferAppend
+// isn't called should the appendBuffer be immediately aborted.
+void
+TrackBuffersManager::AbortAppendData()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mAppendPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+}
+
+void
+TrackBuffersManager::ResetParserState()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mAppendPromise.IsEmpty(), "AbortAppendData must have been called");
+
+  // 1. If the append state equals PARSING_MEDIA_SEGMENT and the input buffer contains some complete coded frames, then run the coded frame processing algorithm until all of these complete coded frames have been processed.
+  if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableMethod(this, &TrackBuffersManager::FinishCodedFrameProcessing);
+    GetTaskQueue()->Dispatch(task.forget());
+  } else {
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableMethod(this, &TrackBuffersManager::CompleteResetParserState);
+    GetTaskQueue()->Dispatch(task.forget());
+  }
+}
+
+nsRefPtr<TrackBuffersManager::RangeRemovalPromise>
+TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mEnded = false;
+
+  nsRefPtr<RangeRemovalPromise> p = mRangeRemovalPromise.Ensure(__func__);
+
+  nsCOMPtr<nsIRunnable> task =
+  NS_NewRunnableMethodWithArg<TimeInterval>(
+    this, &TrackBuffersManager::CodedFrameRemoval, TimeInterval(aStart, aEnd));
+  GetTaskQueue()->Dispatch(task.forget());
+  return p;
+}
+
+TrackBuffersManager::EvictDataResult
+TrackBuffersManager::EvictData(TimeUnit aPlaybackTime,
+                               uint32_t aThreshold,
+                               TimeUnit* aBufferStartTime)
+{
+  // TODO.
+  return EvictDataResult::NO_DATA_EVICTED;
+}
+
+void
+TrackBuffersManager::EvictBefore(TimeUnit aTime)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIRunnable> task =
+  NS_NewRunnableMethodWithArg<TimeInterval>(
+    this, &TrackBuffersManager::CodedFrameRemoval,
+    TimeInterval(TimeUnit::FromSeconds(0), aTime));
+  GetTaskQueue()->Dispatch(task.forget());
+}
+
+media::TimeIntervals
+TrackBuffersManager::Buffered()
+{
+  MonitorAutoLock mon(mMonitor);
+  // http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
+  // 2. Let highest end time be the largest track buffer ranges end time across all the track buffers managed by this SourceBuffer object.
+  TimeUnit highestEndTime;
+
+  nsTArray<TimeIntervals*> tracks;
+  if (HasVideo()) {
+    tracks.AppendElement(&mVideoBufferedRanges);
+  }
+  if (HasAudio()) {
+    tracks.AppendElement(&mAudioBufferedRanges);
+  }
+  for (auto trackRanges : tracks) {
+    highestEndTime = std::max(trackRanges->GetEnd(), highestEndTime);
+  }
+
+  // 3. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time.
+  TimeIntervals intersection{TimeInterval(TimeUnit::FromSeconds(0), highestEndTime)};
+
+  // 4. For each track buffer managed by this SourceBuffer, run the following steps:
+  //   1. Let track ranges equal the track buffer ranges for the current track buffer.
+  for (auto trackRanges : tracks) {
+    // 2. If readyState is "ended", then set the end time on the last range in track ranges to highest end time.
+    if (mEnded) {
+      trackRanges->Add(TimeInterval(trackRanges->GetEnd(), highestEndTime));
+    }
+    // 3. Let new intersection ranges equal the intersection between the intersection ranges and the track ranges.
+    intersection.Intersection(*trackRanges);
+  }
+  return intersection;
+}
+
+int64_t
+TrackBuffersManager::GetSize()
+{
+  // TODO
+  return 0;
+}
+
+void
+TrackBuffersManager::Ended()
+{
+  mEnded = true;
+}
+
+void
+TrackBuffersManager::Detach()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mAppendPromise.IsEmpty(), "Abort wasn't called");
+  // Abort any pending promises.
+  mRangeRemovalPromise.ResolveIfExists(false, __func__);
+  // Clear our sourcebuffer
+  nsCOMPtr<nsIRunnable> task =
+  NS_NewRunnableMethodWithArg<TimeInterval>(
+    this, &TrackBuffersManager::CodedFrameRemoval,
+    TimeInterval(TimeUnit::FromSeconds(0), TimeUnit::FromInfinity()));
+  GetTaskQueue()->Dispatch(task.forget());
+}
+
+#if defined(DEBUG)
+void
+TrackBuffersManager::Dump(const char* aPath)
+{
+
+}
+#endif
+
+void
+TrackBuffersManager::FinishCodedFrameProcessing()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (mProcessingRequest.Exists()) {
+    NS_WARNING("Processing request pending");
+    mProcessingRequest.Disconnect();
+  }
+  // The spec requires us to complete parsing synchronously any outstanding
+  // frames in the current media segment. This can't be implemented in a way
+  // that makes sense.
+  // As such we simply completely ignore the result of any pending input buffer.
+  // TODO: Link to W3C bug.
+
+  CompleteResetParserState();
+}
+
+void
+TrackBuffersManager::CompleteResetParserState()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  for (auto track : GetTracksList()) {
+    // 2. Unset the last decode timestamp on all track buffers.
+    track->mLastDecodeTimestamp.reset();
+    // 3. Unset the last frame duration on all track buffers.
+    track->mLastFrameDuration.reset();
+    // 4. Unset the highest end timestamp on all track buffers.
+    track->mHighestEndTimestamp.reset();
+    // 5. Set the need random access point flag on all track buffers to true.
+    track->mNeedRandomAccessPoint = true;
+
+    // if we have been aborted, we may have pending frames that we are going
+    // to discard now.
+    track->mQueuedSamples.Clear();
+  }
+  // 6. Remove all bytes from the input buffer.
+  mIncomingBuffers.Clear();
+  mInputBuffer->Clear();
+  if (mCurrentInputBuffer) {
+    mCurrentInputBuffer->EvictAll();
+    mCurrentInputBuffer = new SourceBufferResource(mType);
+  }
+
+  // We could be left with a demuxer in an unusable state. It needs to be
+  // recreated. We store in the InputBuffer an init segment which will be parsed
+  // during the next Segment Parser Loop and a new demuxer will be created and
+  // initialized.
+  if (mFirstInitializationSegmentReceived) {
+    nsRefPtr<MediaLargeByteBuffer> initData = mParser->InitData();
+    MOZ_ASSERT(initData->Length(), "we must have an init segment");
+    // The aim here is really to destroy our current demuxer.
+    CreateDemuxerforMIMEType();
+    mInputBuffer->AppendElements(*initData);
+  }
+  RecreateParser();
+
+  // 7. Set append state to WAITING_FOR_SEGMENT.
+  SetAppendState(AppendState::WAITING_FOR_SEGMENT);
+
+  // We're done.
+  mAbort = false;
+}
+
+void
+TrackBuffersManager::CodedFrameRemoval(TimeInterval aInterval)
+{
+  MSE_DEBUG("From %.2fs to %.2f",
+            aInterval.mStart.ToSeconds(), aInterval.mEnd.ToSeconds());
+  TimeUnit duration{TimeUnit::FromSeconds(mParentDecoder->GetMediaSourceDuration())};
+
+  MSE_DEBUG("duration:%.2f", duration.ToSeconds());
+  MSE_DEBUG("before video ranges=%s", DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
+  MSE_DEBUG("before audio ranges=%s", DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
+
+  // 1. Let start be the starting presentation timestamp for the removal range.
+  TimeUnit start = aInterval.mStart;
+  // 2. Let end be the end presentation timestamp for the removal range.
+  TimeUnit end = aInterval.mEnd;
+
+  // 3. For each track buffer in this source buffer, run the following steps:
+  for (auto track : GetTracksList()) {
+    MSE_DEBUGV("Processing %s track", track->mInfo->mMimeType.get());
+    // 1. Let remove end timestamp be the current value of duration
+    // See bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=28727
+    TimeUnit removeEndTimestamp = std::max(duration, track->mBufferedRanges.GetEnd());
+
+    // 2. If this track buffer has a random access point timestamp that is greater than or equal to end,
+    // then update remove end timestamp to that random access point timestamp.
+    if (end < track->mBufferedRanges.GetEnd()) {
+      for (auto& frame : track->mBuffers.LastElement()) {
+        if (frame->mKeyframe && frame->mTime >= end.ToMicroseconds()) {
+          removeEndTimestamp = TimeUnit::FromMicroseconds(frame->mTime);
+          break;
+        }
+      }
+    }
+    // 3. Remove all media data, from this track buffer, that contain starting
+    // timestamps greater than or equal to start and less than the remove end timestamp.
+    TimeInterval removedInterval;
+    int32_t firstRemovedIndex = -1;
+    TrackBuffer& data = track->mBuffers.LastElement();
+    for (uint32_t i = 0; i < data.Length(); i++) {
+      const auto& frame = data[i];
+      if (frame->mTime >= start.ToMicroseconds() &&
+          frame->mTime < removeEndTimestamp.ToMicroseconds()) {
+        if (firstRemovedIndex < 0) {
+          removedInterval =
+            TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
+                         TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration));
+          firstRemovedIndex = i;
+        } else {
+          removedInterval = removedInterval.Span(
+            TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
+                         TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration)));
+        }
+        data.RemoveElementAt(i);
+      }
+    }
+    // 4. Remove decoding dependencies of the coded frames removed in the previous step:
+    // Remove all coded frames between the coded frames removed in the previous step and the next random access point after those removed frames.
+    if (firstRemovedIndex >= 0) {
+      for (uint32_t i = firstRemovedIndex; i < data.Length(); i++) {
+        const auto& frame = data[i];
+        if (frame->mKeyframe) {
+          break;
+        }
+        removedInterval = removedInterval.Span(
+          TimeInterval(TimeUnit::FromMicroseconds(frame->mTime),
+                       TimeUnit::FromMicroseconds(frame->mTime + frame->mDuration)));
+        data.RemoveElementAt(i);
+      }
+    }
+    track->mBufferedRanges -= removedInterval;
+
+    // 5. If this object is in activeSourceBuffers, the current playback position
+    // is greater than or equal to start and less than the remove end timestamp,
+    // and HTMLMediaElement.readyState is greater than HAVE_METADATA, then set the
+    // HTMLMediaElement.readyState attribute to HAVE_METADATA and stall playback.
+    // This will be done by the MDSM during playback.
+    // TODO properly, so it works even if paused.
+  }
+  // 4. If buffer full flag equals true and this object is ready to accept more bytes, then set the buffer full flag to false.
+  // TODO.
+  mBufferFull = false;
+  {
+    MonitorAutoLock mon(mMonitor);
+    mVideoBufferedRanges = mVideoTracks.mBufferedRanges;
+    mAudioBufferedRanges = mAudioTracks.mBufferedRanges;
+  }
+  MSE_DEBUG("after video ranges=%s", DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
+  MSE_DEBUG("after audio ranges=%s", DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
+
+  mRangeRemovalPromise.ResolveIfExists(true, __func__);
+}
+
+void
+TrackBuffersManager::InitSegmentParserLoop()
+{
+  AppendIncomingBuffers();
+  SegmentParserLoop();
+}
+
+void
+TrackBuffersManager::AppendIncomingBuffers()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  MonitorAutoLock mon(mMonitor);
+  for (auto& incomingBuffer : mIncomingBuffers) {
+    if (!mInputBuffer->AppendElements(*incomingBuffer.first())) {
+      RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
+    }
+  }
+  if (!mIncomingBuffers.IsEmpty()) {
+    mTimestampOffset = mIncomingBuffers.LastElement().second();
+    mLastTimestampOffset = mTimestampOffset;
+  }
+  mIncomingBuffers.Clear();
+}
+
+void
+TrackBuffersManager::SegmentParserLoop()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  while (true) {
+    bool completeMediaHeader;
+    // 1. If the input buffer is empty, then jump to the need more data step below.
+    if (!mInputBuffer || mInputBuffer->IsEmpty()) {
+      NeedMoreData();
+      return;
+    }
+    // 2. If the input buffer contains bytes that violate the SourceBuffer
+    // byte stream format specification, then run the append error algorithm with
+    // the decode error parameter set to true and abort this algorithm.
+    // TODO
+
+    // 3. Remove any bytes that the byte stream format specifications say must be
+    // ignored from the start of the input buffer.
+    // TODO
+
+    // 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
+    // steps:
+    if (mAppendState == AppendState::WAITING_FOR_SEGMENT) {
+      if (mParser->IsInitSegmentPresent(mInputBuffer)) {
+        SetAppendState(AppendState::PARSING_INIT_SEGMENT);
+        continue;
+      }
+      if (mParser->IsMediaSegmentPresent(mInputBuffer)) {
+        SetAppendState(AppendState::PARSING_MEDIA_SEGMENT);
+        continue;
+      }
+      // We have neither an init segment nor a media segment, this is invalid
+      // data. However, as we do not remove any bytes that are supposed to be
+      // ignored, we simply ignore them.
+      MSE_DEBUG("Found invalid data, ignoring for now");
+      NeedMoreData();
+      return;
+    }
+
+    int64_t start, end;
+    completeMediaHeader =
+      mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+
+    // 5. If the append state equals PARSING_INIT_SEGMENT, then run the
+    // following steps:
+    if (mAppendState == AppendState::PARSING_INIT_SEGMENT) {
+      if (mParser->InitSegmentRange().IsNull()) {
+        NeedMoreData();
+        return;
+      }
+      InitializationSegmentReceived();
+      return;
+    }
+    if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
+      // 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm.
+      if (!mFirstInitializationSegmentReceived) {
+        RejectAppend(NS_ERROR_FAILURE, __func__);
+        return;
+      }
+      // 2. If the input buffer does not contain a complete media segment header yet, then jump to the need more data step below.
+      if (!completeMediaHeader) {
+        NeedMoreData();
+        return;
+      }
+      // 3. If the input buffer contains one or more complete coded frames, then run the coded frame processing algorithm.
+      nsRefPtr<TrackBuffersManager> self = this;
+      mProcessingRequest.Begin(CodedFrameProcessing()
+          ->Then(GetTaskQueue(), __func__,
+                 [self] (bool aNeedMoreData) {
+                   self->mProcessingRequest.Complete();
+                   if (aNeedMoreData || self->mAbort) {
+                     self->NeedMoreData();
+                   } else {
+                     self->ScheduleSegmentParserLoop();
+                   }
+                 },
+                 [self] (nsresult aRejectValue) {
+                   self->mProcessingRequest.Complete();
+                   self->RejectAppend(aRejectValue, __func__);
+                 }));
+      return;
+    }
+  }
+}
+
+void
+TrackBuffersManager::NeedMoreData()
+{
+  MSE_DEBUG("");
+  // The entire mInputBuffer will be reparsed on the next Segment Parser Loop
+  // run, clear the current ContainerParser so it won't treat the same data
+  // twice which would shift all our offsets incorrectly.
+  RecreateParser();
+  RestoreCachedVariables();
+  mAppendPromise.ResolveIfExists(mActiveTrack, __func__);
+}
+
+void
+TrackBuffersManager::RejectAppend(nsresult aRejectValue, const char* aName)
+{
+  MSE_DEBUG("rv=%d", aRejectValue);
+  mAppendPromise.RejectIfExists(aRejectValue, aName);
+}
+
+void
+TrackBuffersManager::ScheduleSegmentParserLoop()
+{
+  nsCOMPtr<nsIRunnable> task =
+    NS_NewRunnableMethod(this, &TrackBuffersManager::SegmentParserLoop);
+  GetTaskQueue()->Dispatch(task.forget());
+}
+
+void
+TrackBuffersManager::CreateDemuxerforMIMEType()
+{
+  if (mVideoTracks.mDemuxer) {
+    mVideoTracks.mDemuxer->BreakCycles();
+    mVideoTracks.mDemuxer = nullptr;
+  }
+  if (mAudioTracks.mDemuxer) {
+    mAudioTracks.mDemuxer->BreakCycles();
+    mAudioTracks.mDemuxer = nullptr;
+  }
+  mInputDemuxer = nullptr;
+  if (mType.LowerCaseEqualsLiteral("video/webm") || mType.LowerCaseEqualsLiteral("audio/webm")) {
+    MOZ_ASSERT(false, "Waiting on WebMDemuxer");
+  // mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer);
+  }
+
+#ifdef MOZ_FMP4
+  if (mType.LowerCaseEqualsLiteral("video/mp4") || mType.LowerCaseEqualsLiteral("audio/mp4")) {
+    mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
+    return;
+  }
+#endif
+  MOZ_ASSERT(false, "Not supported (yet)");
+}
+
+void
+TrackBuffersManager::InitializationSegmentReceived()
+{
+  MOZ_ASSERT(mParser->HasCompleteInitData());
+  mCurrentInputBuffer = new SourceBufferResource(mType);
+  mCurrentInputBuffer->AppendData(mParser->InitData());
+  CreateDemuxerforMIMEType();
+  if (!mInputDemuxer) {
+    MOZ_ASSERT(false, "TODO type not supported");
+    return;
+  }
+  mDemuxerInitRequest.Begin(mInputDemuxer->Init()
+                      ->Then(GetTaskQueue(), __func__,
+                             this,
+                             &TrackBuffersManager::OnDemuxerInitDone,
+                             &TrackBuffersManager::OnDemuxerInitFailed));
+}
+
+void
+TrackBuffersManager::OnDemuxerInitDone(nsresult)
+{
+  mDemuxerInitRequest.Complete();
+
+  if (mAbort) {
+    RejectAppend(NS_ERROR_ABORT, __func__);
+    return;
+  }
+
+  MediaInfo info;
+
+  uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
+  if (numVideos) {
+    // We currently only handle the first video track.
+    mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
+    MOZ_ASSERT(mVideoTracks.mDemuxer);
+    info.mVideo = *mVideoTracks.mDemuxer->GetInfo()->GetAsVideoInfo();
+  }
+
+  uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
+  if (numAudios) {
+    // We currently only handle the first audio track.
+    mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
+    MOZ_ASSERT(mAudioTracks.mDemuxer);
+    info.mAudio = *mAudioTracks.mDemuxer->GetInfo()->GetAsAudioInfo();
+  }
+
+  int64_t videoDuration = numVideos ? info.mVideo.mDuration : 0;
+  int64_t audioDuration = numAudios ? info.mAudio.mDuration : 0;
+
+  int64_t duration = std::max(videoDuration, audioDuration);
+  // 1. Update the duration attribute if it currently equals NaN.
+  // Those steps are performed by the MediaSourceDecoder::SetInitialDuration
+  nsCOMPtr<nsIRunnable> task =
+    NS_NewRunnableMethodWithArg<int64_t>(mParentDecoder,
+                                         &MediaSourceDecoder::SetInitialDuration,
+                                         duration ? duration : -1);
+  AbstractThread::MainThread()->Dispatch(task.forget());
+
+  // 2. If the initialization segment has no audio, video, or text tracks, then
+  // run the append error algorithm with the decode error parameter set to true
+  // and abort these steps.
+  if (!numVideos && !numAudios) {
+    RejectAppend(NS_ERROR_FAILURE, __func__);
+    return;
+  }
+
+  // 3. If the first initialization segment received flag is true, then run the following steps:
+  if (mFirstInitializationSegmentReceived) {
+    if (numVideos != mVideoTracks.mNumTracks ||
+        numAudios != mAudioTracks.mNumTracks ||
+        (numVideos && info.mVideo.mMimeType != mVideoTracks.mInfo->mMimeType) ||
+        (numAudios && info.mAudio.mMimeType != mAudioTracks.mInfo->mMimeType)) {
+      RejectAppend(NS_ERROR_FAILURE, __func__);
+      return;
+    }
+    // 1. If more than one track for a single type are present (ie 2 audio tracks),
+    // then the Track IDs match the ones in the first initialization segment.
+    // TODO
+    // 2. Add the appropriate track descriptions from this initialization
+    // segment to each of the track buffers.
+    // TODO
+    // 3. Set the need random access point flag on all track buffers to true.
+    mVideoTracks.mNeedRandomAccessPoint = true;
+    mAudioTracks.mNeedRandomAccessPoint = true;
+  }
+
+  // 4. Let active track flag equal false.
+  mActiveTrack = false;
+
+  // 5. If the first initialization segment received flag is false, then run the following steps:
+  if (!mFirstInitializationSegmentReceived) {
+    mAudioTracks.mNumTracks = numAudios;
+    // TODO:
+    // 1. If the initialization segment contains tracks with codecs the user agent
+    // does not support, then run the append error algorithm with the decode
+    // error parameter set to true and abort these steps.
+
+    // 2. For each audio track in the initialization segment, run following steps:
+    // for (uint32_t i = 0; i < numAudios; i++) {
+    if (numAudios) {
+      // 1. Let audio byte stream track ID be the Track ID for the current track being processed.
+      // 2. Let audio language be a BCP 47 language tag for the language specified in the initialization segment for this track or an empty string if no language info is present.
+      // 3. If audio language equals an empty string or the 'und' BCP 47 value, then run the default track language algorithm with byteStreamTrackID set to audio byte stream track ID and type set to "audio" and assign the value returned by the algorithm to audio language.
+      // 4. Let audio label be a label specified in the initialization segment for this track or an empty string if no label info is present.
+      // 5. If audio label equals an empty string, then run the default track label algorithm with byteStreamTrackID set to audio byte stream track ID and type set to "audio" and assign the value returned by the algorithm to audio label.
+      // 6. Let audio kinds be an array of kind strings specified in the initialization segment for this track or an empty array if no kind information is provided.
+      // 7. If audio kinds equals an empty array, then run the default track kinds algorithm with byteStreamTrackID set to audio byte stream track ID and type set to "audio" and assign the value returned by the algorithm to audio kinds.
+      // 8. For each value in audio kinds, run the following steps:
+      //   1. Let current audio kind equal the value from audio kinds for this iteration of the loop.
+      //   2. Let new audio track be a new AudioTrack object.
+      //   3. Generate a unique ID and assign it to the id property on new audio track.
+      //   4. Assign audio language to the language property on new audio track.
+      //   5. Assign audio label to the label property on new audio track.
+      //   6. Assign current audio kind to the kind property on new audio track.
+      //   7. If audioTracks.length equals 0, then run the following steps:
+      //     1. Set the enabled property on new audio track to true.
+      //     2. Set active track flag to true.
+      mActiveTrack = true;
+      //   8. Add new audio track to the audioTracks attribute on this SourceBuffer object.
+      //   9. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object referenced by the audioTracks attribute on this SourceBuffer object.
+      //   10. Add new audio track to the audioTracks attribute on the HTMLMediaElement.
+      //   11. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the AudioTrackList object referenced by the audioTracks attribute on the HTMLMediaElement.
+      mAudioTracks.mBuffers.AppendElement(TrackBuffer());
+      // 10. Add the track description for this track to the track buffer.
+      mAudioTracks.mInfo = info.mAudio.Clone();
+    }
+
+    mVideoTracks.mNumTracks = numVideos;
+    // 3. For each video track in the initialization segment, run following steps:
+    // for (uint32_t i = 0; i < numVideos; i++) {
+    if (numVideos) {
+      // 1. Let video byte stream track ID be the Track ID for the current track being processed.
+      // 2. Let video language be a BCP 47 language tag for the language specified in the initialization segment for this track or an empty string if no language info is present.
+      // 3. If video language equals an empty string or the 'und' BCP 47 value, then run the default track language algorithm with byteStreamTrackID set to video byte stream track ID and type set to "video" and assign the value returned by the algorithm to video language.
+      // 4. Let video label be a label specified in the initialization segment for this track or an empty string if no label info is present.
+      // 5. If video label equals an empty string, then run the default track label algorithm with byteStreamTrackID set to video byte stream track ID and type set to "video" and assign the value returned by the algorithm to video label.
+      // 6. Let video kinds be an array of kind strings specified in the initialization segment for this track or an empty array if no kind information is provided.
+      // 7. If video kinds equals an empty array, then run the default track kinds algorithm with byteStreamTrackID set to video byte stream track ID and type set to "video" and assign the value returned by the algorithm to video kinds.
+      // 8. For each value in video kinds, run the following steps:
+      //   1. Let current video kind equal the value from video kinds for this iteration of the loop.
+      //   2. Let new video track be a new VideoTrack object.
+      //   3. Generate a unique ID and assign it to the id property on new video track.
+      //   4. Assign video language to the language property on new video track.
+      //   5. Assign video label to the label property on new video track.
+      //   6. Assign current video kind to the kind property on new video track.
+      //   7. If videoTracks.length equals 0, then run the following steps:
+      //     1. Set the selected property on new video track to true.
+      //     2. Set active track flag to true.
+      mActiveTrack = true;
+      //   8. Add new video track to the videoTracks attribute on this SourceBuffer object.
+      //   9. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object referenced by the videoTracks attribute on this SourceBuffer object.
+      //   10. Add new video track to the videoTracks attribute on the HTMLMediaElement.
+      //   11. Queue a task to fire a trusted event named addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the VideoTrackList object referenced by the videoTracks attribute on the HTMLMediaElement.
+      mVideoTracks.mBuffers.AppendElement(TrackBuffer());
+      // 10. Add the track description for this track to the track buffer.
+      mVideoTracks.mInfo = info.mVideo.Clone();
+    }
+    // 4. For each text track in the initialization segment, run following steps:
+    // 5. If active track flag equals true, then run the following steps:
+    // This is handled by SourceBuffer once the promise is resolved.
+
+    // 6. Set first initialization segment received flag to true.
+    mFirstInitializationSegmentReceived = true;
+  }
+
+  // TODO CHECK ENCRYPTION
+  UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
+  if (crypto && crypto->IsEncrypted()) {
+#ifdef MOZ_EME
+    // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
+    for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
+//      NS_DispatchToMainThread(
+//        new DispatchKeyNeededEvent(mParentDecoder, crypto->mInitDatas[i].mInitData, NS_LITERAL_STRING("cenc")));
+    }
+#endif // MOZ_EME
+    info.mCrypto = *crypto;
+    mEncrypted = true;
+  }
+
+  {
+    MonitorAutoLock mon(mMonitor);
+    mInfo = info;
+  }
+
+  // 3. Remove the initialization segment bytes from the beginning of the input buffer.
+  mInputBuffer->RemoveElementsAt(0, mParser->InitSegmentRange().mEnd);
+  RecreateParser();
+
+  // 4. Set append state to WAITING_FOR_SEGMENT.
+  SetAppendState(AppendState::WAITING_FOR_SEGMENT);
+  // 5. Jump to the loop top step above.
+  ScheduleSegmentParserLoop();
+}
+
+void
+TrackBuffersManager::OnDemuxerInitFailed(DemuxerFailureReason aFailure)
+{
+  MOZ_ASSERT(aFailure != DemuxerFailureReason::WAITING_FOR_DATA);
+  mDemuxerInitRequest.Complete();
+
+  RejectAppend(NS_ERROR_FAILURE, __func__);
+}
+
+nsRefPtr<TrackBuffersManager::CodedFrameProcessingPromise>
+TrackBuffersManager::CodedFrameProcessing()
+{
+  MOZ_ASSERT(mProcessingPromise.IsEmpty());
+  nsRefPtr<CodedFrameProcessingPromise> p = mProcessingPromise.Ensure(__func__);
+
+  int64_t offset = mCurrentInputBuffer->GetLength();
+  MediaByteRange mediaRange = mParser->MediaSegmentRange();
+  // The mediaRange is offset by the init segment position previously added.
+  int64_t rangeOffset = mParser->InitData()->Length();
+  int64_t length;
+  if (mediaRange.IsNull()) {
+    length = mInputBuffer->Length();
+    mCurrentInputBuffer->AppendData(mInputBuffer);
+  } else {
+    nsRefPtr<MediaLargeByteBuffer> segment = new MediaLargeByteBuffer;
+    length = mediaRange.Length();
+    MOZ_ASSERT(mInputBuffer->Length() >= uint64_t(mediaRange.mEnd - rangeOffset),
+               "We're missing some data");
+    MOZ_ASSERT(mediaRange.mStart >= rangeOffset, "Invalid media segment range");
+    if (!segment->AppendElements(mInputBuffer->Elements() +
+                                 mediaRange.mStart - rangeOffset, length)) {
+      return CodedFrameProcessingPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
+    }
+    mCurrentInputBuffer->AppendData(segment);
+  }
+  mInputDemuxer->NotifyDataArrived(length, offset);
+
+  DoDemuxVideo();
+
+  return p;
+}
+
+void
+TrackBuffersManager::OnDemuxFailed(TrackType aTrack,
+                                   DemuxerFailureReason aFailure)
+{
+  MSE_DEBUG("Failed to demux %s, failure = %d",
+      aTrack == TrackType::kVideoTrack ? "video" : "audio", aFailure);
+  if (mAbort) {
+    mProcessingPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+    return;
+  }
+  switch (aFailure) {
+    case DemuxerFailureReason::END_OF_STREAM:
+    case DemuxerFailureReason::WAITING_FOR_DATA:
+      if (aTrack == TrackType::kVideoTrack) {
+        DoDemuxAudio();
+      } else {
+        CompleteCodedFrameProcessing();
+      }
+      break;
+    case DemuxerFailureReason::DEMUXER_ERROR:
+      mProcessingPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+      break;
+    case DemuxerFailureReason::CANCELED:
+    case DemuxerFailureReason::SHUTDOWN:
+      mProcessingPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+      break;
+    default:
+      MOZ_ASSERT(false);
+      break;
+  }
+}
+
+void
+TrackBuffersManager::DoDemuxVideo()
+{
+  if (!HasVideo()) {
+    DoDemuxAudio();
+    return;
+  }
+  mVideoTracks.mDemuxRequest.Begin(mVideoTracks.mDemuxer->GetSamples(-1)
+                             ->Then(GetTaskQueue(), __func__, this,
+                                    &TrackBuffersManager::OnVideoDemuxCompleted,
+                                    &TrackBuffersManager::OnVideoDemuxFailed));
+}
+
+void
+TrackBuffersManager::OnVideoDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
+{
+  MSE_DEBUG("%d video samples demuxed", aSamples->mSamples.Length());
+  mVideoTracks.mDemuxRequest.Complete();
+  if (mAbort) {
+    mProcessingPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+    return;
+  }
+  mVideoTracks.mQueuedSamples.AppendElements(aSamples->mSamples);
+  DoDemuxAudio();
+}
+
+void
+TrackBuffersManager::DoDemuxAudio()
+{
+  if (mAbort) {
+    mProcessingPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+    return;
+  }
+  if (!HasAudio()) {
+    CompleteCodedFrameProcessing();
+    return;
+  }
+  mAudioTracks.mDemuxRequest.Begin(mAudioTracks.mDemuxer->GetSamples(-1)
+                             ->Then(GetTaskQueue(), __func__, this,
+                                    &TrackBuffersManager::OnAudioDemuxCompleted,
+                                    &TrackBuffersManager::OnAudioDemuxFailed));
+}
+
+void
+TrackBuffersManager::OnAudioDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
+{
+  MSE_DEBUG("%d audio samples demuxed", aSamples->mSamples.Length());
+  mAudioTracks.mDemuxRequest.Complete();
+  if (mAbort) {
+    mProcessingPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+    return;
+  }
+  mAudioTracks.mQueuedSamples.AppendElements(aSamples->mSamples);
+  CompleteCodedFrameProcessing();
+}
+
+void
+TrackBuffersManager::CompleteCodedFrameProcessing()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  // 1. For each coded frame in the media segment run the following steps:
+
+  for (auto& sample : mVideoTracks.mQueuedSamples) {
+    while (true) {
+      if (!ProcessFrame(sample, mVideoTracks)) {
+        break;
+      }
+    }
+  }
+  mVideoTracks.mQueuedSamples.Clear();
+
+  for (auto& sample : mAudioTracks.mQueuedSamples) {
+    while (true) {
+      if (!ProcessFrame(sample, mAudioTracks)) {
+        break;
+      }
+    }
+  }
+  mAudioTracks.mQueuedSamples.Clear();
+
+  {
+    MonitorAutoLock mon(mMonitor);
+
+    // Save our final tracks buffered ranges.
+    mVideoBufferedRanges = mVideoTracks.mBufferedRanges;
+    mAudioBufferedRanges = mAudioTracks.mBufferedRanges;
+  }
+
+  // Return to step 6.4 of Segment Parser Loop algorithm
+  // 4. If this SourceBuffer is full and cannot accept more media data, then set the buffer full flag to true.
+  // TODO
+  mBufferFull = false;
+
+  // 5. If the input buffer does not contain a complete media segment, then jump to the need more data step below.
+  if (mParser->MediaSegmentRange().IsNull()) {
+    mProcessingPromise.ResolveIfExists(true, __func__);
+    return;
+  }
+
+  // 6. Remove the media segment bytes from the beginning of the input buffer.
+  int64_t rangeOffset = mParser->InitSegmentRange().mEnd;
+  mInputBuffer->RemoveElementsAt(0, mParser->MediaSegmentRange().mEnd - rangeOffset);
+  RecreateParser();
+
+  // Clear our demuxer from any already processed data.
+  // As we have handled a complete media segment, it is safe to evict all data
+  // from the resource.
+  mCurrentInputBuffer->EvictAll();
+  mInputDemuxer->NotifyDataRemoved();
+
+  // 7. Set append state to WAITING_FOR_SEGMENT.
+  SetAppendState(AppendState::WAITING_FOR_SEGMENT);
+
+  // 8. Jump to the loop top step above.
+  mProcessingPromise.ResolveIfExists(false, __func__);
+}
+
+bool
+TrackBuffersManager::ProcessFrame(MediaRawData* aSample,
+                                  TrackData& aTrackData)
+{
+  TrackData* tracks[] = { &mVideoTracks, &mAudioTracks };
+  TimeUnit presentationTimestamp;
+  TimeUnit decodeTimestamp;
+
+  if (!mParent->mGenerateTimestamp) {
+    presentationTimestamp = TimeUnit::FromMicroseconds(aSample->mTime);
+    decodeTimestamp = TimeUnit::FromMicroseconds(aSample->mTimecode);
+  }
+
+  // 2. Let frame duration be a double precision floating point representation of the coded frame's duration in seconds.
+  TimeUnit frameDuration{TimeUnit::FromMicroseconds(aSample->mDuration)};
+
+  // 3. If mode equals "sequence" and group start timestamp is set, then run the following steps:
+  if (mParent->mAppendMode == SourceBufferAppendMode::Sequence &&
+      mGroupStartTimestamp.isSome()) {
+    mTimestampOffset = mGroupStartTimestamp.ref();
+    mGroupEndTimestamp = mGroupStartTimestamp.ref();
+    mVideoTracks.mNeedRandomAccessPoint = true;
+    mAudioTracks.mNeedRandomAccessPoint = true;
+    mGroupStartTimestamp.reset();
+  }
+
+  // 4. If timestampOffset is not 0, then run the following steps:
+  if (mTimestampOffset != TimeUnit::FromSeconds(0)) {
+    presentationTimestamp += mTimestampOffset;
+    decodeTimestamp += mTimestampOffset;
+  }
+
+  MSE_DEBUGV("Processing %s frame(pts:%lld end:%lld, dts:%lld, duration:%lld, "
+             "kf:%d)",
+             aTrackData.mInfo->mMimeType.get(),
+             presentationTimestamp.ToMicroseconds(),
+             (presentationTimestamp + frameDuration).ToMicroseconds(),
+             decodeTimestamp.ToMicroseconds(),
+             frameDuration.ToMicroseconds(),
+             aSample->mKeyframe);
+
+  // 5. Let track buffer equal the track buffer that the coded frame will be added to.
+  auto& trackBuffer = aTrackData;
+
+  // 6. If last decode timestamp for track buffer is set and decode timestamp is less than last decode timestamp:
+  // OR
+  // If last decode timestamp for track buffer is set and the difference between decode timestamp and last decode timestamp is greater than 2 times last frame duration:
+
+  // TODO: Maybe we should be using TimeStamp and TimeDuration instead?
+
+  if ((trackBuffer.mLastDecodeTimestamp.isSome() &&
+       decodeTimestamp < trackBuffer.mLastDecodeTimestamp.ref()) ||
+      (trackBuffer.mLastDecodeTimestamp.isSome() &&
+       decodeTimestamp - trackBuffer.mLastDecodeTimestamp.ref() > 2*trackBuffer.mLastFrameDuration.ref())) {
+    if (mParent->mAppendMode == SourceBufferAppendMode::Segments) {
+      mGroupEndTimestamp = presentationTimestamp;
+    }
+    if (mParent->mAppendMode == SourceBufferAppendMode::Sequence) {
+      mGroupStartTimestamp = Some(mGroupEndTimestamp);
+    }
+    for (auto& track : tracks) {
+      track->mLastDecodeTimestamp.reset();
+      track->mLastFrameDuration.reset();
+      track->mHighestEndTimestamp.reset();
+      track->mNeedRandomAccessPoint = true;
+    }
+    return true;
+  }
+
+  // 7. Let frame end timestamp equal the sum of presentation timestamp and frame duration.
+  TimeUnit frameEndTimestamp = presentationTimestamp + frameDuration;
+
+  // 8. If presentation timestamp is less than appendWindowStart, then set the need random access point flag to true, drop the coded frame, and jump to the top of the loop to start processing the next coded frame.
+  if (presentationTimestamp.ToSeconds() < mParent->mAppendWindowStart) {
+    trackBuffer.mNeedRandomAccessPoint = true;
+    return false;
+  }
+
+  // 9. If frame end timestamp is greater than appendWindowEnd, then set the need random access point flag to true, drop the coded frame, and jump to the top of the loop to start processing the next coded frame.
+  if (frameEndTimestamp.ToSeconds() > mParent->mAppendWindowEnd) {
+    trackBuffer.mNeedRandomAccessPoint = true;
+    return false;
+  }
+
+  // 10. If the need random access point flag on track buffer equals true, then run the following steps:
+  if (trackBuffer.mNeedRandomAccessPoint) {
+    // 1. If the coded frame is not a random access point, then drop the coded frame and jump to the top of the loop to start processing the next coded frame.
+    if (!aSample->mKeyframe) {
+      return false;
+    }
+    // 2. Set the need random access point flag on track buffer to false.
+    trackBuffer.mNeedRandomAccessPoint = false;
+  }
+
+  // TODO: Handle splicing of audio (and text) frames.
+  // 11. Let spliced audio frame be an unset variable for holding audio splice information
+  // 12. Let spliced timed text frame be an unset variable for holding timed text splice information
+
+  // 13. If last decode timestamp for track buffer is unset and presentation timestamp falls within the presentation interval of a coded frame in track buffer,then run the following steps:
+  // For now we only handle replacing existing frames with the new ones. So we
+  // skip this step.
+
+  // 14. Remove existing coded frames in track buffer:
+
+  // There is an ambiguity on how to remove frames, which was lodged with:
+  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28710, implementing as per
+  // bug description.
+  int firstRemovedIndex = -1;
+  TimeInterval removedInterval;
+  TrackBuffer& data = trackBuffer.mBuffers.LastElement();
+  if (trackBuffer.mBufferedRanges.Contains(presentationTimestamp)) {
+    if (trackBuffer.mHighestEndTimestamp.isNothing()) {
+      for (uint32_t i = 0; i < data.Length(); i++) {
+        MediaRawData* sample = data[i].get();
+        if (sample->mTime >= presentationTimestamp.ToMicroseconds() &&
+            sample->mTime < frameEndTimestamp.ToMicroseconds()) {
+          if (firstRemovedIndex < 0) {
+            removedInterval =
+              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration));
+            firstRemovedIndex = i;
+          } else {
+            removedInterval = removedInterval.Span(
+              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
+          }
+          data.RemoveElementAt(i);
+        }
+      }
+    } else if (trackBuffer.mHighestEndTimestamp.ref() <= presentationTimestamp) {
+      for (uint32_t i = 0; i < data.Length(); i++) {
+        MediaRawData* sample = data[i].get();
+        if (sample->mTime >= trackBuffer.mHighestEndTimestamp.ref().ToMicroseconds() &&
+            sample->mTime < frameEndTimestamp.ToMicroseconds()) {
+          if (firstRemovedIndex < 0) {
+            removedInterval =
+              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration));
+            firstRemovedIndex = i;
+          } else {
+            removedInterval = removedInterval.Span(
+              TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                           TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
+          }
+          data.RemoveElementAt(i);
+        }
+      }
+    }
+  }
+  // 15. Remove decoding dependencies of the coded frames removed in the previous step:
+  // Remove all coded frames between the coded frames removed in the previous step and the next random access point after those removed frames.
+  if (firstRemovedIndex >= 0) {
+    for (uint32_t i = firstRemovedIndex; i < data.Length(); i++) {
+      MediaRawData* sample = data[i].get();
+      if (sample->mKeyframe) {
+        break;
+      }
+      removedInterval = removedInterval.Span(
+        TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
+                     TimeUnit::FromMicroseconds(sample->mTime + sample->mDuration)));
+      data.RemoveElementAt(i);
+    }
+    // Update our buffered range to exclude the range just removed.
+    trackBuffer.mBufferedRanges -= removedInterval;
+  }
+
+  // 16. Add the coded frame with the presentation timestamp, decode timestamp, and frame duration to the track buffer.
+  aSample->mTime = presentationTimestamp.ToMicroseconds();
+  aSample->mTimecode = decodeTimestamp.ToMicroseconds();
+  if (firstRemovedIndex >= 0) {
+    data.InsertElementAt(firstRemovedIndex, aSample);
+  } else {
+    data.AppendElement(aSample);
+  }
+  // 17. Set last decode timestamp for track buffer to decode timestamp.
+  trackBuffer.mLastDecodeTimestamp = Some(decodeTimestamp);
+  // 18. Set last frame duration for track buffer to frame duration.
+  trackBuffer.mLastFrameDuration = Some(TimeUnit::FromMicroseconds(aSample->mDuration));
+  // 19. If highest end timestamp for track buffer is unset or frame end timestamp is greater than highest end timestamp, then set highest end timestamp for track buffer to frame end timestamp.
+  if (trackBuffer.mHighestEndTimestamp.isNothing() ||
+      frameEndTimestamp > trackBuffer.mHighestEndTimestamp.ref()) {
+    trackBuffer.mHighestEndTimestamp = Some(frameEndTimestamp);
+  }
+  // 20. If frame end timestamp is greater than group end timestamp, then set group end timestamp equal to frame end timestamp.
+  if (frameEndTimestamp > mGroupEndTimestamp) {
+    mGroupEndTimestamp = frameEndTimestamp;
+  }
+  // 21. If generate timestamps flag equals true, then set timestampOffset equal to frame end timestamp.
+  if (mParent->mGenerateTimestamp) {
+    mTimestampOffset = frameEndTimestamp;
+  }
+
+  // Update our buffered range with new sample interval.
+  // We allow a fuzz factor in our interval of half a frame length,
+  // as fuzz is +/- value, giving an effective leeway of a full frame
+  // length.
+  trackBuffer.mBufferedRanges +=
+    TimeInterval(presentationTimestamp, frameEndTimestamp,
+                 TimeUnit::FromMicroseconds(aSample->mDuration / 2));
+  return false;
+}
+
+void
+TrackBuffersManager::RecreateParser()
+{
+  // Recreate our parser for only the data remaining. This is required
+  // as it has parsed the entire InputBuffer provided.
+  // Once the old TrackBuffer/MediaSource implementation is removed
+  // we can optimize this part. TODO
+  nsRefPtr<MediaLargeByteBuffer> initData = mParser->InitData();
+  mParser = ContainerParser::CreateForMIMEType(mType);
+  if (initData) {
+    int64_t start, end;
+    mParser->ParseStartAndEndTimestamps(initData, start, end);
+  }
+}
+
+nsTArray<TrackBuffersManager::TrackData*>
+TrackBuffersManager::GetTracksList()
+{
+  nsTArray<TrackData*> tracks;
+  if (HasVideo()) {
+    tracks.AppendElement(&mVideoTracks);
+  }
+  if (HasAudio()) {
+    tracks.AppendElement(&mAudioTracks);
+  }
+  return tracks;
+}
+
+void
+TrackBuffersManager::RestoreCachedVariables()
+{
+  if (mTimestampOffset != mLastTimestampOffset) {
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableMethodWithArg<TimeUnit>(
+        mParent.get(),
+        static_cast<void (dom::SourceBuffer::*)(const TimeUnit&)>(&dom::SourceBuffer::SetTimestampOffset), /* beauty uh? :) */
+        mTimestampOffset);
+    AbstractThread::MainThread()->Dispatch(task.forget());
+  }
+}
+
+void
+TrackBuffersManager::SetAppendState(TrackBuffersManager::AppendState aAppendState)
+{
+  MSE_DEBUG("AppendState changed from %s to %s",
+            AppendStateToStr(mAppendState), AppendStateToStr(aAppendState));
+  mAppendState = aAppendState;
+}
+
+void
+TrackBuffersManager::SetGroupStartTimestamp(const TimeUnit& aGroupStartTimestamp)
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableMethodWithArg<TimeUnit>(
+        this,
+        &TrackBuffersManager::SetGroupStartTimestamp,
+        aGroupStartTimestamp);
+    GetTaskQueue()->Dispatch(task.forget());
+    return;
+  }
+  MOZ_ASSERT(OnTaskQueue());
+  mGroupStartTimestamp = Some(aGroupStartTimestamp);
+}
+
+void
+TrackBuffersManager::RestartGroupStartTimestamp()
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableMethod(this, &TrackBuffersManager::RestartGroupStartTimestamp);
+    GetTaskQueue()->Dispatch(task.forget());
+    return;
+  }
+  MOZ_ASSERT(OnTaskQueue());
+  mGroupStartTimestamp = Some(mGroupEndTimestamp);
+}
+
+TrackBuffersManager::~TrackBuffersManager()
+{
+  mTaskQueue->BeginShutdown();
+  mTaskQueue = nullptr;
+}
+
+MediaInfo
+TrackBuffersManager::GetMetadata()
+{
+  MonitorAutoLock mon(mMonitor);
+  return mInfo;
+}
+
+const TimeIntervals&
+TrackBuffersManager::Buffered(TrackInfo::TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return GetTracksData(aTrack).mBufferedRanges;
+}
+
+const TrackBuffersManager::TrackBuffer&
+TrackBuffersManager::GetTrackBuffer(TrackInfo::TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return GetTracksData(aTrack).mBuffers.LastElement();
+}
+
+}
+#undef MSE_DEBUG
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -0,0 +1,239 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_TRACKBUFFERSMANAGER_H_
+#define MOZILLA_TRACKBUFFERSMANAGER_H_
+
+#include "SourceBufferContentManager.h"
+#include "MediaSourceDecoder.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Pair.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class ContainerParser;
+class MediaLargeByteBuffer;
+class MediaRawData;
+class SourceBuffer;
+class SourceBufferResource;
+
+using media::TimeUnit;
+using media::TimeInterval;
+using media::TimeIntervals;
+using dom::SourceBufferAppendMode;
+
+class TrackBuffersManager : public SourceBufferContentManager {
+public:
+  typedef MediaPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
+  typedef TrackInfo::TrackType TrackType;
+  typedef MediaData::Type MediaType;
+  typedef nsTArray<nsRefPtr<MediaRawData>> TrackBuffer;
+
+  TrackBuffersManager(dom::SourceBuffer* aParent, MediaSourceDecoder* aParentDecoder, const nsACString& aType);
+
+  bool AppendData(MediaLargeByteBuffer* aData, TimeUnit aTimestampOffset) override;
+
+  nsRefPtr<AppendPromise> BufferAppend() override;
+
+  void AbortAppendData() override;
+
+  void ResetParserState() override;
+
+  nsRefPtr<RangeRemovalPromise> RangeRemoval(TimeUnit aStart, TimeUnit aEnd) override;
+
+  EvictDataResult
+  EvictData(TimeUnit aPlaybackTime, uint32_t aThreshold, TimeUnit* aBufferStartTime) override;
+
+  void EvictBefore(TimeUnit aTime) override;
+
+  TimeIntervals Buffered() override;
+
+  int64_t GetSize() override;
+
+  void Ended() override;
+
+  void Detach() override;
+
+  AppendState GetAppendState() override
+  {
+    return mAppendState;
+  }
+
+  void SetGroupStartTimestamp(const TimeUnit& aGroupStartTimestamp) override;
+  void RestartGroupStartTimestamp() override;
+
+  // Interface for MediaSourceDemuxer
+  MediaInfo GetMetadata();
+  const TrackBuffer& GetTrackBuffer(TrackInfo::TrackType aTrack);
+  const TimeIntervals& Buffered(TrackInfo::TrackType);
+  bool IsEnded() const
+  {
+    return mEnded;
+  }
+
+#if defined(DEBUG)
+  void Dump(const char* aPath) override;
+#endif
+
+private:
+  virtual ~TrackBuffersManager();
+  void InitSegmentParserLoop();
+  void ScheduleSegmentParserLoop();
+  void SegmentParserLoop();
+  void AppendIncomingBuffers();
+  void InitializationSegmentReceived();
+  void CreateDemuxerforMIMEType();
+  void NeedMoreData();
+  void RejectAppend(nsresult aRejectValue, const char* aName);
+  // Will return a promise that will be resolved once all frames of the current
+  // media segment have been processed.
+  nsRefPtr<CodedFrameProcessingPromise> CodedFrameProcessing();
+  // Called by ResetParserState. Complete parsing the input buffer for the
+  // current media segment
+  void FinishCodedFrameProcessing();
+  void CompleteCodedFrameProcessing();
+  void CompleteResetParserState();
+  void CodedFrameRemoval(TimeInterval aInterval);
+  void SetAppendState(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
+  nsRefPtr<MediaLargeByteBuffer> mInputBuffer;
+  // The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
+  // Accessed on both the main thread and the task queue.
+  Atomic<AppendState> mAppendState;
+  // 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.
+  // TODO: Unused for now.
+  Atomic<bool> mBufferFull;
+  bool mFirstInitializationSegmentReceived;
+  bool mActiveTrack;
+  Maybe<TimeUnit> mGroupStartTimestamp;
+  TimeUnit mGroupEndTimestamp;
+  nsCString mType;
+
+  // ContainerParser objects and methods.
+  // Those are used to parse the incoming input buffer.
+
+  // Recreate the ContainerParser and only feed it with the previous init
+  // segment found.
+  void RecreateParser();
+  nsAutoPtr<ContainerParser> mParser;
+
+  // Demuxer objects and methods.
+  nsRefPtr<SourceBufferResource> mCurrentInputBuffer;
+  nsRefPtr<MediaDataDemuxer> mInputDemuxer;
+  void OnDemuxerInitDone(nsresult);
+  void OnDemuxerInitFailed(DemuxerFailureReason aFailure);
+  MediaPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest;
+  bool mEncrypted;
+
+  void OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure);
+  void DoDemuxVideo();
+  void OnVideoDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
+  void OnVideoDemuxFailed(DemuxerFailureReason aFailure)
+  {
+    mVideoTracks.mDemuxRequest.Complete();
+    OnDemuxFailed(TrackType::kVideoTrack, aFailure);
+  }
+  void DoDemuxAudio();
+  void OnAudioDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
+  void OnAudioDemuxFailed(DemuxerFailureReason aFailure)
+  {
+    mAudioTracks.mDemuxRequest.Complete();
+    OnDemuxFailed(TrackType::kAudioTrack, aFailure);
+  }
+
+  struct TrackData {
+    TrackData()
+      : mNumTracks(0)
+      , mNeedRandomAccessPoint(true)
+    {}
+    uint32_t mNumTracks;
+    Maybe<TimeUnit> mLastDecodeTimestamp;
+    Maybe<TimeUnit> mLastFrameDuration;
+    Maybe<TimeUnit> mHighestEndTimestamp;
+    bool mNeedRandomAccessPoint;
+    nsRefPtr<MediaTrackDemuxer> mDemuxer;
+    TrackBuffer mQueuedSamples;
+    MediaPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
+    UniquePtr<TrackInfo> mInfo;
+    // We only manage a single track of each type at this time.
+    nsTArray<TrackBuffer> mBuffers;
+    TimeIntervals mBufferedRanges;
+  };
+  bool ProcessFrame(MediaRawData* aSample, TrackData& aTrackData);
+  MediaPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest;
+  MediaPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise;
+
+  // SourceBuffer media promise (resolved on the main thread)
+  MediaPromiseHolder<AppendPromise> mAppendPromise;
+  MediaPromiseHolder<RangeRemovalPromise> mRangeRemovalPromise;
+
+  // Trackbuffers definition.
+  nsTArray<TrackData*> GetTracksList();
+  TrackData& GetTracksData(TrackType aTrack)
+  {
+    switch(aTrack) {
+      case TrackType::kVideoTrack:
+        return mVideoTracks;
+      case TrackType::kAudioTrack:
+      default:
+        return mAudioTracks;
+    }
+  }
+  TrackData mVideoTracks;
+  TrackData mAudioTracks;
+
+  // TaskQueue methods and objects.
+  AbstractThread* GetTaskQueue() {
+    return mTaskQueue;
+  }
+  bool OnTaskQueue()
+  {
+    return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
+  }
+  RefPtr<MediaTaskQueue> mTaskQueue;
+
+  TimeUnit mTimestampOffset;
+  TimeUnit mLastTimestampOffset;
+  void RestoreCachedVariables();
+
+  // Strong references to external objects.
+  nsMainThreadPtrHandle<dom::SourceBuffer> mParent;
+  nsMainThreadPtrHandle<MediaSourceDecoder> mParentDecoder;
+
+  // Set to true if abort is called.
+  Atomic<bool> mAbort;
+  // Set to true if mediasource state changed to ended.
+  Atomic<bool> mEnded;
+
+  // Monitor to protect following objects accessed across multipple threads.
+  mutable Monitor mMonitor;
+  // Set by the main thread, but only when all our tasks are completes
+  // (e.g. when SourceBuffer.updating is false). So the monitor isn't
+  // technically required for mIncomingBuffer.
+  typedef Pair<nsRefPtr<MediaLargeByteBuffer>, TimeUnit> IncomingBuffer;
+  nsTArray<IncomingBuffer> mIncomingBuffers;
+  TimeIntervals mVideoBufferedRanges;
+  TimeIntervals mAudioBufferedRanges;
+  MediaInfo mInfo;
+};
+
+} // namespace mozilla
+#endif /* MOZILLA_TRACKBUFFERSMANAGER_H_ */
--- a/dom/media/mediasource/moz.build
+++ b/dom/media/mediasource/moz.build
@@ -26,13 +26,14 @@ UNIFIED_SOURCES += [
     'MediaSourceUtils.cpp',
     'ResourceQueue.cpp',
     'SourceBuffer.cpp',
     'SourceBufferContentManager.cpp',
     'SourceBufferDecoder.cpp',
     'SourceBufferList.cpp',
     'SourceBufferResource.cpp',
     'TrackBuffer.cpp',
+    'TrackBuffersManager.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'