Bug 1096089: MSE: Partially implement Range Removal algorithm. r=mattwoodrow, r=cajbir
authorJean-Yves Avenard <jyavenard@mozilla.com>
Sat, 24 Jan 2015 21:45:58 +1100
changeset 225513 19608b0262aba7282ff3c18c7ee1bc7a26e8bff5
parent 225512 1efd2c0dfbb41889b74fb5c36cea7283627d6e39
child 225514 2cff252b37e2332d79e5a40d9bce36c5613ba3aa
push id28163
push userphilringnalda@gmail.com
push dateSat, 24 Jan 2015 16:27:39 +0000
treeherdermozilla-central@1cf171c1a177 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, cajbir
bugs1096089
milestone38.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 1096089: MSE: Partially implement Range Removal algorithm. r=mattwoodrow, r=cajbir Data is only properly evicted when we remove the entire buffered range. Otherwise, only the buffered times are updated.
dom/media/mediasource/MediaSource.cpp
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBuffer.h
dom/media/mediasource/SourceBufferDecoder.cpp
dom/media/mediasource/SourceBufferDecoder.h
dom/media/mediasource/TrackBuffer.cpp
dom/media/mediasource/TrackBuffer.h
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -481,17 +481,18 @@ MediaSource::QueueAsyncSimpleEvent(const
 
 void
 MediaSource::DurationChange(double aOldDuration, double aNewDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("MediaSource(%p)::DurationChange(aOldDuration=%f, aNewDuration=%f)", this, aOldDuration, aNewDuration);
 
   if (aNewDuration < aOldDuration) {
-    mSourceBuffers->RangeRemoval(aNewDuration, aOldDuration);
+    // Remove all buffered data from aNewDuration.
+    mSourceBuffers->RangeRemoval(aNewDuration, PositiveInfinity<double>());
   }
   // TODO: If partial audio frames/text cues exist, clamp duration based on mSourceBuffers.
 }
 
 void
 MediaSource::NotifyEvicted(double aStart, double aEnd)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -118,23 +118,33 @@ MediaSourceReader::RequestAudioData()
     mAudioPromise.Reject(DECODE_ERROR, __func__);
     return p;
   }
   if (mAudioIsSeeking) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestAudioData called mid-seek. Rejecting.", this);
     mAudioPromise.Reject(CANCELED, __func__);
     return p;
   }
