| <!DOCTYPE html> |
| <title>Test that stalled, timeupdate and waiting events are sent when media load stalls in the middle.</title> |
| <video id="test_video"></video> |
| <div id="log_console"></div> |
| <script src=../../media-resources/media-file.js></script> |
| <script src="/w3c/resources/testharness.js"></script> |
| <script src="/w3c/resources/testharnessreport.js"></script> |
| <script> |
| async_test(t => { |
| let video = document.getElementById('test_video'); |
| let log_console = document.getElementById('log_console'); |
| |
| // For debugging. |
| function logMessage(msg) { |
| let span = document.createElement("span"); |
| span.innerHTML = msg + '<br>'; |
| log_console.appendChild(span); |
| }; |
| |
| function logEvent(e) { |
| logMessage('event: ' + e.type); |
| }; |
| |
| playback_ew = new EventWatcher(t, video, [ |
| 'canplay', |
| 'canplaythrough', |
| 'durationchange', |
| 'loadedmetadata', |
| 'loadeddata', |
| 'play', |
| 'playing', |
| 'waiting']); |
| |
| // The stalled event needs a separate watcher as it can be fired at any |
| // point during the sequence of other playback events. Stalled is triggered |
| // by prolonged network inactivity. |
| stalled_ew = new EventWatcher(t, video, [ |
| 'stalled']); |
| |
| // This helper is an alternative to EventWatcher for events that fire on a |
| // recurring basis. EventWatcher is not suitable because you must always be |
| // "waiting" for the event to fire every time, whereas this method allows |
| // you to just verify that it fired once and move on. |
| function waitForRecurringEvent(name) { |
| let resolve_cb; |
| let promise = new Promise(function(resolve, reject) { |
| resolve_cb = resolve; |
| }); |
| video.addEventListener(name, t.step_func((evt) => resolve_cb(evt))); |
| return promise; |
| } |
| |
| // Monitor timeupdate w.r.t. readyState throughout test. Time should not |
| // advance while readyState <= HAVE_CURRENT_DATA. |
| let timeAtStartOfWaiting = null; |
| video.addEventListener('timeupdate', t.step_func(function(e) { |
| logMessage('event: timeupdate at time: ' + e.target.currentTime +' readystate:' + e.target.readyState); |
| if (e.target.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA) { |
| if (timeAtStartOfWaiting == null) { |
| // Note the time when waiting begins. |
| timeAtStartOfWaiting = e.target.currentTime; |
| } else { |
| // Verify that current time is not advancing while waiting. |
| assert_equals(e.target.currentTime, timeAtStartOfWaiting); |
| } |
| } else if (timeAtStartOfWaiting != null) { |
| // Waiting has ended, so clear timeAtStartOfWaiting. |
| timeAtStartOfWaiting = null; |
| } |
| })); |
| |
| // NOTE: Event sequence verification is achieved by chaining together |
| // promises via then(). To verify separate parallel event sequences (e.g. |
| // playback vs network), we setup separate chains of promises. Promise.all |
| // ensures that all separate sequences complete. |
| |
| // Verify playback progresses then runs out of data. |
| Promise.all([ |
| // First wait for the resource to load. |
| playback_ew.wait_for('durationchange').then(logEvent) |
| .then(() => playback_ew.wait_for('loadedmetadata')).then(logEvent) |
| .then(() => playback_ew.wait_for('loadeddata')).then(logEvent) |
| .then(() => playback_ew.wait_for('canplay')).then(logEvent) |
| .then(() => playback_ew.wait_for('canplaythrough')).then(logEvent) |
| .then(t.step_func(function() { |
| assert_true(video.readyState > HTMLMediaElement.HAVE_CURRENT_DATA); |
| })) |
| // Now play the file and wait for playback to stall (fire waiting). |
| .then(t.step_func(function() { |
| video.play(); |
| // NOTE: setting the wait_for here because we will miss it if we do |
| // it after the play call resolves its promise. |
| return playback_ew.wait_for('play').then(logEvent); |
| })) |
| .then(() => playback_ew.wait_for('playing')).then(logEvent) |
| // Now observe waiting event and verify readyState |
| .then(() => playback_ew.wait_for('waiting')).then(logEvent) |
| .then(t.step_func(function(){ |
| assert_equals(video.readyState, HTMLMediaElement.HAVE_CURRENT_DATA); |
| })), |
| |
| // timeupdate should fire throughout playback. Make sure we see one. |
| waitForRecurringEvent('timeupdate').then(logEvent), |
| |
| // progress should fire throughout download. Make sure we see one. |
| // Later the download should stall. |
| waitForRecurringEvent('progress').then(logEvent) |
| .then(() => stalled_ew.wait_for('stalled')).then(logEvent) |
| |
| // Verify download and playback resume. |
| ]).then(() => Promise.all([ |
| // Playback should resume when download again makes progress. |
| waitForRecurringEvent('progress').then(logEvent), |
| |
| // timeupdate should fire throughout playback. Make sure we see one. |
| waitForRecurringEvent('timeupdate').then(logEvent), |
| |
| // Verify correct sequence of playback events. |
| playback_ew.wait_for('canplay').then(logEvent) |
| .then(t.step_func(function() { |
| assert_true(video.readyState > HTMLMediaElement.HAVE_CURRENT_DATA); |
| })) |
| .then(() => playback_ew.wait_for('playing')).then(logEvent) |
| .then(() => playback_ew.wait_for('canplaythrough')).then(logEvent) |
| ])).then(t.step_func_done()); |
| |
| |
| // Find a supported media file. |
| var mediaFile = "content/test.ogv"; |
| var mimeType = mimeTypeForFile(mediaFile); |
| // URL will load part of the file, pause for 8 seconds, then load the rest. |
| // The delay of 8 seconds is chosen to reduce flakiness in waiting for the |
| // stalled event, which should arrive after roughly 3 seconds of inactivity. |
| video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000&stallFor=8"; |
| |
| }, "Stalled download pauses playback. When download resumes playback continues. Verify events and readyStates."); |
| </script> |