blob: e438d35b39ea6de3b57d824bb26f729e73b946b9 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<script src="/w3c/resources/testharness.js"></script>
<script src="/w3c/resources/testharnessreport.js"></script>
<script src="mediasource-util.js"></script>
<link rel='stylesheet' href='/w3c/resources/testharness.css'>
</head>
<body>
<div id="log"></div>
<script>
function mediasource_duration_below_currentTime_seek_test(testFunction, description, options)
{
return mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
assert_greater_than(segmentInfo.duration, 2, 'Sufficient test media duration');
var fullDuration = segmentInfo.duration;
var seekTo = fullDuration / 2.0;
var reducedDuration = seekTo / 2.0;
var receivedTimeupdate = false;
var timeupdateEventHandler = test.step_func(function(event) { receivedTimeupdate = true; });
mediaElement.play();
// Append all the segments
test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer append completed');
test.expectEvent(mediaElement, 'playing', 'Playing triggered');
sourceBuffer.appendBuffer(mediaData);
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.duration, fullDuration, 'mediaElement duration matches fullDuration');
assert_equals(mediaSource.duration, fullDuration, 'mediaSource duration matches fullDuration');
test.expectEvent(mediaElement, 'seeking', 'seeking to seekTo');
// Intentionally not expecting exactly 1 timeupdate here. currentTime is moving, so extra events could occur.
mediaElement.addEventListener('timeupdate', timeupdateEventHandler, { once: true });
test.expectEvent(mediaElement, 'seeked', 'seeked to seekTo');
mediaElement.currentTime = seekTo;
assert_true(mediaElement.seeking, 'mediaElement.seeking (to seekTo)');
});
test.waitForExpectedEvents(function()
{
assert_true(receivedTimeupdate, 'mediaElement timeupdate occurred at least once since playing and through starting, doing and completing seek');
assert_greater_than_equal(mediaElement.currentTime, seekTo, 'Playback time has reached seekTo');
assert_equals(mediaElement.duration, fullDuration, 'mediaElement duration matches fullDuration after seekTo');
assert_equals(mediaSource.duration, fullDuration, 'mediaSource duration matches fullDuration after seekTo');
assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to seekTo');
// Explicitly remove buffered media beyond the new reduced duration prior to reducing duration.
// Implicit removal of buffered media as part of duration reduction is disallowed as of
// https://github.com/w3c/media-source/pull/65/
test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer range removal completed');
sourceBuffer.remove(reducedDuration, fullDuration);
assert_true(sourceBuffer.updating, 'sourceBuffer.updating during range removal');
});
test.waitForExpectedEvents(function()
{
assert_false(sourceBuffer.updating, 'sourceBuffer.updating after range removal');
assert_greater_than_equal(mediaElement.currentTime, seekTo,
'Playback time is still at least seekTo after range removal');
test.expectEvent(mediaElement, 'seeking', 'Seeking to reduced duration');
mediaSource.duration = reducedDuration;
assert_true(mediaElement.seeking, 'Seeking after setting reducedDuration');
});
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.currentTime, reducedDuration,
'Playback time is reducedDuration while seeking');
assert_true(mediaElement.seeking, 'mediaElement.seeking while seeking to reducedDuration');
assert_equals(mediaElement.duration, reducedDuration,
'mediaElement duration matches reducedDuration during seek to it');
assert_equals(mediaSource.duration, reducedDuration,
'mediaSource duration matches reducedDuration during seek to it');
// FIXME: Confirm 'waiting' and then 'stalled' fire here. See http://crbug.com/266592.
testFunction(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData,
reducedDuration);
});
}, description, options);
}
mediasource_duration_below_currentTime_seek_test(function(test, mediaElement, mediaSource, segmentInfo,
sourceBuffer, mediaData, reducedDuration)
{
// Tests that duration reduction below current playback position
// starts seek to new duration.
test.done();
}, 'Test seek starts on duration reduction below currentTime');
mediasource_duration_below_currentTime_seek_test(function(test, mediaElement, mediaSource, segmentInfo,
sourceBuffer, mediaData, reducedDuration)
{
// The duration has been reduced at this point, and there is an
// outstanding seek pending.
test.expectEvent(sourceBuffer, 'updateend', 'updateend after appending more data');
// FIXME: Confirm 'playing' fires here. See http://crbug.com/266592.
test.expectEvent(mediaElement, 'timeupdate', 'timeupdate while finishing seek to reducedDuration');
test.expectEvent(mediaElement, 'seeked', 'seeked to reducedDuration');
// Allow seek to complete by appending more data beginning at the
// reduced duration timestamp.
sourceBuffer.timestampOffset = reducedDuration;
sourceBuffer.appendBuffer(mediaData);
test.waitForExpectedEvents(function()
{
assert_greater_than_equal(mediaElement.currentTime, reducedDuration,
'Playback time has reached reducedDuration');
assert_approx_equals(mediaElement.duration, reducedDuration + segmentInfo.duration, 0.05,
'mediaElement duration increased by new append');
assert_equals(mediaSource.duration, mediaElement.duration,
'mediaSource duration increased by new append');
assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to reducedDuration');
test.done();
});
}, 'Test appendBuffer completes previous seek to reduced duration');
mediasource_duration_below_currentTime_seek_test(function(test, mediaElement, mediaSource, segmentInfo,
sourceBuffer, mediaData, reducedDuration)
{
// The duration has been reduced at this point, and there is an
// outstanding seek pending.
test.expectEvent(mediaSource, 'sourceended', 'endOfStream acknowledged');
// FIXME: Investigate if 'playing' should fire here. See http://crbug.com/266592.
test.expectEvent(mediaElement, 'timeupdate', 'timeupdate while finishing seek to reducedDuration');
test.expectEvent(mediaElement, 'seeked', 'seeked to reducedDuration');
// Call endOfStream() to complete the pending seek.
mediaSource.endOfStream();
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.currentTime, reducedDuration,
'Playback time has reached reducedDuration');
assert_equals(mediaElement.duration, reducedDuration,
'mediaElement duration matches reducedDuration after seek to it');
assert_equals(mediaSource.duration, reducedDuration,
'mediaSource duration matches reducedDuration after seek to it');
assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to reducedDuration');
test.done();
});
}, 'Test endOfStream completes previous seek to reduced duration');
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
assert_greater_than(segmentInfo.duration, 2, 'Sufficient test media duration');
var fullDuration = segmentInfo.duration;
var twiceFullDuration = fullDuration * 2;
var expectedDuration = fullDuration;
var durationchangeEventCounter = 0;
var durationchangeEventHandler = test.step_func(function(event)
{
assert_equals(mediaElement.duration, expectedDuration, 'mediaElement duration matches expectedDuration');
assert_equals(mediaSource.duration, expectedDuration, 'mediaSource duration matches expectedDuration');
durationchangeEventCounter++;
});
mediaElement.addEventListener('durationchange', durationchangeEventHandler);
// Append all the segments
test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer append completed');
test.expectEvent(mediaElement, 'durationchange', 'mediaElement durationchange as part of reaching loadedmetadata');
test.expectEvent(mediaElement, 'loadedmetadata', 'mediaElement loadedmetadata');
sourceBuffer.appendBuffer(mediaData);
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.duration, fullDuration, 'mediaElement duration matches fullDuration');
assert_equals(mediaSource.duration, fullDuration, 'mediaSource duration matches fullDuration');
assert_equals(mediaElement.currentTime, 0, 'mediaElement currentTime check - we\'re not playing');
// The first append's init segment triggers an initial 'durationchange' event, and
// the expectEvent for durationchange, above, should ensure this assertion passes.
// Note that the init segment in mediaData should have a duration value as large
// as or larger than the duration of media in mediaData, so that append should
// trigger precisely 1 'durationchange' event.
assert_equals(durationchangeEventCounter, 1, 'Should have exactly 1 "durationchange" event processed here');
// Increase duration. This should result in a second 'durationchange' fired.
expectedDuration = twiceFullDuration;
mediaSource.duration = twiceFullDuration;
assert_false(sourceBuffer.updating, 'sourceBuffer.updating after duration set to twiceFullDuration');
assert_equals(mediaElement.duration, twiceFullDuration, 'mediaElement duration matches twiceFullDuration');
// Set duration again. Later, we verify this doesn't trigger another
// 'durationchange' event.
mediaSource.duration = twiceFullDuration;
assert_false(sourceBuffer.updating, 'sourceBuffer.updating after duration set again to twiceFullDuration');
assert_equals(mediaElement.duration, twiceFullDuration, 'mediaElement duration matches twiceFullDuration after mediaSource duration set again to twiceFullDuration');
// Use a seek to let any currently queued 'durationchange' events fire.
assert_false(mediaElement.seeking, 'mediaElement should not be seeking after setting twiceFullDuration twice');
assert_equals(mediaElement.currentTime, 0, 'mediaElement currentTime before seeking after setting twiceFullDuration twice');
test.expectEvent(mediaElement, 'seeked', 'mediaElement seeked after setting twiceFullDuration twice');
mediaElement.currentTime = fullDuration / 2;
});
test.waitForExpectedEvents(function()
{
assert_equals(durationchangeEventCounter, 2, 'durationchange count check before endOfStream()');
// Mark endOfStream to trigger duration reduction.
test.expectEvent(mediaSource, 'sourceended', 'endOfStream acknowledged');
expectedDuration = fullDuration;
mediaSource.endOfStream();
// endOfStream should reduce the duration back to fullDuration (and queue another 'durationchange').
assert_equals(mediaElement.duration, fullDuration, 'mediaElement duration returns to fullDuration after endOfStream()');
assert_equals(mediaSource.duration, fullDuration, 'mediaSource duration returns to fullDuration after endOfStream()');
// Use another seek to a distinct time to let any currently queued
// 'durationchange' events fire.
assert_false(mediaElement.seeking, 'mediaElement should not be seeking after calling endOfStream()');
test.expectEvent(mediaElement, 'seeked', 'mediaElement completed final seek');
mediaElement.currentTime -= fullDuration / 4;
});
test.waitForExpectedEvents(function()
{
mediaElement.removeEventListener('durationchange', durationchangeEventHandler);
// Counter should be 3 because each of the following should have triggered
// durationchange:
// 1) initial appendBuffer()'s causing transition to loadedmetadata
// 2) explicitly increasing mediaSource.duration (to same value twice, so just
// one corresponding durationchange should have been triggered).
// 3) endOfStream() reducing the duration
assert_equals(durationchangeEventCounter, 3, 'final durationchange count check');
test.done();
});
}, 'Test setting same duration multiple times does not fire duplicate durationchange');
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
assert_greater_than(segmentInfo.duration, 2, 'Sufficient test media duration');
var fullDuration = segmentInfo.duration;
// Append all the segments
test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
// TODO(wolenetz): Remove this flakiness workaround. See https://crbug.com/641121#c2.
test.expectEvent(mediaElement, 'loadedmetadata', 'mediaElement');
sourceBuffer.appendBuffer(mediaData);
test.waitForExpectedEvents(function()
{
assert_equals(mediaElement.duration, fullDuration, 'mediaElement duration matches fullDuration');
assert_equals(mediaSource.duration, fullDuration, 'mediaSource duration matches fullDuration');
// TODO(wolenetz): Fine-tune this test to use the buffered attribute's highest end time
// instead of fullDuration once Chrome correctly reports buffered PTS, not DTS.
// See https://crbug.com/398130.
// Setting duration to same as current, or increasing it, should not trigger exception.
mediaSource.duration = fullDuration;
mediaSource.duration = fullDuration + 1;
// Reducing duration to below the highest buffered PTS should trigger exception.
assert_throws_dom('InvalidStateError',
function() { mediaSource.duration = fullDuration - 0.05; },
'Duration reduction that truncates at least one whole coded frame throws an exception.');
assert_equals(mediaSource.duration, fullDuration + 1, 'mediaSource duration matches fullDuration+1');
// Reducing duration without truncating any buffered media should not trigger exception.
mediaSource.duration = fullDuration;
// Reducing duration by less than the minimum of the last test audio and video frame
// durations should not trigger exception.
mediaSource.duration = fullDuration - 0.001;
test.done();
});
}, 'Test duration reduction below highest buffered presentation time is disallowed');
</script>
</body>
</html>