-  if (SwitchAudioReader(mLastAudioTime)) {
-    mAudioReader->Seek(mLastAudioTime, 0)
-                ->Then(GetTaskQueue(), __func__, this,
-                       &MediaSourceReader::RequestAudioDataComplete,
-                       &MediaSourceReader::RequestAudioDataFailed);
-  } else {
-    RequestAudioDataComplete(0);
+  SwitchReaderResult ret = SwitchAudioReader(mLastAudioTime);
+  switch (ret) {
+    case READER_NEW:
+      mAudioReader->Seek(mLastAudioTime, 0)
+                  ->Then(GetTaskQueue(), __func__, this,
+                         &MediaSourceReader::RequestAudioDataComplete,
+                         &MediaSourceReader::RequestAudioDataFailed);
+      break;
+    case READER_ERROR:
+      if (mLastAudioTime) {
+        CheckForWaitOrEndOfStream(MediaData::AUDIO_DATA);
+        break;
+      }
+      // Fallback to using current reader
+    default:
+      RequestAudioDataComplete(0);
+      break;
   }
   return p;
 }
 
 void
 MediaSourceReader::RequestAudioDataComplete(int64_t aTime)
 {
   mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
@@ -215,33 +225,25 @@ MediaSourceReader::OnAudioNotDecoded(Not
   MOZ_ASSERT(aReason == END_OF_STREAM);
   if (mAudioReader) {
     AdjustEndTime(&mLastAudioTime, mAudioReader);
   }
 
   // See if we can find a different reader that can pick up where we left off. We use the
   // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
   // 1065207.
-  if (SwitchAudioReader(mLastAudioTime, EOS_FUZZ_US)) {
+  if (SwitchAudioReader(mLastAudioTime, EOS_FUZZ_US) == READER_NEW) {
     mAudioReader->Seek(mLastAudioTime, 0)
                 ->Then(GetTaskQueue(), __func__, this,
                        &MediaSourceReader::RequestAudioDataComplete,
                        &MediaSourceReader::RequestAudioDataFailed);
     return;
   }
 
-  // If the entire MediaSource is done, generate an EndOfStream.
-  if (IsEnded()) {
-    mAudioPromise.Reject(END_OF_STREAM, __func__);
-    return;
-  }
-
-  // We don't have the data the caller wants. Tell that we're waiting for JS to
-  // give us more data.
-  mAudioPromise.Reject(WAITING_FOR_DATA, __func__);
+  CheckForWaitOrEndOfStream(MediaData::AUDIO_DATA);
 }
 
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaSourceReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold)
 {
   nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
   MSE_DEBUGV("MediaSourceReader(%p)::RequestVideoData(%d, %lld)",
@@ -256,25 +258,35 @@ MediaSourceReader::RequestVideoData(bool
     mDropAudioBeforeThreshold = true;
     mDropVideoBeforeThreshold = true;
   }
   if (mVideoIsSeeking) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestVideoData called mid-seek. Rejecting.", this);
     mVideoPromise.Reject(CANCELED, __func__);
     return p;
   }
-  if (SwitchVideoReader(mLastVideoTime)) {
-    mVideoReader->Seek(mLastVideoTime, 0)
-                ->Then(GetTaskQueue(), __func__, this,
-                       &MediaSourceReader::RequestVideoDataComplete,
-                       &MediaSourceReader::RequestVideoDataFailed);
-  } else {
-    mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold)
-                ->Then(GetTaskQueue(), __func__, this,
-                       &MediaSourceReader::OnVideoDecoded, &MediaSourceReader::OnVideoNotDecoded);
+  SwitchReaderResult ret = SwitchVideoReader(mLastVideoTime);
+  switch (ret) {
+    case READER_NEW:
+      mVideoReader->Seek(mLastVideoTime, 0)
+                  ->Then(GetTaskQueue(), __func__, this,
+                         &MediaSourceReader::RequestVideoDataComplete,
+                         &MediaSourceReader::RequestVideoDataFailed);
+      break;
+    case READER_ERROR:
+      if (mLastVideoTime) {
+        CheckForWaitOrEndOfStream(MediaData::VIDEO_DATA);
+        break;
+      }
+      // Fallback to using current reader.
+    default:
+      mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold)
+                  ->Then(GetTaskQueue(), __func__, this,
+                         &MediaSourceReader::OnVideoDecoded, &MediaSourceReader::OnVideoNotDecoded);
+      break;
   }
 
   return p;
 }
 
 void
 MediaSourceReader::RequestVideoDataComplete(int64_t aTime)
 {
@@ -330,33 +342,47 @@ MediaSourceReader::OnVideoNotDecoded(Not
   MOZ_ASSERT(aReason == END_OF_STREAM);
   if (mVideoReader) {
     AdjustEndTime(&mLastVideoTime, mVideoReader);
   }
 
   // See if we can find a different reader that can pick up where we left off. We use the
   // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
   // 1065207.
-  if (SwitchVideoReader(mLastVideoTime, EOS_FUZZ_US)) {
+  if (SwitchVideoReader(mLastVideoTime, EOS_FUZZ_US) == READER_NEW) {
     mVideoReader->Seek(mLastVideoTime, 0)
                 ->Then(GetTaskQueue(), __func__, this,
                        &MediaSourceReader::RequestVideoDataComplete,
                        &MediaSourceReader::RequestVideoDataFailed);
     return;
   }
 
+  CheckForWaitOrEndOfStream(MediaData::VIDEO_DATA);
+}
+
+void
+MediaSourceReader::CheckForWaitOrEndOfStream(MediaData::Type aType)
+{
   // If the entire MediaSource is done, generate an EndOfStream.
   if (IsEnded()) {
-    mVideoPromise.Reject(END_OF_STREAM, __func__);
+    if (aType == MediaData::AUDIO_DATA) {
+      mAudioPromise.Reject(END_OF_STREAM, __func__);
+    } else {
+      mVideoPromise.Reject(END_OF_STREAM, __func__);
+    }
     return;
   }
 
-  // We don't have the data the caller wants. Tell that we're waiting for JS to
-  // give us more data.
-  mVideoPromise.Reject(WAITING_FOR_DATA, __func__);
+  if (aType == MediaData::AUDIO_DATA) {
+    // We don't have the data the caller wants. Tell that we're waiting for JS to
+    // give us more data.
+    mAudioPromise.Reject(WAITING_FOR_DATA, __func__);
+  } else {
+    mVideoPromise.Reject(WAITING_FOR_DATA, __func__);
+  }
 }
 
 nsRefPtr<ShutdownPromise>
 MediaSourceReader::Shutdown()
 {
   mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
 
   MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
@@ -442,50 +468,50 @@ bool
 MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType)
 {
   TrackBuffer* trackBuffer = aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
   MOZ_ASSERT(trackBuffer);
   nsRefPtr<MediaDecoderReader> reader = SelectReader(aTarget, EOS_FUZZ_US, trackBuffer->Decoders());
   return !!reader;
 }
 
-bool
+MediaSourceReader::SwitchReaderResult
 MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aError)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   // XXX: Can't handle adding an audio track after ReadMetadata.
   if (!mAudioTrack) {
-    return false;
+    return READER_ERROR;
   }
   nsRefPtr<MediaDecoderReader> newReader = SelectReader(aTarget, aError, mAudioTrack->Decoders());
   if (newReader && newReader != mAudioReader) {
     mAudioReader->SetIdle();
     mAudioReader = newReader;
     MSE_DEBUGV("MediaSourceReader(%p)::SwitchAudioReader switched reader to %p", this, mAudioReader.get());
-    return true;
+    return READER_NEW;
   }
-  return false;
+  return newReader ? READER_EXISTING : READER_ERROR;
 }
 
