Source code

Revision control

Copy as Markdown

Other Tools

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