diff options
Diffstat (limited to 'dom/media/mediasource/test/mediasource.js')
-rw-r--r-- | dom/media/mediasource/test/mediasource.js | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/dom/media/mediasource/test/mediasource.js b/dom/media/mediasource/test/mediasource.js new file mode 100644 index 0000000000..440b05009f --- /dev/null +++ b/dom/media/mediasource/test/mediasource.js @@ -0,0 +1,233 @@ +// Helpers for Media Source Extensions tests + +let gMSETestPrefs = [ + ["media.mediasource.enabled", true], + ["media.audio-max-decode-error", 0], + ["media.video-max-decode-error", 0], +]; + +// Called before runWithMSE() to set the prefs before running MSE tests. +function addMSEPrefs(...prefs) { + gMSETestPrefs = gMSETestPrefs.concat(prefs); +} + +async function runWithMSE(testFunction) { + await once(window, "load"); + await SpecialPowers.pushPrefEnv({ set: gMSETestPrefs }); + + const ms = new MediaSource(); + + const el = document.createElement("video"); + el.src = URL.createObjectURL(ms); + el.preload = "auto"; + + document.body.appendChild(el); + SimpleTest.registerCleanupFunction(() => { + el.remove(); + el.removeAttribute("src"); + el.load(); + }); + try { + await testFunction(ms, el); + } catch (e) { + ok(false, `${testFunction.name} failed with error ${e.name}`); + throw e; + } +} + +async function fetchWithXHR(uri) { + return new Promise(resolve => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", uri, true); + xhr.responseType = "arraybuffer"; + xhr.addEventListener("load", function() { + is( + xhr.status, + 200, + "fetchWithXHR load uri='" + uri + "' status=" + xhr.status + ); + resolve(xhr.response); + }); + xhr.send(); + }); +} + +function range(start, end) { + const rv = []; + for (let i = start; i < end; ++i) { + rv.push(i); + } + return rv; +} + +function must_throw(f, msg, error = true) { + try { + f(); + ok(!error, msg); + } catch (e) { + ok(error, msg); + if (error === true) { + ok( + false, + `Please provide name of expected error! Got ${e.name}: ${e.message}.` + ); + } else if (e.name != error) { + throw e; + } + } +} + +async function must_reject(f, msg, error = true) { + try { + await f(); + ok(!error, msg); + } catch (e) { + ok(error, msg); + if (error === true) { + ok( + false, + `Please provide name of expected error! Got ${e.name}: ${e.message}.` + ); + } else if (e.name != error) { + throw e; + } + } +} + +const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); + +const must_not_throw = (f, msg) => must_throw(f, msg, false); +const must_not_reject = (f, msg) => must_reject(f, msg, false); + +async function once(target, name) { + return new Promise(r => target.addEventListener(name, r, { once: true })); +} + +function timeRangeToString(r) { + let str = "TimeRanges: "; + for (let i = 0; i < r.length; i++) { + str += "[" + r.start(i) + ", " + r.end(i) + ")"; + } + return str; +} + +async function loadSegment(sb, typedArrayOrArrayBuffer) { + const typedArray = + typedArrayOrArrayBuffer instanceof ArrayBuffer + ? new Uint8Array(typedArrayOrArrayBuffer) + : typedArrayOrArrayBuffer; + info( + `Loading buffer: [${typedArray.byteOffset}, ${typedArray.byteOffset + + typedArray.byteLength})` + ); + const beforeBuffered = timeRangeToString(sb.buffered); + const p = once(sb, "update"); + sb.appendBuffer(typedArray); + await p; + const afterBuffered = timeRangeToString(sb.buffered); + info( + `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}` + ); +} + +async function fetchAndLoad(sb, prefix, chunks, suffix) { + // Fetch the buffers in parallel. + const buffers = await Promise.all( + chunks.map(c => fetchWithXHR(prefix + c + suffix)) + ); + + // Load them in series, as required per spec. + for (const buffer of buffers) { + await loadSegment(sb, buffer); + } +} + +function loadSegmentAsync(sb, typedArrayOrArrayBuffer) { + const typedArray = + typedArrayOrArrayBuffer instanceof ArrayBuffer + ? new Uint8Array(typedArrayOrArrayBuffer) + : typedArrayOrArrayBuffer; + info( + `Loading buffer2: [${typedArray.byteOffset}, ${typedArray.byteOffset + + typedArray.byteLength})` + ); + const beforeBuffered = timeRangeToString(sb.buffered); + return sb.appendBufferAsync(typedArray).then(() => { + const afterBuffered = timeRangeToString(sb.buffered); + info( + `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}` + ); + }); +} + +function fetchAndLoadAsync(sb, prefix, chunks, suffix) { + // Fetch the buffers in parallel. + const buffers = {}; + const fetches = []; + for (const chunk of chunks) { + fetches.push( + fetchWithXHR(prefix + chunk + suffix).then( + ((c, x) => (buffers[c] = x)).bind(null, chunk) + ) + ); + } + + // Load them in series, as required per spec. + return Promise.all(fetches).then(function() { + let rv = Promise.resolve(); + for (const chunk of chunks) { + rv = rv.then(loadSegmentAsync.bind(null, sb, buffers[chunk])); + } + return rv; + }); +} + +// Register timeout function to dump debugging logs. +SimpleTest.registerTimeoutFunction(async function() { + for (const v of document.getElementsByTagName("video")) { + console.log(await SpecialPowers.wrap(v).mozRequestDebugInfo()); + } + for (const a of document.getElementsByTagName("audio")) { + console.log(await SpecialPowers.wrap(a).mozRequestDebugInfo()); + } +}); + +async function waitUntilTime(target, targetTime) { + await new Promise(resolve => { + target.addEventListener("waiting", function onwaiting() { + info("Got a waiting event at " + target.currentTime); + if (target.currentTime >= targetTime) { + target.removeEventListener("waiting", onwaiting); + resolve(); + } + }); + }); + ok(true, "Reached target time of: " + targetTime); +} + +// Log events for debugging. + +function logEvents(el) { + [ + "suspend", + "play", + "canplay", + "canplaythrough", + "loadstart", + "loadedmetadata", + "loadeddata", + "playing", + "ended", + "error", + "stalled", + "emptied", + "abort", + "waiting", + "pause", + "durationchange", + "seeking", + "seeked", + ].forEach(type => + el.addEventListener(type, e => info(`got ${e.type} event`)) + ); +} |