-bool
+MediaSourceReader::SwitchReaderResult
 MediaSourceReader::SwitchVideoReader(int64_t aTarget, int64_t aError)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   // XXX: Can't handle adding a video track after ReadMetadata.
   if (!mVideoTrack) {
-    return false;
+    return READER_ERROR;
   }
   nsRefPtr<MediaDecoderReader> newReader = SelectReader(aTarget, aError, mVideoTrack->Decoders());
   if (newReader && newReader != mVideoReader) {
     mVideoReader->SetIdle();
     mVideoReader = newReader;
     MSE_DEBUGV("MediaSourceReader(%p)::SwitchVideoReader switched reader to %p", this, mVideoReader.get());
-    return true;
+    return READER_NEW;
   }
-  return false;
+  return newReader ? READER_EXISTING : READER_ERROR;
 }
 
 bool
 MediaSourceReader::IsDormantNeeded()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mVideoReader) {
     return mVideoReader->IsDormantNeeded();
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -141,22 +141,30 @@ public:
 
   // Returns true if aReader is a currently active audio or video
   bool IsActiveReader(MediaDecoderReader* aReader);
 
 private:
   // Switch the current audio/video reader to the reader that
   // contains aTarget (or up to aError after target). Both
   // aTarget and aError are in microseconds.
-  bool SwitchAudioReader(int64_t aTarget, int64_t aError = 0);
-  bool SwitchVideoReader(int64_t aTarget, int64_t aError = 0);
+  enum SwitchReaderResult {
+    READER_ERROR = -1,
+    READER_EXISTING = 0,
+    READER_NEW = 1,
+  };
+  SwitchReaderResult SwitchAudioReader(int64_t aTarget, int64_t aError = 0);
+  SwitchReaderResult SwitchVideoReader(int64_t aTarget, int64_t aError = 0);
   void RequestAudioDataComplete(int64_t aTime);
   void RequestAudioDataFailed(nsresult aResult);
   void RequestVideoDataComplete(int64_t aTime);
   void RequestVideoDataFailed(nsresult aResult);
+  // Will reject the MediaPromise with END_OF_STREAM if mediasource has ended
+  // or with WAIT_FOR_DATA otherwise.
+  void CheckForWaitOrEndOfStream(MediaData::Type aType);
 
   // Return a reader from the set available in aTrackDecoders that has data
   // available in the range requested by aTarget.
   already_AddRefed<MediaDecoderReader> SelectReader(int64_t aTarget,
                                                     int64_t aError,
                                                     const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
   bool HaveData(int64_t aTarget, MediaData::Type aType);
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -31,20 +31,47 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
 #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
+// RangeRemoval must be synchronous if appendBuffer is also synchronous.
+// While waiting for bug 1118589 to land, ensure RangeRemoval is synchronous
+#define APPENDBUFFER_IS_SYNCHRONOUS
+
 namespace mozilla {
 
 namespace dom {
 
+class RangeRemovalRunnable : public nsRunnable {
+public:
+  RangeRemovalRunnable(SourceBuffer* aSourceBuffer,
+                     double aStart,
+                     double aEnd)
+  : mSourceBuffer(aSourceBuffer)
+  , mStart(aStart)
+  , mEnd(aEnd)
+  { }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL {
+
+    mSourceBuffer->DoRangeRemoval(mStart, mEnd);
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<SourceBuffer> mSourceBuffer;
+  double mStart;
+  double mEnd;
+};
+
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SourceBuffer(%p)::SetMode(aMode=%d)", this, aMode);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
@@ -207,23 +234,47 @@ SourceBuffer::Remove(double aStart, doub
   }
   RangeRemoval(aStart, aEnd);
 }
 
 void
 SourceBuffer::RangeRemoval(double aStart, double aEnd)
 {
   StartUpdating();
-  /// TODO: Run coded frame removal algorithm.
+
+#if defined(APPENDBUFFER_IS_SYNCHRONOUS)
+  DoRangeRemoval(aStart, aEnd);
 
-  // Run the final step of the coded frame removal algorithm asynchronously
-  // to ensure the SourceBuffer's updating flag transition behaves as
-  // required by the spec.
+  // Run the final step of the buffer append algorithm asynchronously to
+  // ensure the SourceBuffer's updating flag transition behaves as required
+  // by the spec.
   nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &SourceBuffer::StopUpdating);
   NS_DispatchToMainThread(event);
+#else
+  nsRefPtr<nsIRunnable> task = new RangeRemovalRunnable(this, aStart, aEnd);
+  NS_DispatchToMainThread(task);
+#endif
+}
+
+void
+SourceBuffer::DoRangeRemoval(double aStart, double aEnd)
+{
+  if (!mUpdating) {
+    // abort was called in between.
+    return;
+  }
+  if (mTrackBuffer && !IsInfinite(aStart)) {
+    int64_t start = aStart * USECS_PER_S;
+    int64_t end = IsInfinite(aEnd) ? INT64_MAX : (int64_t)(aEnd * USECS_PER_S);
+    mTrackBuffer->RangeRemoval(start, end);
+  }
+
+#if !defined(APPENDBUFFER_IS_SYNCHRONOUS)
+  StopUpdating();
+#endif
 }
 
 void
 SourceBuffer::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("SourceBuffer(%p)::Detach", this);
   Abort();
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -105,17 +105,19 @@ public:
 
   // Evict data in the source buffer in the given time range.
   void Evict(double aStart, double aEnd);
 
   double GetBufferedStart();
   double GetBufferedEnd();
 
   // Runs the range removal algorithm as defined by the MSE spec.
+  // RangeRemoval will queue a call to DoRangeRemoval.
   void RangeRemoval(double aStart, double aEnd);
+  void DoRangeRemoval(double aStart, double aEnd);
 
 #if defined(DEBUG)
   void Dump(const char* aPath);
 #endif
 
 private:
   ~SourceBuffer();
 
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -35,16 +35,18 @@ NS_IMPL_ISUPPORTS0(SourceBufferDecoder)
 SourceBufferDecoder::SourceBufferDecoder(MediaResource* aResource,
                                          AbstractMediaDecoder* aParentDecoder,
                                          int64_t aTimestampOffset)
   : mResource(aResource)
   , mParentDecoder(aParentDecoder)
   , mReader(nullptr)
   , mTimestampOffset(aTimestampOffset)
   , mMediaDuration(-1)
+  , mRealMediaDuration(0)
+  , mTrimmedOffset(-1)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(SourceBufferDecoder);
 }
 
 SourceBufferDecoder::~SourceBufferDecoder()
 {
   MOZ_COUNT_DTOR(SourceBufferDecoder);
@@ -186,16 +188,28 @@ SourceBufferDecoder::NotifyDecodedFrames
 
 void
 SourceBufferDecoder::SetMediaDuration(int64_t aDuration)
 {
   mMediaDuration = aDuration;
 }
 
 void
+SourceBufferDecoder::SetRealMediaDuration(int64_t aDuration)
+{
+  mRealMediaDuration = aDuration;
+}
+
+void
+SourceBufferDecoder::Trim(int64_t aDuration)
+{
+  mTrimmedOffset = (double)aDuration / USECS_PER_S;
+}
+
+void
 SourceBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   MSE_DEBUG("SourceBufferDecoder(%p)::UpdateEstimatedMediaDuration UNIMPLEMENTED", this);
 }
 
 void
 SourceBufferDecoder::SetMediaSeekable(bool aMediaSeekable)
 {
@@ -224,33 +238,43 @@ SourceBufferDecoder::NotifyDataArrived(c
   // force parent decoder's state machine to recompute end time for
   // infinite length media.
   mParentDecoder->NotifyDataArrived(nullptr, 0, 0);
 }
 
 nsresult
 SourceBufferDecoder::GetBuffered(dom::TimeRanges* aBuffered)
 {
-  return mReader->GetBuffered(aBuffered);
+  nsresult rv = mReader->GetBuffered(aBuffered);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!WasTrimmed()) {
+    return NS_OK;
+  }
+  nsRefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
+  tr->Add(0, mTrimmedOffset);
+  aBuffered->Intersection(tr);
+  return NS_OK;
 }
 
 int64_t
 SourceBufferDecoder::ConvertToByteOffset(double aTime)
 {
   int64_t readerOffset = mReader->GetEvictionOffset(aTime);
   if (readerOffset >= 0) {
     return readerOffset;
   }
 
   // Uses a conversion based on (aTime/duration) * length.  For the
   // purposes of eviction this should be adequate since we have the
   // byte threshold as well to ensure data actually gets evicted and
   // we ensure we don't evict before the current playable point.
-  if (mMediaDuration <= 0) {
+  if (mRealMediaDuration <= 0) {
     return -1;
   }
   int64_t length = GetResource()->GetLength();
   MOZ_ASSERT(length > 0);
-  int64_t offset = (aTime / (double(mMediaDuration) / USECS_PER_S)) * length;
+  int64_t offset = (aTime / (double(mRealMediaDuration) / USECS_PER_S)) * length;
   return offset;
 }
 
 } // namespace mozilla
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -116,28 +116,54 @@ public:
     return mCDMProxy;
   }
 #endif
 
   // Given a time convert it into an approximate byte offset from the
   // cached data. Returns -1 if no such value is computable.
   int64_t ConvertToByteOffset(double aTime);
 
+  // All durations are in usecs.
+
+  // We can't at this stage, accurately remove coded frames.
+  // Trim is a work around that hides data located after a given time by
+  // preventing playback beyond the trim point.
+  // No data is actually removed.
+  // aDuration is were data will be trimmed from.
+  void Trim(int64_t aDuration);
+  bool WasTrimmed()
+  {
+    return mTrimmedOffset >= 0;
+  }
+
+  // returns the real duration of the resource, including trimmed data.
+  void SetRealMediaDuration(int64_t aDuration);
+  int64_t GetRealMediaDuration()
+  {
+    return mRealMediaDuration;
+  }
+
 private:
   virtual ~SourceBufferDecoder();
 
   // Our TrackBuffer's task queue, this is only non-null during initialization.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   nsRefPtr<MediaResource> mResource;
 
   AbstractMediaDecoder* mParentDecoder;
   nsRefPtr<MediaDecoderReader> mReader;
+  // in microseconds
   int64_t mTimestampOffset;
+  // mMediaDuration contains the apparent buffer duration, excluding trimmed data.
   int64_t mMediaDuration;
+  // mRealMediaDuration contains the real buffer duration, including trimmed data.
+  int64_t mRealMediaDuration;
+  // in seconds
+  double mTrimmedOffset;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 };
 
 } // namespace mozilla
 
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -30,17 +30,17 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
 // Time in seconds to substract from the current time when deciding the
 // time point to evict data before in a decoder. This is used to help
