[MSE] Implement Range Removal algorithm.
https://bugs.webkit.org/show_bug.cgi?id=140622.

Patch by Bartlomiej Gajda <b.gajda@samsung.com> on 2015-01-23
Reviewed by Jer Noble.

Source/WebCore:

This extract Range Removal algorithm (Editor's Draft version, bug:26316) from remove(),
to separate function to deal with old FIXME since bug in spec was resolved.
This should both guarantee good order of events, and prevent from switching to 'open' state
during end of stream.

Test: media/media-source/media-source-end-of-stream-readyState.html

* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::setDurationInternal): update to use rangeRemoval(), not remove()
(WebCore::MediaSource::streamEndedWithError): remove FIXME, brigning back correct order of events.
* Modules/mediasource/SourceBuffer.cpp:
(WebCore::SourceBuffer::remove): comments up to spec, extract rangeRemoval algorithm.
(WebCore::SourceBuffer::rangeRemoval):
(WebCore::SourceBuffer::removeTimerFired): comments up to spec.
* Modules/mediasource/SourceBuffer.h:

LayoutTests:

Added short test to check whether endOfStream incorrectly switches back
to 'open' state.

* media/media-source/media-source-end-of-stream-readyState.html: Added.
* media/media-source/media-source-end-of-stream-readyState-expected.txt: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@179044 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 143638d..d0bfdee 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2015-01-23  Bartlomiej Gajda  <b.gajda@samsung.com>
+
+        [MSE] Implement Range Removal algorithm.
+        https://bugs.webkit.org/show_bug.cgi?id=140622.
+
+        Reviewed by Jer Noble.
+
+        Added short test to check whether endOfStream incorrectly switches back
+        to 'open' state.
+
+        * media/media-source/media-source-end-of-stream-readyState.html: Added.
+        * media/media-source/media-source-end-of-stream-readyState-expected.txt: Added.
+
 2015-01-23  Brent Fulgham  <bfulgham@apple.com>
 
         [Win] Test gardening. Mark a few failures after filing bugs.
diff --git a/LayoutTests/media/media-source/media-source-end-of-stream-readyState-expected.txt b/LayoutTests/media/media-source/media-source-end-of-stream-readyState-expected.txt
new file mode 100644
index 0000000..d088d55
--- /dev/null
+++ b/LayoutTests/media/media-source/media-source-end-of-stream-readyState-expected.txt
@@ -0,0 +1,13 @@
+
+RUN(video.src = URL.createObjectURL(source))
+EVENT(sourceopen)
+RUN(sourceBuffer = source.addSourceBuffer("video/mock; codecs=mock"))
+RUN(sourceBuffer.appendBuffer(mediaSegment))
+EVENT(updateend)
+EXPECTED (source.duration.toFixed(3) == '10') OK
+EXPECTED (sourceBuffer.buffered.end(0).toFixed(3) == '5') OK
+RUN(source.endOfStream())
+EXPECTED (source.duration.toFixed(3) == '5') OK
+EVENT(updateend)
+END OF TEST
+
diff --git a/LayoutTests/media/media-source/media-source-end-of-stream-readyState.html b/LayoutTests/media/media-source/media-source-end-of-stream-readyState.html
new file mode 100644
index 0000000..f026893
--- /dev/null
+++ b/LayoutTests/media/media-source/media-source-end-of-stream-readyState.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>mock-media-source</title>
+    <script src="mock-media-source.js"></script>
+    <script src="../video-test.js"></script>
+    <script>
+    var source;
+    var sourceBuffer;
+    var mediaSegment;
+
+    if (window.internals)
+        internals.initializeMockMediaSource();
+
+    function runTest() {
+        findMediaElement();
+
+        source = new MediaSource();
+        waitForEventOn(source, 'sourceopen', sourceOpen, false, true);
+        run('video.src = URL.createObjectURL(source)');
+    }
+
+    function sourceOpen() {
+        run('sourceBuffer = source.addSourceBuffer("video/mock; codecs=mock")');
+        waitForEventOn(sourceBuffer, 'updateend', updateEnd1, false, true);
+        mediaSegment = concatenateSamples([
+            makeAInit(10, [makeATrack(1, 'mock', TRACK_KIND.VIDEO)]),
+            makeASample(0, 0, 5, 1, SAMPLE_FLAG.SYNC, 0),
+        ]);
+        run('sourceBuffer.appendBuffer(mediaSegment)');
+
+    }
+
+    function updateEnd1() {
+        testExpected('source.duration.toFixed(3)', 10);
+        testExpected('sourceBuffer.buffered.end(0).toFixed(3)', 5);
+
+        waitForEventOn(source, 'sourceopen', function() { failTest("Should not transit to 'open' state during endOfStream().") }, false, true);
+        waitForEventOn(sourceBuffer, 'updateend', endTest, false, true);
+        run('source.endOfStream()');
+        testExpected('source.duration.toFixed(3)', 5);
+    }
+
+    </script>
+</head>
+<body onload="runTest()">
+    <video></video>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 3a7955d..0d3cf1d 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,26 @@
+2015-01-23  Bartlomiej Gajda  <b.gajda@samsung.com>
+
+        [MSE] Implement Range Removal algorithm.
+        https://bugs.webkit.org/show_bug.cgi?id=140622.
+
+        Reviewed by Jer Noble.
+
+        This extract Range Removal algorithm (Editor's Draft version, bug:26316) from remove(),
+        to separate function to deal with old FIXME since bug in spec was resolved.
+        This should both guarantee good order of events, and prevent from switching to 'open' state
+        during end of stream.
+
+        Test: media/media-source/media-source-end-of-stream-readyState.html
+
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::setDurationInternal): update to use rangeRemoval(), not remove()
+        (WebCore::MediaSource::streamEndedWithError): remove FIXME, brigning back correct order of events.
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::SourceBuffer::remove): comments up to spec, extract rangeRemoval algorithm.
+        (WebCore::SourceBuffer::rangeRemoval):
+        (WebCore::SourceBuffer::removeTimerFired): comments up to spec.
+        * Modules/mediasource/SourceBuffer.h:
+
 2015-01-23  Enrica Casucci  <enrica@apple.com>
 
         Hit test returns incorrect results when performed in paginated content over the page gaps.
diff --git a/Source/WebCore/Modules/mediasource/MediaSource.cpp b/Source/WebCore/Modules/mediasource/MediaSource.cpp
index cfbb111..7ae6a75 100644
--- a/Source/WebCore/Modules/mediasource/MediaSource.cpp
+++ b/Source/WebCore/Modules/mediasource/MediaSource.cpp
@@ -371,7 +371,7 @@
     // on all objects in sourceBuffers.
     if (oldDuration.isValid() && duration < oldDuration) {
         for (auto& sourceBuffer : *m_sourceBuffers)
-            sourceBuffer->remove(duration, oldDuration, IGNORE_EXCEPTION);
+            sourceBuffer->rangeRemoval(duration, oldDuration);
     }
 
     // 5. If a user agent is unable to partially render audio frames or text cues that start before and end after the
@@ -447,6 +447,10 @@
 
     // 2.4.7 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#end-of-stream-algorithm
 
+    // 1. Change the readyState attribute value to "ended".
+    // 2. Queue a task to fire a simple event named sourceended at the MediaSource.
+    setReadyState(endedKeyword());
+
     // 3.
     if (error.isEmpty()) {
         // ↳ If error is not set, is null, or is an empty string
@@ -463,13 +467,6 @@
         m_private->markEndOfStream(MediaSourcePrivate::EosNoError);
     }
 
