<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[202829] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/202829">202829</a></dd>
<dt>Author</dt> <dd>jer.noble@apple.com</dd>
<dt>Date</dt> <dd>2016-07-05 13:22:58 -0700 (Tue, 05 Jul 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>REGRESSION (<a href="http://trac.webkit.org/projects/webkit/changeset/202641">r202641</a>): Netflix playback stalls after a few seconds
https://bugs.webkit.org/show_bug.cgi?id=159365

Reviewed by Eric Carlson.

Source/WebCore:

Test: LayoutTests/media/media-source/media-source-small-gap.html

In <a href="http://trac.webkit.org/projects/webkit/changeset/202641">r202641</a>, we removed a &quot;fudge factor&quot; of 1 millisecond added onto the duration
of every sample for the purposes of calculating a SourceBuffer's buffered ranges.
Netflix (and likely other providers) have streams that have 1 &quot;timeScale&quot; gaps
between segments (e.g., 1/9000s, 1/3003s, etc.). Fill those gaps by looking for
the previous and next samples and extending the buffered range to cover the gaps
if they're short enough. We have to ensure that we correctly remove those extended
durations when we remove samples from the SourceBuffer as well.

* Modules/mediasource/SourceBuffer.cpp:
(WebCore::removeSamplesFromTrackBuffer):
(WebCore::SourceBuffer::removeCodedFrames):
(WebCore::SourceBuffer::sourceBufferPrivateDidReceiveSample):

Source/WTF:

Add a isBetween() convenience method.

* wtf/MediaTime.cpp:
(WTF::MediaTime::isBetween):
* wtf/MediaTime.h:

LayoutTests:

* media/media-source/media-source-small-gap-expected.txt: Added.
* media/media-source/media-source-small-gap.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWTFChangeLog">trunk/Source/WTF/ChangeLog</a></li>
<li><a href="#trunkSourceWTFwtfMediaTimecpp">trunk/Source/WTF/wtf/MediaTime.cpp</a></li>
<li><a href="#trunkSourceWTFwtfMediaTimeh">trunk/Source/WTF/wtf/MediaTime.h</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreModulesmediasourceSourceBuffercpp">trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsmediamediasourcemediasourcesmallgapexpectedtxt">trunk/LayoutTests/media/media-source/media-source-small-gap-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamediasourcemediasourcesmallgaphtml">trunk/LayoutTests/media/media-source/media-source-small-gap.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/LayoutTests/ChangeLog        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -1,3 +1,13 @@
</span><ins>+2016-07-05  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        REGRESSION (r202641): Netflix playback stalls after a few seconds
+        https://bugs.webkit.org/show_bug.cgi?id=159365
+
+        Reviewed by Eric Carlson.
+
+        * media/media-source/media-source-small-gap-expected.txt: Added.
+        * media/media-source/media-source-small-gap.html: Added.
+
</ins><span class="cx"> 2016-07-05  Myles C. Maxfield  &lt;mmaxfield@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [Sierra] Rebaseline tests to use un-mocked system font metrics
</span></span></pre></div>
<a id="trunkLayoutTestsmediamediasourcemediasourcesmallgapexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/media-source/media-source-small-gap-expected.txt (0 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/media-source/media-source-small-gap-expected.txt                                (rev 0)
+++ trunk/LayoutTests/media/media-source/media-source-small-gap-expected.txt        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -0,0 +1,17 @@
</span><ins>+This tests the SourceBuffer.buffered() API. This test will add 8 samples with a small gap (0.01s) between each sample. The SourceBuffer should coalesce such small gaps to make a single contiguous range. The video element should also play continuously from the beginning to the end.
+
+RUN(video.src = URL.createObjectURL(source))
+EVENT(sourceopen)
+RUN(sourceBuffer = source.addSourceBuffer(&quot;video/mock; codecs=mock&quot;))
+RUN(sourceBuffer.appendBuffer(initSegment))
+EVENT(updateend)
+RUN(sourceBuffer.appendBuffer(samples))
+EVENT(updateend)
+EXPECTED (sourceBuffer.buffered.length == '1') OK
+EXPECTED (sourceBuffer.buffered.start(0) == '0') OK
+EXPECTED (sourceBuffer.buffered.end(0) == '8') OK
+RUN(video.play())
+EVENT(ended)
+EXPECTED (video.currentTime == '8') OK
+END OF TEST
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamediasourcemediasourcesmallgaphtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/media-source/media-source-small-gap.html (0 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/media-source/media-source-small-gap.html                                (rev 0)
+++ trunk/LayoutTests/media/media-source/media-source-small-gap.html        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -0,0 +1,65 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;mock-media-source&lt;/title&gt;
+    &lt;script src=&quot;mock-media-source.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;../video-test.js&quot;&gt;&lt;/script&gt;
+    &lt;script&gt;
+    var source;
+    var sourceBuffer;
+    var initSegment;
+
+    if (window.internals)
+        internals.initializeMockMediaSource();
+
+    function runTest() {
+        findMediaElement();
+
+        source = new MediaSource();
+        waitForEventOn(source, 'sourceopen', sourceOpen);
+        run('video.src = URL.createObjectURL(source)');
+    }
+
+    function sourceOpen() {
+        run('sourceBuffer = source.addSourceBuffer(&quot;video/mock; codecs=mock&quot;)');
+        waitForEventOn(sourceBuffer, 'updateend', loadSamples, false, true);
+        initSegment = makeAInit(8, [makeATrack(1, 'mock', TRACK_KIND.VIDEO)]);
+        run('sourceBuffer.appendBuffer(initSegment)');
+    }
+
+    function loadSamples() {
+        samples = concatenateSamples([
+            makeASample(0, 0, 0.99, 1, SAMPLE_FLAG.SYNC),
+            makeASample(1, 1, 0.99, 1, SAMPLE_FLAG.NONE),
+            makeASample(2, 2, 0.99, 1, SAMPLE_FLAG.NONE),
+            makeASample(3, 3, 0.99, 1, SAMPLE_FLAG.NONE),
+            makeASample(4, 4, 0.99, 1, SAMPLE_FLAG.SYNC),
+            makeASample(5, 5, 0.99, 1, SAMPLE_FLAG.NONE),
+            makeASample(6, 6, 0.99, 1, SAMPLE_FLAG.NONE),
+            makeASample(7, 7, 1, 1, SAMPLE_FLAG.NONE),
+        ]);
+        waitForEventOn(sourceBuffer, 'updateend', checkBuffered, false, true);
+        run('sourceBuffer.appendBuffer(samples)');
+    }
+
+    function checkBuffered() {
+        testExpected('sourceBuffer.buffered.length', 1);
+        testExpected('sourceBuffer.buffered.start(0)', 0);
+        testExpected('sourceBuffer.buffered.end(0)', 8);
+
+        run('video.play()');
+        waitForEvent('ended', ended);
+    }
+
+    function ended() {
+        testExpected('video.currentTime', 8);
+        endTest();
+    }
+
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;runTest()&quot;&gt;
+    &lt;div&gt;This tests the SourceBuffer.buffered() API. This test will add 8 samples with a small gap (0.01s) between each sample. The SourceBuffer should coalesce such small gaps to make a single contiguous range. The video element should also play continuously from the beginning to the end.&lt;/div&gt;
+    &lt;video&gt;&lt;/video&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkSourceWTFChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/ChangeLog (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/ChangeLog        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/Source/WTF/ChangeLog        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -1,3 +1,16 @@
</span><ins>+2016-07-01  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        REGRESSION (r202641): Netflix playback stalls after a few seconds
+        https://bugs.webkit.org/show_bug.cgi?id=159365
+
+        Reviewed by Eric Carlson.
+
+        Add a isBetween() convenience method.
+
+        * wtf/MediaTime.cpp:
+        (WTF::MediaTime::isBetween):
+        * wtf/MediaTime.h:
+
</ins><span class="cx"> 2016-07-03  Per Arne Vollan  &lt;pvollan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [Win] DLLs are missing version information.
</span></span></pre></div>
<a id="trunkSourceWTFwtfMediaTimecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/wtf/MediaTime.cpp (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/wtf/MediaTime.cpp        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/Source/WTF/wtf/MediaTime.cpp        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -433,6 +433,13 @@
</span><span class="cx">     return lhsFactor &gt; rhsFactor ? GreaterThan : LessThan;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+bool MediaTime::isBetween(const MediaTime&amp; a, const MediaTime&amp; b) const
+{
+    if (a &gt; b)
+        return *this &gt; b &amp;&amp; *this &lt; a;
+    return *this &gt; a &amp;&amp; *this &lt; b;
+}
+
</ins><span class="cx"> const MediaTime&amp; MediaTime::zeroTime()
</span><span class="cx"> {
</span><span class="cx">     static const MediaTime* time = new MediaTime(0, 1, Valid);
</span></span></pre></div>
<a id="trunkSourceWTFwtfMediaTimeh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/wtf/MediaTime.h (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/wtf/MediaTime.h        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/Source/WTF/wtf/MediaTime.h        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -88,6 +88,7 @@
</span><span class="cx">     } ComparisonFlags;
</span><span class="cx"> 
</span><span class="cx">     ComparisonFlags compare(const MediaTime&amp; rhs) const;
</span><ins>+    bool isBetween(const MediaTime&amp;, const MediaTime&amp;) const;
</ins><span class="cx"> 
</span><span class="cx">     bool isValid() const { return m_timeFlags &amp; Valid; }
</span><span class="cx">     bool isInvalid() const { return !isValid(); }
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/Source/WebCore/ChangeLog        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -1,3 +1,25 @@
</span><ins>+2016-07-01  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        REGRESSION (r202641): Netflix playback stalls after a few seconds
+        https://bugs.webkit.org/show_bug.cgi?id=159365
+
+        Reviewed by Eric Carlson.
+
+        Test: LayoutTests/media/media-source/media-source-small-gap.html
+
+        In r202641, we removed a &quot;fudge factor&quot; of 1 millisecond added onto the duration
+        of every sample for the purposes of calculating a SourceBuffer's buffered ranges.
+        Netflix (and likely other providers) have streams that have 1 &quot;timeScale&quot; gaps
+        between segments (e.g., 1/9000s, 1/3003s, etc.). Fill those gaps by looking for
+        the previous and next samples and extending the buffered range to cover the gaps
+        if they're short enough. We have to ensure that we correctly remove those extended
+        durations when we remove samples from the SourceBuffer as well.
+
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::removeSamplesFromTrackBuffer):
+        (WebCore::SourceBuffer::removeCodedFrames):
+        (WebCore::SourceBuffer::sourceBufferPrivateDidReceiveSample):
+
</ins><span class="cx"> 2016-07-05  Brady Eidson  &lt;beidson@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Database process crashes deleting a corrupt SQLite database file (null deref).
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmediasourceSourceBuffercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp (202828 => 202829)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp        2016-07-05 20:05:02 UTC (rev 202828)
+++ trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp        2016-07-05 20:22:58 UTC (rev 202829)
</span><span class="lines">@@ -652,7 +652,7 @@
</span><span class="cx">     return a.second-&gt;decodeTime() &lt; b.second-&gt;decodeTime();
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static PassRefPtr&lt;TimeRanges&gt; removeSamplesFromTrackBuffer(const DecodeOrderSampleMap::MapType&amp; samples, SourceBuffer::TrackBuffer&amp; trackBuffer, const SourceBuffer* buffer, const char* logPrefix)
</del><ins>+static PlatformTimeRanges removeSamplesFromTrackBuffer(const DecodeOrderSampleMap::MapType&amp; samples, SourceBuffer::TrackBuffer&amp; trackBuffer, const SourceBuffer* buffer, const char* logPrefix)
</ins><span class="cx"> {
</span><span class="cx"> #if !LOG_DISABLED
</span><span class="cx">     MediaTime earliestSample = MediaTime::positiveInfiniteTime();
</span><span class="lines">@@ -663,7 +663,7 @@
</span><span class="cx">     UNUSED_PARAM(buffer);
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><del>-    auto erasedRanges = TimeRanges::create();
</del><ins>+    PlatformTimeRanges erasedRanges;
</ins><span class="cx">     for (auto sampleIt : samples) {
</span><span class="cx">         const DecodeOrderSampleMap::KeyType&amp; decodeKey = sampleIt.first;
</span><span class="cx"> #if !LOG_DISABLED
</span><span class="lines">@@ -681,7 +681,7 @@
</span><span class="cx"> 
</span><span class="cx">         auto startTime = sample-&gt;presentationTime();
</span><span class="cx">         auto endTime = startTime + sample-&gt;duration();
</span><del>-        erasedRanges-&gt;ranges().add(startTime, endTime);
</del><ins>+        erasedRanges.add(startTime, endTime);
</ins><span class="cx"> 
</span><span class="cx"> #if !LOG_DISABLED
</span><span class="cx">         bytesRemoved += startBufferSize - trackBuffer.samples.sizeInBytes();
</span><span class="lines">@@ -692,12 +692,40 @@
</span><span class="cx"> #endif
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // Because we may have added artificial padding in the buffered ranges when adding samples, we may
+    // need to remove that padding when removing those same samples. Walk over the erased ranges looking
+    // for unbuffered areas and expand erasedRanges to encompass those areas.
+    PlatformTimeRanges additionalErasedRanges;
+    for (unsigned i = 0; i &lt; erasedRanges.length(); ++i) {
+        auto erasedStart = erasedRanges.start(i);
+        auto erasedEnd = erasedRanges.end(i);
+        auto startIterator = trackBuffer.samples.presentationOrder().reverseFindSampleBeforePresentationTime(erasedStart);
+        if (startIterator == trackBuffer.samples.presentationOrder().rend())
+            additionalErasedRanges.add(MediaTime::zeroTime(), erasedStart);
+        else {
+            auto&amp; previousSample = *startIterator-&gt;second;
+            if (previousSample.presentationTime() + previousSample.duration() &lt; erasedStart)
+                additionalErasedRanges.add(previousSample.presentationTime() + previousSample.duration(), erasedStart);
+        }
+
+        auto endIterator = trackBuffer.samples.presentationOrder().findSampleOnOrAfterPresentationTime(erasedEnd);
+        if (endIterator == trackBuffer.samples.presentationOrder().end())
+            additionalErasedRanges.add(erasedEnd, MediaTime::positiveInfiniteTime());
+        else {
+            auto&amp; nextSample = *endIterator-&gt;second;
+            if (nextSample.presentationTime() &gt; erasedEnd)
+                additionalErasedRanges.add(erasedEnd, nextSample.presentationTime());
+        }
+    }
+    if (additionalErasedRanges.length())
+        erasedRanges.unionWith(additionalErasedRanges);
+
</ins><span class="cx"> #if !LOG_DISABLED
</span><span class="cx">     if (bytesRemoved)
</span><span class="cx">         LOG(MediaSource, &quot;SourceBuffer::%s(%p) removed %zu bytes, start(%lf), end(%lf)&quot;, logPrefix, buffer, bytesRemoved, earliestSample.toDouble(), latestSample.toDouble());
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><del>-    return WTFMove(erasedRanges);
</del><ins>+    return erasedRanges;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void SourceBuffer::removeCodedFrames(const MediaTime&amp; start, const MediaTime&amp; end)
</span><span class="lines">@@ -742,19 +770,19 @@
</span><span class="cx">         DecodeOrderSampleMap::iterator removeDecodeStart = trackBuffer.samples.decodeOrder().findSampleWithDecodeKey(decodeKey);
</span><span class="cx"> 
</span><span class="cx">         DecodeOrderSampleMap::MapType erasedSamples(removeDecodeStart, removeDecodeEnd);
</span><del>-        RefPtr&lt;TimeRanges&gt; erasedRanges = removeSamplesFromTrackBuffer(erasedSamples, trackBuffer, this, &quot;removeCodedFrames&quot;);
</del><ins>+        PlatformTimeRanges erasedRanges = removeSamplesFromTrackBuffer(erasedSamples, trackBuffer, this, &quot;removeCodedFrames&quot;);
</ins><span class="cx"> 
</span><span class="cx">         // Only force the TrackBuffer to re-enqueue if the removed ranges overlap with enqueued and possibly
</span><span class="cx">         // not yet displayed samples.
</span><span class="cx">         if (currentMediaTime &lt; trackBuffer.lastEnqueuedPresentationTime) {
</span><span class="cx">             PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
</span><del>-            possiblyEnqueuedRanges.intersectWith(erasedRanges-&gt;ranges());
</del><ins>+            possiblyEnqueuedRanges.intersectWith(erasedRanges);
</ins><span class="cx">             if (possiblyEnqueuedRanges.length())
</span><span class="cx">                 trackBuffer.needsReenqueueing = true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        erasedRanges-&gt;invert();
-        m_buffered-&gt;intersectWith(*erasedRanges);
</del><ins>+        erasedRanges.invert();
+        m_buffered-&gt;ranges().intersectWith(erasedRanges);
</ins><span class="cx">         setBufferedDirty(true);
</span><span class="cx"> 
</span><span class="cx">         // 3.4 If this object is in activeSourceBuffers, the current playback position is greater than or equal to start
</span><span class="lines">@@ -1532,7 +1560,7 @@
</span><span class="cx">             auto nextSyncIter = trackBuffer.samples.decodeOrder().findSyncSampleAfterDecodeIterator(lastDecodeIter);
</span><span class="cx">             dependentSamples.insert(firstDecodeIter, nextSyncIter);
</span><span class="cx"> 
</span><del>-            RefPtr&lt;TimeRanges&gt; erasedRanges = removeSamplesFromTrackBuffer(dependentSamples, trackBuffer, this, &quot;sourceBufferPrivateDidReceiveSample&quot;);
</del><ins>+            PlatformTimeRanges erasedRanges = removeSamplesFromTrackBuffer(dependentSamples, trackBuffer, this, &quot;sourceBufferPrivateDidReceiveSample&quot;);
</ins><span class="cx"> 
</span><span class="cx">             // Only force the TrackBuffer to re-enqueue if the removed ranges overlap with enqueued and possibly
</span><span class="cx">             // not yet displayed samples.
</span><span class="lines">@@ -1539,13 +1567,13 @@
</span><span class="cx">             MediaTime currentMediaTime = m_source-&gt;currentTime();
</span><span class="cx">             if (currentMediaTime &lt; trackBuffer.lastEnqueuedPresentationTime) {
</span><span class="cx">                 PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
</span><del>-                possiblyEnqueuedRanges.intersectWith(erasedRanges-&gt;ranges());
</del><ins>+                possiblyEnqueuedRanges.intersectWith(erasedRanges);
</ins><span class="cx">                 if (possiblyEnqueuedRanges.length())
</span><span class="cx">                     trackBuffer.needsReenqueueing = true;
</span><span class="cx">             }
</span><span class="cx"> 
</span><del>-            erasedRanges-&gt;invert();
-            m_buffered-&gt;intersectWith(*erasedRanges);
</del><ins>+            erasedRanges.invert();
+            m_buffered-&gt;ranges().intersectWith(erasedRanges);
</ins><span class="cx">             setBufferedDirty(true);
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -1585,7 +1613,18 @@
</span><span class="cx">         if (m_shouldGenerateTimestamps)
</span><span class="cx">             m_timestampOffset = frameEndTimestamp;
</span><span class="cx"> 
</span><del>-        m_buffered-&gt;ranges().add(presentationTimestamp, presentationTimestamp + frameDuration);
</del><ins>+        // Eliminate small gaps between buffered ranges by coalescing
+        // disjoint ranges separated by less than a &quot;fudge factor&quot;.
+        auto presentationEndTime = presentationTimestamp + frameDuration;
+        auto nearestToPresentationStartTime = m_buffered-&gt;ranges().nearest(presentationTimestamp);
+        if ((presentationTimestamp - nearestToPresentationStartTime).isBetween(MediaTime::zeroTime(), currentTimeFudgeFactor()))
+            presentationTimestamp = nearestToPresentationStartTime;
+
+        auto nearestToPresentationEndTime = m_buffered-&gt;ranges().nearest(presentationEndTime);
+        if ((nearestToPresentationEndTime - presentationEndTime).isBetween(MediaTime::zeroTime(), currentTimeFudgeFactor()))
+            presentationEndTime = nearestToPresentationEndTime;
+
+        m_buffered-&gt;ranges().add(presentationTimestamp, presentationEndTime);
</ins><span class="cx">         m_bufferedSinceLastMonitor += frameDuration.toDouble();
</span><span class="cx">         setBufferedDirty(true);
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>