-// precent evicting the current playback point.
+// prevent evicting the current playback point.
 #define MSE_EVICT_THRESHOLD_TIME 2.0
 
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
@@ -162,58 +162,59 @@ TrackBuffer::AppendData(const uint8_t* a
   int64_t start, end;
   if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
     start += aTimestampOffset;
     end += aTimestampOffset;
     if (mParser->IsMediaSegmentPresent(aData, aLength) &&
         mLastEndTimestamp &&
         (!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
          mLastTimestampOffset != aTimestampOffset ||
-         mDecoderPerSegment)) {
+         mDecoderPerSegment || mCurrentDecoder->WasTrimmed())) {
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
                 this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);
 
       // This data is earlier in the timeline than data we have already
       // processed, so we must create a new decoder to handle the decoding.
       if (!decoders.NewDecoder(aTimestampOffset)) {
         return false;
       }
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
       const nsTArray<uint8_t>& initData = mParser->InitData();
-      AppendDataToCurrentResource(initData.Elements(), initData.Length());
+      AppendDataToCurrentResource(initData.Elements(), initData.Length(), end - start);
       mLastStartTimestamp = start;
     } else {
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
                 this, mLastStartTimestamp, mLastEndTimestamp ? mLastEndTimestamp.value() : 0, start, end);
     }
     mLastEndTimestamp.reset();
     mLastEndTimestamp.emplace(end);
   }
 