-    // NOTE: Do steps 1 & 2 after step 3 (with an empty error) to avoid the MediaSource's readyState being re-opened by a
-    // remove() operation resulting from a duration change.
-    // FIXME: Re-number or update this section once <https://www.w3.org/Bugs/Public/show_bug.cgi?id=26316> is resolved.
-    // 1. Change the readyState attribute value to "ended".
-    // 2. Queue a task to fire a simple event named sourceended at the MediaSource.
-    setReadyState(endedKeyword());
-
     if (error == network) {
         // ↳ If error is set to "network"
         ASSERT(m_mediaElement);
diff --git a/Source/WebCore/Modules/mediasource/SourceBuffer.cpp b/Source/WebCore/Modules/mediasource/SourceBuffer.cpp
index 861cd24..5702ea6d 100644
--- a/Source/WebCore/Modules/mediasource/SourceBuffer.cpp
+++ b/Source/WebCore/Modules/mediasource/SourceBuffer.cpp
@@ -360,33 +360,49 @@
     LOG(MediaSource, "SourceBuffer::remove(%p) - start(%lf), end(%lf)", this, start.toDouble(), end.toDouble());
 
     // Section 3.2 remove() method steps.
-    // 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
-    // 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
-    if (start < MediaTime::zeroTime() || (m_source && (!m_source->duration().isValid() || start > m_source->duration())) || end <= start) {
+    // 1. If duration equals NaN, then throw an InvalidAccessError exception and abort these steps.
+    // 2. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
+    // 3. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
+
+    // FIXME: reorder/revisit this section once <https://www.w3.org/Bugs/Public/show_bug.cgi?id=27857> got resolved
+    // as it seems wrong to check mediaSource duration before checking isRemoved().
+    if ((m_source && m_source->duration().isInvalid())
+        || start < MediaTime::zeroTime() || (m_source && start > m_source->duration())
+        || end <= start) {
         ec = INVALID_ACCESS_ERR;
         return;
     }
 
-    // 3. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
+    // 4. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an
     //    InvalidStateError exception and abort these steps.
-    // 4. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
+    // 5. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
     if (isRemoved() || m_updating) {
         ec = INVALID_STATE_ERR;
         return;
     }
 
-    // 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
-    // 5.1. Set the readyState attribute of the parent media source to "open"
-    // 5.2. Queue a task to fire a simple event named sourceopen at the parent media source .
+    // 6. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
+    // 6.1. Set the readyState attribute of the parent media source to "open"
+    // 6.2. Queue a task to fire a simple event named sourceopen at the parent media source .
     m_source->openIfInEndedState();
 
-    // 6. Set the updating attribute to true.
+    // 7. Run the range removal algorithm with start and end as the start and end of the removal range.
+    rangeRemoval(start, end);
+}
+
+void SourceBuffer::rangeRemoval(const MediaTime& start, const MediaTime& end)
+{
+    // 3.5.7 Range Removal
+    // https://rawgit.com/w3c/media-source/7bbe4aa33c61ec025bc7acbd80354110f6a000f9/media-source.html#sourcebuffer-range-removal
+    // 1. Let start equal the starting presentation timestamp for the removal range.
+    // 2. Let end equal the end presentation timestamp for the removal range.
+    // 3. Set the updating attribute to true.
     m_updating = true;
 
-    // 7. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
+    // 4. Queue a task to fire a simple event named updatestart at this SourceBuffer object.
     scheduleEvent(eventNames().updatestartEvent);
 
-    // 8. Return control to the caller and run the rest of the steps asynchronously.
+    // 5. Return control to the caller and run the rest of the steps asynchronously.
     m_pendingRemoveStart = start;
     m_pendingRemoveEnd = end;
     m_removeTimer.startOneShot(0);
@@ -789,21 +805,21 @@
     ASSERT(m_pendingRemoveStart.isValid());
     ASSERT(m_pendingRemoveStart < m_pendingRemoveEnd);
 
-    // Section 3.2 remove() method steps
-    // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-remove-void-double-start-double-end
+    // Section 3.5.7 Range Removal
+    // http://w3c.github.io/media-source/#sourcebuffer-range-removal
 
-    // 9. Run the coded frame removal algorithm with start and end as the start and end of the removal range.
+    // 6. Run the coded frame removal algorithm with start and end as the start and end of the removal range.
     removeCodedFrames(m_pendingRemoveStart, m_pendingRemoveEnd);
 
-    // 10. Set the updating attribute to false.
+    // 7. Set the updating attribute to false.
     m_updating = false;
     m_pendingRemoveStart = MediaTime::invalidTime();
     m_pendingRemoveEnd = MediaTime::invalidTime();
 
-    // 11. Queue a task to fire a simple event named update at this SourceBuffer object.
+    // 8. Queue a task to fire a simple event named update at this SourceBuffer object.
     scheduleEvent(eventNames().updateEvent);
 
-    // 12. Queue a task to fire a simple event named updateend at this SourceBuffer object.
+    // 9. Queue a task to fire a simple event named updateend at this SourceBuffer object.
     scheduleEvent(eventNames().updateendEvent);
 }
 
diff --git a/Source/WebCore/Modules/mediasource/SourceBuffer.h b/Source/WebCore/Modules/mediasource/SourceBuffer.h
index 4d335dc..5b9f703 100644
--- a/Source/WebCore/Modules/mediasource/SourceBuffer.h
+++ b/Source/WebCore/Modules/mediasource/SourceBuffer.h
@@ -126,6 +126,7 @@
     bool shouldGenerateTimestamps() const { return m_shouldGenerateTimestamps; }
     void setShouldGenerateTimestamps(bool flag) { m_shouldGenerateTimestamps = flag; }
 
+    void rangeRemoval(const MediaTime&, const MediaTime&);
 protected:
     // EventTarget interface
     virtual void refEventTarget() override { ref(); }