// 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`)) ); }