-  if (!AppendDataToCurrentResource(aData, aLength)) {
+  if (!AppendDataToCurrentResource(aData, aLength, end - start)) {
     return false;
   }
 
   // Tell our reader that we have more data to ensure that playback starts if
   // required when data is appended.
   mParentDecoder->GetReader()->MaybeNotifyHaveData();
   return true;
 }
 
 bool
-TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength)
+TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength, uint32_t aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mCurrentDecoder) {
     return false;
   }
 
   SourceBufferResource* resource = mCurrentDecoder->GetResource();
   int64_t appendOffset = resource->GetLength();
   resource->AppendData(aData, aLength);
+  mCurrentDecoder->SetRealMediaDuration(mCurrentDecoder->GetRealMediaDuration() + aDuration);
   // XXX: For future reference: NDA call must run on the main thread.
   mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
                                      aLength, appendOffset);
   mParentDecoder->NotifyBytesDownloaded();
   mParentDecoder->NotifyTimeRangesChanged();
 
   return true;
 }
@@ -283,18 +284,18 @@ TrackBuffer::EvictData(double aPlaybackT
     }
 
     MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u/%u threshold=%u "
               "toEvict=%lld current=%s pastCurrent=%s",
               this, i, decoders.Length(), aThreshold, toEvict,
               onCurrent ? "true" : "false",
               pastCurrentDecoder ? "true" : "false");
 
