Commit 163cc6a6 authored by acolwell@chromium.org's avatar acolwell@chromium.org

Fix MediaSource.duration setter behavior to match the current spec.

This change fixes non-compliant MSE behavior when the duration is set
to a smaller value. The spec calls for remove() to get called in this
case which wasn't happening before. This causes SourceBuffer.updating
to become true which prevents other operations like endOfStream() from
being allowed until the remove completes. Tests that relied on this
broken behavior have been updated.

BUG=381302

Review URL: https://codereview.chromium.org/319213002

git-svn-id: svn://svn.chromium.org/blink/trunk@176373 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent b42ddf33
......@@ -64,7 +64,18 @@ function mediaSourceConfigChangeTest(directory, idA, idB, description)
test.waitForExpectedEvents(function()
{
// Truncate the presentation to a duration of 2 seconds.
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
mediaSource.duration = 2;
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
test.expectEvent(sourceBuffer, "updatestart");
test.expectEvent(sourceBuffer, "update");
test.expectEvent(sourceBuffer, "updateend");
});
test.waitForExpectedEvents(function()
{
mediaSource.endOfStream();
if (expectResizeEvents) {
......
......@@ -143,6 +143,15 @@
var fullDuration = segmentInfo.duration;
var newDuration = 0.5;
var durationchangeEventCounter = 0;
var expectedDurationChangeEventCount = 1;
var durationchangeEventHandler = test.step_func(function(event)
{
assert_equals(mediaElement.duration, newDuration, 'mediaElement newDuration');
assert_equals(mediaSource.duration, newDuration, 'mediaSource newDuration');
durationchangeEventCounter++;
});
mediaElement.play();
// Append all the segments
......@@ -156,25 +165,23 @@
assert_equals(mediaSource.duration, fullDuration, 'mediaSource fullDuration');
assert_less_than(mediaElement.currentTime, newDuration / 2, 'mediaElement currentTime');
var durationchangeEventCounter = 0;
var expectedDurationChangeEventCount = 1;
var durationchangeEventHandler = test.step_func(function(event)
{
assert_equals(mediaSource.readyState, 'ended', 'mediaSource ended');
assert_equals(mediaElement.duration, newDuration, 'mediaElement newDuration');
assert_equals(mediaSource.duration, newDuration, 'mediaSource newDuration');
durationchangeEventCounter++;
});
// Media load also fires 'durationchange' event, so only start counting them now.
mediaElement.addEventListener('durationchange', durationchangeEventHandler);
// Truncate duration. This should result in one 'durationchange' fired.
mediaSource.duration = newDuration;
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
test.expectEvent(sourceBuffer, "updateend");
});
test.waitForExpectedEvents(function()
{
// Set duration again to make sure it does not trigger another 'durationchange' event.
mediaSource.duration = newDuration;
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
// Mark endOfStream so that playback can reach 'ended' at the new duration.
test.expectEvent(mediaSource, 'sourceended', 'endOfStream acknowledged');
mediaSource.endOfStream();
......
......@@ -33,21 +33,42 @@
timeUpdateCount++;
}));
mediaElement.addEventListener("ended", test.step_func(function(e)
test.failOnEvent(mediaElement, 'error');
test.expectEvent(sourceBuffer, "updatestart");
test.expectEvent(sourceBuffer, "update");
test.expectEvent(sourceBuffer, "updateend");
sourceBuffer.appendBuffer(mediaData);
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
test.waitForExpectedEvents(function()
{
assert_greater_than(timeUpdateCount, 2, "timeUpdateCount");
test.done();
}));
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
test.failOnEvent(mediaElement, 'error');
test.expectEvent(sourceBuffer, "updatestart");
test.expectEvent(sourceBuffer, "update");
test.expectEvent(sourceBuffer, "updateend");
mediaSource.duration = 1;
sourceBuffer.addEventListener('updateend', test.step_func(function()
{
mediaSource.duration = 1;
mediaSource.endOfStream();
mediaElement.play();
}));
sourceBuffer.appendBuffer(mediaData);
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
});
test.waitForExpectedEvents(function()
{
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
mediaSource.endOfStream();
mediaElement.play();
test.expectEvent(mediaElement, "ended");
});
test.waitForExpectedEvents(function()
{
assert_greater_than(timeUpdateCount, 2, "timeUpdateCount");
test.done();
});
}, "Test HTMLVideoElement.getVideoPlaybackQuality() with MediaSource API", {timeout: 5000});
</script>
</body>
......
......@@ -12,16 +12,32 @@
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
test.failOnEvent(mediaElement, 'error');
test.endOnEvent(mediaElement, 'ended');
sourceBuffer.addEventListener('updateend', test.step_func(function()
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
sourceBuffer.appendBuffer(mediaData);
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
test.expectEvent(sourceBuffer, "updateend");
test.waitForExpectedEvents(function()
{
assert_false(sourceBuffer.updating, "sourceBuffer.updating");
mediaSource.duration = 1;
assert_true(sourceBuffer.updating, "sourceBuffer.updating");
test.expectEvent(sourceBuffer, "updateend");
});
test.waitForExpectedEvents(function()
{
mediaSource.endOfStream();
mediaElement.play();
}));
sourceBuffer.appendBuffer(mediaData);
test.endOnEvent(mediaElement, 'ended');
});
}, "Test normal playback case with MediaSource API", {timeout: 5000});
</script>
</body>
......
......@@ -6,6 +6,7 @@ PASS Test remove with a start larger than the end.
PASS Test remove after SourceBuffer removed from mediaSource.
PASS Test remove while update pending.
PASS Test aborting a remove operation.
PASS Test remove with a start at the duration.
PASS Test remove transitioning readyState from 'ended' to 'open'.
PASS Test removing all appended data.
PASS Test removing beginning of appended data.
......
......@@ -129,6 +129,34 @@
});
}, "Test aborting a remove operation.");
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
sourceBuffer.appendBuffer(mediaData);
test.expectEvent(sourceBuffer, "updatestart");
test.expectEvent(sourceBuffer, "update");
test.expectEvent(sourceBuffer, "updateend");
test.waitForExpectedEvents(function()
{
assert_less_than(mediaSource.duration, 10)
mediaSource.duration = 10;
sourceBuffer.remove(mediaSource.duration, mediaSource.duration + 2);
assert_true(sourceBuffer.updating, "updating");
test.expectEvent(sourceBuffer, "updatestart");
test.expectEvent(sourceBuffer, "update");
test.expectEvent(sourceBuffer, "updateend");
});
test.waitForExpectedEvents(function()
{
test.done();
});
}, "Test remove with a start at the duration.");
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
......
......@@ -1776,7 +1776,9 @@ void HTMLMediaElement::setReadyState(ReadyState state)
selectInitialTracksIfNecessary();
m_duration = duration();
scheduleEvent(EventTypeNames::durationchange);
if (isHTMLVideoElement(*this))
scheduleEvent(EventTypeNames::resize);
scheduleEvent(EventTypeNames::loadedmetadata);
......@@ -3063,17 +3065,22 @@ void HTMLMediaElement::mediaPlayerTimeChanged()
void HTMLMediaElement::mediaPlayerDurationChanged()
{
WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged");
durationChanged(duration());
// FIXME: Change MediaPlayerClient & WebMediaPlayer to convey
// the currentTime when the duration change occured. The current
// WebMediaPlayer implementations always clamp currentTime() to
// duration() so the requestSeek condition here is always false.
durationChanged(duration(), currentTime() > duration());
}
void HTMLMediaElement::durationChanged(double duration)
void HTMLMediaElement::durationChanged(double duration, bool requestSeek)
{
WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f)", duration);
WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f, %d)", duration, requestSeek);
// Abort if duration unchanged.
if (m_duration == duration)
return;
WTF_LOG(Media, "HTMLMediaElement::durationChanged : %f -> %f", m_duration, duration);
m_duration = duration;
scheduleEvent(EventTypeNames::durationchange);
......@@ -3082,7 +3089,7 @@ void HTMLMediaElement::durationChanged(double duration)
if (renderer())
renderer()->updateFromElement();
if (currentTime() > duration)
if (requestSeek)
seek(duration, IGNORE_EXCEPTION);
}
......
......@@ -155,7 +155,7 @@ public:
// media source extensions
void closeMediaSource();
void durationChanged(double duration);
void durationChanged(double duration, bool requestSeek);
// controls
bool controls() const;
......
......@@ -360,10 +360,37 @@ void MediaSource::setDuration(double duration, ExceptionState& exceptionState)
// 4. Run the duration change algorithm with new duration set to the value being
// assigned to this attribute.
// Synchronously process duration change algorithm to enforce any required
// seek is started prior to returning.
m_attachedElement->durationChanged(duration);
m_webMediaSource->setDuration(duration);
durationChangeAlgorithm(duration);
}
void MediaSource::durationChangeAlgorithm(double newDuration)
{
// Section 2.6.4 Duration change
// https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#duration-change-algorithm
// 1. If the current value of duration is equal to new duration, then return.
if (newDuration == duration())
return;
// 2. Set old duration to the current value of duration.
double oldDuration = duration();
bool requestSeek = m_attachedElement->currentTime() > newDuration;
// 3. Update duration to new duration.
m_webMediaSource->setDuration(newDuration);
// 4. If the new duration is less than old duration, then call remove(new duration, old duration) on all all objects in sourceBuffers.
if (newDuration < oldDuration) {
for (size_t i = 0; i < m_sourceBuffers->length(); ++i)
m_sourceBuffers->item(i)->remove(newDuration, oldDuration, ASSERT_NO_EXCEPTION);
}
// 5. 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:
// NOTE: Currently we assume that the media engine is able to render partial frames/cues. If a media
// engine gets added that doesn't support this, then we'll need to add logic to handle the substeps.
// 6. Update the media controller duration to new duration and run the HTMLMediaElement duration change algorithm.
m_attachedElement->durationChanged(newDuration, requestSeek);
}
void MediaSource::setReadyState(const AtomicString& state)
......
......@@ -122,6 +122,10 @@ private:
void scheduleEvent(const AtomicString& eventName);
void endOfStreamInternal(const blink::WebMediaSource::EndOfStreamStatus, ExceptionState&);
// Implements the duration change algorithm.
// https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#duration-change-algorithm
void durationChangeAlgorithm(double newDuration);
OwnPtr<blink::WebMediaSource> m_webMediaSource;
AtomicString m_readyState;
OwnPtrWillBeMember<GenericEventQueue> m_asyncEventQueue;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment