Bug 1286810: [MSE] P2. Update duration change as per new MSE spec. r?gerald draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 15 Jul 2016 23:38:33 +1000
changeset 388820 76fd0a4446c5495545b79d0ba0ccd9e8e10517ce
parent 388819 eeadae49d4eee58765d2fc3623f843ed105af22e
child 388821 8a94235ee03941d2467e9a5b9dfa463eeb6a812e
push id23241
push userbmo:jyavenard@mozilla.com
push dateMon, 18 Jul 2016 02:31:08 +0000
reviewersgerald
bugs1286810
milestone50.0a1
Bug 1286810: [MSE] P2. Update duration change as per new MSE spec. r?gerald See w3c/MSE Issue 19, 20 & 26. Changing the duration now can never call the range removal algorithm. An explicit call to remove must be used for range removal. This change performs the following: - Require remove() for all Range Removals - Error on Duration Changes that need remove first MozReview-Commit-ID: 1fK2O1slnQ1
dom/media/mediasource/MediaSource.cpp
dom/media/mediasource/MediaSource.h
dom/media/mediasource/MediaSourceDecoder.cpp
dom/media/mediasource/MediaSourceDecoder.h
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBuffer.h
dom/media/mediasource/SourceBufferList.cpp
dom/media/mediasource/SourceBufferList.h
dom/media/mediasource/TrackBuffersManager.cpp
dom/media/mediasource/TrackBuffersManager.h
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -187,42 +187,42 @@ MediaSource::ReadyState()
 double
 MediaSource::Duration()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mReadyState == MediaSourceReadyState::Closed) {
     return UnspecifiedNaN<double>();
   }
   MOZ_ASSERT(mDecoder);