-    if (pastCurrentDecoder
-        && !mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+    if (pastCurrentDecoder &&
+        !mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
       // Remove data from older decoders than the current one.
       // Don't remove data if it is currently active.
       MSE_DEBUG("TrackBuffer(%p)::EvictData evicting all before start "
                 "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
                 this, buffered->GetStartTime(), buffered->GetEndTime(),
                 aPlaybackTime, decoders[i]->GetResource()->GetSize());
       toEvict -= decoders[i]->GetResource()->EvictAll();
     } else {
@@ -715,9 +716,51 @@ TrackBuffer::RemoveDecoder(SourceBufferD
 
     if (mCurrentDecoder == aDecoder) {
       DiscardDecoder();
     }
   }
   aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);
 }
 
+bool
+TrackBuffer::RangeRemoval(int64_t aStart, int64_t aEnd)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+
+  nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+  int64_t bufferedEnd = Buffered(buffered) * USECS_PER_S;
+  int64_t bufferedStart = buffered->GetStartTime() * USECS_PER_S;
+
+  if (bufferedStart < 0 || aStart > bufferedEnd || aEnd < bufferedStart) {
+    // Nothing to remove.
+    return false;
+  }
+  if (aEnd < bufferedEnd) {
+    // TODO. We only handle trimming.
+    NS_WARNING("TrackBuffer::RangeRemoval unsupported arguments. "
+               "Can only handle trimming");
+    return false;
+  }
+
+  nsTArray<SourceBufferDecoder*> decoders;
+  decoders.AppendElements(mInitializedDecoders);
+
+  // Only trimming existing buffers.
+  for (size_t i = 0; i < decoders.Length(); ++i) {
+    decoders[i]->Trim(aStart);
+    if (aStart <= buffered->GetStartTime()) {
+      // We've completely emptied it, can clear the data.
+      int64_t size = decoders[i]->GetResource()->GetSize();
+      decoders[i]->GetResource()->EvictData(size, size);
+      if (decoders[i] == mCurrentDecoder ||
+          mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+        continue;
+      }
+      MSE_DEBUG("TrackBuffer(%p):RangeRemoval remove empty decoders=%d", this, i);
+      RemoveDecoder(decoders[i]);
+    }
+  }
+  return true;
+}
+
 } // namespace mozilla
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -87,16 +87,23 @@ public:
   // http://w3c.github.io/media-source/#sourcebuffer-reset-parser-state
   void ResetParserState();
 
   // Returns a reference to mInitializedDecoders, used by MediaSourceReader
   // to select decoders.
   // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader.
   const nsTArray<nsRefPtr<SourceBufferDecoder>>& Decoders();
 
