summaryrefslogtreecommitdiffstats
path: root/dom/media/mediasource/test/mediasource.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mediasource/test/mediasource.js')
-rw-r--r--dom/media/mediasource/test/mediasource.js235
1 files changed, 235 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..71d8d4ef9f
--- /dev/null
+++ b/dom/media/mediasource/test/mediasource.js
@@ -0,0 +1,235 @@
+// 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`))
+ );
+}