-  return mDecoder->GetMediaSourceDuration();
+  return mDecoder->GetDuration();
 }
 
 void
 MediaSource::SetDuration(double aDuration, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetDuration(aDuration=%f, ErrorResult)", aDuration);
   if (aDuration < 0 || IsNaN(aDuration)) {
     aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
     return;
   }
   if (mReadyState != MediaSourceReadyState::Open ||
       mSourceBuffers->AnyUpdating()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
-  SetDuration(aDuration, MSRangeRemovalAction::RUN);
+  DurationChange(aDuration, aRv);
 }
 
 void
-MediaSource::SetDuration(double aDuration, MSRangeRemovalAction aAction)
+MediaSource::SetDuration(double aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetDuration(aDuration=%f)", aDuration);
-  mDecoder->SetMediaSourceDuration(aDuration, aAction);
+  mDecoder->SetMediaSourceDuration(aDuration);
 }
 
 already_AddRefed<SourceBuffer>
 MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
   nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
@@ -318,21 +318,17 @@ MediaSource::EndOfStream(const Optional<
       mSourceBuffers->AnyUpdating()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   SetReadyState(MediaSourceReadyState::Ended);
   mSourceBuffers->Ended();
   if (!aError.WasPassed()) {
-    mDecoder->SetMediaSourceDuration(mSourceBuffers->GetHighestBufferedEndTime(),
-                                     MSRangeRemovalAction::SKIP);
-    if (aRv.Failed()) {
-      return;
-    }
+    DurationChange(mSourceBuffers->GetHighestBufferedEndTime(), aRv);
     // Notify reader that all data is now available.
     mDecoder->Ended(true);
     return;
   }
   switch (aError.Value()) {
   case MediaSourceEndOfStreamError::Network:
     mDecoder->NetworkError();
     break;
@@ -520,26 +516,48 @@ void
 MediaSource::QueueAsyncSimpleEvent(const char* aName)
 {
   MSE_DEBUG("Queuing event '%s'", aName);
   nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName);
   NS_DispatchToMainThread(event);
 }
 
 void
-MediaSource::DurationChange(double aOldDuration, double aNewDuration)
+MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("DurationChange(aOldDuration=%f, aNewDuration=%f)", aOldDuration, aNewDuration);
+  MSE_DEBUG("DurationChange(aNewDuration=%f)", aNewDuration);
+
+  // 1. If the current value of duration is equal to new duration, then return.
+  if (mDecoder->GetDuration() == aNewDuration) {
+    return;
+  }
+
+  // 2. If new duration is less than the highest starting presentation timestamp
+  // of any buffered coded frames for all SourceBuffer objects in sourceBuffers,
+  // then throw an InvalidStateError exception and abort these steps.
+  if (aNewDuration < mSourceBuffers->HighestStartTime()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
 
-  if (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.
+  // 3. Update duration to new duration.
+  // 4. If a user agent is unable to partially render audio frames or text cues
+  // that start before and end after the duration, then run the following steps:
+  // Update new duration to the highest end time reported by the buffered
+  // attribute across all SourceBuffer objects in sourceBuffers.
+  //   1. Update new duration to the highest end time reported by the buffered
+  //      attribute across all SourceBuffer objects in sourceBuffers.
+  //   2. Update duration to new duration.
+  aNewDuration =
+    std::max(aNewDuration, mSourceBuffers->GetHighestBufferedEndTime());
+
+  // 5. Update the media duration to new duration and run the HTMLMediaElement
+  // duration change algorithm.
+  mDecoder->SetMediaSourceDuration(aNewDuration);
 }
 
 void
 MediaSource::GetMozDebugReaderData(nsAString& aString)
 {
   mDecoder->GetMozDebugReaderData(aString);
 }
 
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -25,21 +25,16 @@ struct JSContext;
 class JSObject;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 
 class ErrorResult;
 template <typename T> class AsyncEventRunner;
 
-enum MSRangeRemovalAction: uint8_t {
-  RUN = 0,
-  SKIP = 1
-};
-
 namespace dom {
 
 class GlobalObject;
 class SourceBuffer;
 class SourceBufferList;
 template <typename T> class Optional;
 
 #define MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID \
@@ -111,34 +106,31 @@ public:
 
   bool HasLiveSeekableRange() const { return mLiveSeekableRange.isSome(); }
   media::TimeInterval LiveSeekableRange() const
   {
     return mLiveSeekableRange.value();
   }
 
 private:
-  // MediaSourceDecoder uses DurationChange to set the duration
-  // without hitting the checks in SetDuration.
-  friend class mozilla::MediaSourceDecoder;
   // SourceBuffer uses SetDuration and SourceBufferIsActive
   friend class mozilla::dom::SourceBuffer;
 
   ~MediaSource();
 
   explicit MediaSource(nsPIDOMWindowInner* aWindow);
 
   friend class AsyncEventRunner<MediaSource>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
-  void DurationChange(double aOldDuration, double aNewDuration);
+  void DurationChange(double aNewDuration, ErrorResult& aRv);
 
   // SetDuration with no checks.
-  void SetDuration(double aDuration, MSRangeRemovalAction aAction);
+  void SetDuration(double aDuration);
 
   // Mark SourceBuffer as active and rebuild ActiveSourceBuffers.
   void SourceBufferIsActive(SourceBuffer* aSourceBuffer);
 
   RefPtr<SourceBufferList> mSourceBuffers;
   RefPtr<SourceBufferList> mActiveSourceBuffers;
 
   RefPtr<MediaSourceDecoder> mDecoder;
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -213,46 +213,34 @@ MediaSourceDecoder::SetInitialDuration(i
   if (!mMediaSource || !IsNaN(ExplicitDuration())) {
     return;
   }
   double duration = aDuration;
   // A duration of -1 is +Infinity.
   if (aDuration >= 0) {
     duration /= USECS_PER_S;
   }
-  SetMediaSourceDuration(duration, MSRangeRemovalAction::SKIP);
+  SetMediaSourceDuration(duration);
 }
 
 void
-MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction)
+MediaSourceDecoder::SetMediaSourceDuration(double aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  double oldDuration = ExplicitDuration();
   if (aDuration >= 0) {
     int64_t checkedDuration;
     if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
       // INT64_MAX is used as infinity by the state machine.
       // We want a very bigger number, but not infinity.
       checkedDuration = INT64_MAX - 1;
     }
     SetExplicitDuration(aDuration);
   } else {
     SetExplicitDuration(PositiveInfinity<double>());
   }
-
-  if (mMediaSource && aAction != MSRangeRemovalAction::SKIP) {
-    mMediaSource->DurationChange(oldDuration, aDuration);
-  }
-}
-
-double
-MediaSourceDecoder::GetMediaSourceDuration()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return ExplicitDuration();
 }
 
 void
 MediaSourceDecoder::GetMozDebugReaderData(nsAString& aString)
 {
   if (mReader && mDemuxer) {
     mReader->GetMozDebugReaderData(aString);
     mDemuxer->GetMozDebugReaderData(aString);
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -59,18 +59,17 @@ public:
   void DetachMediaSource();
 
   void Ended(bool aEnded);
 
   // Return the duration of the video in seconds.
   double GetDuration() override;
 
   void SetInitialDuration(int64_t aDuration);
-  void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction);
-  double GetMediaSourceDuration();
+  void SetMediaSourceDuration(double aDuration);
 
   MediaSourceDemuxer* GetDemuxer()
   {
     return mDemuxer;
   }
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -395,17 +395,17 @@ SourceBuffer::AbortUpdating()
 void
 SourceBuffer::CheckEndTime()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Check if we need to update mMediaSource duration
   double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds();
   double duration = mMediaSource->Duration();
   if (endTime > duration) {
-    mMediaSource->SetDuration(endTime, MSRangeRemovalAction::SKIP);
+    mMediaSource->SetDuration(endTime);
   }
 }
 
 void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   MSE_DEBUG("AppendData(aLength=%u)", aLength);
 
@@ -543,16 +543,25 @@ double
 SourceBuffer::GetBufferedEnd()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ErrorResult dummy;
   RefPtr<TimeRanges> ranges = GetBuffered(dummy);
   return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
 }
 