+  // Runs MSE range removal algorithm.
+  // http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
+  // Implementation is only partial, we can only trim a buffer.
+  // Returns true if data was evicted.
+  // Times are in microseconds.
+  bool RangeRemoval(int64_t aStart, int64_t aEnd);
+
 #ifdef MOZ_EME
   nsresult SetCDMProxy(CDMProxy* aProxy);
 #endif
 
 #if defined(DEBUG)
   void Dump(const char* aPath);
 #endif
 
@@ -108,17 +115,18 @@ private:
   // returns it. The new decoder must be queued using QueueInitializeDecoder
   // for initialization.
   // The decoder is not considered initialized until it is added to
   // mInitializedDecoders.
   already_AddRefed<SourceBufferDecoder> NewDecoder(int64_t aTimestampOffset /* microseconds */);
 
   // Helper for AppendData, ensures NotifyDataArrived is called whenever
   // data is appended to the current decoder's SourceBufferResource.
-  bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength);
+  bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength,
+                                   uint32_t aDuration /* microseconds */);
 
   // Queue execution of InitializeDecoder on mTaskQueue.
   bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder);
 
   // Runs decoder initialization including calling ReadMetadata.  Runs as an
   // event on the decode thread pool.
   void InitializeDecoder(SourceBufferDecoder* aDecoder);