| <html> | |
| <body onload="RunTest();"> | |
| <div id="player_container"></div> | |
| <div id="logs"></div> | |
| </body> | |
| <script type="text/javascript"> | |
| // <audio> or <video> player element. | |
| var player; | |
| var tag = ''; | |
| var logs = document.getElementById('logs'); | |
| var heartbeatCount = 0; | |
| var isSizeTest = false; | |
| function Log(message) { | |
| logs.innerHTML = message + '<br>' + logs.innerHTML; | |
| console.log(message); | |
| } | |
| function onLogEvent(e) { | |
| Log('Event: ' + e.type + ', State: {' + getMediaStatus(e.target) + '}'); | |
| } | |
| function getTimeRanges(range) { | |
| const result = []; | |
| for (let i = 0; i < range.length; i++) { | |
| result.push(`[${range.start(i)},${range.end(i)}]`); | |
| } | |
| return '[' + result.join(', ') + ']'; | |
| } | |
| function getMediaStatus(video) { | |
| if (video == null) { | |
| return 'null'; | |
| } | |
| const result = []; | |
| result.push(`readyState: ${video.readyState}`); | |
| result.push(`networkState: ${video.networkState}`); | |
| result.push(`paused: ${video.paused}`); | |
| result.push(`ended: ${video.ended}`); | |
| result.push(`currentTime: ${video.currentTime}`); | |
| result.push(`duration: ${video.duration}`); | |
| result.push(`buffered: ${getTimeRanges(video.buffered)}`); | |
| result.push(`played: ${getTimeRanges(video.played)}`); | |
| if (tag == 'video') { | |
| result.push(`size: ${video.videoWidth}x${video.videoHeight}`); | |
| } | |
| if (video.error) { | |
| result.push(`error: {${video.error.code},${video.error.message}}`); | |
| } | |
| if (window.internals) { | |
| result.push(`suspended: ${internals.isMediaElementSuspended(video) ? | |
| 'true' : 'false'}`); | |
| } | |
| return result.join(', '); | |
| } | |
| // Listen for |event| from |element|, set document.title = |event| upon event. | |
| function InstallTitleEventHandler(element, event) { | |
| element.addEventListener(event, function(e) { | |
| onLogEvent(e); | |
| if (!isSizeTest) { | |
| document.title = event.toUpperCase(); | |
| return; | |
| } | |
| var video = e.target; | |
| document.title = | |
| event.toUpperCase() + ` ${video.videoWidth} ${video.videoHeight}`; | |
| }, false); | |
| } | |
| function InstallTitleErrorEventHandler(element, error_substr) { | |
| element.addEventListener('error', function(e) { | |
| onLogEvent(e); | |
| // Element's error attribute must be populated. | |
| var mediaError = element.error; | |
| if (mediaError == null) { | |
| Log('ERROR: Null element error attribute while handling error event.'); | |
| Failed(); | |
| return; | |
| } | |
| Log('INFO: Element error message is ' + mediaError.message); | |
| if (error_substr != null && !mediaError.message.includes(error_substr)) { | |
| Log('ERROR: Element error message (' + mediaError.message + | |
| ') does not contain expected substring (' + error_substr + ')'); | |
| Failed(); | |
| return; | |
| } | |
| document.title = 'ERROR'; | |
| }, false); | |
| } | |
| function Failed() { | |
| document.title = 'FAILED'; | |
| return false; | |
| } | |
| function SeekTestStep(e) { | |
| if (e) onLogEvent(e); | |
| // Remove the previous triggers for this step. | |
| player.removeEventListener('ended', SeekTestStep, false); | |
| player.removeEventListener('timeupdate', SeekTestTimeoutSetup, false); | |
| // Test completes on the next ended event. | |
| InstallTitleEventHandler(player, 'ended'); | |
| player.currentTime = 0.9 * player.duration; | |
| Log('Seeking back to ' + 0.9 * player.duration); | |
| player.play().catch(function(error) { | |
| Log(error); | |
| }); | |
| } | |
| function SeekTestTimeoutSetup() { | |
| if (player.currentTime < 2) | |
| return; | |
| Log('Triggering seek due to timeout @ ' + player.currentTime); | |
| SeekTestStep(); | |
| } | |
| // Uses URL query parameters to create an audio or video element using a given | |
| // source. URL must be of the form: | |
| // "player.html?[tag]=[media_url][&loop=[true/false]][&error_substr=substr]". | |
| // | |
| // Plays the media and waits for X seconds of playback or the ended event, at | |
| // which point the test seeks near the end of the file and resumes playback. | |
| // Test completes when the second ended event occurs or an error event occurs at | |
| // any time. | |
| // There is an optional loop query parameter, which when set to true will cause | |
| // the created media element to loop. | |
| // There is an optional error_substr query parameter, which when set will cause | |
| // any error event handling to fail if the MediaError.message does not contain | |
| // the specified substring resulting from decodeURIComponent(). | |
| function RunTest() { | |
| var url_parts = window.location.href.split('?'); | |
| if (url_parts.length != 2) | |
| return Failed(); | |
| var media_url = ''; | |
| var loop = false; | |
| var error_substr = null; | |
| var query_params = url_parts[1].split('&'); | |
| for (var query_param in query_params) { | |
| var query_parts = query_params[query_param].split('='); | |
| if (query_parts.length != 2) { | |
| return Failed(); | |
| } | |
| if (query_parts[0] == 'audio' || query_parts[0] == 'video') { | |
| tag = query_parts[0]; | |
| media_url = query_parts[1]; | |
| continue; | |
| } | |
| if (query_parts[0] == 'loop') { | |
| loop = (query_parts[1] == 'true'); | |
| continue; | |
| } | |
| if (query_parts[0] == 'error_substr') { | |
| error_substr = decodeURIComponent(query_parts[1]); | |
| continue; | |
| } | |
| if (query_parts[0] == 'sizetest') { | |
| isSizeTest = true; | |
| continue; | |
| } | |
| } | |
| if (tag != 'audio' && tag != 'video') { | |
| return Failed(); | |
| } | |
| // Create player and insert into DOM. | |
| player = document.createElement(tag); | |
| player.controls = true; | |
| document.getElementById('player_container').appendChild(player); | |
| // Transition to the seek test after X seconds of playback or when the ended | |
| // event occurs, whichever happens first. | |
| player.addEventListener('ended', SeekTestStep, false); | |
| player.addEventListener('timeupdate', SeekTestTimeoutSetup, false); | |
| // Log events. | |
| player.addEventListener('abort', onLogEvent); | |
| player.addEventListener('canplay', onLogEvent); | |
| player.addEventListener('canplaythrough', onLogEvent); | |
| player.addEventListener('durationchange', onLogEvent); | |
| player.addEventListener('emptied', onLogEvent); | |
| player.addEventListener('loadeddata', onLogEvent); | |
| player.addEventListener('loadedmetadata', onLogEvent); | |
| player.addEventListener('loadstart', onLogEvent); | |
| player.addEventListener('play', onLogEvent); | |
| player.addEventListener('playing', onLogEvent); | |
| player.addEventListener('progress', onLogEvent); | |
| player.addEventListener('resize', onLogEvent); | |
| player.addEventListener('stalled', onLogEvent); | |
| player.addEventListener('suspend', onLogEvent); | |
| player.addEventListener('timeupdate', onLogEvent); | |
| player.addEventListener('waiting', onLogEvent); | |
| setInterval(function () { | |
| onLogEvent({'type': 'heartbeat #' + ++heartbeatCount, 'target': player}); | |
| }, 1000); | |
| // Ensure we percolate up any error events, and optionally verify they contain | |
| // the expected error substring in their message. | |
| InstallTitleErrorEventHandler(player, error_substr); | |
| // Starts the player. | |
| player.loop = loop; | |
| player.preload = 'auto'; | |
| player.src = media_url; | |
| Log('Starting test by calling ' + tag + '.play()'); | |
| player.play().catch(function(error) { | |
| Log(error); | |
| }); | |
| } | |
| </script> | |
| </html> |