+double
+SourceBuffer::HighestStartTime()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mTrackBuffersManager
+         ? mTrackBuffersManager->HighestStartTime().ToSeconds()
+         : 0.0;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer)
   // Tell the TrackBuffer to end its current SourceBufferResource.
   TrackBuffersManager* manager = tmp->mTrackBuffersManager;
   if (manager) {
     manager->Detach();
   }
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -112,16 +112,17 @@ public:
   {
     return mMediaSource != nullptr;
   }
 
   void Ended();
 
   double GetBufferedStart();
   double GetBufferedEnd();
+  double HighestStartTime();
 
   // Runs the range removal algorithm as defined by the MSE spec.
   void RangeRemoval(double aStart, double aEnd);
 
   bool IsActive() const
   {
     return mActive;
   }
--- a/dom/media/mediasource/SourceBufferList.cpp
+++ b/dom/media/mediasource/SourceBufferList.cpp
@@ -174,16 +174,28 @@ SourceBufferList::SourceBufferList(Media
 }
 
 MediaSource*
 SourceBufferList::GetParentObject() const
 {
   return mMediaSource;
 }
 
+double
+SourceBufferList::HighestStartTime()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  double highestStartTime = 0;
+  for (auto& sourceBuffer : mSourceBuffers) {
+    highestStartTime =
+      std::max(sourceBuffer->HighestStartTime(), highestStartTime);
+  }
+  return highestStartTime;
+}
+
 JSObject*
 SourceBufferList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return SourceBufferListBinding::Wrap(aCx, this, aGivenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBufferList, DOMEventTargetHelper,
                                    mMediaSource, mSourceBuffers)
--- a/dom/media/mediasource/SourceBufferList.h
+++ b/dom/media/mediasource/SourceBufferList.h
@@ -80,16 +80,18 @@ public:
 
   // Append a SourceBuffer to the list. No event is fired.
   void AppendSimple(SourceBuffer* aSourceBuffer);
 
   // Remove all SourceBuffers from mSourceBuffers.
   //  No event is fired and no action is performed on the sourcebuffers.
   void ClearSimple();
 
