| <!DOCTYPE html> |
| <!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> |
| <html> |
| <head> |
| <title>MediaSource.duration & HTMLMediaElement.duration test cases.</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="mediasource-util.js"></script> |
| </head> |
| <body> |
| <div id="log"></div> |
| <script> |
| |
| var subType = MediaSourceUtil.getSubType(MediaSourceUtil.AUDIO_ONLY_TYPE); |
| var manifestFilenameAudio = subType + "/test-a-128k-44100Hz-1ch-manifest.json"; |
| var manifestFilenameVideo = subType + "/test-v-128k-320x240-30fps-10kfr-manifest.json"; |
| |
| function mediasource_truncated_duration_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 truncatedDuration = seekTo / 2.0; |
| |
| mediaElement.play(); |
| |
| // Append all the segments |
| test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer'); |
| test.expectEvent(mediaElement, 'playing', 'Playing triggered'); |
| sourceBuffer.appendBuffer(mediaData); |
| |
| test.waitForExpectedEvents(function() |
| { |
| test.expectEvent(mediaElement, 'seeking', 'seeking to seekTo'); |
| test.expectEvent(mediaElement, 'timeupdate', 'timeupdate while seeking to seekTo'); |
| test.expectEvent(mediaElement, 'seeked', 'seeked to seekTo'); |
| mediaElement.currentTime = seekTo; |
| assert_true(mediaElement.seeking, 'mediaElement.seeking (to seekTo)'); |
| }); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_greater_than_equal(mediaElement.currentTime, seekTo, 'Playback time has reached seekTo'); |
| assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to seekTo'); |
| |
| assert_false(sourceBuffer.updating, 'sourceBuffer.updating'); |
| |
| sourceBuffer.remove(truncatedDuration, Infinity); |
| |
| assert_true(sourceBuffer.updating, 'sourceBuffer.updating'); |
| test.expectEvent(sourceBuffer, 'updatestart', 'sourceBuffer'); |
| test.expectEvent(sourceBuffer, 'update', 'sourceBuffer'); |
| test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer'); |
| }); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_greater_than_equal(mediaElement.currentTime, seekTo, 'Playback time has reached seekTo'); |
| assert_false(sourceBuffer.updating, 'sourceBuffer.updating'); |
| |
| // Remove will not remove partial frames, so the resulting duration is the highest end time |
| // of the track buffer ranges, and is greater than or equal to the highest coded frame |
| // presentation time across all track buffer ranges. We first obtain the intersected track buffer |
| // ranges end time and set the duration to that value. |
| truncatedDuration = sourceBuffer.buffered.end(sourceBuffer.buffered.length-1); |
| assert_less_than(truncatedDuration, seekTo, |
| 'remove has removed the current playback position from at least one track buffer'); |
| |
| mediaSource.duration = truncatedDuration; |
| test.expectEvent(mediaElement, 'seeking', 'Seeking to truncated duration'); |
| |
| // The actual duration may be slightly higher than truncatedDuration because the |
| // duration is adjusted upwards if necessary to be the highest end time across all track buffer |
| // ranges. Allow that increase here. |
| assert_less_than_equal(truncatedDuration, mediaSource.duration, |
| 'Duration should not be less than what was set'); |
| // Here, we assume no test media coded frame duration is longer than 100ms. |
| assert_less_than(mediaSource.duration - truncatedDuration, 0.1); |
| |
| // Update our truncatedDuration to be the actual new duration. |
| truncatedDuration = mediaSource.duration; |
| |
| assert_true(mediaElement.seeking, 'Seeking after setting truncatedDuration'); |
| }); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_equals(mediaElement.currentTime, truncatedDuration, |
| 'Playback time is truncatedDuration while seeking'); |
| assert_true(mediaElement.seeking, 'mediaElement.seeking while seeking to truncatedDuration'); |
| assert_equals(mediaElement.duration, truncatedDuration, |
| 'mediaElement truncatedDuration during seek to it'); |
| assert_equals(mediaSource.duration, truncatedDuration, |
| 'mediaSource truncatedDuration during seek to it'); |
| |
| testFunction(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData, |
| truncatedDuration); |
| }); |
| }, description, options); |
| } |
| |
| mediasource_truncated_duration_seek_test(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, |
| mediaData, truncatedDuration) |
| { |
| // Tests that duration truncation below current playback position |
| // starts seek to new duration. |
| test.done(); |
| }, 'Test seek starts on duration truncation below currentTime'); |
| |
| mediasource_truncated_duration_seek_test(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, |
| mediaData, truncatedDuration) |
| { |
| // The duration has been truncated at this point, and there is an |
| // outstanding seek pending. |
| test.expectEvent(sourceBuffer, 'updateend', 'updateend after appending more data'); |
| |
| test.expectEvent(mediaElement, 'timeupdate', 'timeupdate while finishing seek to truncatedDuration'); |
| test.expectEvent(mediaElement, 'seeked', 'seeked to truncatedDuration'); |
| |
| // Allow seek to complete by appending more data beginning at the |
| // truncated duration timestamp. |
| sourceBuffer.timestampOffset = truncatedDuration; |
| sourceBuffer.appendBuffer(mediaData); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_greater_than_equal(mediaElement.currentTime, truncatedDuration, |
| 'Playback time has reached truncatedDuration'); |
| assert_approx_equals(mediaElement.duration, truncatedDuration + 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 truncatedDuration'); |
| |
| test.done(); |
| }); |
| }, 'Test appendBuffer completes previous seek to truncated duration'); |
| |
| mediasource_truncated_duration_seek_test(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, |
| mediaData, truncatedDuration) |
| { |
| // The duration has been truncated at this point, and there is an |
| // outstanding seek pending. |
| test.expectEvent(mediaSource, 'sourceended', 'endOfStream acknowledged'); |
| |
| test.expectEvent(mediaElement, 'timeupdate', 'timeupdate while finishing seek to truncatedDuration'); |
| test.expectEvent(mediaElement, 'seeked', 'seeked to truncatedDuration'); |
| |
| // Call endOfStream() to complete the pending seek. |
| mediaSource.endOfStream(); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_greater_than_equal(mediaElement.currentTime, truncatedDuration, |
| 'Playback time has reached truncatedDuration'); |
| // The mediaSource.readyState is "ended". Buffered ranges have been adjusted to the longest track. |
| truncatedDuration = sourceBuffer.buffered.end(sourceBuffer.buffered.length-1); |
| assert_equals(mediaElement.duration, truncatedDuration, |
| 'mediaElement truncatedDuration after seek to it'); |
| assert_equals(mediaSource.duration, truncatedDuration, |
| 'mediaSource truncatedDuration after seek to it'); |
| assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to truncatedDuration'); |
| |
| test.done(); |
| }); |
| }, 'Test endOfStream completes previous seek to truncated 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 newDuration = 0.5; |
| |
| var expectedDurationChangeEventCount = 1; |
| var durationchangeEventCounter = 0; |
| var durationchangeEventHandler = test.step_func(function(event) |
| { |
| assert_equals(mediaElement.duration, mediaSource.duration, 'mediaElement newDuration'); |
| // Final duration may be greater than originally set as per MSE's 2.4.6 Duration change |
| // Adjust newDuration accordingly. |
| assert_less_than_equal(newDuration, mediaSource.duration, 'mediaSource newDuration'); |
| durationchangeEventCounter++; |
| }); |
| |
| mediaElement.play(); |
| |
| // Append all the segments |
| test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer'); |
| test.expectEvent(mediaElement, 'playing', 'Playing triggered'); |
| sourceBuffer.appendBuffer(mediaData); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_less_than(mediaElement.currentTime, newDuration / 2, 'mediaElement currentTime'); |
| |
| assert_false(sourceBuffer.updating, "updating"); |
| |
| // Truncate duration. This should result in one 'durationchange' fired. |
| sourceBuffer.remove(newDuration, Infinity); |
| |
| assert_true(sourceBuffer.updating, "updating"); |
| test.expectEvent(sourceBuffer, 'updatestart', 'sourceBuffer'); |
| test.expectEvent(sourceBuffer, 'update', 'sourceBuffer'); |
| test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer'); |
| }); |
| |
| test.waitForExpectedEvents(function() |
| { |
| // Media load also fires 'durationchange' event, so only start counting them now. |
| mediaElement.addEventListener('durationchange', durationchangeEventHandler); |
| |
| assert_false(sourceBuffer.updating, "updating"); |
| |
| // Truncate duration. This should result in one 'durationchange' fired. |
| mediaSource.duration = newDuration; |
| |
| // Final duration may be greater than originally set as per MSE's 2.4.6 Duration change |
| // Adjust newDuration accordingly. |
| assert_true(newDuration <= mediaSource.duration, 'adjusted duration'); |
| newDuration = mediaSource.duration; |
| |
| // Set duration again to make sure it does not trigger another 'durationchange' event. |
| mediaSource.duration = newDuration; |
| |
| // Mark endOfStream so that playback can reach 'ended' at the new duration. |
| test.expectEvent(mediaSource, 'sourceended', 'endOfStream acknowledged'); |
| mediaSource.endOfStream(); |
| |
| // endOfStream can change duration slightly. |
| // Allow for one more 'durationchange' event only in this case. |
| var currentDuration = mediaSource.duration; |
| if (currentDuration != newDuration) { |
| newDuration = currentDuration; |
| ++expectedDurationChangeEventCount; |
| } |
| |
| // Allow media to play to end while counting 'durationchange' events. |
| test.expectEvent(mediaElement, 'ended', 'Playback ended'); |
| test.waitForExpectedEvents(function() |
| { |
| mediaElement.removeEventListener('durationchange', durationchangeEventHandler); |
| assert_equals(durationchangeEventCounter, expectedDurationChangeEventCount, 'durationchanges'); |
| 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; |
| var newDuration = fullDuration / 2; |
| |
| // Append all the segments |
| test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer'); |
| test.expectEvent(mediaElement, 'loadedmetadata', 'mediaElement'); |
| sourceBuffer.appendBuffer(mediaData); |
| |
| test.waitForExpectedEvents(function() |
| { |
| assert_false(sourceBuffer.updating, "updating"); |
| |
| assert_throws("InvalidStateError", function() |
| { |
| mediaSource.duration = newDuration; |
| }, "duration"); |
| |
| test.done(); |
| }); |
| }, 'Test setting the duration to less than the highest starting presentation timestamp will throw'); |
| |
| mediasource_test(function(test, mediaElement, mediaSource) |
| { |
| mediaElement.addEventListener("error", test.unreached_func("Unexpected event 'error'")); |
| MediaSourceUtil.fetchManifestAndData(test, manifestFilenameAudio, function(typeAudio, dataAudio) |
| { |
| MediaSourceUtil.fetchManifestAndData(test, manifestFilenameVideo, function(typeVideo, dataVideo) |
| { |
| var sourceBufferAudio = mediaSource.addSourceBuffer(typeAudio); |
| var sourceBufferVideo = mediaSource.addSourceBuffer(typeVideo); |
| var newDuration = 1.2; |
| |
| sourceBufferAudio.appendWindowEnd = 2.0; |
| sourceBufferAudio.appendWindowStart = newDuration / 2.0; |
| sourceBufferAudio.appendBuffer(dataAudio); |
| |
| sourceBufferVideo.appendWindowEnd = 2.0; |
| sourceBufferVideo.appendWindowStart = newDuration * 1.3; |
| sourceBufferVideo.appendBuffer(dataVideo); |
| |
| test.expectEvent(sourceBufferAudio, "updateend"); |
| test.expectEvent(sourceBufferVideo, "updateend"); |
| test.waitForExpectedEvents(function() |
| { |
| assert_equals(sourceBufferAudio.buffered.length, 1); |
| assert_equals(sourceBufferVideo.buffered.length, 1); |
| assert_less_than(sourceBufferAudio.buffered.start(0), newDuration); |
| assert_greater_than(sourceBufferVideo.buffered.start(0), newDuration); |
| assert_throws("InvalidStateError", function () { mediaSource.duration = newDuration; }); |
| test.done(); |
| }); |
| }); |
| }); |
| }, "Truncating the duration throws an InvalidStateError exception when new duration is less than the highest buffered range start time of one of the track buffers"); |
| |
| mediasource_test(function(test, mediaElement, mediaSource) |
| { |
| mediaElement.addEventListener("error", test.unreached_func("Unexpected event 'error'")); |
| MediaSourceUtil.fetchManifestAndData(test, manifestFilenameAudio, function(typeAudio, dataAudio) |
| { |
| MediaSourceUtil.fetchManifestAndData(test, manifestFilenameVideo, function(typeVideo, dataVideo) |
| { |
| var sourceBufferAudio = mediaSource.addSourceBuffer(typeAudio); |
| var sourceBufferVideo = mediaSource.addSourceBuffer(typeVideo); |
| |
| // Buffer audio [0.8,1.8) |
| sourceBufferAudio.timestampOffset = 0.8; |
| sourceBufferAudio.appendWindowEnd = 1.8; |
| sourceBufferAudio.appendBuffer(dataAudio); |
| |
| // Buffer video [1.5,3) |
| sourceBufferVideo.timestampOffset = 1.5; |
| sourceBufferVideo.appendWindowEnd = 3; |
| sourceBufferVideo.appendBuffer(dataVideo); |
| |
| test.expectEvent(sourceBufferAudio, "updateend"); |
| test.expectEvent(sourceBufferVideo, "updateend"); |
| test.waitForExpectedEvents(function() |
| { |
| var newDuration = 2.0; |
| |
| // Verify the test setup |
| assert_equals(sourceBufferAudio.buffered.length, 1); |
| assert_equals(sourceBufferVideo.buffered.length, 1); |
| assert_greater_than(sourceBufferAudio.buffered.end(0), 1.5); |
| assert_less_than(sourceBufferAudio.buffered.end(0), newDuration); |
| assert_less_than(sourceBufferVideo.buffered.start(0), newDuration); |
| assert_greater_than(sourceBufferVideo.buffered.end(0), newDuration + 0.5); |
| |
| // Verify the expected error |
| // We assume relocated test video has at least one coded |
| // frame presentation interval which fits in [>2.0,>2.5) |
| assert_throws("InvalidStateError", function () { mediaSource.duration = newDuration; }); |
| test.done(); |
| }); |
| }); |
| }); |
| }, "Truncating the duration throws an InvalidStateError exception when new duration is less than a buffered coded frame presentation time"); |
| |
| mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData) |
| { |
| assert_less_than(segmentInfo.duration, 60, 'Sufficient test media duration'); |
| sourceBuffer.appendBuffer(mediaData); |
| test.expectEvent(sourceBuffer, 'updateend', 'Media data appended to the SourceBuffer'); |
| test.waitForExpectedEvents(function() |
| { |
| mediaSource.duration = 60; |
| assert_false(sourceBuffer.updating, 'No SourceBuffer update when duration is increased'); |
| test.done(); |
| }); |
| }, 'Increasing the duration does not trigger any SourceBuffer update'); |
| |
| mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData) |
| { |
| assert_greater_than(segmentInfo.duration, 2, 'Sufficient test media duration'); |
| mediaElement.play(); |
| sourceBuffer.appendBuffer(mediaData); |
| test.expectEvent(sourceBuffer, 'updateend', 'Media data appended to the SourceBuffer'); |
| test.waitForExpectedEvents(function() |
| { |
| mediaSource.duration = 60; |
| assert_false(sourceBuffer.updating, 'No SourceBuffer update when duration is increased'); |
| test.done(); |
| }); |
| }, 'Increasing the duration during media playback does not trigger any SourceBuffer update'); |
| </script> |
| </body> |
| </html> |