+  double HighestStartTime();
+
 private:
   ~SourceBufferList();
 
   friend class AsyncEventRunner<SourceBufferList>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   RefPtr<MediaSource> mMediaSource;
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -1359,16 +1359,19 @@ TrackBuffersManager::ProcessFrames(Track
   TrackBuffer samples; // array that will contain the frames to be added
                        // to our track buffer.
 
   // We assume that no frames are contiguous within a media segment and as such
   // don't need to check for discontinuity except for the first frame and should
   // a frame be ignored due to the target window.
   bool needDiscontinuityCheck = true;
 
+  // Highest presentation time seen in samples block.
+  TimeUnit highestSampleTime;
+
   if (aSamples.Length()) {
     aTrackData.mLastParsedEndTime = TimeUnit();
   }
 
   for (auto& sample : aSamples) {
     SAMPLE_DEBUG("Processing %s frame(pts:%lld end:%lld, dts:%lld, duration:%lld, "
                "kf:%d)",
                aTrackData.mInfo->mMimeType.get(),
@@ -1491,16 +1494,17 @@ TrackBuffersManager::ProcessFrames(Track
       if (samples.Length()) {
         // We are creating a discontinuity in the samples.
         // Insert the samples processed so far.
         InsertFrames(samples, samplesRange, trackBuffer);
         samples.Clear();
         samplesRange = TimeIntervals();
         trackBuffer.mSizeBuffer += sizeNewSamples;
         sizeNewSamples = 0;
+        UpdateHighestTimestamp(trackBuffer, highestSampleTime);
       }
       trackBuffer.mNeedRandomAccessPoint = true;
       needDiscontinuityCheck = true;
       continue;
     }
 
     samplesRange += sampleInterval;
     sizeNewSamples += sample->ComputedSizeOfIncludingThis();
@@ -1523,29 +1527,33 @@ TrackBuffersManager::ProcessFrames(Track
     // 18. Set last frame duration for track buffer to frame duration.
     trackBuffer.mLastFrameDuration = Some(sampleDuration);
 
     // 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() ||
         sampleInterval.mEnd > trackBuffer.mHighestEndTimestamp.ref()) {
       trackBuffer.mHighestEndTimestamp = Some(sampleInterval.mEnd);
     }
+    if (sampleInterval.mStart > highestSampleTime) {
+      highestSampleTime = sampleInterval.mStart;
+    }
     // 20. If frame end timestamp is greater than group end timestamp, then set group end timestamp equal to frame end timestamp.
     if (sampleInterval.mEnd > mSourceBufferAttributes->GetGroupEndTimestamp()) {
       mSourceBufferAttributes->SetGroupEndTimestamp(sampleInterval.mEnd);
     }
     // 21. If generate timestamps flag equals true, then set timestampOffset equal to frame end timestamp.
     if (mSourceBufferAttributes->mGenerateTimestamps) {
       mSourceBufferAttributes->SetTimestampOffset(sampleInterval.mEnd);
     }
   }
 
   if (samples.Length()) {
     InsertFrames(samples, samplesRange, trackBuffer);
     trackBuffer.mSizeBuffer += sizeNewSamples;
+    UpdateHighestTimestamp(trackBuffer, highestSampleTime);
   }
 }
 
 bool
 TrackBuffersManager::CheckNextInsertionIndex(TrackData& aTrackData,
                                              const TimeUnit& aSampleTime)
 {
   if (aTrackData.mNextInsertionIndex.isSome()) {
@@ -1674,16 +1682,26 @@ TrackBuffersManager::InsertFrames(TrackB
   // length.
   if (aIntervals.Length()) {
     TimeIntervals range(aIntervals);
     range.SetFuzz(trackBuffer.mLongestFrameDuration / 2);
     trackBuffer.mSanitizedBufferedRanges += range;
   }
 }
 
+void
+TrackBuffersManager::UpdateHighestTimestamp(TrackData& aTrackData,
+                                            const media::TimeUnit& aHighestTime)
+{
+  if (aHighestTime > aTrackData.mHighestStartTimestamp) {
+    MonitorAutoLock mon(mMonitor);
+    aTrackData.mHighestStartTimestamp = aHighestTime;
+  }
+}
+
 size_t
 TrackBuffersManager::RemoveFrames(const TimeIntervals& aIntervals,
                                   TrackData& aTrackData,
                                   uint32_t aStartIndex)
 {
   TrackBuffer& data = aTrackData.mBuffers.LastElement();
   Maybe<uint32_t> firstRemovedIndex;
   uint32_t lastRemovedIndex = 0;
@@ -1771,16 +1789,30 @@ TrackBuffersManager::RemoveFrames(const 
 
   // Recalculate sanitized buffered ranges.
   aTrackData.mSanitizedBufferedRanges = aTrackData.mBufferedRanges;
   aTrackData.mSanitizedBufferedRanges.SetFuzz(TimeUnit::FromMicroseconds(maxSampleDuration/2));
 
   data.RemoveElementsAt(firstRemovedIndex.ref(),
                         lastRemovedIndex - firstRemovedIndex.ref() + 1);
 
+  if (aIntervals.GetEnd() >= aTrackData.mHighestStartTimestamp) {
+    // The sample with the highest presentation time got removed.
+    // Rescan the trackbuffer to determine the new one.
+    int64_t highestStartTime = 0;
+    for (const auto& sample : data) {
+      if (sample->mTime > highestStartTime) {
+        highestStartTime = sample->mTime;
+      }
+    }
+    MonitorAutoLock mon(mMonitor);
+    aTrackData.mHighestStartTimestamp =
+      TimeUnit::FromMicroseconds(highestStartTime);
+  }
+
   return firstRemovedIndex.ref();
 }
 
 void
 TrackBuffersManager::RecreateParser(bool aReuseInitData)
 {
   MOZ_ASSERT(OnTaskQueue());
   // Recreate our parser for only the data remaining. This is required
@@ -1795,17 +1827,16 @@ TrackBuffersManager::RecreateParser(bool
   } else {
     mProcessedInput = 0;
   }
 }
 
 nsTArray<TrackBuffersManager::TrackData*>
 TrackBuffersManager::GetTracksList()
 {
-  MOZ_ASSERT(OnTaskQueue());
   nsTArray<TrackData*> tracks;
   if (HasVideo()) {
     tracks.AppendElement(&mVideoTracks);
   }
   if (HasAudio()) {
     tracks.AppendElement(&mAudioTracks);
   }
   return tracks;
@@ -1837,16 +1868,28 @@ TimeIntervals
 TrackBuffersManager::SafeBuffered(TrackInfo::TrackType aTrack) const
 {
   MonitorAutoLock mon(mMonitor);
   return aTrack == TrackInfo::kVideoTrack
     ? mVideoBufferedRanges
     : mAudioBufferedRanges;
 }
 
+TimeUnit
+TrackBuffersManager::HighestStartTime()
+{
+  MonitorAutoLock mon(mMonitor);
+  TimeUnit highestStartTime;
+  for (auto& track : GetTracksList()) {
+    highestStartTime =
+      std::max(track->mHighestStartTimestamp, highestStartTime);
+  }
+  return highestStartTime;
+}
+
 const TrackBuffersManager::TrackBuffer&
 TrackBuffersManager::GetTrackBuffer(TrackInfo::TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   return GetTracksData(aTrack).mBuffers.LastElement();
 }
 
 uint32_t TrackBuffersManager::FindSampleIndex(const TrackBuffer& aTrackBuffer,
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -117,16 +117,17 @@ public:
   // Eviction is done in two steps, first remove data up to aPlaybackTime
   // and if still more space is needed remove from the end.
   EvictDataResult EvictData(const media::TimeUnit& aPlaybackTime, int64_t aSize);
 
   // Returns the buffered range currently managed.
   // This may be called on any thread.
   // Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
   media::TimeIntervals Buffered();
+  media::TimeUnit HighestStartTime();
 
   // Return the size of the data managed by this SourceBufferContentManager.
   int64_t GetSize() const;
 
   // Indicate that the MediaSource parent object got into "ended" state.
   void Ended();
 
   // The parent SourceBuffer is about to be destroyed.
@@ -275,16 +276,20 @@ private:
     // been appended yet.
     Maybe<media::TimeUnit> mLastFrameDuration;
     // Highest end timestamp variable that stores the highest coded frame end
     // timestamp across all coded frames in the current coded frame group that
     // were appended to this track buffer.
     // The variable is initially unset to indicate that no coded frames have
     // been appended yet.
     Maybe<media::TimeUnit> mHighestEndTimestamp;
+    // Highest presentation timestamp in track buffer.
+    // Protected by global monitor, except when reading on the task queue as it
+    // is only written there.
+    media::TimeUnit mHighestStartTimestamp;
     // Longest frame duration seen since last random access point.
     // Only ever accessed when mLastDecodeTimestamp and mLastFrameDuration are
     // set.
     media::TimeUnit mLongestFrameDuration;
     // Need random access point flag variable that keeps track of whether the
     // track buffer is waiting for a random access point coded frame.
     // The variable is initially set to true to indicate that random access
     // point coded frame is needed before anything can be added to the track
@@ -340,16 +345,18 @@ private:
 
   void CheckSequenceDiscontinuity(const media::TimeUnit& aPresentationTime);
   void ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData);
   bool CheckNextInsertionIndex(TrackData& aTrackData,
                                const media::TimeUnit& aSampleTime);
   void InsertFrames(TrackBuffer& aSamples,
                     const media::TimeIntervals& aIntervals,
                     TrackData& aTrackData);
+  void UpdateHighestTimestamp(TrackData& aTrackData,
+                              const media::TimeUnit& aHighestTime);
   // Remove all frames and their dependencies contained in aIntervals.
   // Return the index at which frames were first removed or 0 if no frames
   // removed.
   size_t RemoveFrames(const media::TimeIntervals& aIntervals,
                       TrackData& aTrackData,
                       uint32_t aStartIndex);
   // Find index of sample. Return a negative value if not found.
   uint32_t FindSampleIndex(const TrackBuffer& aTrackBuffer,