diff options
Diffstat (limited to 'dom/media/test')
1140 files changed, 30988 insertions, 0 deletions
diff --git a/dom/media/test/.eslintrc.js b/dom/media/test/.eslintrc.js new file mode 100644 index 0000000000..845ed3f013 --- /dev/null +++ b/dom/media/test/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/mochitest-test"], +}; diff --git a/dom/media/test/16bit_wave_extrametadata.wav b/dom/media/test/16bit_wave_extrametadata.wav Binary files differnew file mode 100644 index 0000000000..443ec73a3d --- /dev/null +++ b/dom/media/test/16bit_wave_extrametadata.wav diff --git a/dom/media/test/16bit_wave_extrametadata.wav^headers^ b/dom/media/test/16bit_wave_extrametadata.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/16bit_wave_extrametadata.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/320x240.ogv b/dom/media/test/320x240.ogv Binary files differnew file mode 100644 index 0000000000..093158432a --- /dev/null +++ b/dom/media/test/320x240.ogv diff --git a/dom/media/test/320x240.ogv^headers^ b/dom/media/test/320x240.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/320x240.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/448636.ogv b/dom/media/test/448636.ogv Binary files differnew file mode 100644 index 0000000000..628df924f8 --- /dev/null +++ b/dom/media/test/448636.ogv diff --git a/dom/media/test/448636.ogv^headers^ b/dom/media/test/448636.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/448636.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/A4.ogv b/dom/media/test/A4.ogv Binary files differnew file mode 100644 index 0000000000..de99616ece --- /dev/null +++ b/dom/media/test/A4.ogv diff --git a/dom/media/test/A4.ogv^headers^ b/dom/media/test/A4.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/A4.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/VID_0001.ogg b/dom/media/test/VID_0001.ogg Binary files differnew file mode 100644 index 0000000000..0068b9af85 --- /dev/null +++ b/dom/media/test/VID_0001.ogg diff --git a/dom/media/test/VID_0001.ogg^headers^ b/dom/media/test/VID_0001.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/VID_0001.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/allowed.sjs b/dom/media/test/allowed.sjs new file mode 100644 index 0000000000..176a1a24a9 --- /dev/null +++ b/dom/media/test/allowed.sjs @@ -0,0 +1,56 @@ +function parseQuery(request, key) { + var params = request.queryString.split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +var types = { + js: "text/javascript", + m4s: "video/mp4", + mp4: "video/mp4", + ogg: "video/ogg", + ogv: "video/ogg", + oga: "audio/ogg", + webm: "video/webm", + wav: "audio/x-wav" +}; + +// Return file with name as per the query string with access control +// allow headers. +function handleRequest(request, response) +{ + var resource = parseQuery(request, ""); + + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/" + resource; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + dump("file=" + file + "\n"); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length); + response.setHeader("Content-Length", ""+bytes.length, false); + var ext = resource.substring(resource.lastIndexOf(".")+1); + response.setHeader("Content-Type", types[ext], false); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/dom/media/test/ambisonics.mp4 b/dom/media/test/ambisonics.mp4 Binary files differnew file mode 100644 index 0000000000..4f5bcdfd26 --- /dev/null +++ b/dom/media/test/ambisonics.mp4 diff --git a/dom/media/test/ambisonics.mp4^headers^ b/dom/media/test/ambisonics.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/ambisonics.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/audio-gaps-short.ogg b/dom/media/test/audio-gaps-short.ogg Binary files differnew file mode 100644 index 0000000000..e01a24bfda --- /dev/null +++ b/dom/media/test/audio-gaps-short.ogg diff --git a/dom/media/test/audio-gaps-short.ogg^headers^ b/dom/media/test/audio-gaps-short.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/audio-gaps-short.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/audio-gaps.ogg b/dom/media/test/audio-gaps.ogg Binary files differnew file mode 100644 index 0000000000..ce96748ccd --- /dev/null +++ b/dom/media/test/audio-gaps.ogg diff --git a/dom/media/test/audio-gaps.ogg^headers^ b/dom/media/test/audio-gaps.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/audio-gaps.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/audio-overhang.ogg b/dom/media/test/audio-overhang.ogg Binary files differnew file mode 100644 index 0000000000..c07986e7a1 --- /dev/null +++ b/dom/media/test/audio-overhang.ogg diff --git a/dom/media/test/audio-overhang.ogg^headers^ b/dom/media/test/audio-overhang.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/audio-overhang.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/audio.wav b/dom/media/test/audio.wav Binary files differnew file mode 100644 index 0000000000..c6fd5cb869 --- /dev/null +++ b/dom/media/test/audio.wav diff --git a/dom/media/test/audio.wav^headers^ b/dom/media/test/audio.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/audio.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/av1.mp4 b/dom/media/test/av1.mp4 Binary files differnew file mode 100644 index 0000000000..28de929a29 --- /dev/null +++ b/dom/media/test/av1.mp4 diff --git a/dom/media/test/av1.mp4^headers^ b/dom/media/test/av1.mp4^headers^ new file mode 100644 index 0000000000..2567dc2fe5 --- /dev/null +++ b/dom/media/test/av1.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
\ No newline at end of file diff --git a/dom/media/test/background_video.js b/dom/media/test/background_video.js new file mode 100644 index 0000000000..508f8fd89a --- /dev/null +++ b/dom/media/test/background_video.js @@ -0,0 +1,224 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file expects manager to be defined in the global scope. +/* global manager */ +/* import-globals-from manifest.js */ + +"use strict"; + +function startTest(test) { + info(test.desc); + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({ set: test.prefs }, () => { + manager.runTests(test.tests, test.runTest); + }); +} + +function nextVideoEnded(video) { + return nextEvent(video, "ended"); +} + +function nextVideoPlaying(video) { + return nextEvent(video, "playing"); +} + +function nextVideoResumes(video) { + return nextEvent(video, "mozexitvideosuspend"); +} + +function nextVideoSuspends(video) { + return nextEvent(video, "mozentervideosuspend"); +} + +/** + * @param {string} url video src. + * @returns {HTMLMediaElement} The created video element. + */ +function appendVideoToDoc(url, token, width, height) { + // Default size of (160, 120) is used by other media tests. + if (width === undefined) { + width = 160; + } + if (height === undefined) { + height = (3 * width) / 4; + } + + let v = document.createElement("video"); + v.token = token; + v.width = width; + v.height = height; + v.src = url; + document.body.appendChild(v); + return v; +} + +function appendVideoToDocWithoutLoad(token, width, height) { + // Default size of (160, 120) is used by other media tests. + if (width === undefined) { + width = 160; + } + if (height === undefined) { + height = (3 * width) / 4; + } + + let v = document.createElement("video"); + v.token = token; + document.body.appendChild(v); + v.width = width; + v.height = height; + return v; +} + +function loadAndWaitUntilLoadedmetadata(video, url, preloadType = "metadata") { + return new Promise((resolve, reject) => { + video.preload = preloadType; + video.addEventListener( + "loadedmetadata", + () => { + resolve(); + }, + true + ); + video.src = url; + }); +} + +/** + * @param {HTMLMediaElement} video Video element with under test. + * @returns {Promise} Promise that is resolved when video 'visibilitychanged' event fires. + */ +function waitUntilVisible(video) { + let videoChrome = SpecialPowers.wrap(video); + if (videoChrome.isInViewPort) { + return Promise.resolve(); + } + + return new Promise(resolve => { + videoChrome.addEventListener("visibilitychanged", () => { + if (videoChrome.isInViewPort) { + ok(true, `${video.token} is visible.`); + videoChrome.removeEventListener("visibilitychanged", this); + resolve(); + } + }); + }); +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video 'playing' event fires. + */ +function waitUntilPlaying(video) { + var p = once(video, "playing", () => { + ok(true, `${video.token} played.`); + }); + Log(video.token, "Start playing"); + video.play(); + return p; +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise which is resolved when video 'ended' event fires. + */ +function waitUntilEnded(video) { + Log(video.token, "Waiting for ended"); + if (video.ended) { + ok(true, video.token + " already ended"); + return Promise.resolve(); + } + + return once(video, "ended", () => { + ok(true, `${video.token} ended`); + }); +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode starts + * suspend timer. + */ +function testSuspendTimerStartedWhenHidden(video) { + var p = once(video, "mozstartvideosuspendtimer").then(() => { + ok(true, `${video.token} suspend begins`); + }); + Log(video.token, "Set Hidden"); + video.setVisible(false); + return p; +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode suspends. + */ +function testVideoSuspendsWhenHidden(video) { + let p = once(video, "mozentervideosuspend").then(() => { + ok(true, `${video.token} suspends`); + }); + Log(video.token, "Set hidden"); + video.setVisible(false); + return p; +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode resumes. + */ +function testVideoResumesWhenShown(video) { + var p = once(video, "mozexitvideosuspend").then(() => { + ok(true, `${video.token} resumes`); + }); + Log(video.token, "Set visible"); + video.setVisible(true); + return p; +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode resumes. + */ +function testVideoOnlySeekCompletedWhenShown(video) { + var p = once(video, "mozvideoonlyseekcompleted").then(() => { + ok(true, `${video.token} resumes`); + }); + Log(video.token, "Set visible"); + video.setVisible(true); + return p; +} + +/** + * @param {HTMLVideoElement} video Video element under test. + * @returns {Promise} Promise that is resolved if video ends and rejects if video suspends. + */ +function checkVideoDoesntSuspend(video) { + let p = Promise.race([ + waitUntilEnded(video).then(() => { + ok(true, `${video.token} ended before decode was suspended`); + }), + once(video, "mozentervideosuspend", () => { + Promise.reject(new Error(`${video.token} suspended`)); + }), + ]); + Log(video.token, "Set hidden."); + video.setVisible(false); + return p; +} + +/** + * @param {HTMLMediaElement} video Video element under test. + * @param {number} time video current time to wait til. + * @returns {Promise} Promise that is resolved once currentTime passes time. + */ +function waitTil(video, time) { + Log(video.token, `Waiting for time to reach ${time}s`); + return new Promise(resolve => { + video.addEventListener("timeupdate", function timeUpdateEvent() { + if (video.currentTime > time) { + video.removeEventListener(name, timeUpdateEvent); + resolve(); + } + }); + }); +} diff --git a/dom/media/test/badtags.ogg b/dom/media/test/badtags.ogg Binary files differnew file mode 100644 index 0000000000..12d8358730 --- /dev/null +++ b/dom/media/test/badtags.ogg diff --git a/dom/media/test/badtags.ogg^headers^ b/dom/media/test/badtags.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/badtags.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4 b/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4 Binary files differnew file mode 100644 index 0000000000..dc4f197ffa --- /dev/null +++ b/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4 diff --git a/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4 b/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4 Binary files differnew file mode 100644 index 0000000000..916c64e9ee --- /dev/null +++ b/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4 diff --git a/dom/media/test/beta-phrasebook.ogg b/dom/media/test/beta-phrasebook.ogg Binary files differnew file mode 100644 index 0000000000..7e6ef77ec4 --- /dev/null +++ b/dom/media/test/beta-phrasebook.ogg diff --git a/dom/media/test/beta-phrasebook.ogg^headers^ b/dom/media/test/beta-phrasebook.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/beta-phrasebook.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s Binary files differnew file mode 100644 index 0000000000..266ec4c100 --- /dev/null +++ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4 b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4 Binary files differnew file mode 100644 index 0000000000..7aeb3eca8a --- /dev/null +++ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4 diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/big-short.wav b/dom/media/test/big-short.wav Binary files differnew file mode 100644 index 0000000000..c850e5fd14 --- /dev/null +++ b/dom/media/test/big-short.wav diff --git a/dom/media/test/big-short.wav^headers^ b/dom/media/test/big-short.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/big-short.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/big.wav b/dom/media/test/big.wav Binary files differnew file mode 100644 index 0000000000..5f66bc1f02 --- /dev/null +++ b/dom/media/test/big.wav diff --git a/dom/media/test/big.wav^headers^ b/dom/media/test/big.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/big.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-audio-key1.xml b/dom/media/test/bipbop-cenc-audio-key1.xml new file mode 100644 index 0000000000..a1672eecef --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio-key1.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To + generate the encrypted files, run bipbop-cenc.sh +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 1 --> + <BS bits="32" value="1" /> + <!-- KeyID --> + <BS ID128="0x7e571d047e571d047e571d047e571d21" /> + </DRMInfo> + + <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d047e571d047e571d047e571d21" + value="0x7e5744447e5744447e5744447e574421" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/bipbop-cenc-audio-key2.xml b/dom/media/test/bipbop-cenc-audio-key2.xml new file mode 100644 index 0000000000..b706609052 --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio-key2.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To + generate the encrypted files, run bipbop-cenc.sh +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 1 --> + <BS bits="32" value="1" /> + <!-- KeyID --> + <BS ID128="0x7e571d047e571d047e571d047e571d22" /> + </DRMInfo> + + <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d047e571d047e571d047e571d22" + value="0x7e5744447e5744447e5744447e574422" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/bipbop-cenc-audio1.m4s b/dom/media/test/bipbop-cenc-audio1.m4s Binary files differnew file mode 100644 index 0000000000..63cfd66f7e --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio1.m4s diff --git a/dom/media/test/bipbop-cenc-audio1.m4s^headers^ b/dom/media/test/bipbop-cenc-audio1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-audio2.m4s b/dom/media/test/bipbop-cenc-audio2.m4s Binary files differnew file mode 100644 index 0000000000..04a6cb6ff9 --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio2.m4s diff --git a/dom/media/test/bipbop-cenc-audio2.m4s^headers^ b/dom/media/test/bipbop-cenc-audio2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-audio3.m4s b/dom/media/test/bipbop-cenc-audio3.m4s Binary files differnew file mode 100644 index 0000000000..ad0cd72f90 --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio3.m4s diff --git a/dom/media/test/bipbop-cenc-audio3.m4s^headers^ b/dom/media/test/bipbop-cenc-audio3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-audio3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-audioinit.mp4 b/dom/media/test/bipbop-cenc-audioinit.mp4 Binary files differnew file mode 100644 index 0000000000..b827aa49aa --- /dev/null +++ b/dom/media/test/bipbop-cenc-audioinit.mp4 diff --git a/dom/media/test/bipbop-cenc-audioinit.mp4^headers^ b/dom/media/test/bipbop-cenc-audioinit.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-audioinit.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-video-10s.mp4 b/dom/media/test/bipbop-cenc-video-10s.mp4 Binary files differnew file mode 100644 index 0000000000..abbe4561fd --- /dev/null +++ b/dom/media/test/bipbop-cenc-video-10s.mp4 diff --git a/dom/media/test/bipbop-cenc-video-10s.mp4^headers^ b/dom/media/test/bipbop-cenc-video-10s.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-video-10s.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-video-key1.xml b/dom/media/test/bipbop-cenc-video-key1.xml new file mode 100644 index 0000000000..f0d9878fa2 --- /dev/null +++ b/dom/media/test/bipbop-cenc-video-key1.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To + generate the encrypted files, run bipbop-cenc.sh +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 1 --> + <BS bits="32" value="1" /> + <!-- KeyID --> + <BS ID128="0x7e571d037e571d037e571d037e571d11" /> + </DRMInfo> + + <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d037e571d037e571d037e571d11" + value="0x7e5733337e5733337e5733337e573311" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/bipbop-cenc-video-key2.xml b/dom/media/test/bipbop-cenc-video-key2.xml new file mode 100644 index 0000000000..1f320e6336 --- /dev/null +++ b/dom/media/test/bipbop-cenc-video-key2.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To + generate the encrypted files, run bipbop-cenc.sh +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 1 --> + <BS bits="32" value="1" /> + <!-- KeyID --> + <BS ID128="0x7e571d037e571d037e571d037e571d12" /> + </DRMInfo> + + <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d037e571d037e571d037e571d12" + value="0x7e5733337e5733337e5733337e573312" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/bipbop-cenc-video1.m4s b/dom/media/test/bipbop-cenc-video1.m4s Binary files differnew file mode 100644 index 0000000000..755013c11c --- /dev/null +++ b/dom/media/test/bipbop-cenc-video1.m4s diff --git a/dom/media/test/bipbop-cenc-video1.m4s^headers^ b/dom/media/test/bipbop-cenc-video1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-video1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-video2.m4s b/dom/media/test/bipbop-cenc-video2.m4s Binary files differnew file mode 100644 index 0000000000..c884bd95fc --- /dev/null +++ b/dom/media/test/bipbop-cenc-video2.m4s diff --git a/dom/media/test/bipbop-cenc-video2.m4s^headers^ b/dom/media/test/bipbop-cenc-video2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-video2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc-videoinit.mp4 b/dom/media/test/bipbop-cenc-videoinit.mp4 Binary files differnew file mode 100644 index 0000000000..aa87d0bbe6 --- /dev/null +++ b/dom/media/test/bipbop-cenc-videoinit.mp4 diff --git a/dom/media/test/bipbop-cenc-videoinit.mp4^headers^ b/dom/media/test/bipbop-cenc-videoinit.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-cenc-videoinit.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-cenc.sh b/dom/media/test/bipbop-cenc.sh new file mode 100644 index 0000000000..a00c38ae80 --- /dev/null +++ b/dom/media/test/bipbop-cenc.sh @@ -0,0 +1,29 @@ +mkdir work.tmp + +for r in 225w_175kbps 300_215kbps 300wp_227kbps 360w_253kbps 480_624kbps 480wp_663kbps 480_959kbps 480wp_1001kbps +do + for k in 1 2 + do + # Encrypt bipbop_<res>.mp4 with the keys specified in this file, + # and output to |bipbop_<res>-cenc-{video,audio}.mp4| + MP4Box -crypt bipbop-cenc-audio-key${k}.xml -rem 1 -out work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4 bipbop_${r}.mp4 + MP4Box -crypt bipbop-cenc-video-key${k}.xml -rem 2 -out work.tmp/bipbop_${r}-cenc-video-key${k}.mp4 bipbop_${r}.mp4 + + # Fragment |bipbop_<res>-cenc-*.mp4| into 500ms segments: + MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-audio-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4 + MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-video-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-video-key${k}.mp4 + + # The above command will generate a set of fragments |bipbop_<res>-cenc-{video,audio}-*.m4s + # and |bipbop_<res>-cenc-{video,audio}-init.mp4| containing just the init segment. + + # Remove unneeded mpd files. + rm bipbop_${r}-cenc-{audio,video}-key${k}_dash.mpd + done +done + +# Only keep the first 4 audio & 2 video segments: +cp work.tmp/*-init[.]mp4 ./ +cp work.tmp/*audio*-[1234][.]m4s ./ +cp work.tmp/*video*-[12][.]m4s ./ + +rm -Rf work.tmp diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4 b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4 Binary files differnew file mode 100644 index 0000000000..5e5e30c255 --- /dev/null +++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4 diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^ new file mode 100644 index 0000000000..12a01c4a22 --- /dev/null +++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4 b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4 Binary files differnew file mode 100644 index 0000000000..447c657475 --- /dev/null +++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4 diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^ new file mode 100644 index 0000000000..12a01c4a22 --- /dev/null +++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
diff --git a/dom/media/test/bipbop-frag-cenc.xml b/dom/media/test/bipbop-frag-cenc.xml new file mode 100644 index 0000000000..6f6a4d90a9 --- /dev/null +++ b/dom/media/test/bipbop-frag-cenc.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to |bipbop-cenc*|. To + generate the bipbop-cenc files, run the following commands: + + Encrypt bipbop-no-edts.mp4 with the keys specified in this file, + and output to |bipbop-cenc-{video,audio}.mp4| + MP4Box -crypt bipbop-frag-cenc.xml -rem 2 -out bipbop-cenc-video.mp4 bipbop-no-edts.mp4 + MP4Box -crypt bipbop-frag-cenc.xml -rem 1 -out bipbop-cenc-audio.mp4 bipbop-no-edts.mp4 + + Fragment |bipbop-cenc-*.mp4| into 500ms segments: + MP4Box -dash 500 -rap -segment-name bipbop-cenc-video -subsegs-per-sidx 5 bipbop-cenc-video.mp4 + MP4Box -dash 500 -rap -segment-name bipbop-cenc-audio -subsegs-per-sidx 5 bipbop-cenc-audio.mp4 + + The above command will generate a set of fragments in |bipbop-cenc-{video,audio}*.m4s + and |bipbop-cenc-{video,audio}init.mp4| containing just the init segment. + + To cut down the duration, we throw out all but the first 3 audio & 2 video segments: + rm bipbop-cenc-audio{[^123],[123][^.]}.m4s + rm bipbop-cenc-video{[^12],[12][^.]}.m4s + + MP4Box will also have generated some *.mpd files we don't need: + rm bipbop-cenc-*.mpd + + Delete intermediate encrypted files: + rm bipbop-cenc-{audio,video}.mp4 +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 2 --> + <BS bits="32" value="2" /> + <!-- KeyID --> + <BS ID128="0x7e571d037e571d037e571d037e571d03" /> + <BS ID128="0x7e571d047e571d047e571d047e571d04" /> + </DRMInfo> + + <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d037e571d037e571d037e571d03" + value="0x7e5733337e5733337e5733337e573333" /> + </CrypTrack> + + <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d047e571d047e571d047e571d04" + value="0x7e5744447e5744447e5744447e574444" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/bipbop-lateaudio.mp4 b/dom/media/test/bipbop-lateaudio.mp4 Binary files differnew file mode 100644 index 0000000000..5b4cc57095 --- /dev/null +++ b/dom/media/test/bipbop-lateaudio.mp4 diff --git a/dom/media/test/bipbop-lateaudio.mp4^headers^ b/dom/media/test/bipbop-lateaudio.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop-lateaudio.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop-no-edts.mp4 b/dom/media/test/bipbop-no-edts.mp4 Binary files differnew file mode 100644 index 0000000000..63435887df --- /dev/null +++ b/dom/media/test/bipbop-no-edts.mp4 diff --git a/dom/media/test/bipbop.mp4 b/dom/media/test/bipbop.mp4 Binary files differnew file mode 100644 index 0000000000..017d658f31 --- /dev/null +++ b/dom/media/test/bipbop.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..40c3a7bb98 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..986e5fb186 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..547950e516 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..3214f131d4 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..08713078d9 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..0b13fed5f0 --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_225w_175kbps.mp4 b/dom/media/test/bipbop_225w_175kbps.mp4 Binary files differnew file mode 100644 index 0000000000..abe37b9f9d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps.mp4 diff --git a/dom/media/test/bipbop_225w_175kbps.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_225w_175kbps.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..21f3863274 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..bc741cdf86 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..9c6818d06f --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..f327aaa573 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..543f18c24b --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..f850ceaf0a --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..a28a106daf --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..a05a879970 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300_215kbps.mp4 b/dom/media/test/bipbop_300_215kbps.mp4 Binary files differnew file mode 100644 index 0000000000..084d477430 --- /dev/null +++ b/dom/media/test/bipbop_300_215kbps.mp4 diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..40c3a7bb98 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..986e5fb186 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..9c6818d06f --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..ce2e64eb33 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..8592a5b0a3 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..f850ceaf0a --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..d07ce9753e --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..9d2fa23dd4 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_300wp_227kbps.mp4 b/dom/media/test/bipbop_300wp_227kbps.mp4 Binary files differnew file mode 100644 index 0000000000..1499355313 --- /dev/null +++ b/dom/media/test/bipbop_300wp_227kbps.mp4 diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..40c3a7bb98 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..986e5fb186 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..a571d47cfb --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..42dbfec1ed --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..9e4224cac8 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..21763ecbdd --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm Binary files differnew file mode 100644 index 0000000000..4be8f340c3 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm Binary files differnew file mode 100644 index 0000000000..56cf4c483c --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm Binary files differnew file mode 100644 index 0000000000..9f411d0e34 --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_360w_253kbps.mp4 b/dom/media/test/bipbop_360w_253kbps.mp4 Binary files differnew file mode 100644 index 0000000000..6c796f4e1f --- /dev/null +++ b/dom/media/test/bipbop_360w_253kbps.mp4 diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..e626fa4564 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..d7cbb2b6b0 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..805f4bbf3f --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..5bf9994733 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..77c7daba5a --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..c5127beec9 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..b0ff51f74a --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..cfa099c043 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_624kbps.mp4 b/dom/media/test/bipbop_480_624kbps.mp4 Binary files differnew file mode 100644 index 0000000000..27928b85f4 --- /dev/null +++ b/dom/media/test/bipbop_480_624kbps.mp4 diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..c9106aad99 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..888b20ab63 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..796ad13670 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..d02be53198 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..6e0c60f986 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..06778e6f2b --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..4c1c603e8d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..f4a98eca97 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480_959kbps.mp4 b/dom/media/test/bipbop_480_959kbps.mp4 Binary files differnew file mode 100644 index 0000000000..4a9f2ee823 --- /dev/null +++ b/dom/media/test/bipbop_480_959kbps.mp4 diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..416bc7a7ca --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..73d542cfe0 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..796ad13670 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..80824e9ffc --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..5db21d091b --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..06778e6f2b --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..38a081187a --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..bc8bddf505 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_1001kbps.mp4 b/dom/media/test/bipbop_480wp_1001kbps.mp4 Binary files differnew file mode 100644 index 0000000000..600376cf83 --- /dev/null +++ b/dom/media/test/bipbop_480wp_1001kbps.mp4 diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..e2bd754c7e --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..347835feee --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s Binary files differnew file mode 100644 index 0000000000..64b0da69a0 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s Binary files differnew file mode 100644 index 0000000000..864f4248af --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..416bc7a7ca --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4 diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..a8896e069a --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..0f0a35ce79 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s Binary files differnew file mode 100644 index 0000000000..fece52ff42 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s Binary files differnew file mode 100644 index 0000000000..70e61e3d5f --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..73d542cfe0 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4 diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s Binary files differnew file mode 100644 index 0000000000..805f4bbf3f --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s Binary files differnew file mode 100644 index 0000000000..0a40d1cb73 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4 Binary files differnew file mode 100644 index 0000000000..5db21d091b --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4 diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s Binary files differnew file mode 100644 index 0000000000..c5127beec9 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s Binary files differnew file mode 100644 index 0000000000..3f344022a4 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4 Binary files differnew file mode 100644 index 0000000000..bc8bddf505 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4 diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bipbop_480wp_663kbps.mp4 b/dom/media/test/bipbop_480wp_663kbps.mp4 Binary files differnew file mode 100644 index 0000000000..3cc1da69d2 --- /dev/null +++ b/dom/media/test/bipbop_480wp_663kbps.mp4 diff --git a/dom/media/test/black100x100-aspect3to2.ogv b/dom/media/test/black100x100-aspect3to2.ogv Binary files differnew file mode 100644 index 0000000000..81fe51ffb3 --- /dev/null +++ b/dom/media/test/black100x100-aspect3to2.ogv diff --git a/dom/media/test/black100x100-aspect3to2.ogv^headers^ b/dom/media/test/black100x100-aspect3to2.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/black100x100-aspect3to2.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bogus.duh b/dom/media/test/bogus.duh new file mode 100644 index 0000000000..528ae275d0 --- /dev/null +++ b/dom/media/test/bogus.duh @@ -0,0 +1,45 @@ +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus diff --git a/dom/media/test/bogus.ogv b/dom/media/test/bogus.ogv new file mode 100644 index 0000000000..528ae275d0 --- /dev/null +++ b/dom/media/test/bogus.ogv @@ -0,0 +1,45 @@ +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus diff --git a/dom/media/test/bogus.ogv^headers^ b/dom/media/test/bogus.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bogus.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bogus.wav b/dom/media/test/bogus.wav new file mode 100644 index 0000000000..528ae275d0 --- /dev/null +++ b/dom/media/test/bogus.wav @@ -0,0 +1,45 @@ +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus +bogus bogus bogus diff --git a/dom/media/test/bogus.wav^headers^ b/dom/media/test/bogus.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bogus.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/browser/browser.ini b/dom/media/test/browser/browser.ini new file mode 100644 index 0000000000..d828f4b6f9 --- /dev/null +++ b/dom/media/test/browser/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +tags = mediacontrol +support-files = + file_media.html + ../gizmo.mp4 + +[browser_tab_visibility_and_play_time.js] diff --git a/dom/media/test/browser/browser_tab_visibility_and_play_time.js b/dom/media/test/browser/browser_tab_visibility_and_play_time.js new file mode 100644 index 0000000000..4eb956b9ea --- /dev/null +++ b/dom/media/test/browser/browser_tab_visibility_and_play_time.js @@ -0,0 +1,120 @@ +/** + * This test is used to ensure that invisible play time would be accumulated + * when tab is in background. However, this test won't directly check the + * reported telemetry result, because we can't check the snapshot histogram in + * the content process. + * The actual probe checking happens in `test_accumulated_play_time.html`. + */ +"use strict"; + +const PAGE_URL = + "https://example.com/browser/dom/media/test/browser/file_media.html"; + +add_task(async function testChangingTabVisibilityAffectsInvisiblePlayTime() { + const originalTab = gBrowser.selectedTab; + const mediaTab = await openMediaTab(PAGE_URL); + + info(`measuring play time when tab is in foreground`); + await startMedia({ + mediaTab, + shouldAccumulateTime: true, + shouldAccumulateInvisibleTime: false, + }); + await pauseMedia(mediaTab); + + info(`measuring play time when tab is in foreground`); + await BrowserTestUtils.switchTab(window.gBrowser, originalTab); + await startMedia({ + mediaTab, + shouldAccumulateTime: true, + shouldAccumulateInvisibleTime: true, + }); + await pauseMedia(mediaTab); + + BrowserTestUtils.removeTab(mediaTab); +}); + +/** + * Following are helper functions. + */ +async function openMediaTab(url) { + info(`open tab for media playback`); + const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url); + info(`add content helper functions and variables`); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + content.assertAttributeDefined = (videoChrome, checkType) => { + ok(videoChrome[checkType] != undefined, `${checkType} exists`); + }; + content.assertValueEqualTo = (videoChrome, checkType, expectedValue) => { + content.assertAttributeDefined(videoChrome, checkType); + is( + videoChrome[checkType], + expectedValue, + `${checkType} equals to ${expectedValue}` + ); + }; + content.assertValueConstantlyIncreases = (videoChrome, checkType) => { + content.assertAttributeDefined(videoChrome, checkType); + const valueSnapshot = videoChrome[checkType]; + ok( + videoChrome[checkType] > valueSnapshot, + `${checkType} keeps increasing` + ); + }; + content.assertValueKeptUnchanged = (videoChrome, checkType) => { + content.assertAttributeDefined(videoChrome, checkType); + const valueSnapshot = videoChrome[checkType]; + ok( + videoChrome[checkType] == valueSnapshot, + `${checkType} keeps unchanged` + ); + }; + }); + return tab; +} + +function startMedia({ + mediaTab, + shouldAccumulateTime, + shouldAccumulateInvisibleTime, +}) { + return SpecialPowers.spawn( + mediaTab.linkedBrowser, + [shouldAccumulateTime, shouldAccumulateInvisibleTime], + async (accumulateTime, accumulateInvisibleTime) => { + const video = content.document.getElementById("video"); + ok( + await video.play().then( + () => true, + () => false + ), + "video started playing" + ); + const videoChrome = SpecialPowers.wrap(video); + if (accumulateTime) { + content.assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + } else { + content.assertValueKeptUnchanged(videoChrome, "totalPlayTime"); + } + if (accumulateInvisibleTime) { + content.assertValueConstantlyIncreases( + videoChrome, + "invisiblePlayTime" + ); + } else { + content.assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + } + } + ); +} + +function pauseMedia(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { + const video = content.document.getElementById("video"); + video.pause(); + ok(true, "video paused"); + const videoChrome = SpecialPowers.wrap(video); + content.assertValueKeptUnchanged(videoChrome, "totalPlayTime"); + content.assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + }); +} diff --git a/dom/media/test/browser/file_media.html b/dom/media/test/browser/file_media.html new file mode 100644 index 0000000000..498c2eaad6 --- /dev/null +++ b/dom/media/test/browser/file_media.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>Non-Autoplay page</title> +</head> +<body> +<video id="video" src="gizmo.mp4" loop></video> +</body> +</html> diff --git a/dom/media/test/bug1066943.webm b/dom/media/test/bug1066943.webm Binary files differnew file mode 100644 index 0000000000..64a24ec898 --- /dev/null +++ b/dom/media/test/bug1066943.webm diff --git a/dom/media/test/bug1066943.webm^headers^ b/dom/media/test/bug1066943.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug1066943.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug1301226-odd.wav b/dom/media/test/bug1301226-odd.wav Binary files differnew file mode 100644 index 0000000000..dd2df4e4dd --- /dev/null +++ b/dom/media/test/bug1301226-odd.wav diff --git a/dom/media/test/bug1301226-odd.wav^headers^ b/dom/media/test/bug1301226-odd.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug1301226-odd.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug1301226.wav b/dom/media/test/bug1301226.wav Binary files differnew file mode 100644 index 0000000000..0128486f07 --- /dev/null +++ b/dom/media/test/bug1301226.wav diff --git a/dom/media/test/bug1301226.wav^headers^ b/dom/media/test/bug1301226.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug1301226.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug1377278.webm b/dom/media/test/bug1377278.webm Binary files differnew file mode 100644 index 0000000000..802019f39f --- /dev/null +++ b/dom/media/test/bug1377278.webm diff --git a/dom/media/test/bug1377278.webm^headers^ b/dom/media/test/bug1377278.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug1377278.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug461281.ogg b/dom/media/test/bug461281.ogg Binary files differnew file mode 100644 index 0000000000..d7f6a0ccf4 --- /dev/null +++ b/dom/media/test/bug461281.ogg diff --git a/dom/media/test/bug461281.ogg^headers^ b/dom/media/test/bug461281.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug461281.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug482461-theora.ogv b/dom/media/test/bug482461-theora.ogv Binary files differnew file mode 100644 index 0000000000..941b8d8efd --- /dev/null +++ b/dom/media/test/bug482461-theora.ogv diff --git a/dom/media/test/bug482461-theora.ogv^headers^ b/dom/media/test/bug482461-theora.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug482461-theora.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug482461.ogv b/dom/media/test/bug482461.ogv Binary files differnew file mode 100644 index 0000000000..6cf6aed330 --- /dev/null +++ b/dom/media/test/bug482461.ogv diff --git a/dom/media/test/bug482461.ogv^headers^ b/dom/media/test/bug482461.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug482461.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug495129.ogv b/dom/media/test/bug495129.ogv Binary files differnew file mode 100644 index 0000000000..44eb9296f5 --- /dev/null +++ b/dom/media/test/bug495129.ogv diff --git a/dom/media/test/bug495129.ogv^headers^ b/dom/media/test/bug495129.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug495129.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug495794.ogg b/dom/media/test/bug495794.ogg Binary files differnew file mode 100644 index 0000000000..1c19a64061 --- /dev/null +++ b/dom/media/test/bug495794.ogg diff --git a/dom/media/test/bug495794.ogg^headers^ b/dom/media/test/bug495794.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug495794.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug498380.ogv b/dom/media/test/bug498380.ogv Binary files differnew file mode 100644 index 0000000000..1179ecb70a --- /dev/null +++ b/dom/media/test/bug498380.ogv diff --git a/dom/media/test/bug498380.ogv^headers^ b/dom/media/test/bug498380.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug498380.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug498855-1.ogv b/dom/media/test/bug498855-1.ogv Binary files differnew file mode 100644 index 0000000000..95a524da4c --- /dev/null +++ b/dom/media/test/bug498855-1.ogv diff --git a/dom/media/test/bug498855-1.ogv^headers^ b/dom/media/test/bug498855-1.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug498855-1.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug498855-2.ogv b/dom/media/test/bug498855-2.ogv Binary files differnew file mode 100644 index 0000000000..795a308ae1 --- /dev/null +++ b/dom/media/test/bug498855-2.ogv diff --git a/dom/media/test/bug498855-2.ogv^headers^ b/dom/media/test/bug498855-2.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug498855-2.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug498855-3.ogv b/dom/media/test/bug498855-3.ogv Binary files differnew file mode 100644 index 0000000000..714858dfed --- /dev/null +++ b/dom/media/test/bug498855-3.ogv diff --git a/dom/media/test/bug498855-3.ogv^headers^ b/dom/media/test/bug498855-3.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug498855-3.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug499519.ogv b/dom/media/test/bug499519.ogv Binary files differnew file mode 100644 index 0000000000..62c0922d36 --- /dev/null +++ b/dom/media/test/bug499519.ogv diff --git a/dom/media/test/bug499519.ogv^headers^ b/dom/media/test/bug499519.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug499519.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug500311.ogv b/dom/media/test/bug500311.ogv Binary files differnew file mode 100644 index 0000000000..2cf27ef1ee --- /dev/null +++ b/dom/media/test/bug500311.ogv diff --git a/dom/media/test/bug500311.ogv^headers^ b/dom/media/test/bug500311.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug500311.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug501279.ogg b/dom/media/test/bug501279.ogg Binary files differnew file mode 100644 index 0000000000..e266f07ee8 --- /dev/null +++ b/dom/media/test/bug501279.ogg diff --git a/dom/media/test/bug501279.ogg^headers^ b/dom/media/test/bug501279.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug501279.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug504613.ogv b/dom/media/test/bug504613.ogv Binary files differnew file mode 100644 index 0000000000..5c7fd015e9 --- /dev/null +++ b/dom/media/test/bug504613.ogv diff --git a/dom/media/test/bug504613.ogv^headers^ b/dom/media/test/bug504613.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug504613.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug504644.ogv b/dom/media/test/bug504644.ogv Binary files differnew file mode 100644 index 0000000000..46fb4a876b --- /dev/null +++ b/dom/media/test/bug504644.ogv diff --git a/dom/media/test/bug504644.ogv^headers^ b/dom/media/test/bug504644.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug504644.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug504843.ogv b/dom/media/test/bug504843.ogv Binary files differnew file mode 100644 index 0000000000..94b4750865 --- /dev/null +++ b/dom/media/test/bug504843.ogv diff --git a/dom/media/test/bug504843.ogv^headers^ b/dom/media/test/bug504843.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug504843.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug506094.ogv b/dom/media/test/bug506094.ogv Binary files differnew file mode 100644 index 0000000000..142b7b9ad1 --- /dev/null +++ b/dom/media/test/bug506094.ogv diff --git a/dom/media/test/bug506094.ogv^headers^ b/dom/media/test/bug506094.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug506094.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug516323.indexed.ogv b/dom/media/test/bug516323.indexed.ogv Binary files differnew file mode 100644 index 0000000000..7bd76eeccc --- /dev/null +++ b/dom/media/test/bug516323.indexed.ogv diff --git a/dom/media/test/bug516323.indexed.ogv^headers^ b/dom/media/test/bug516323.indexed.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug516323.indexed.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug516323.ogv b/dom/media/test/bug516323.ogv Binary files differnew file mode 100644 index 0000000000..8f2f38b983 --- /dev/null +++ b/dom/media/test/bug516323.ogv diff --git a/dom/media/test/bug516323.ogv^headers^ b/dom/media/test/bug516323.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug516323.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug520493.ogg b/dom/media/test/bug520493.ogg Binary files differnew file mode 100644 index 0000000000..6eb23198f4 --- /dev/null +++ b/dom/media/test/bug520493.ogg diff --git a/dom/media/test/bug520493.ogg^headers^ b/dom/media/test/bug520493.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug520493.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug520500.ogg b/dom/media/test/bug520500.ogg Binary files differnew file mode 100644 index 0000000000..b91d3dd97d --- /dev/null +++ b/dom/media/test/bug520500.ogg diff --git a/dom/media/test/bug520500.ogg^headers^ b/dom/media/test/bug520500.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug520500.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug520908.ogv b/dom/media/test/bug520908.ogv Binary files differnew file mode 100644 index 0000000000..093158432a --- /dev/null +++ b/dom/media/test/bug520908.ogv diff --git a/dom/media/test/bug520908.ogv^headers^ b/dom/media/test/bug520908.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug520908.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug523816.ogv b/dom/media/test/bug523816.ogv Binary files differnew file mode 100644 index 0000000000..ca9a31b6da --- /dev/null +++ b/dom/media/test/bug523816.ogv diff --git a/dom/media/test/bug523816.ogv^headers^ b/dom/media/test/bug523816.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug523816.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug533822.ogg b/dom/media/test/bug533822.ogg Binary files differnew file mode 100644 index 0000000000..a8e506910e --- /dev/null +++ b/dom/media/test/bug533822.ogg diff --git a/dom/media/test/bug533822.ogg^headers^ b/dom/media/test/bug533822.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug533822.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug556821.ogv b/dom/media/test/bug556821.ogv Binary files differnew file mode 100644 index 0000000000..8d76fee45e --- /dev/null +++ b/dom/media/test/bug556821.ogv diff --git a/dom/media/test/bug556821.ogv^headers^ b/dom/media/test/bug556821.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug556821.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug557094.ogv b/dom/media/test/bug557094.ogv Binary files differnew file mode 100644 index 0000000000..b4fc0799a6 --- /dev/null +++ b/dom/media/test/bug557094.ogv diff --git a/dom/media/test/bug557094.ogv^headers^ b/dom/media/test/bug557094.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug557094.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug603918.webm b/dom/media/test/bug603918.webm Binary files differnew file mode 100644 index 0000000000..c430e97f40 --- /dev/null +++ b/dom/media/test/bug603918.webm diff --git a/dom/media/test/bug603918.webm^headers^ b/dom/media/test/bug603918.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug603918.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bug604067.webm b/dom/media/test/bug604067.webm Binary files differnew file mode 100644 index 0000000000..86bdfdc91f --- /dev/null +++ b/dom/media/test/bug604067.webm diff --git a/dom/media/test/bug604067.webm^headers^ b/dom/media/test/bug604067.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/bug604067.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/bunny.webm b/dom/media/test/bunny.webm Binary files differnew file mode 100644 index 0000000000..823439d1c5 --- /dev/null +++ b/dom/media/test/bunny.webm diff --git a/dom/media/test/can_play_type_dash.js b/dom/media/test/can_play_type_dash.js new file mode 100644 index 0000000000..b4760545db --- /dev/null +++ b/dom/media/test/can_play_type_dash.js @@ -0,0 +1,27 @@ +function check_dash(v, enabled) { + function check(type, expected) { + is(v.canPlayType(type), enabled ? expected : "", type); + } + + // DASH types + check("application/dash+xml", "probably"); + + // Supported Webm codecs + check("application/dash+xml; codecs=vorbis", "probably"); + check("application/dash+xml; codecs=vorbis", "probably"); + check("application/dash+xml; codecs=vorbis,vp8", "probably"); + check("application/dash+xml; codecs=vorbis,vp8.0", "probably"); + check('application/dash+xml; codecs="vorbis,vp8"', "probably"); + check('application/dash+xml; codecs="vorbis,vp8.0"', "probably"); + check('application/dash+xml; codecs="vp8, vorbis"', "probably"); + check('application/dash+xml; codecs="vp8.0, vorbis"', "probably"); + check("application/dash+xml; codecs=vp8", "probably"); + check("application/dash+xml; codecs=vp8.0", "probably"); + + // Unsupported codecs + check("application/dash+xml; codecs=xyz", ""); + check("application/dash+xml; codecs=xyz,vorbis", ""); + check("application/dash+xml; codecs=vorbis,xyz", ""); + check("application/dash+xml; codecs=xyz,vp8.0", ""); + check("application/dash+xml; codecs=vp8.0,xyz", ""); +} diff --git a/dom/media/test/can_play_type_ogg.js b/dom/media/test/can_play_type_ogg.js new file mode 100644 index 0000000000..c03f8b2d3e --- /dev/null +++ b/dom/media/test/can_play_type_ogg.js @@ -0,0 +1,72 @@ +function check_ogg(v, enabled, finish) { + function check(type, expected) { + is(v.canPlayType(type), enabled ? expected : "", type); + } + + function basic_test() { + return new Promise(function(resolve, reject) { + // Ogg types + check("video/ogg", "maybe"); + check("audio/ogg", "maybe"); + check("application/ogg", "maybe"); + + // Supported Ogg codecs + check("audio/ogg; codecs=vorbis", "probably"); + check("video/ogg; codecs=vorbis", "probably"); + check("video/ogg; codecs=vorbis,theora", "probably"); + check('video/ogg; codecs="vorbis, theora"', "probably"); + check("video/ogg; codecs=theora", "probably"); + + resolve(); + }); + } + + // Verify Opus support + function verify_opus_support() { + return new Promise(function(resolve, reject) { + var OpusEnabled = SpecialPowers.getBoolPref( + "media.opus.enabled", + undefined + ); + if (OpusEnabled != undefined) { + resolve(); + } else { + console.log( + "media.opus.enabled pref not found; skipping Opus validation" + ); + reject(); + } + }); + } + + function opus_enable() { + return SpecialPowers.pushPrefEnv({ + set: [["media.opus.enabled", true]], + }).then(function() { + check("audio/ogg; codecs=opus", "probably"); + }); + } + + function opus_disable() { + return SpecialPowers.pushPrefEnv({ + set: [["media.opus.enabled", false]], + }).then(function() { + check("audio/ogg; codecs=opus", ""); + }); + } + + function unspported_ogg() { + // Unsupported Ogg codecs + check("video/ogg; codecs=xyz", ""); + check("video/ogg; codecs=xyz,vorbis", ""); + check("video/ogg; codecs=vorbis,xyz", ""); + + finish.call(); + } + + basic_test() + .then(verify_opus_support) + .then(opus_enable) + .then(opus_disable) + .then(unspported_ogg, unspported_ogg); +} diff --git a/dom/media/test/can_play_type_wave.js b/dom/media/test/can_play_type_wave.js new file mode 100644 index 0000000000..a5e087aa40 --- /dev/null +++ b/dom/media/test/can_play_type_wave.js @@ -0,0 +1,30 @@ +function check_wave(v, enabled) { + function check(type, expected) { + is(v.canPlayType(type), enabled ? expected : "", type); + } + + // Wave types + check("audio/wave", "maybe"); + check("audio/wav", "maybe"); + check("audio/x-wav", "maybe"); + check("audio/x-pn-wav", "maybe"); + + // Supported Wave codecs + check("audio/wave; codecs=1", "probably"); + check("audio/wave; codecs=3", "probably"); + check("audio/wave; codecs=6", "probably"); + check("audio/wave; codecs=7", "probably"); + // "no codecs" should be supported, I guess + check("audio/wave; codecs=", "maybe"); + check('audio/wave; codecs=""', "maybe"); + + // Unsupported Wave codecs + check("audio/wave; codecs=0", ""); + check("audio/wave; codecs=2", ""); + check("audio/wave; codecs=xyz,1", ""); + check("audio/wave; codecs=1,xyz", ""); + check('audio/wave; codecs="xyz, 1"', ""); + // empty codec names + check("audio/wave; codecs=,", ""); + check('audio/wave; codecs="0, 1,"', ""); +} diff --git a/dom/media/test/can_play_type_webm.js b/dom/media/test/can_play_type_webm.js new file mode 100644 index 0000000000..ee57da66c6 --- /dev/null +++ b/dom/media/test/can_play_type_webm.js @@ -0,0 +1,39 @@ +async function check_webm(v, enabled) { + function check(type, expected) { + is( + v.canPlayType(type), + enabled ? expected : "", + type + "='" + expected + "'" + ); + } + + // WebM types + check("video/webm", "maybe"); + check("audio/webm", "maybe"); + + var video = ["vp8", "vp8.0", "vp9", "vp9.0"]; + var audio = ["vorbis", "opus"]; + + audio.forEach(function(acodec) { + check("audio/webm; codecs=" + acodec, "probably"); + check("video/webm; codecs=" + acodec, "probably"); + }); + video.forEach(function(vcodec) { + check("video/webm; codecs=" + vcodec, "probably"); + audio.forEach(function(acodec) { + check('video/webm; codecs="' + vcodec + ", " + acodec + '"', "probably"); + check('video/webm; codecs="' + acodec + ", " + vcodec + '"', "probably"); + }); + }); + + // Unsupported WebM codecs + check("video/webm; codecs=xyz", ""); + check("video/webm; codecs=xyz,vorbis", ""); + check("video/webm; codecs=vorbis,xyz", ""); + + await SpecialPowers.pushPrefEnv({ set: [["media.av1.enabled", true]] }); + check('video/webm; codecs="av1"', "probably"); + + await SpecialPowers.pushPrefEnv({ set: [["media.av1.enabled", false]] }); + check('video/webm; codecs="av1"', ""); +} diff --git a/dom/media/test/cancellable_request.sjs b/dom/media/test/cancellable_request.sjs new file mode 100644 index 0000000000..9a4a12076e --- /dev/null +++ b/dom/media/test/cancellable_request.sjs @@ -0,0 +1,153 @@ +function parseQuery(request, key) { + var params = request.queryString.split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +function push32BE(array, input) { + array.push(String.fromCharCode((input >> 24) & 0xff)); + array.push(String.fromCharCode((input >> 16) & 0xff)); + array.push(String.fromCharCode((input >> 8) & 0xff)); + array.push(String.fromCharCode((input) & 0xff)); +} + +function push32LE(array, input) { + array.push(String.fromCharCode((input) & 0xff)); + array.push(String.fromCharCode((input >> 8) & 0xff)); + array.push(String.fromCharCode((input >> 16) & 0xff)); + array.push(String.fromCharCode((input >> 24) & 0xff)); +} + +function push16LE(array, input) { + array.push(String.fromCharCode((input) & 0xff)); + array.push(String.fromCharCode((input >> 8) & 0xff)); +} + +function buildWave(samples, sample_rate) { + const RIFF_MAGIC = 0x52494646; + const WAVE_MAGIC = 0x57415645; + const FRMT_MAGIC = 0x666d7420; + const DATA_MAGIC = 0x64617461; + const RIFF_SIZE = 44; + + var header = []; + push32BE(header, RIFF_MAGIC); + push32LE(header, RIFF_SIZE + samples.length * 2); + push32BE(header, WAVE_MAGIC); + push32BE(header, FRMT_MAGIC); + push32LE(header, 16); + push16LE(header, 1); + push16LE(header, 1); + push32LE(header, sample_rate); + push32LE(header, sample_rate); + push16LE(header, 2); + push16LE(header, 16); + push32BE(header, DATA_MAGIC); + push32LE(header, samples.length * 2); + for (var i = 0; i < samples.length; ++i) { + push16LE(header, samples[i], 2); + } + return header; +} + +const CC = Components.Constructor; +const Timer = CC("@mozilla.org/timer;1", "nsITimer", "initWithCallback"); +const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + +function poll(f) { + if (f()) { + return; + } + new Timer(function() { poll(f); }, 100, Ci.nsITimer.TYPE_ONE_SHOT); +} + +function handleRequest(request, response) +{ + var cancel = parseQuery(request, "cancelkey"); + if (cancel) { + setState(cancel[1], "cancelled"); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write("Cancel approved!"); + return; + } + + var samples = []; + for (var i = 0; i < 1000000; ++i) { + samples.push(0); + } + var bytes = buildWave(samples, 44100).join(""); + + var key = parseQuery(request, "key"); + response.setHeader("Content-Type", "audio/x-wav"); + response.setHeader("Content-Length", ""+bytes.length, false); + + var out = new BinaryOutputStream(response.bodyOutputStream); + + var start = 0, end = bytes.length - 1; + if (request.hasHeader("Range")) + { + var rangeMatch = request.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/); + + if (rangeMatch[1] !== undefined) + start = parseInt(rangeMatch[1], 10); + + if (rangeMatch[2] !== undefined) + end = parseInt(rangeMatch[2], 10); + + // No start given, so the end is really the count of bytes from the + // end of the file. + if (start === undefined) + { + start = Math.max(0, bytes.length - end); + end = bytes.length - 1; + } + + // start and end are inclusive + if (end === undefined || end >= bytes.length) + end = bytes.length - 1; + + if (end < start) + { + response.setStatusLine(request.httpVersion, 200, "OK"); + start = 0; + end = bytes.length - 1; + } + else + { + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + var contentRange = "bytes " + start + "-" + end + "/" + bytes.length; + response.setHeader("Content-Range", contentRange); + } + } + + if (start > 0) { + // Send all requested data + out.write(bytes.slice(start, end + 1), end + 1 - start); + return; + } + + // Write the first 1.2M of the Wave file. We know the cache size is set to + // 100K so this will fill the cache and and cause a "suspend" event on + // the loading element. + out.write(bytes, 1200000); + + response.processAsync(); + // Now wait for the message to cancel this response + poll(function() { + if (getState(key[1]) != "cancelled") { + return false; + } + response.finish(); + return true; + }); +} diff --git a/dom/media/test/chain.ogg b/dom/media/test/chain.ogg Binary files differnew file mode 100644 index 0000000000..3535b280f4 --- /dev/null +++ b/dom/media/test/chain.ogg diff --git a/dom/media/test/chain.ogg^headers^ b/dom/media/test/chain.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/chain.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/chain.ogv b/dom/media/test/chain.ogv Binary files differnew file mode 100644 index 0000000000..3e684b64a5 --- /dev/null +++ b/dom/media/test/chain.ogv diff --git a/dom/media/test/chain.ogv^headers^ b/dom/media/test/chain.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/chain.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/chain.opus b/dom/media/test/chain.opus Binary files differnew file mode 100644 index 0000000000..9fa67f94c3 --- /dev/null +++ b/dom/media/test/chain.opus diff --git a/dom/media/test/chain.opus^headers^ b/dom/media/test/chain.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/chain.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/chained-audio-video.ogg b/dom/media/test/chained-audio-video.ogg Binary files differnew file mode 100644 index 0000000000..adda68bb47 --- /dev/null +++ b/dom/media/test/chained-audio-video.ogg diff --git a/dom/media/test/chained-audio-video.ogg^headers^ b/dom/media/test/chained-audio-video.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/chained-audio-video.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/chained-video.ogv b/dom/media/test/chained-video.ogv Binary files differnew file mode 100644 index 0000000000..a6288ef6c9 --- /dev/null +++ b/dom/media/test/chained-video.ogv diff --git a/dom/media/test/chained-video.ogv^headers^ b/dom/media/test/chained-video.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/chained-video.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/chrome/chrome.ini b/dom/media/test/chrome/chrome.ini new file mode 100644 index 0000000000..429c3a093c --- /dev/null +++ b/dom/media/test/chrome/chrome.ini @@ -0,0 +1,6 @@ +[DEFAULT] +subsuite = media +support-files = + ../gizmo.mp4 + +[test_accumulated_play_time.html] diff --git a/dom/media/test/chrome/test_accumulated_play_time.html b/dom/media/test/chrome/test_accumulated_play_time.html new file mode 100644 index 0000000000..509ebd5a07 --- /dev/null +++ b/dom/media/test/chrome/test_accumulated_play_time.html @@ -0,0 +1,355 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test Video Play Time Related Permenant Telemetry Probes</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + +/** + * This test is used to ensure that we accumulate time for video playback + * correctly, and the results would be used in Telemetry probes. + * Currently this test covers following probes + * - VIDEO_PLAY_TIME_MS + * - VIDEO_HIDDEN_PLAY_TIME_MS + * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE + * - VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE + */ +const histNames = ["VIDEO_PLAY_TIME_MS", "VIDEO_HIDDEN_PLAY_TIME_MS"]; +const keyedHistNames = ["VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE"]; + +add_task(async function setTestPref() { + await SpecialPowers.pushPrefEnv({ + set: [["media.testing-only-events", true], + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + ["media.suspend-bkgnd-video.delay-ms", 0]]}); +}); + +add_task(async function testTotalPlayTime() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + + info(`all accumulated time should be zero`); + const videoChrome = SpecialPowers.wrap(video); + await new Promise(r => video.onloadeddata = r); + assertValueEqualTo(videoChrome, "totalPlayTime", 0); + assertValueEqualTo(videoChrome, "invisiblePlayTime", 0); + assertValueEqualTo(videoChrome, "videoDecodeSuspendedTime", 0); + + info(`start accumulating play time after media starts`); + video.autoplay = true; + await Promise.all([ + once(video, "playing"), + once(video, "moztotalplaytimestarted"), + ]); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + + info(`should not accumulate time for paused video`); + video.pause(); + await once(video, "moztotalplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "totalPlayTime"); + + info(`should start accumulating time again`); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function testHiddenPlayTime() { + const invisibleReasons = ["notInTree", "notInConnectedTree", "invisibleInDisplay"]; + for (let reason of invisibleReasons) { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + video.loop = true; + info(`invisible video due to '${reason}'`); + + if (reason == "notInConnectedTree") { + let disconnected = document.createElement("div") + disconnected.appendChild(video); + } else if (reason == "invisibleInDisplay") { + document.body.appendChild(video); + video.style.display = "none"; + } else if (reason == "notInTree") { + // video is already created in the `notInTree` situation. + } else { + ok(false, "undefined reason"); + } + + info(`start invisible video should start accumulating timers`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); + assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + + info(`should not accumulate time for paused video`); + video.pause(); + await once(video, "mozinvisibleplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + + info(`should start accumulating time again`); + rv = await Promise.all([ + onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + + info(`make video visible should stop accumulating invisible related time`); + if (reason == "notInTree" || reason == "notInConnectedTree") { + document.body.appendChild(video); + } else if (reason == "invisibleInDisplay") { + // If we set only `display` the video would still be hidden, so setting + // width and height to make it visible. + video.style.display = "unset"; + video.style.width = "300px"; + video.style.height = "150px"; + } else { + ok(false, "undefined reason"); + } + await once(video, "mozinvisibleplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + await cleanUpMediaAndCheckTelemetry(video); + } +}); + +// Note that video suspended time is not always align with the invisible play +// time even if `media.suspend-bkgnd-video.delay-ms` is `0`, because not all +// invisible videos would be suspended under current strategy. +add_task(async function testDecodeSuspendedTime() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + + info(`start video should start accumulating timers`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + + info(`make it invisible and force to suspend decoding`); + video.setVisible(false); + await once(video, "mozvideodecodesuspendedstarted"); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime"); + + info(`make it visible and resume decoding`); + video.setVisible(true); + await Promise.all([ + once(video, "mozinvisibleplaytimepaused"), + once(video, "mozvideodecodesuspendedpaused"), + ]); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function reuseSameElementForPlayback() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + + info(`start accumulating play time after media starts`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + + info(`reset its src and all accumulated value should be reset after then`); + // After setting its src to nothing, that would trigger a failed load and set + // the error. If the following step tries to set the new resource and `play()` + // , then they should be done after receving the `error` from that failed load + // first. + await Promise.all([ + once(video, "error"), + cleanUpMediaAndCheckTelemetry(video), + ]); + // video doesn't have a decoder, so the return value would be -1 (error). + assertValueEqualTo(videoChrome, "totalPlayTime", -1); + assertValueEqualTo(videoChrome, "invisiblePlayTime", -1); + + info(`resue same element, make it visible and start playback again`); + video.src = "gizmo.mp4"; + rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started"); + assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function testNoReportedTelemetryResult() { + info(`No result for empty video`); + const video = document.createElement('video'); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); + + info(`No result for video which hasn't started playing`); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + ok(await once(video, "loadeddata").then(_ => true), "video loaded data"); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); + + info(`No result for video with error`); + video.src = "filedoesnotexist.mp4"; + ok(await video.play().then(_ => false, _ => true), "video failed to play"); + ok(video.error != undefined, "video got error"); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); + + info(`No result for playing an audio`); + const audio = document.createElement('audio'); + audio.src = "gizmo.mp4"; + document.body.appendChild(audio); + ok(await audio.play().then(_ => true, _ => false), "audio started playing"); + audio.pause(); + await assertNoReportedTelemetryResult(audio); +}); + +/** + * Following are helper functions + */ +async function cleanUpMediaAndCheckTelemetry(media, { shouldReport = true } = {}) { + media.src = ""; + await checkReportedTelemetry(media, shouldReport); +} + +async function assertNoReportedTelemetryResult(media) { + await checkReportedTelemetry(media, false); +} + +async function checkReportedTelemetry(media, shouldReport) { + const reportResultPromise = once(media, "mozreportedtelemetry"); + info(`check telemetry result, shouldReport=${shouldReport}`); + if (shouldReport) { + await reportResultPromise; + } + for (const name of histNames) { + try { + const hist = SpecialPowers.Services.telemetry.getHistogramById(name); + /** + * Histogram's snapshot looks like that + * { + * "bucket_count": X, + * "histogram_type": Y, + * "sum": Z, + * "range": [min, max], + * "values": { "value1" : "num1", "value2" : "num2", ...} + * } + */ + const entriesNums = Object.entries(hist.snapshot().values).length; + if (shouldReport) { + ok(entriesNums > 0, `Reported result for ${name}`); + } else { + ok(entriesNums == 0, `Reported nothing for ${name}`); + } + hist.clear(); + } catch (e) { + ok(false , `histogram '${name}' doesn't exist`); + } + } + for (const name of keyedHistNames) { + try { + const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name); + /** + * Keyed Histogram's snapshot looks like that + * { + * "Key1" : { + * "bucket_count": X, + * "histogram_type": Y, + * "sum": Z, + * "range": [min, max], + * "values": { "value1" : "num1", "value2" : "num2", ...} + * }, + * "Key2" : {...}, + * } + */ + const items = Object.entries(hist.snapshot()); + if (items.length > 0) { + for (const [key, value] of items) { + const entriesNums = Object.entries(value.values).length; + ok(shouldReport && entriesNums > 0, `Reported ${key} for ${name}`); + } + } else { + ok(!shouldReport, `Reported nothing for ${name}`); + } + // Avoid to pollute next test task. + hist.clear(); + } catch (e) { + ok(false , `keyed histogram '${name}' doesn't exist`); + } + } +} + +function once(target, name) { + return new Promise(r => target.addEventListener(name, r, { once: true })); +} + +function onceWithTrueReturn(target, name) { + return once(target, name).then(_ => true); +} + +function returnTrueWhenAllValuesAreTrue(arr) { + for (let val of arr) { + if (!val) { + return false; + } + } + return true; +} + +function assertAttributeDefined(mediaChrome, checkType) { + ok(mediaChrome[checkType] != undefined, `${checkType} exists`); +} + +function assertValueEqualTo(mediaChrome, checkType, expectedValue) { + assertAttributeDefined(mediaChrome, checkType); + is(mediaChrome[checkType], expectedValue, `${checkType} equals to ${expectedValue}`); +} + +function assertValueConstantlyIncreases(mediaChrome, checkType) { + assertAttributeDefined(mediaChrome, checkType); + const valueSnapshot = mediaChrome[checkType]; + ok(mediaChrome[checkType] > valueSnapshot, `${checkType} keeps increasing`); +} + +function assertValueKeptUnchanged(mediaChrome, checkType) { + assertAttributeDefined(mediaChrome, checkType); + const valueSnapshot = mediaChrome[checkType]; + ok(mediaChrome[checkType] == valueSnapshot, `${checkType} keeps unchanged`); +} + +function assertAllProbeRelatedAttributesKeptUnchanged(video) { + const videoChrome = SpecialPowers.wrap(video); + assertValueKeptUnchanged(videoChrome, "totalPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); +} + +</script> +</head> +<body> +</body> +</html> diff --git a/dom/media/test/chromeHelper.js b/dom/media/test/chromeHelper.js new file mode 100644 index 0000000000..ff83660fe2 --- /dev/null +++ b/dom/media/test/chromeHelper.js @@ -0,0 +1,23 @@ +/* -*- Mode: javascript; indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/frame-script */ + +"use strict"; + +// eslint-disable-next-line mozilla/use-services +const dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( + Ci.nsIProperties +); + +addMessageListener("media-test:getcwd", () => { + let cwd; + try { + cwd = dirSvc.get("CurWorkD", Ci.nsIFile).path; + } finally { + sendAsyncMessage("media-test:cwd", cwd); + } +}); diff --git a/dom/media/test/cloneElementVisually_helpers.js b/dom/media/test/cloneElementVisually_helpers.js new file mode 100644 index 0000000000..ab81aad9ed --- /dev/null +++ b/dom/media/test/cloneElementVisually_helpers.js @@ -0,0 +1,232 @@ +const TEST_VIDEO_1 = + "http://mochi.test:8888/tests/dom/media/test/bipbop_225w_175kbps.mp4"; +const TEST_VIDEO_2 = + "http://mochi.test:8888/tests/dom/media/test/pixel_aspect_ratio.mp4"; +const LONG_VIDEO = "http://mochi.test:8888/tests/dom/media/test/gizmo.mp4"; + +/** + * Ensure that the original <video> is prepped and ready to play + * before starting any other tests. + */ +async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + ["media.suspend-bkgnd-video.delay-ms", 500], + ["media.dormant-on-pause-timeout-ms", 0], + ["media.cloneElementVisually.testing", true], + ], + }); + + let originalVideo = document.getElementById("original"); + await setVideoSrc(originalVideo, TEST_VIDEO_1); +} + +/** + * Given a canvas, as well as a width and height of something to be + * blitted onto that canvas, makes any necessary adjustments to the + * canvas to work with the current display resolution. + * + * @param canvas (<canvas> element) + * The canvas to be adjusted. + * @param width (int) + * The width of the image to be blitted. + * @param height (int) + * The height of the image to be blitted. + * @return CanvasRenderingContext2D (SpecialPowers wrapper) + */ +function getWrappedScaledCanvasContext(canvas, width, height) { + let devRatio = window.devicePixelRatio || 1; + let ctx = SpecialPowers.wrap(canvas.getContext("2d")); + let backingRatio = ctx.webkitBackingStorePixelRatio || 1; + + let ratio = 1; + ratio = devRatio / backingRatio; + canvas.width = ratio * width; + canvas.height = ratio * height; + canvas.style.width = width + "px"; + canvas.style.height = height + "px"; + ctx.scale(ratio, ratio); + + return ctx; +} + +/** + * Given a <video> element in the DOM, figures out its location, and captures + * a snapshot of what it's currently presenting. + * + * @param video (<video> element) + * @return ImageData (SpecialPowers wrapper) + */ +function captureFrameImageData(video) { + // Flush layout first, just in case the decoder has recently + // updated the dimensions of the video. + let rect = video.getBoundingClientRect(); + + let width = video.videoWidth; + let height = video.videoHeight; + + let canvas = document.createElement("canvas"); + let ctx = getWrappedScaledCanvasContext(canvas, width, height); + ctx.drawWindow(window, rect.left, rect.top, width, height, "rgb(0,0,0)"); + + return ctx.getImageData(0, 0, width, height); +} + +/** + * Given two <video> elements, captures snapshots of what they're currently + * displaying, and asserts that they're identical. + * + * @param video1 (<video> element) + * A video element to compare against. + * @param video2 (<video> element) + * A video to compare with video1. + * @return Promise + * @resolves + * Resolves as true if the two videos match. + */ +async function assertVideosMatch(video1, video2) { + let video1Frame = captureFrameImageData(video1); + let video2Frame = captureFrameImageData(video2); + + let left = document.getElementById("left"); + let leftCtx = getWrappedScaledCanvasContext( + left, + video1Frame.width, + video1Frame.height + ); + leftCtx.putImageData(video1Frame, 0, 0); + + let right = document.getElementById("right"); + let rightCtx = getWrappedScaledCanvasContext( + right, + video2Frame.width, + video2Frame.height + ); + rightCtx.putImageData(video2Frame, 0, 0); + + if (video1Frame.data.length != video2Frame.data.length) { + return false; + } + + let leftDataURL = left.toDataURL(); + let rightDataURL = right.toDataURL(); + + if (leftDataURL != rightDataURL) { + dump("Left frame: " + leftDataURL + "\n\n"); + dump("Right frame: " + rightDataURL + "\n\n"); + + return false; + } + + return true; +} + +/** + * Testing helper function that constructs a node clone of a video, + * injects it into the DOM, and then runs an async testing function. + * This also does the work of removing the clone before resolving. + * + * @param video (<video> element) + * The video to clone the node from. + * @param asyncFn (async function) + * A test function that will be passed the new clone as its + * only argument. + * @return Promise + * @resolves + * When the asyncFn resolves and the clone has been removed + * from the DOM. + */ +async function withNewClone(video, asyncFn) { + let clone = video.cloneNode(); + clone.id = "clone"; + clone.src = ""; + let content = document.getElementById("content"); + content.appendChild(clone); + + try { + await asyncFn(clone); + } finally { + clone.remove(); + } +} + +/** + * Sets the src on a video and waits until its ready. + * + * @param video (<video> element) + * The video to set the src on. + * @param src (string) + * The URL to set as the source on a video. + * @return Promise + * @resolves + * When the video fires the "canplay" event. + */ +async function setVideoSrc(video, src) { + let promiseReady = waitForEventOnce(video, "canplay"); + video.src = src; + await promiseReady; +} + +/** + * Returns a Promise once target emits a particular event + * once. + * + * @param target (DOM node) + * The target to monitor for the event. + * @param event (string) + * The name of the event to wait for. + * @return Promise + * @resolves + * When the event fires, and resolves to the event. + */ +function waitForEventOnce(target, event) { + return new Promise(resolve => { + target.addEventListener(event, resolve, { once: true }); + }); +} + +/** + * Polls the video debug data as a hacky way of knowing when + * when the decoders have shut down. + * + * @param video (<video> element) + * @return Promise + * @resolves + * When the decoder has shut down. + */ +async function waitForShutdownDecoder(video) { + await SimpleTest.promiseWaitForCondition(async () => { + let readerData = SpecialPowers.wrap(video).mozDebugReaderData; + return readerData.includes(": shutdown"); + }, "Video decoder should eventually shut down."); +} + +/** + * Ensures that both hiding and pausing the video causes the + * video to suspend and make dormant its decoders, respectively. + * + * @param video (<video element) + */ +async function ensureVideoSuspendable(video) { + video = SpecialPowers.wrap(video); + + ok(!video.hasSuspendTaint(), "Should be suspendable"); + + // First, we'll simulate putting the video in the background by + // making it invisible. + let suspendPromise = waitForEventOnce(video, "mozentervideosuspend"); + video.setVisible(false); + await suspendPromise; + ok(true, "Suspended after the video was made invisible."); + video.setVisible(true); + + ok(!video.hasSuspendTaint(), "Should still be suspendable."); + + // Next, we'll pause the video. + await video.pause(); + await waitForShutdownDecoder(video); + ok(true, "Shutdown decoder after the video was paused."); + await video.play(); +} diff --git a/dom/media/test/contentType.sjs b/dom/media/test/contentType.sjs new file mode 100644 index 0000000000..a7a0caf669 --- /dev/null +++ b/dom/media/test/contentType.sjs @@ -0,0 +1,77 @@ +// Parse the query string, and give us the value for a certain key, or false if +// it does not exist. +function parseQuery(request, key) { + var params = request.queryString.split('?')[0].split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +function handleRequest(request, response) { + try { + // Get the filename to send back. + var filename = parseQuery(request, "file"); + + const CC = Components.Constructor; + const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/" + filename; + dump(paths + '\n'); + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + + // handle range requests + var partialstart = 0, + partialend = file.fileSize - 1; + if (request.hasHeader("Range")) { + var range = request.getHeader("Range"); + var parts = range.replace(/bytes=/, "").split("-"); + var partialstart = parts[0]; + var partialend = parts[1]; + if (!partialend.length) { + partialend = file.fileSize - 1; + } + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + var contentRange = "bytes " + partialstart + "-" + partialend + "/" + file.fileSize; + response.setHeader("Content-Range", contentRange); + } + + fis.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, partialstart); + bis.setInputStream(fis); + + var sendContentType = parseQuery(request, "nomime"); + if (sendContentType == false) { + var contentType = parseQuery(request, "type"); + if (contentType == false) { + // This should not happen. + dump("No type specified without having \'nomime\' in parameters."); + return; + } + response.setHeader("Content-Type", contentType, false); + } + response.setHeader("Content-Length", ""+bis.available(), false); + + var bytes = bis.readBytes(bis.available()); + response.write(bytes, bytes.length); + } catch (e) { + dump ("ERROR : " + e + "\n"); + } +} diff --git a/dom/media/test/crashtests/0-timescale.html b/dom/media/test/crashtests/0-timescale.html new file mode 100644 index 0000000000..db845096dd --- /dev/null +++ b/dom/media/test/crashtests/0-timescale.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +</head> +<body> +<!-- This video file has a timescale of 0 in the mdhd atom --> +<video src="0-timescale.mp4" + autoplay + onerror="document.documentElement.className=undefined" + onloadedmetadata="this.src=''; document.documentElement.className=undefined"> +<!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. --> +</video> +</body> +</html> diff --git a/dom/media/test/crashtests/0-timescale.mp4 b/dom/media/test/crashtests/0-timescale.mp4 Binary files differnew file mode 100644 index 0000000000..32df5dc5a5 --- /dev/null +++ b/dom/media/test/crashtests/0-timescale.mp4 diff --git a/dom/media/test/crashtests/1012609.html b/dom/media/test/crashtests/1012609.html new file mode 100644 index 0000000000..1dad783a07 --- /dev/null +++ b/dom/media/test/crashtests/1012609.html @@ -0,0 +1,9 @@ +<script> +try{var r0=new AudioContext();}catch(e){} +try{var r32=r0.createOscillator();}catch(e){} +try{var r58=r0.createPeriodicWave(new Float32Array(1997),new Float32Array(1997));}catch(e){} +try{r32.start(0);}catch(e){} +try{r32.setPeriodicWave(r58);}catch(e){} +try{r32.frequency.value=-1;}catch(e){} +</script> + diff --git a/dom/media/test/crashtests/1015662.html b/dom/media/test/crashtests/1015662.html new file mode 100644 index 0000000000..20407c807d --- /dev/null +++ b/dom/media/test/crashtests/1015662.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<body> +<video><track src="javascript:5"></track></video> +</body> diff --git a/dom/media/test/crashtests/1028458.html b/dom/media/test/crashtests/1028458.html new file mode 100644 index 0000000000..bb01e3343a --- /dev/null +++ b/dom/media/test/crashtests/1028458.html @@ -0,0 +1,23 @@ +<html class="reftest-wait"> +<audio id="testAudio" controls></audio> +<script type="text/javascript"> +navigator.mozGetUserMedia({audio: true, fake: true}, function(stream) { + stream.getAudioTracks()[0].enabled = false; + var testAudio = document.getElementById('testAudio'); + // Wait some time for good measure + var eventReceived = 0; + testAudio.addEventListener("timeupdate", function() { + if (++eventReceived == 3) { + document.querySelector("html").className = ""; + } + }) + testAudio.srcObject = stream; + testAudio.play(); + }, function(err) { + // Don't go orange if we can't get an audio input stream, + // as this is not what we are trying to test and can happen on Windows. + document.querySelector("html").className = ""; + }); +</script> + +</html> diff --git a/dom/media/test/crashtests/1041466.html b/dom/media/test/crashtests/1041466.html new file mode 100644 index 0000000000..0f064d186c --- /dev/null +++ b/dom/media/test/crashtests/1041466.html @@ -0,0 +1,21 @@ +<html> +<script> +try{var Context1= new (window.webkitAudioContext || window.AudioContext)()}catch(e){} +try{var Delay0=Context1.createDelay();}catch(e){} +try{var ScriptProcessor0=Context1.createScriptProcessor(512,26,7);}catch(e){} +try{var ChannelSplitter0=Context1.createChannelSplitter(91);}catch(e){} +try{var Gain1=Context1.createGain();}catch(e){} +try{var WaveShaper0=Context1.createWaveShaper();}catch(e){} +try{var Analyser1=Context1.createAnalyser();}catch(e){} +try{Gain1.connect(Delay0);}catch(e){} +try{Analyser1.connect(BiquadFilter0);}catch(e){} +try{Gain1.connect(Context1.destination);}catch(e){} +try{WaveShaper0.connect(ScriptProcessor0);}catch(e){} +try{ChannelSplitter0.connect(BiquadFilter1);}catch(e){} +try{Delay0.connect(Gain1);}catch(e){} +try{ScriptProcessor0.connect(Context1.destination);}catch(e){} +try{WaveShaper0.connect(Gain1);}catch(e){} +try{WaveShaper0.connect(ChannelSplitter0);}catch(e){} +try{ScriptProcessor0.connect(WaveShaper0);}catch(e){} +</script> +</html> diff --git a/dom/media/test/crashtests/1045650.html b/dom/media/test/crashtests/1045650.html new file mode 100644 index 0000000000..32acd2ce75 --- /dev/null +++ b/dom/media/test/crashtests/1045650.html @@ -0,0 +1,18 @@ +<html><body><script> + +var r0=new AudioContext(); + +var splitter=r0.createChannelSplitter(); +var delay=r0.createDelay(); +var scriptp=r0.createScriptProcessor(); +var cmerger=r0.createChannelMerger(); +var gain=r0.createGain(); + +splitter.connect(delay,2); +delay.connect(scriptp); +scriptp.connect(cmerger); +cmerger.connect(splitter); +gain.connect(gain); +gain.connect(cmerger); + +</script></body></html> diff --git a/dom/media/test/crashtests/1080986.html b/dom/media/test/crashtests/1080986.html new file mode 100644 index 0000000000..1de3075169 --- /dev/null +++ b/dom/media/test/crashtests/1080986.html @@ -0,0 +1,3 @@ +<html> +<audio autoplay src="1080986.wav"></audio> +</html> diff --git a/dom/media/test/crashtests/1080986.wav b/dom/media/test/crashtests/1080986.wav Binary files differnew file mode 100644 index 0000000000..b96c59b7ec --- /dev/null +++ b/dom/media/test/crashtests/1080986.wav diff --git a/dom/media/test/crashtests/1122218.html b/dom/media/test/crashtests/1122218.html new file mode 100644 index 0000000000..984487dd21 --- /dev/null +++ b/dom/media/test/crashtests/1122218.html @@ -0,0 +1,24 @@ +<html> +<head> +<script> +function boom() { + var r0=new AudioContext(); + + var cm=r0.createChannelMerger(20); + + var o1=r0.createOscillator(); + var o2=r0.createOscillator(); + + var pw=r0.createPeriodicWave(new Float32Array(4),new Float32Array(4)); + o2.setPeriodicWave(pw); + + o1.connect(cm); + cm.connect(o2.frequency); + + o1.start(); + o2.start(0.476); +} +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/1127188.html b/dom/media/test/crashtests/1127188.html new file mode 100644 index 0000000000..650f07dd47 --- /dev/null +++ b/dom/media/test/crashtests/1127188.html @@ -0,0 +1,3 @@ +<script> + (new AudioContext).close(); +</script> diff --git a/dom/media/test/crashtests/1157994.html b/dom/media/test/crashtests/1157994.html new file mode 100644 index 0000000000..2e3001302a --- /dev/null +++ b/dom/media/test/crashtests/1157994.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + var ac = new AudioContext(); + // first "suspended" -> "running" transition + ac.onstatechange = function() { + ac.onstatechange = null; + ac.suspend(); + ac.close(); + } +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/media/test/crashtests/1158427.html b/dom/media/test/crashtests/1158427.html new file mode 100644 index 0000000000..b544a942a1 --- /dev/null +++ b/dom/media/test/crashtests/1158427.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + dump("before capture\n"); + document.createElement("audio").mozCaptureStreamUntilEnded(); + dump("before context\n"); + new window.AudioContext(); + dump("before gc\n"); + SpecialPowers.forceCC(); + dump("after gc\n"); +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/media/test/crashtests/1180881.html b/dom/media/test/crashtests/1180881.html new file mode 100644 index 0000000000..d5bf2f5642 --- /dev/null +++ b/dom/media/test/crashtests/1180881.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<video src="1180881.webm" autoplay></video> +</body> +</html> diff --git a/dom/media/test/crashtests/1180881.webm b/dom/media/test/crashtests/1180881.webm Binary files differnew file mode 100644 index 0000000000..2fb2be7a7f --- /dev/null +++ b/dom/media/test/crashtests/1180881.webm diff --git a/dom/media/test/crashtests/1185176.html b/dom/media/test/crashtests/1185176.html new file mode 100644 index 0000000000..d5e9b68a29 --- /dev/null +++ b/dom/media/test/crashtests/1185176.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> + +function boom() +{ + var ac = new window.AudioContext(); + var oscillator = ac.createOscillator(); + oscillator.start(0); + oscillator.stop(0.1); + setTimeout(function() { + oscillator.channelCount = 1; + oscillator.channelCountMode = "explicit"; + oscillator.channelInterpretation = "speakers"; + document.documentElement.removeAttribute("class"); + }, 1000); +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/media/test/crashtests/1185192.html b/dom/media/test/crashtests/1185192.html new file mode 100644 index 0000000000..46fa311aa2 --- /dev/null +++ b/dom/media/test/crashtests/1185192.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + new Audio().mozCaptureStreamUntilEnded(); + var ac = new window.AudioContext(); + ac.resume(); + ac.close(); +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/media/test/crashtests/1197935.html b/dom/media/test/crashtests/1197935.html new file mode 100644 index 0000000000..dd8ad0382d --- /dev/null +++ b/dom/media/test/crashtests/1197935.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<video src="1197935.mp4" autoplay></video> +</body> +</html> diff --git a/dom/media/test/crashtests/1197935.mp4 b/dom/media/test/crashtests/1197935.mp4 Binary files differnew file mode 100644 index 0000000000..f00de75627 --- /dev/null +++ b/dom/media/test/crashtests/1197935.mp4 diff --git a/dom/media/test/crashtests/1223670.html b/dom/media/test/crashtests/1223670.html new file mode 100644 index 0000000000..94cad43e23 --- /dev/null +++ b/dom/media/test/crashtests/1223670.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> + +function boom() +{ + + var ac = new window.AudioContext("publicnotification"); + + setTimeout(function() { + document.documentElement.removeAttribute("class"); + var htmlAudio = new Audio(); + var stream = htmlAudio.mozCaptureStreamUntilEnded(); + ac.createMediaStreamSource(stream); + }, 0); +} + +</script> +</head> +<body onload="boom();"> +</body> +</html> diff --git a/dom/media/test/crashtests/1236639.html b/dom/media/test/crashtests/1236639.html new file mode 100644 index 0000000000..5c4634a4d4 --- /dev/null +++ b/dom/media/test/crashtests/1236639.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <title>Bug 1236639: Crash on audio playback due to invalid XING headers</title> +</head> +<body> +<audio autoplay src="1236639.mp3"></audio> +</body> +</html> diff --git a/dom/media/test/crashtests/1236639.mp3 b/dom/media/test/crashtests/1236639.mp3 Binary files differnew file mode 100644 index 0000000000..8ef96e8cc7 --- /dev/null +++ b/dom/media/test/crashtests/1236639.mp3 diff --git a/dom/media/test/crashtests/1257700.html b/dom/media/test/crashtests/1257700.html new file mode 100644 index 0000000000..5377b17da5 --- /dev/null +++ b/dom/media/test/crashtests/1257700.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<video src="1257700.webm" autoplay></video> +</body> +</html> diff --git a/dom/media/test/crashtests/1257700.webm b/dom/media/test/crashtests/1257700.webm Binary files differnew file mode 100644 index 0000000000..63e53c8c0a --- /dev/null +++ b/dom/media/test/crashtests/1257700.webm diff --git a/dom/media/test/crashtests/1267263.html b/dom/media/test/crashtests/1267263.html new file mode 100644 index 0000000000..a4d0e621cd --- /dev/null +++ b/dom/media/test/crashtests/1267263.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +function boom() +{ + vid.setMediaKeys(null); + vid.fastSeek(111); +} + +</script> +</head> + +<body onload="boom();"> + <video id="vid" src="../../../../layout/reftests/webm-video/frames.webm"></video> +</body> +</html> diff --git a/dom/media/test/crashtests/1270303.html b/dom/media/test/crashtests/1270303.html new file mode 100644 index 0000000000..23608bb2b8 --- /dev/null +++ b/dom/media/test/crashtests/1270303.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<video src="1270303.webm" autoplay></video> +</body> +</html> diff --git a/dom/media/test/crashtests/1270303.webm b/dom/media/test/crashtests/1270303.webm Binary files differnew file mode 100644 index 0000000000..c9b0003d6b --- /dev/null +++ b/dom/media/test/crashtests/1270303.webm diff --git a/dom/media/test/crashtests/1291702.html b/dom/media/test/crashtests/1291702.html new file mode 100644 index 0000000000..facc52af7b --- /dev/null +++ b/dom/media/test/crashtests/1291702.html @@ -0,0 +1,72 @@ +<script> +Logger={}; Logger.JSError=function(e){}; +try { o0 = new Audio("media/audio/mono-uncompressed-8bit-44100hz.wav") } catch(e) { Logger.JSError(e); } +try { o1 = o0.mozCaptureStreamUntilEnded() } catch(e) { Logger.JSError(e); } +try { o2 = new window.AudioContext(); } catch(e) { Logger.JSError(e); } +try { o3 = o2.createBufferSource(); } catch(e) { Logger.JSError(e); } +try { o5 = o2.createChannelMerger(3); } catch(e) { Logger.JSError(e); } +try { o3.start(0) } catch(e) { Logger.JSError(e); } +try { o2.listener.setPosition(0.0417049336344248, 0.9932504594310304, 32) } catch(e) { Logger.JSError(e); } +try { o5.disconnect(0) } catch(e) { Logger.JSError(e); } +try { o0.mozGetMetadata() } catch(e) { Logger.JSError(e); } +try { o3.channelCount = 1; } catch(e) { Logger.JSError(e); } +try { o3.buffer = function anonymous() { +var buffer = o2.createBuffer(1, 512, o2.sampleRate);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<512;i++) {data[i] = i % 512}}return buffer; +}(); } catch(e) { Logger.JSError(e); } +try { o3.loop = false; } catch(e) { Logger.JSError(e); } +try { o0.mozPreservesPitch = false; } catch(e) { Logger.JSError(e); } +try { o2.destination.channelCount = 1; } catch(e) { Logger.JSError(e); } +try { o3.loopStart = 8; } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.value = 0.46271130895770884; } catch(e) { Logger.JSError(e); } +try { o2.listener.setVelocity(0.34781960219792546, 4, 2048) } catch(e) { Logger.JSError(e); } +try { o5.connect(o5, 0, 0) } catch(e) { Logger.JSError(e); } +try { o3.loopStart = -0.24696638021780326; } catch(e) { Logger.JSError(e); } +try { o0.mozSetup(1, 44100) } catch(e) { Logger.JSError(e); } +try { o3.buffer.copyToChannel(function anonymous() { +var buffer=new Float32Array(256);for(var i=0;i<256;i++){buffer[i]=i / 256}return buffer; +}(), 1, 2048, 64) } catch(e) { Logger.JSError(e); } +try { o3.loop = false; } catch(e) { Logger.JSError(e); } +try { setInterval(function anonymous() { +try { o0.pause() } catch(e) { Logger.JSError(e); } +}, 12.902067779658143) } catch(e) { Logger.JSError(e); } +try { o2.listener.setPosition(256, 256, 16) } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.setValueCurveAtTime(function anonymous() { +var buffer=new Float32Array(4);for(var i=0;i<4;i++){buffer[i]=i / 4}return buffer; +}(), 2, 0.40792575814014437) } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.value = 0.4997270553139334; } catch(e) { Logger.JSError(e); } +try { o0.loop = true; } catch(e) { Logger.JSError(e); } +try { o3.loopStart = 4; } catch(e) { Logger.JSError(e); } +try { setInterval(function anonymous() { +try { o3.buffer = function anonymous() { +var buffer = o2.createBuffer(1, 1, o2.sampleRate);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<1;i++) {data[i] = Math.sin(Math.sin(i))}}return buffer; +}() } catch(e) { Logger.JSError(e); } +}, 54.32078602859342) } catch(e) { Logger.JSError(e); } +try { o3.connect(o2.destination); } catch(e) { Logger.JSError(e); } +try { o3.channelCountMode = 'max'; } catch(e) { Logger.JSError(e); } +try { setInterval(function anonymous() { +try { o3.channelCount = 1; } catch(e) { Logger.JSError(e); } +}, 55.448587039802966) } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.cancelScheduledValues(0.7190983131805198) } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.cancelScheduledValues(16) } catch(e) { Logger.JSError(e); } +try { o3.connect(o5, 0, 2) } catch(e) { Logger.JSError(e); } +try { o3.loopEnd = 0.5864771678080962; } catch(e) { Logger.JSError(e); } +try { o0.playbackRate = 0.2781783298771312; } catch(e) { Logger.JSError(e); } +try { o0.loop = false; } catch(e) { Logger.JSError(e); } +try { setInterval(function anonymous() { +try { o5.disconnect(0) } catch(e) { Logger.JSError(e); } +}, 29.75776777646425) } catch(e) { Logger.JSError(e); } +try { o3.playbackRate.setValueCurveAtTime(function anonymous() { +var buffer=new Float32Array(8);for(var i=0;i<8;i++){buffer[i]=8 % 8}return buffer; +}(), 0.4972710112336257, 64) } catch(e) { Logger.JSError(e); } +try { setInterval(function anonymous() { +try { o0.controls = false; } catch(e) { Logger.JSError(e); } +}, 22.550249570567694) } catch(e) { Logger.JSError(e); } +try { o2.listener.setOrientation(0.6531494410366634, 64, 0.5120918081402992, -64, 0.32912433155093446, 256) } catch(e) { Logger.JSError(e); } +try { o3.loop = true; } catch(e) { Logger.JSError(e); } +try { o3.connect(o5, 0, 0) } catch(e) { Logger.JSError(e); } +try { o3.buffer = function anonymous() { +var buffer = o2.createBuffer(1, 2048, 48000);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<2048;i++) {data[i] = Math.sin(Math.sin(2048 * 0.2519529190035427))}}return buffer; +}(); } catch(e) { Logger.JSError(e); } +try { o3.disconnect(0) } catch(e) { Logger.JSError(e); } +</script> + diff --git a/dom/media/test/crashtests/1368490.html b/dom/media/test/crashtests/1368490.html new file mode 100644 index 0000000000..8a2d9f9674 --- /dev/null +++ b/dom/media/test/crashtests/1368490.html @@ -0,0 +1,30 @@ +<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title> Bug 1368490 : Crash if media recorder source stream reduces number of channels. </title>
+</head>
+<meta charset="utf-8">
+<script type="text/javascript">
+
+function boom() {
+ let audioContext = new window.AudioContext();
+ let oscillator = audioContext.createOscillator();
+ let dst = audioContext.createMediaStreamDestination();
+ oscillator.channelCount = 4;
+ dst.channelCount = 4;
+ oscillator.connect(dst, 0, 0);
+ oscillator.start();
+ mediaRec = new MediaRecorder(dst.stream);
+
+ mediaRec.start(100);
+ setTimeout(() => {
+ dst.channelCount = 1;
+ setTimeout(() => {
+ mediaRec.stop();
+ document.documentElement.removeAttribute("class");
+ }, 100);
+ }, 100);
+}
+</script>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/1378826.html b/dom/media/test/crashtests/1378826.html new file mode 100644 index 0000000000..e1913cd0f5 --- /dev/null +++ b/dom/media/test/crashtests/1378826.html @@ -0,0 +1,46 @@ +<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<title>Bug 1378826 : Removing last video track from recorder stream crashes.</title>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<script type="text/javascript">
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function boom() {
+ let canvas = document.getElementById("canvas");
+ let ctx = canvas.getContext('2d');
+ ctx.fillRect(10, 10, 100, 100);
+ let stream = canvas.captureStream();
+ let rec = new MediaRecorder(stream);
+ // At the time of fixing this bug onstop would fire, but this may change in
+ // future. As such defensively listen for onerror too to prevent this test
+ // timing out.
+ let stoppedPromise = new Promise(y => (rec.onstop = y,
+ rec.onerror = e => y));
+ rec.onstart = () => {
+ // Remove the video track from the stream we're recording
+ stream.removeTrack(stream.getTracks()[0]);
+ // Recorder should stop or error in response to the above
+ return stoppedPromise
+ .then(() => {
+ // Little wait to help get bad writes if they're going to happen
+ wait(100)
+ .then(() => {
+ // Didn't crash, finish
+ document.documentElement.removeAttribute("class");
+ });
+ });
+ };
+ rec.start();
+}
+
+window.onload = boom;
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1384248.html b/dom/media/test/crashtests/1384248.html new file mode 100644 index 0000000000..5d9c60edda --- /dev/null +++ b/dom/media/test/crashtests/1384248.html @@ -0,0 +1,10 @@ +<html> + <head> + <script> + try { o1 = document.createElement('audio') } catch(e) { } + try { o2 = document.implementation.createDocument('', '', null).adoptNode(o1); } catch(e) { }; + try { o3 = new AudioContext('alarm') } catch(e) { } + try { o3.createMediaElementSource(o1) } catch(e) { } + </script> + </head> +</html> diff --git a/dom/media/test/crashtests/1388372.html b/dom/media/test/crashtests/1388372.html new file mode 100644 index 0000000000..977ebddf53 --- /dev/null +++ b/dom/media/test/crashtests/1388372.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +navigator.mediaDevices.getUserMedia({audio: { + echoCancellation: false, + noiseSuppression: false, + autoGainControl: false + }, fake: true +}).then((stream) => { + document.documentElement.removeAttribute("class"); +}) +</script> +</html> diff --git a/dom/media/test/crashtests/1389304.html b/dom/media/test/crashtests/1389304.html new file mode 100644 index 0000000000..df419c51d7 --- /dev/null +++ b/dom/media/test/crashtests/1389304.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: Negative duration.</title> +</head> +<body> + +<video id="v" controls src="1389304.mp4"> +</video> +<p id="msg"></p> + +<script type="text/javascript"> + +function log(x) { + msg.innerHTML = x + "<br>"; +} + +v.play(); +v.onended = function() { + log("endded!"); + let seekable = v.seekable; + for (let i = 0; i < seekable.length; ++i) { + let start = seekable.start(i); + let end = seekable.end(i); + log(`[${i}]: start=${start} end=${end}`); + } +} + +</script> + +</body> +</html> diff --git a/dom/media/test/crashtests/1389304.mp4 b/dom/media/test/crashtests/1389304.mp4 Binary files differnew file mode 100644 index 0000000000..25cd746972 --- /dev/null +++ b/dom/media/test/crashtests/1389304.mp4 diff --git a/dom/media/test/crashtests/1393272.webm b/dom/media/test/crashtests/1393272.webm Binary files differnew file mode 100644 index 0000000000..1f1cade6dc --- /dev/null +++ b/dom/media/test/crashtests/1393272.webm diff --git a/dom/media/test/crashtests/1411322.html b/dom/media/test/crashtests/1411322.html new file mode 100644 index 0000000000..772b68f0cc --- /dev/null +++ b/dom/media/test/crashtests/1411322.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1411322: Shutdown after getting memory reports from MediaRecorder</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<audio id="audio"></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +let recorder = new MediaRecorder(audio.mozCaptureStream()); +recorder.start(); +SpecialPowers.getMemoryReports(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/crashtests/1450845.html b/dom/media/test/crashtests/1450845.html new file mode 100644 index 0000000000..451d116e83 --- /dev/null +++ b/dom/media/test/crashtests/1450845.html @@ -0,0 +1,34 @@ +<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Bug 1450845: Avoid seek to next frame when already seeking</title>
+ <script>
+ async function boom() {
+ let video = document.getElementById('video');
+
+ // Internally play causes a seek, make sure we don't crash during this
+ video.play();
+ try {
+ await document.getElementById('video').seekToNextFrame();
+ } catch (e) {
+ // We don't mind if the promise was rejected so long as we don't crash
+ }
+ // Didn't crash
+
+ // Stop playback and cause a seek to 0
+ video.pause();
+ video.currentTime = 0;
+ try {
+ await document.getElementById('video').seekToNextFrame();
+ } finally {
+ // Didn't crash
+ document.documentElement.removeAttribute("class");
+ }
+ }
+ window.addEventListener('load', boom)
+ </script>
+</head>
+<body>
+ <video id='video' src='data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBQoWBAhhTgGcBAAAAAAAB6BFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsggHL7AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUqTYCNTGF2ZjU3LjI5LjEwMVdBjUxhdmY1Ny4yOS4xMDFzpJBAb17Yv2oNAF1ZEESuco33RImIQFCAAAAAAAAWVK5rAQAAAAAAADyuAQAAAAAAADPXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDmDgQEj44OEAfygVeABAAAAAAAAB7CCAUC6gfAfQ7Z1AQAAAAAAAEfngQCjqYEAAICCSYNCABPwDvYAOCQcGFQAAFBh9jAAABML7AAATEnjdRwIJ+gAo5eBACEAhgBAkpwATEAABCasAABekcXgAB'>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1489160.html b/dom/media/test/crashtests/1489160.html new file mode 100644 index 0000000000..c4f643700c --- /dev/null +++ b/dom/media/test/crashtests/1489160.html @@ -0,0 +1,10 @@ +<html> +<head> +<script> +audio = new AudioContext() +audio.close() +audio.close() +</script> +</head> +</html> + diff --git a/dom/media/test/crashtests/1494073.html b/dom/media/test/crashtests/1494073.html new file mode 100644 index 0000000000..41e7a36554 --- /dev/null +++ b/dom/media/test/crashtests/1494073.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Bug 1494073: Setting playback rate too high</title> + <script> + function finish() { + document.documentElement.removeAttribute("class"); + } + var src="data:audio/wav;base64,UklGRqsTAABXQVZFZm10IBAAAAABAAMA8FUAANABAQADAAgAZGF0YYcTAAAUCnYAPm6YALYJSgA4IO8A9UuWALiiZQAYWc4AnMNoAKcc9gBmWLcAtL0NAAGU0QDbSgMA777tAEkfrQD14E4A2nb/AERxxAD5c+0AmplbADUo0AC14DQAXWgRAGsl4wB0oGIAkVlkAOWmTwCm/fEAZqErAEHpqgAMXjkA5AlGAMsOOwAUQAsABvRfAJyVUwDTKM8AtIMYAKh6lQDPkq0A9JtqAKiudAD2I3wAHTsPAOy36ACLtz8ATQELAJCXQgDpxksA4BzzABSR7gD7vMEAgo/6AKovngCZNLoA0A5UALhiFABNVRQAjXIuABmhFACj8/sAt+bQAP71dgCbdGoAXuf5AN5zVAACgggAlHiqAGTPswC0x5oAsgxDAEimygCYlMUAS3r2AKFS0gCZzCYAnXPZAEU8cgAf2gQADqF2ACCY+ABSVIEAW17CANxZWQCk0s0AIF1kALYn7gC9U3MAQqRFAHSUegC1UR8Au99GADrbaQCQQksAOQ6kALUmMgAC0YQAS9rjAMNL9QAQKkcAayg7AOjHXgBNZxcAzIUrAAgq+ACVJPMAV5s2APTVsgAsxX4AMP9cAInlqADcltMAv10FAAzJAgDiHQ4AbsW4ACDTowC897AAx1A8AGw3JwBe6msAf/jJAL1PbwCYYRkAscdyACi8+QCXCEwABWnAAOz4QQC0W60AcToLAH4/+wBion0AzzBXAJCuHgAsv+gAvDmdAA1LaAC04xgA/KU6AAurkwCbpwkAzkTeAA9+SABQqJoAPFHNAK96FgDxMNkAaPelAOG5YgCJsS8AtUp8ALhn8gD7sIYAxoR+AMWFHgA3aRIA00/wABwXJwCmQVQAxU7xAGnkjABvBaUAMz8UAL7qjQBokUEAx8eOAAd/ogDKKqcAsx8EAEpW/AADoDAA/D1+AKVqMwDnkmYA/fOAAGpcfwAlm3UA5+t8AM5tMwCdW3cA2UzjABHxWQBKIx4A7bf3AC+RkgARtBAA27gWADcb/ADjO68AS+g0AMutgwAzRawAtbgLAGjfnwCGINIAowqrAFsDmACIyhgAHfz+AByCiABBwdYASnWAANadowA7KFYADTx1ALG1WAAm4csAti5iACQMZgAeg7MAtrnPAMRmsAAsONAAKR6XAEu5TQCrqMYAd0hcABAU8gCLBFEA2C0MAJ7nZwAETAYA6i53APxR2gDRwBsAf392AH/csgAidosA3MaAAKgCrQBwid4A4xUfAHpqnACYWcQAHfIoAO67cQBlKVUAfR6pAEvQyQDFcWoA82ivAGsVXAA5gcQA8YCIAH9igAC6EzIAvrJEACTfdgBM21wAE6W+ABkqTwAL0+wA85kAALdRfQDbetYAT9RxALUxoQDScSoACRY8AIOzSwAUQS4A6yG1ALDLIwCf5vUAUvQTAK8r+QC5zVoAckBEABQfYwBFzoQAEUyXANlojQBGhjMA0WNjAI0qOQC7TIkAMbboAAp5igBw640ArVL3ADsU7QBoR/cAemWCAChwZQDuILoApIhRAOXapADr/IAApX7IAKvnxABvSHgAV1k1ABqwegD4s6IAT4m1AH/wPgBEnc0AwuIVAO5lzgDsHIAA8O8QAPZZeAC68JcAMzVvAAVKUwBGXv0AaOoBACuF6QD9uBQAwGpOAEiF1wCq/1kAXLJ4AGP4xgC8uzMAHgMmAF3WXwCssiUAya68AP7FUwAkZqYAqnfsAJkuUgC+08cANBobALJvTgAtK7oAaIIoALfiPgAW5qAAO1YyAM9+owCFIMMAbL82ABfR7wBJrDgASc3DAB8q7AAi/80AxleNALQ+3wARtIIA3D9uAFWiEwBtshQAoPHFAITU8gA1S4EAB8w6ANZxHgBhLBEAx70EADf8KgADzHIAQ4ONAEyVRgAXxdIAoS0hAHkIhwBUeFgAOP1YAM6KtgAJaJ0AuaUnADdSkQDNaVIAJkoIAFVlkAARLYwAc2jBAEHoeQBMnFIAQS8hAKUu2ABZafMAQRGbAElVMgAdHRgA9ImMAF4/NADLcZMAiaXbAB0rpQB1RTEAGqsMAGYPWgAB4nkAEk/2AOg7lQC5bI8A7x5cALOosgDQV7YA8mwTACI31wCjuIAAegycABfkWwBSqBQApdm/AIqmCgBEp3QANDopAHSFjgDOIKgABWL8AOppagBvyq0AXyT+AGvkZwDaq1MA8J0CAO0qCADs3gcAj/T/AFHxnwBnObIAAHr0AGNgYQCyAeoAerH5ANuJsQAZnSwAqPWRABmF5gBKI5IAkQpVAOQq0gCdZxQArSpTAFC4aACZFzgA2vozAKKrjwADOGYAxejeANjGkgAogccAnJtJAHa0iQDNsMQAvkg0AAxqhQABL88A0+rRAGam/AAZaiwAakBeAIkbewAlIpIAMQjsAHKsxAAVPRIAH5HeABuShQBm4ocAtOdpAIvy0QDAdbAAnRZ2AFNL6QCFcQYACIehACvVNgChty8Ah5A8AFc4iwBQwRwAv3PeAO+FjABRt0kAMfNNAMz2/QDaFEEAzLRaAJ5hlQBihAoAP/CjAPH4bAC/fxwAPapOAO3BCQDgg2gA7IyRABgp/AAO+QgAXrMFAHPiZwDc83IAmMzHALPPaAAlyrkAAk6uAD33pwBo0iAAC56eAOolMACgdmQAKdJeAA0UPgBtua8AIH66AJMoxwCYv7UA4JcKAN5c/wDgWDwAcWbUANvmLwD73kIAu/vCAFUiCQB5CJIAxOlxADKN8AB4H8EAh8gnAC8h8QCdBUgAX7cyADXejABFCWYAm7v7AOENigCsVMYAY8UeABEpaADYLmMAOWcMANk9hAANhM8AAQHaAMfmVwB2p2AA5MbYAOw44gCKrqQADZOHAM1RlwBhnvAAGkToADueDQDmbJ0AFYYTAGE6UgDjbEgAeJhCAOOGgAC0s6QANh6iAMu3kQAMMBQAl7YOANsIXwCkd9oA8mqMANf38wCxC7EAv7gjAKa0HwCgqV4ApgI/AIe1oQBaGE8AcxcBAObgYQBMMWwA1G0gAF+J9ABkAl8A2buoAIlKWAA/ZykAl6S0ABtBMQAkyOEAzFtyAHIMdgCCfMgAG9m4ABOX7wBfi/wAeg74AELcSAC7RgEAP1V0AD/FBwABALUAdGX+APlznwBqD1MAfTHIALA+HgDRP/QAaraOANCTrgBWaVUANUQjAFVulwBFlpkAsU3sAN86OACsleQAcFHoANanugC10xgAjD4TAA0g3gDcmx4A/eNDAIsxFAC6N5UAJ7fDAFRwZQD/+pAALV5yACfwHwBzltQA9CAuAD0nQQDbjogAKLenAHydkQDwD+IABvePAF9zWAAvQBwA4Ae3AJk+hQCB0ugAHXjgAOBbKQC4/ksAP4CtAAELZgDZ6kEAO2TIAPH6ZAC80CwAd9CIANrJ3QBbk7QAcT0AAE58HwDa9QMAOYjoABoHGQArjcwADSF4AFNcRgCnkjIAsG/cAC7iCwCh3qEAramDAAYS3gBO0jcA+2p2ADpCZwATvIsAa0FuABWrbwD+0jUA+PYEAPXTnQCnvXQAYtYoAKfT2wCPsNUA0bqlAG1+sQBnnqEA7hvxABQYewBmeO4Ackj6AJhuRADlYpAA9O9TAPSSeACW+ZAAa0nLAErmuAABaxcA+WJsAAJrYgDjOEkAwldNAC3lLwDy81wAoYZzADYpmwC509wAtFJdAIFkwgDiIEsAkyBvAMmSIgDb/sgAjTw2AJzz6QA+0Z8AwXaTAD/3lwB0Kg8A63iFAFe95wARFK8AFOY5AMAmfwB+Rj4AVleaAJNp0wBDKCUAh08qAEy8BwAX0CUAs+xgABUglAD3+TAA8jt9AM83uwBUVaEAvErrACRYKgB8iGkAb5fUAP71dwDEJIoAx7wfAAIduQALb3kAjm+cABeJCADKk10AdfclABorYAC0WOgA167pAGsx/QC8htoA+7auAATccACYfLsAszNLADwo8ACOsr0AdJmfABl+xgD7MQ4Ax2x6AMpK4gBV7FkA0g/QAFmDRQA01bkAFKhtAH9PEwCEoukAV2UiAC2B+wCuwRsA560TAK92ZAABWWcAovTVAPS/6AC+1C8AXCkSAM2NegBYrWQAmozZAE2CIgBAgY4A9YmBAKY0DgD3ZLAAOgXaAOOGSwDofs8AHlvXAN204wCjH2YAlRVHAA2cxwBDjN8AwumLAC5MEwAKK9YAyELrANJYkACbtlYAKfF2AGpW8ACRmC0AWHqZAB1CzwAGY9sA0N9+AOS4igAHJNkACm+yAA+sXwBKUUIAaipZAJuECwDrgV0AFMJpAAKMjgAtdggAJYIXAHDtegAgFIoASxGoALACPgA2KPEASXlaAGIX7QAjLsYA62u0ABm8HQB5dUsATxadAK6qQwD8VEkAd1ARAFOFzADC26QAfe2GANRbdwC7nK8AEwy4AB2XrgDAHIsAZSA6AEo5AgAM8GUAth6kAINTsACbAcEAxoCEAIZGPAD4GlwA3aV9AK7CDgCjga4ArWf0AHD1VQBd2TgA+eGcABMlUACIulIAOzg4AHP9zACB2VIAkyv0ALSeowCib4MA3fyrAMP7+gBVtSMAzpdQAMFI1AATJq8ALcZ1AGvp0gC/4oMAH3/0AFPf7wB5xTwAHeDZAALL8gDOEuQA+iN+AMs3AgCP3lIAs+6FABBzswBwlFkAxPuuAEg6pgCvKoIAU9EoAGAPigBqilYACnYGADJYkQCkihkA7FTKAISADQCODaoAYE04ALH1iwAUi7MAzURTAORgwQCts3wAc78IAFkTXwB+A5UAJiRXAJFADwAjr7MAdjo6ANNvLACcXagA09XGAOzjAwC0GJ0AV3hVAI1RkwAuDVgAtcfaABkNCQAljBgABQNSABEgDQB5kmwAIre6AFs6+wAnad4AT4iAAMSvawA7Fn8AlLC9AOscDABRf54ATp8wAMzgnwD29YQASW0XAEj2NwDw2U4A9NokAMTdYQBw+5UAgxCpAEwmpwCyDPwANRUYALHKyQB5mGsA1yJ8AJqjDQCaHDEAMtJrAEvLIQCDVUoAwGn3AETZ3wCLIwEAeJNzAMkj8AAUk2wAe+YVAOVR8wDCzpcAu4jkACv7dAA3vOgApBWtAO+tkAD8tu8Ac7noACsS7QAXfvMA7mjTACJc9QCea+gAx2jFALywtACyxdUA7r2wANGY2wD4JvYAg5VOANb0ggDtytkA8WGyAHMOtwA0vqAAwO5NAJSJDAAUaRUAoDvEAIfetwA9PqoA1jC3AA61XADx70MA7fFUAPQtRgBpiV0AP2XDAKNQvgCyW6UAJ9esADvTdQBm6f4AF1lDADTKvgCXNQQA6SFtAEuSVQBsXjUAmGbSANERuABhSpUAD2OJAGLtFABRMFUA0IDAAPc3TwCtToMASSGaAMDpVwCjm40At6keAG6chAAgkRAAz7GGAG8lHQA7qgwA8BucAMnEEQDEC+4A9g4MALMwnwAEFpMAqzf0AK8BoAA6L6sA0l0OACWT/gCVqp8AfumWABzjDQCtsygAHG0JAHebagCmi2EAztDRALUodwCq1+sAEcGyAGmDQQAVnXMAxMu8APC5wACRds0AzdJ4AEUdzwD/LLAAUdAQADW6IQBIUcUAXGxOAHdGZAARzd8AA7Y5ADE7cABegAcA0eDuAMBHYwA6skcARPfCAEYMxwB2/g8APBr5AFNusABVe1kAw4+OAOxwgwB7LiwAOltxAI0eAwALkk4AG9EZAESUzgD9fdQA4YOFAJVkHACkj4MAsWJxABeFJABHocwATeLwAG+OuQDdhzIAabHNAPogKwCTMKQAkanLAAsZLQDFv6YAXjQFAK1LdwDK4XsAcM7eAGCurgA7dq8AgD8dAHK87QDpwi8AwH3WAM/BcgBW7sAAyUeVAGw91wCHzM8Aa5fYABuR9QBhOk8Ay+OSAGYDiwAHCJAAYjifABxdQABItm0Ame2tAE/4XABIC6sA0aPxAAwi2ACitbQAtsWpAA+jPwB2lCgAqaATAMx3JADF2o4A6fhSABNVmwC6giQAYfj6AD0dkwBkmecAU5sTANWMgwD7D+cAp9QWAI6+QQAkZvcAkvGvAMaNOgAtsuEAF+LgADEdxgDAc9MA7+36APqe/wD41wQAIIDKAHsx1gATEKgAnWdaAFgB0wAyMAEAxTsPADTrDACBAuMALov9APMo1wB1wawAlCj7AP3EbAD1lDQA3xUvAAPdwABvPXQAwPeDAGxwkQAaAUEAbJQ2ACGgEQBn8XAAuu81AKaQhgAzfIQAkp8jADQ4TQBSSaIALjCgAJuAjQBP8AsAHL5SAKmH7ACyx5cAP/VjAG71zgCuL+IAV2JLAN+yFgA7QvgAG0HdAFO6AwCx+AwArlawAB83cABUNtcAe9LbACB2gAAZVTUA4DRYAEFV4wByz2QAWUgkALXS9ACCaEYAxjjiAI0qiAArChIAr7twAPvaOwDhyfkAEoYVAIM5KABarEsAol47AIBNdQDfMHQAN8GNADM6XwBbJFMAMAEbALILpgCxRIsA3mxlAGqpZACd9SkA9uwTABtT4ABUZDIA3cxyAGnKygA3zW4Age3TANrX0QBc2KEAAab2AEQksQDabWAAdmSYAOBdUwDsLkgAOa4fAPi45QAjr5gAZvEs" + var audio=new Audio(src) + audio.onended = finish; + audio.onerror = finish; + try{audio.play()}catch(e){} + try{audio.preload=1}catch(e){} + try{audio.muted=1}catch(e){} + try{audio.playbackRate=567312.2079031984}catch(e){} + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/1526044.html b/dom/media/test/crashtests/1526044.html new file mode 100644 index 0000000000..1914dfc2f5 --- /dev/null +++ b/dom/media/test/crashtests/1526044.html @@ -0,0 +1,19 @@ +<script> + function start () { + try { o1 = new AudioContext({}) } catch (e) { } + try { o2 = new DynamicsCompressorNode(o1, {}) } catch (e) { } + try { o3 = o2.attack } catch (e) { } + try { o4 = new XMLHttpRequest({mozAnon: true, mozSystem: true}) } catch (e) { } + try { o4.open('GET', '', false) } catch (e) { } + try { o4.send() } catch (e) { } + for (let i = 0; i < 20; i++) { + try { o5 = o1.createGain() } catch (e) { } + try { o1.suspend().then(function () { }) } catch (e) { } + try { o5.connect(o3) } catch (e) { } + } + setTimeout('location.reload()', 200) + } + + window.addEventListener('load', start) +</script> + diff --git a/dom/media/test/crashtests/1538727.html b/dom/media/test/crashtests/1538727.html new file mode 100644 index 0000000000..6371d54383 --- /dev/null +++ b/dom/media/test/crashtests/1538727.html @@ -0,0 +1,14 @@ +<script> +const canvas = document.createElement('canvas') +const context = canvas.getContext('2d', {}) +const xhr = new XMLHttpRequest({}) +const stream = canvas.captureStream(new Float64Array([1593177632.1689904])[0]) +recorder = new MediaRecorder(stream) +recorder.start(100) +xhr.open('G', '', false) +xhr.send() +recorder.stop() +tracks = stream.getVideoTracks() +track = tracks[(1051736525 % tracks.length)] +stream.removeTrack(track) +</script> diff --git a/dom/media/test/crashtests/1545133.html b/dom/media/test/crashtests/1545133.html new file mode 100644 index 0000000000..fb7039aae3 --- /dev/null +++ b/dom/media/test/crashtests/1545133.html @@ -0,0 +1,34 @@ +<html class="reftest-wait"> +<head> +<script> +const xhr = new XMLHttpRequest() + +async function boom () { + await new Promise(r => setTimeout(r, 100)) + + SpecialPowers.forceCC() + SpecialPowers.forceCC() + SpecialPowers.forceCC() + + document.documentElement.removeAttribute("class") +} + +function start () { + const context = new AudioContext({}) + const filter = new BiquadFilterNode(context, {}) + const destination = context.createMediaStreamDestination() + const processor = context.createScriptProcessor(8192, 8, 8) + processor.connect(filter.Q) + processor.disconnect() + xhr.open('G', '', false) + xhr.send() + context.createMediaStreamSource(destination.stream) + processor.connect(filter.Q) + context.close() + context.addEventListener('statechange', boom, true) +} + +document.addEventListener('DOMContentLoaded', start) +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/1547784.html b/dom/media/test/crashtests/1547784.html new file mode 100644 index 0000000000..ee270491f1 --- /dev/null +++ b/dom/media/test/crashtests/1547784.html @@ -0,0 +1,33 @@ +<html class="reftest-wait"> +<head> + <script> + const doc = new Document(); + const video = document.createElementNS('http://www.w3.org/1999/xhtml', 'video'); + const source = new MediaSource(); + + navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ '': [{ '': '' }] }]) + .then(keySystemAccess => { + return keySystemAccess.createMediaKeys(); + }).then(_ => { + video.src = URL.createObjectURL(source); + source.addEventListener('sourceopen', () => { + doc.adoptNode(video); + }); + }); + + navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ '': [{ '': '' }] }]) + .then(keySystemAccess => { + return keySystemAccess.createMediaKeys(); + }).then(mediaKeys => { + return video.setMediaKeys(mediaKeys); + }).then(() => { + video.src = URL.createObjectURL(source); + document.documentElement.removeAttribute("class"); + }).catch(e => { + // Catch JS errors caused by raciness in the test. So long as we're + // not crashing we're good. + document.documentElement.removeAttribute("class"); + }); + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/1547899.html b/dom/media/test/crashtests/1547899.html new file mode 100644 index 0000000000..4ffd90c565 --- /dev/null +++ b/dom/media/test/crashtests/1547899.html @@ -0,0 +1,20 @@ +<html> +<head> + <script> + function start () { + const video = document.getElementById('id_0') + const stream_1 = new MediaStream() + const stream_2 = video.mozCaptureStreamUntilEnded() + const track = stream_2.getTracks()[0] + + video.srcObject = stream_1 + stream_1.addTrack(track) + } + + window.addEventListener('load', start) + </script> +</head> +<body> +<video id="id_0" src="data:audio/mpeg;base64,ZQr/+1DEAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAsAAAnKABcXFxcXFxcXFy4uLi4uLi4uLkVFRUVFRUVFRV1dXV1dXV1dXXR0dHR0dHR0dIuLi4uLi4uLi6KioqKioqKiorq6urq6urq6utHR0dHR0dHR0ejo6Ojo6Ojo6P///////////wAAADlMQU1FMy45OHIBpQAAAAAuHQAAFEAkBElCAABAAAAJyuGI2MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//tQxAAABmQjXHSRAAH6IfB3OVIDAADLlmjIxWKxWTyIABgmGydGjRox4f8Tg+D4OAgc/BwEDn+UBD+qwH/+8Tny7///KO4IYDgcDgcDgcDgcCgQAAAKKBUFc/nnP30a5O0zyYzMNMkzKlPva2GXgMIBbwDQEAYDQNXGLT2EIgEhoFnQNDb4BAPD9wTFYGJBWJw/8AUWgMBQgIEg4KmAKGP/wRG0csCIWDaQxcFhYvv/8WYMUpEIkXhvnRef//kmsuLPH5OvSoAFKxrJIm26DP/7UsQ"> +</body> +</html> diff --git a/dom/media/test/crashtests/1560215.html b/dom/media/test/crashtests/1560215.html new file mode 100644 index 0000000000..1b76e218d7 --- /dev/null +++ b/dom/media/test/crashtests/1560215.html @@ -0,0 +1,20 @@ +<html class="reftest-wait"> +<head> + <script> + async function start () { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + context.fillStyle = "red" + context.fillRect(0, 0, 1, 1) + const recorder = new MediaRecorder( + canvas.captureStream(), { videoBitsPerSecond: 16 }) + recorder.start(100) + await new Promise(r => recorder.onstart = r) + recorder.pause() + document.documentElement.removeAttribute("class") + } + + window.addEventListener('load', start) + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/1569645.html b/dom/media/test/crashtests/1569645.html new file mode 100644 index 0000000000..b1f1247f26 --- /dev/null +++ b/dom/media/test/crashtests/1569645.html @@ -0,0 +1,23 @@ +<html> +<head> + <script> + function start () { + const canvas = document.getElementById("c") + canvas.getContext("2d") + const video = canvas.captureStream() + const ac = new AudioContext() + const dest = ac.createMediaStreamDestination() + const recorder = new MediaRecorder( + new MediaStream([...video.getTracks(), ...dest.stream.getTracks()]), { + 'mimeType': 'audio/ogg' + }) + recorder.start() + } + + window.addEventListener('load', start) + </script> +</head> +<body> +<canvas id="c"></canvas> +</body> +</html> diff --git a/dom/media/test/crashtests/1575271.html b/dom/media/test/crashtests/1575271.html new file mode 100644 index 0000000000..ceda986553 --- /dev/null +++ b/dom/media/test/crashtests/1575271.html @@ -0,0 +1,25 @@ +<html class="reftest-wait"> +<head> +<script> + async function start () { + const canvas = document.createElement("canvas") + const context = canvas.getContext("2d") + context.fillStyle = "blue" + context.fillRect(0, 0, canvas.width, canvas.height) + const stream = canvas.captureStream() + const track = stream.getTracks()[0] + const recorder = new MediaRecorder(stream) + recorder.start() + await new Promise(r => recorder.onstart = r) + recorder.pause() + stream.removeTrack(track) + recorder.resume() + await new Promise(r => recorder.onstop = r) + document.documentElement.removeAttribute("class") + } + + window.addEventListener('load', start) +</script> +</head> +</html> + diff --git a/dom/media/test/crashtests/1577184.html b/dom/media/test/crashtests/1577184.html new file mode 100644 index 0000000000..a38c4a1265 --- /dev/null +++ b/dom/media/test/crashtests/1577184.html @@ -0,0 +1,15 @@ +<html> +<head> +<script> +function start () { + const frame = document.createElementNS('http://www.w3.org/1999/xhtml', 'frame') + document.documentElement.appendChild(frame) + frame.contentWindow.eval('window.top.context=new AudioContext()') + document.documentElement.innerHTML = '' + context.createMediaElementSource(new Audio('')) +} + +document.addEventListener('DOMContentLoaded', start) +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/1587248.html b/dom/media/test/crashtests/1587248.html new file mode 100644 index 0000000000..10c555e2b1 --- /dev/null +++ b/dom/media/test/crashtests/1587248.html @@ -0,0 +1,23 @@ +<html class="reftest-wait"> +<head> +<script> +function start () { + const audio = document.getElementById('id_4') + const doc = new Document() + const stream = new MediaStream() + const track = document.createElementNS('http://www.w3.org/1999/xhtml', 'track') + audio.srcObject = stream + track.textContent = '�' + setTimeout(() => { + track.replaceChild(audio, track.childNodes[0]) + audio.play().then(function (arg4) { }) + document.documentElement.removeAttribute("class") + }, 157) + doc.adoptNode(audio) +} + +document.addEventListener('DOMContentLoaded', start) +</script> +</head> +<audio class="" id="id_4" itemscope></audio> +</html> diff --git a/dom/media/test/crashtests/1594466.html b/dom/media/test/crashtests/1594466.html new file mode 100644 index 0000000000..276c5fe3d1 --- /dev/null +++ b/dom/media/test/crashtests/1594466.html @@ -0,0 +1,22 @@ +<html> +<head> +<script> +function start() { + const ac = new AudioContext(); + const {stream: audioStream} = ac.createMediaStreamDestination(); + const [audioTrack] = audioStream.getTracks(); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const [videoTrack] = canvas.captureStream().getTracks(); + + const rec = new MediaRecorder(new MediaStream([audioTrack, videoTrack]), { + mimeType: 'video/webm; codecs="vp8, opus"' + }); + rec.start(); +} + +document.addEventListener('DOMContentLoaded', start) +</script> +</head> +</html> + diff --git a/dom/media/test/crashtests/1601385.html b/dom/media/test/crashtests/1601385.html new file mode 100644 index 0000000000..7192809076 --- /dev/null +++ b/dom/media/test/crashtests/1601385.html @@ -0,0 +1,12 @@ +<html> +<head> +<script> +const video = document.createElement("video"); +video.srcObject = new MediaStream(); +video.mozCaptureStreamUntilEnded(); +video.src = "sound.ogg"; +video.srcObject = undefined; +</script> +</head> +</html> + diff --git a/dom/media/test/crashtests/1601422.html b/dom/media/test/crashtests/1601422.html new file mode 100644 index 0000000000..9ff3c1a07a --- /dev/null +++ b/dom/media/test/crashtests/1601422.html @@ -0,0 +1,20 @@ +<html class="reftest-wait"> +<head> +<script> +(async _ => { + try { + const video = document.createElement('video') + video.preload = 'metadata' + video.src = 'sound.ogg' + await new Promise(r => video.onloadedmetadata = r) + const stream_1 = video.mozCaptureStreamUntilEnded() + video.src = '' + const stream_2 = video.mozCaptureStreamUntilEnded() + } finally { + document.documentElement.removeAttribute("class") + } +})() +</script> +</head> +</html> + diff --git a/dom/media/test/crashtests/1604941.html b/dom/media/test/crashtests/1604941.html new file mode 100644 index 0000000000..5a9265ea1b --- /dev/null +++ b/dom/media/test/crashtests/1604941.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> + +async function boom() +{ + await SpecialPowers.pushPrefEnv({"set": [ + ["media.cubeb.force_null_context", true], + ]}); + new Audio().mozCaptureStreamUntilEnded(); + var ac = new window.AudioContext(); + ac.resume(); + ac.close(); + document.documentElement.removeAttribute("class"); +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/media/test/crashtests/1608286.html b/dom/media/test/crashtests/1608286.html new file mode 100644 index 0000000000..9f52605be6 --- /dev/null +++ b/dom/media/test/crashtests/1608286.html @@ -0,0 +1,50 @@ +<html class="reftest-wait">
+<head>
+ <script>
+ function test() {
+ function checkResolve(value) {
+ // Let the test timeout and fail
+ throw new Error("This promise should not resolve");
+ }
+
+ function checkReject(reason) {
+ if (reason.message !== "Browsing context is no longer available") {
+ // Let the test timeout and fail
+ throw new Error("Unexpected rejected promise reason");
+ }
+ // Otherwise, successfully rejected a request not attached to a
+ // window without crashing
+ }
+
+ var i = document.querySelector("iframe");
+ var nav = i.contentWindow.navigator;
+ i.remove();
+
+ // First, check with valid args
+ nav.requestMediaKeySystemAccess(
+ "com.widevine.alpha",
+ [{
+ initDataTypes: ["webm"],
+ videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }]
+ }]
+ ).then(
+ checkResolve,
+ (reason) => {
+ checkReject(reason);
+
+ // Then, check with invalid args
+ nav.requestMediaKeySystemAccess("", []).then(
+ checkResolve,
+ (reason) => {
+ checkReject(reason);
+ document.documentElement.removeAttribute("class");
+ }
+ );
+ });
+ }
+ </script>
+</head>
+<body onload="test()">
+ <iframe></iframe>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1673525.html b/dom/media/test/crashtests/1673525.html new file mode 100644 index 0000000000..51de202999 --- /dev/null +++ b/dom/media/test/crashtests/1673525.html @@ -0,0 +1,15 @@ +<html> +<head> + <script> + document.addEventListener("DOMContentLoaded", () => { + const audio = document.getElementById("audio"); + audio.autoplay = true; + audio.mozCaptureStream(); + }) + </script> +</head> +<body> + <!-- The data URL is crafted from a fuzzed mp3 so the base64 can be decoded back to an mp3 as needed --> + <audio id="audio" src="data:audio/mpeg;base64,//tQxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAALAAAJygAXFxcXFxcXFxcuLi4uLi4uLi5FRUVFRUVFRUVdXV1dXV1dXV10dHR0dHR0dHSLi4uLi4uLi4uioqKioqKioqK6urq6urq6urrR0dHR0dHR0dHo6Ojo6Ojo6Oj///////////8AAAA5TEFNRTMuOThyAaUAAAAALh0AABRAJARJQgAAQAAACcrhiNjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/7UMQAAAZkI1x0kQAB+iHwdzlSAwAAy5ZoyMVisVk8iAAYJhsnRo0aMeH/E4Pg+DgIHPwcBA5/lAQ/qsB//vE58u///yjuCGA4HA4HA4HA4HAoEAAACigVBXP55z99GuTtM8mMzDTJMypT72thl4DCAW8A0BAGA0DVxi09hCIBIaBZ0DQ2+AQDw/cExWBiQVicP/AFFoDAUICBIOCpgChj/8ERtHLAiFg2kMXBYWL7//FmDFKRCJF4b50Xn//5JrLizx+Tr0qABSsaySJtugz/+1LEBIALgQmLvLUAMXkorjzSjuWd/+FbPOduga79Dhef0OFYNhp+FEPDPRyRvOEERl/Uwbv9F/UuQN5yt9WPM5xwqlB4+hzmfmoYmcceWPD8s3WA8FhYEPkhR0SgMHw="></audio> +</body> +</html> diff --git a/dom/media/test/crashtests/459439-1.html b/dom/media/test/crashtests/459439-1.html new file mode 100644 index 0000000000..7bb0131d51 --- /dev/null +++ b/dom/media/test/crashtests/459439-1.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +var i = 0; +function boom() +{ + var div = document.getElementById("div"); + var audio = document.getElementById("audio"); + + audio.onload = null; + + div.textContent = "FAIL"; + audio.src += ""; + div.textContent = "PASS?"; + + ++i; + + setTimeout(done, 1); +} + +function done() +{ + // Note we reset 'src' to release decoder resources and cubeb streams to + // prevent OOM or OpenCubeb() failures. + var audio = document.getElementById("audio"); + audio.src = ""; + document.documentElement.removeAttribute("class"); +} +</script> +</head> +<body> +<audio id="audio" autoplay src="sound.ogg" oncanplaythrough="setTimeout(boom, 1);"></audio> +<div id="div"></div> +</body> +</html> diff --git a/dom/media/test/crashtests/466607-1.html b/dom/media/test/crashtests/466607-1.html new file mode 100644 index 0000000000..e154223efe --- /dev/null +++ b/dom/media/test/crashtests/466607-1.html @@ -0,0 +1,14 @@ +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + document.body.appendChild(document.createElementNS("bar", "audio")); + document.body.appendChild(document.createElementNS("bar", "video")); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/466945-1.html b/dom/media/test/crashtests/466945-1.html new file mode 100644 index 0000000000..ac1ba29e36 --- /dev/null +++ b/dom/media/test/crashtests/466945-1.html @@ -0,0 +1,25 @@ +<html class="reftest-wait"> +<head> +<script type="text/javascript"> + +var s; + +function boom() +{ + s = document.createElement("span"); + s.innerHTML = "<video src='data:text/html,' autoplay='autoplay'><\/video>"; + document.body.appendChild(document.createElement("iframe")); + setTimeout(boom2, 0); +} + +function boom2() +{ + s.innerHTML = ""; + document.documentElement.removeAttribute("class"); +} + +</script> +</head> + +<body onload="setTimeout(boom, 0);"></body> +</html> diff --git a/dom/media/test/crashtests/468763-1.html b/dom/media/test/crashtests/468763-1.html new file mode 100644 index 0000000000..d21e5bc388 --- /dev/null +++ b/dom/media/test/crashtests/468763-1.html @@ -0,0 +1 @@ +<html><head></head><body><video src="nosuchprotocol:"></video></body></html>
\ No newline at end of file diff --git a/dom/media/test/crashtests/474744-1.html b/dom/media/test/crashtests/474744-1.html new file mode 100644 index 0000000000..8a7c70cf05 --- /dev/null +++ b/dom/media/test/crashtests/474744-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + document.body.innerHTML += "<video src='aim:yaz'><\/video>"; +} + +</script> +</head> +<body onload="boom();"> +</body> +</html> diff --git a/dom/media/test/crashtests/481136-1.html b/dom/media/test/crashtests/481136-1.html new file mode 100644 index 0000000000..bd264058d9 --- /dev/null +++ b/dom/media/test/crashtests/481136-1.html @@ -0,0 +1,3 @@ +<html> +<div><object data='sound.ogg'></div> +</html> diff --git a/dom/media/test/crashtests/492286-1.xhtml b/dom/media/test/crashtests/492286-1.xhtml new file mode 100644 index 0000000000..627ac38723 --- /dev/null +++ b/dom/media/test/crashtests/492286-1.xhtml @@ -0,0 +1 @@ +<source xmlns="http://www.w3.org/1999/xhtml"/>
\ No newline at end of file diff --git a/dom/media/test/crashtests/493915-1.html b/dom/media/test/crashtests/493915-1.html new file mode 100644 index 0000000000..2a6ae9bd6c --- /dev/null +++ b/dom/media/test/crashtests/493915-1.html @@ -0,0 +1,18 @@ +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + s = document.createElement("span"); + a = document.createElement("audio"); + a['src'] = "javascript:4"; + a['loopend'] = 3; + s.appendChild(a); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/495794-1.html b/dom/media/test/crashtests/495794-1.html new file mode 100644 index 0000000000..2db69206a1 --- /dev/null +++ b/dom/media/test/crashtests/495794-1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html class="reftest-wait"> + <body> + <audio src="495794-1.ogg" autoplay onended="this.src=''; document.documentElement.className=undefined"></audio> + <!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. --> + </body> +</html> + diff --git a/dom/media/test/crashtests/495794-1.ogg b/dom/media/test/crashtests/495794-1.ogg Binary files differnew file mode 100644 index 0000000000..1c19a64061 --- /dev/null +++ b/dom/media/test/crashtests/495794-1.ogg diff --git a/dom/media/test/crashtests/497734-1.xhtml b/dom/media/test/crashtests/497734-1.xhtml new file mode 100644 index 0000000000..6df055da39 --- /dev/null +++ b/dom/media/test/crashtests/497734-1.xhtml @@ -0,0 +1,21 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script type="text/javascript"> + +function boom() +{ + + div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + div.appendChild(document.getElementById("v")); + document.body.appendChild(div); +} + +</script> +</head> + +<body onload="boom();"> + +<video id="v"><source></source></video> + +</body> +</html> diff --git a/dom/media/test/crashtests/497734-2.html b/dom/media/test/crashtests/497734-2.html new file mode 100644 index 0000000000..990ac4af46 --- /dev/null +++ b/dom/media/test/crashtests/497734-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<script> + +function boom() +{ + audio1 = document.createElement("audio"); + (audio1).appendChild(document.createElement("source")); + (audio1).appendChild(document.createElement("source")); + setTimeout(function() { + audio2 = document.createElement("audio"); + audio2.appendChild(audio1); + }, 100); +} + +</script> +<body onload="boom();"></body> diff --git a/dom/media/test/crashtests/576612-1.html b/dom/media/test/crashtests/576612-1.html new file mode 100644 index 0000000000..04f993e780 --- /dev/null +++ b/dom/media/test/crashtests/576612-1.html @@ -0,0 +1,15 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script> +function boom() +{ + + var v = document.getElementById("v"); + v.src = "data:text/plain,_"; + document.documentElement.appendChild(v); + +} +</script> +</head> +<body onload="boom();"><video id="v" src="data:video/ogg;codecs="theora,vorbis",1"></video></body> +</html> diff --git a/dom/media/test/crashtests/691096-1.html b/dom/media/test/crashtests/691096-1.html new file mode 100644 index 0000000000..3c3ebfc12a --- /dev/null +++ b/dom/media/test/crashtests/691096-1.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> +var ITERATIONS = 200; + +function stoptest (evt) +{ + if (evt) { + // Note we reset 'src' to release decoder resources and cubeb streams to + // prevent OOM or OpenCubeb() failures. + evt.target.src = ""; + } + document.documentElement.removeAttribute("class"); +} + +function boom() +{ + for (var i = 0; i < ITERATIONS; ++i) { + a = document.createElementNS("http://www.w3.org/1999/xhtml", "audio"); + a.addEventListener("loadedmetadata", stoptest); + a.setAttributeNS(null, "autoplay", ""); + a.setAttributeNS(null, "src", "sound.ogg"); + } + setTimeout(stoptest, 250); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/752784-1.html b/dom/media/test/crashtests/752784-1.html new file mode 100644 index 0000000000..4644eeb89a --- /dev/null +++ b/dom/media/test/crashtests/752784-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<script> +function boom() +{ + document.getElementById("a").mozCaptureStream(); +} +</script> +</head> + +<body onload="boom();"> +<audio id="a" src="sound.ogg"> +</body> +</html> diff --git a/dom/media/test/crashtests/789075-1.html b/dom/media/test/crashtests/789075-1.html new file mode 100644 index 0000000000..6cd673fb75 --- /dev/null +++ b/dom/media/test/crashtests/789075-1.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +</head> +<body> +<video src="789075.webm" preload="metadata" id="v"> +</video> +<!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. --> +<script type="application/javascript"> + var video = document.getElementById("v"); + video.onloadeddata = function () { + video.play(); + }; + video.onended = function () { + video.src=""; + document.documentElement.className = undefined; + }; +</script> +</body> +</html> diff --git a/dom/media/test/crashtests/789075.webm b/dom/media/test/crashtests/789075.webm Binary files differnew file mode 100644 index 0000000000..602e53fca2 --- /dev/null +++ b/dom/media/test/crashtests/789075.webm diff --git a/dom/media/test/crashtests/795892-1.html b/dom/media/test/crashtests/795892-1.html new file mode 100644 index 0000000000..d73cea7f2e --- /dev/null +++ b/dom/media/test/crashtests/795892-1.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> +function boom() +{ + var a = document.getElementById("a"); + a.play(); + a.onplaying = function () { + a.onplaying = null; + // Note we reset 'src' to release decoder resources and cubeb streams to + // prevent OOM or OpenCubeb() failures. + a.src = ""; + document.documentElement.className = ""; + } +} +</script> +</head> + +<body> +<video id="a" src="cors.webm" crossorigin preload="metadata" onloadedmetadata="boom();"> +</body> +</html> diff --git a/dom/media/test/crashtests/844563.html b/dom/media/test/crashtests/844563.html new file mode 100644 index 0000000000..7f9365511f --- /dev/null +++ b/dom/media/test/crashtests/844563.html @@ -0,0 +1,5 @@ +<script> +var a = document.createElementNS("http://www.w3.org/1999/xhtml", "audio"); +a.mozPreservesPitch = a; +</script> + diff --git a/dom/media/test/crashtests/846612.html b/dom/media/test/crashtests/846612.html new file mode 100644 index 0000000000..070c375381 --- /dev/null +++ b/dom/media/test/crashtests/846612.html @@ -0,0 +1,8 @@ +<script> +document.addEventListener("DOMContentLoaded", function() { + var a = document.querySelector("audio"); + a.playbackRate = 2; + a.play(); +}); +</script> +<audio src="495794-1.ogg"></audio> diff --git a/dom/media/test/crashtests/852838.html b/dom/media/test/crashtests/852838.html new file mode 100644 index 0000000000..0bea29351b --- /dev/null +++ b/dom/media/test/crashtests/852838.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <script> + var o0 = new window.AudioContext(); + var o1 = o0.createBuffer(536870912, 1, 8192); + </script> + </head> + <body> + </body> +</html> diff --git a/dom/media/test/crashtests/865004.html b/dom/media/test/crashtests/865004.html new file mode 100644 index 0000000000..4da39071c4 --- /dev/null +++ b/dom/media/test/crashtests/865004.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<meta charset="UTF-8"> +<script> + +function boom() +{ + var ac = new AudioContext(); + for (var j = 0; j < 200; ++j) { + ac.createScriptProcessor(undefined); + } + document.documentElement.removeAttribute("class"); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/865537-1.html b/dom/media/test/crashtests/865537-1.html new file mode 100644 index 0000000000..4065eb3608 --- /dev/null +++ b/dom/media/test/crashtests/865537-1.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<body onload="doTest()"> +<base href="../unknown"> +<div id="test3"></div> +<video id="test4"><source src="white.webm"></video> +<script> +function doTest() { + test3.appendChild(test4); +} +</script> +</body> +</html> diff --git a/dom/media/test/crashtests/865550.html b/dom/media/test/crashtests/865550.html new file mode 100644 index 0000000000..b8626e8d67 --- /dev/null +++ b/dom/media/test/crashtests/865550.html @@ -0,0 +1,22 @@ +<html class="reftest-wait"> + <head> + <script> + var i = 0; + var interval; + function crash() { + var o0 = new AudioContext(); + o1 = o0.createBufferSource(); + ++i; + if (i == 2000) { + document.documentElement.removeAttribute("class"); + clearInterval(interval); + } + } + function start() { + interval = setInterval("crash()", 0) + } + </script> + </head> + <body onload="start()"> + </body> +</html> diff --git a/dom/media/test/crashtests/868504.html b/dom/media/test/crashtests/868504.html new file mode 100644 index 0000000000..94090c1c09 --- /dev/null +++ b/dom/media/test/crashtests/868504.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + new AudioContext().createBufferSource().playbackRate.linearRampToValueAtTime(0, -1); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/874869.html b/dom/media/test/crashtests/874869.html new file mode 100644 index 0000000000..1fe3dbd2fc --- /dev/null +++ b/dom/media/test/crashtests/874869.html @@ -0,0 +1,15 @@ +<script> +var Context0= new AudioContext() +var Panner0=Context0.createPanner(); +var BufferSource3=Context0.createBufferSource(); +Panner0.channelCount=0; +BufferSource3.connect(Panner0); +BufferSource3.buffer=function(){ + var length=1; + var Buffer=Context0.createBuffer(1,length,'44200'); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = 255;}; + return Buffer; +}(); + +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/874915.html b/dom/media/test/crashtests/874915.html new file mode 100644 index 0000000000..59218b3da3 --- /dev/null +++ b/dom/media/test/crashtests/874915.html @@ -0,0 +1,24 @@ +<script> +var Context0= new AudioContext() +var BufferSource6=Context0.createBufferSource(); + +setInterval(function(){ +BufferSource6.buffer=function(){ + var length=11283; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(624))}; + return Buffer; +}(); +},0) + +BufferSource6.start(0.15831333969254047,0.23571860056836158,0.529235512483865); + +BufferSource6.buffer=function(){ + var length=48517; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(365))}; + return Buffer; +}(); +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/874934.html b/dom/media/test/crashtests/874934.html new file mode 100644 index 0000000000..350ce28101 --- /dev/null +++ b/dom/media/test/crashtests/874934.html @@ -0,0 +1,23 @@ +<script> +var Context0= new AudioContext() +var BufferSource0=Context0.createBufferSource(); +BufferSource0.start(0.01932738965842873,0.33345631847623736,0.3893404237460345); +BufferSource0.buffer=function(){ + var length=35887; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0.39765272522345185))}; + return Buffer; +}(); + +BufferSource0.buffer=function(){ + var length=15952; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(85))}; + return Buffer; +}(); + +BufferSource0.playbackRate.value=20.401213286832185; + +</script> diff --git a/dom/media/test/crashtests/874952.html b/dom/media/test/crashtests/874952.html new file mode 100644 index 0000000000..a9a398fdb2 --- /dev/null +++ b/dom/media/test/crashtests/874952.html @@ -0,0 +1,11 @@ +<script> +var Context0= new AudioContext() +var ChannelSplitter0=Context0.createChannelSplitter(); +var BiquadFilter0=Context0.createBiquadFilter(); +var WaveShaper0=Context0.createWaveShaper(); + +ChannelSplitter0.connect(BiquadFilter0,3,0); +ChannelSplitter0.connect(WaveShaper0); +BiquadFilter0.disconnect(); +WaveShaper0.connect(ChannelSplitter0); +</script> diff --git a/dom/media/test/crashtests/875144.html b/dom/media/test/crashtests/875144.html new file mode 100644 index 0000000000..bf5d0d0861 --- /dev/null +++ b/dom/media/test/crashtests/875144.html @@ -0,0 +1,81 @@ +<script> +Logger = {} +Logger.error = function(e) {} +Logger.comment = function(e) {} + +try { o0 = document.createElement('audio'); } catch(e) { Logger.error(Logger.comment(e)); } +try { (document.body || document.documentElement).appendChild(o0); } catch(e) { Logger.error(Logger.comment(e)); } +try { o1 = new AudioContext(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o2 = o1.createGain(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3 = o1.createBufferSource(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o4 = o1.createBuffer(1, 3, 52970); +o5 = o4.getChannelData(0); +for(var i=0; i<3; ++i) { +o5[i] = Math.sin(i * 63); +} +return o4; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o6 = o1.createBuffer(1, 15, 41218); +o7 = o6.getChannelData(0); +for(var i=0; i<15; ++i) { +o7[i] = Math.sin(i * 0); +} +return o6; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o8 = o1.createBuffer(1, 0, 49074); +o9 = o8.getChannelData(0); +for(var i=0; i<0; ++i) { +o9[i] = Math.sin(i * 0); +} +return o8; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o10 = o1.createBuffer(1, 31, 86527); +o11 = o10.getChannelData(0); +for(var i=0; i<31; ++i) { +o11[i] = Math.sin(i * 127); +} +return o10; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.stop(-1) } catch(e) { Logger.error(Logger.comment(e)); } +/* [Exception... "An attempt was made to use an object that is not, or is no longer, usable" code: "11" nsresult: "0x8053000b (InvalidStateError)" location: "file:///Users/cdiehl/dev/projects/peach/Peach/Utilities/JS/undefined.js Line: 602"] */ +try { o3.channelCountMode = 'explicit'; } catch(e) { Logger.error(Logger.comment(e)); } +try { o12 = o1.createBiquadFilter(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o13 = o1.createBuffer(1, 63, 28347); +o14 = o13.getChannelData(0); +for(var i=0; i<63; ++i) { +o14[i] = Math.sin(i * 15); +} +return o13; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.channelCount = 1; } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.connect(GainNode, 65536, 0) } catch(e) { Logger.error(Logger.comment(e)); } +/* TypeError: Value does not implement interface AudioNode. */ +try { o3.buffer = function() { o15 = o1.createBuffer(1, 1, 72540); +o16 = o15.getChannelData(0); +for(var i=0; i<1; ++i) { +o16[i] = Math.sin(i * 7); +} +return o15; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.getFrequencyResponse(new Float32Array(7), new Float32Array(127), new Float32Array(7)) } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.getFrequencyResponse(new Float32Array(15), new Float32Array(127), new Float32Array(7)) } catch(e) { Logger.error(Logger.comment(e)); } +try { o17 = document.createElement('audio'); } catch(e) { Logger.error(Logger.comment(e)); } +try { (document.body || document.documentElement).appendChild(o0); } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o18 = o1.createBuffer(1, 7, 91261); +o19 = o18.getChannelData(0); +for(var i=0; i<7; ++i) { +o19[i] = Math.sin(i * 7); +} +return o18; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.getFrequencyResponse(new Float32Array(31), new Float32Array(31), new Float32Array(127)) } catch(e) { Logger.error(Logger.comment(e)); } +try { o20 = o1.createChannelSplitter(1, 2, 4, 16, 32); } catch(e) { Logger.error(Logger.comment(e)); } +try { o12.channelCountMode = 'explicit'; } catch(e) { Logger.error(Logger.comment(e)); } +try { o3.buffer = function() { o21 = o1.createBuffer(1, 0, 14451); +o22 = o21.getChannelData(0); +for(var i=0; i<0; ++i) { +o22[i] = Math.sin(i * 63); +} +return o21; +}(); } catch(e) { Logger.error(Logger.comment(e)); } +</script> diff --git a/dom/media/test/crashtests/875596.html b/dom/media/test/crashtests/875596.html new file mode 100644 index 0000000000..7bac09dab7 --- /dev/null +++ b/dom/media/test/crashtests/875596.html @@ -0,0 +1,12 @@ +<script> +try { o1 = new AudioContext(); } catch(e) { } +try { o6 = o1.createGain(); } catch(e) { } +try { o8 = o1.createBufferSource(); } catch(e) { } +try { o6.gain.setValueCurveAtTime(new Float32Array(7), 0, 0) } catch(e) { } +try { o8.connect(o6, 0, 0) } catch(e) { } +try { o8.buffer = function() { + o19 = o1.createBuffer(1, 1, 76309); + for(var i=0; i<1; ++i) { } + return o19; +}(); } catch(e) { } +</script> diff --git a/dom/media/test/crashtests/875911.html b/dom/media/test/crashtests/875911.html new file mode 100644 index 0000000000..fbc52642b4 --- /dev/null +++ b/dom/media/test/crashtests/875911.html @@ -0,0 +1,3 @@ +<script> + new OfflineAudioContext(1, 10, 48000); +</script> diff --git a/dom/media/test/crashtests/876024-1.html b/dom/media/test/crashtests/876024-1.html new file mode 100644 index 0000000000..5502d8e42d --- /dev/null +++ b/dom/media/test/crashtests/876024-1.html @@ -0,0 +1,5 @@ +<script> +o1 = new window.AudioContext(2, 8, 44100); +o4 = o1.createBiquadFilter(); +o4.detune.setValueAtTime(0.0843, 1.7976931348623157e+308); +</script> diff --git a/dom/media/test/crashtests/876024-2.html b/dom/media/test/crashtests/876024-2.html new file mode 100644 index 0000000000..4b9beb7453 --- /dev/null +++ b/dom/media/test/crashtests/876024-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +function boom() +{ + var bufferSource = new AudioContext().createScriptProcessor().context.createBufferSource(); + bufferSource.start(0, 0, 0); + bufferSource.stop(562949953421313); +} + +</script></head> + +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/876118.html b/dom/media/test/crashtests/876118.html new file mode 100644 index 0000000000..bc0630350a --- /dev/null +++ b/dom/media/test/crashtests/876118.html @@ -0,0 +1,16 @@ +<script> +var Context0= new AudioContext() +var BufferSource7=Context0.createBufferSource(); + +BufferSource7.connect(Context0.destination); +BufferSource7.buffer=function(){ + var length=7; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0);for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-1))}; + return Buffer; +}(); + + +Context0.destination.channelCountMode="explicit"; +Context0.destination.channelCount=519910189000000; +</script> diff --git a/dom/media/test/crashtests/876207.html b/dom/media/test/crashtests/876207.html new file mode 100644 index 0000000000..7bfcb096b2 --- /dev/null +++ b/dom/media/test/crashtests/876207.html @@ -0,0 +1,30 @@ +<script> +try { o1 = new window.OfflineAudioContext(1, 10, 44100); } catch(e) { } +try { o2 = o1.createChannelSplitter(1); } catch(e) { } +try { o3 = o1.createAnalyser(); } catch(e) { } +try { o4 = o1.createWaveShaper(); } catch(e) { } +try { o5 = o1.createChannelSplitter(6); } catch(e) { } +try { o6 = o1.createAnalyser(); } catch(e) { } +try { o7 = o1.createBufferSource(); } catch(e) { } +try { o7.connect(o1.destination); } catch(e) { } +try { o7.buffer = function() { +o8 = o1.createBuffer(1, 3, o1.sampleRate); +o9 = o8.getChannelData(0); +for(var i = 0; i < 3; ++i) { +o9[i] = Math.sin(i * 127); +} +return o8; +}() +; } catch(e) { } +try { o7.playbackRate.cancelScheduledValues(0) } catch(e) { } +try { o7.channelInterpretation = 'speakers'; } catch(e) { } +try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { } +try { o5.connect(o1.destination, 1, 1) } catch(e) { } +try { o1.listener.setOrientation(0, 1073741824, 1073741824, 4194304, 1, 0) } catch(e) { } +try { o4.curve = new Float32Array(127); } catch(e) { } +try { o6.getByteFrequencyData(new Uint8Array(12)) } catch(e) { } +try { o6.disconnect() } catch(e) { } +try { o1.destination.channelCount = 33554432; } catch(e) { } +try { o7.playbackRate.setTargetAtTime(0, 8388608, 1) } catch(e) { } +try { o6.getByteTimeDomainData(new Uint8Array(12)) } catch(e) { } +</script> diff --git a/dom/media/test/crashtests/876215.html b/dom/media/test/crashtests/876215.html new file mode 100644 index 0000000000..07135e3628 --- /dev/null +++ b/dom/media/test/crashtests/876215.html @@ -0,0 +1,14 @@ +<script> +try { o1 = new window.OfflineAudioContext(1, 10, 44100); } catch(e) { } +try { o6 = o1.createScriptProcessor(0, 0, 2); } catch(e) { } +try { o8 = o1.createBufferSource(); } catch(e) { } +try { o8.connect(o1.destination); } catch(e) { } +try { o8.connect(o6) } catch(e) { } +try { o3.disconnect() } catch(e) { } +try { o8.buffer = function() { +o21 = o1.createBuffer(1, 3, o1.sampleRate); +o22 = o21.getChannelData(0); +return o21; +}() +; } catch(e) { } +</script> diff --git a/dom/media/test/crashtests/876249.html b/dom/media/test/crashtests/876249.html new file mode 100644 index 0000000000..6f72b40bc1 --- /dev/null +++ b/dom/media/test/crashtests/876249.html @@ -0,0 +1,27 @@ +<script> +var Context0= new AudioContext() +var BufferSource0=Context0.createBufferSource(); +var BiquadFilter0=Context0.createBiquadFilter(); +BufferSource0.start(0,0.6167310480959713,0.7142638498917222); +BiquadFilter0.connect(Context0.destination); +BufferSource0.buffer=function(){ + var length=86333; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-1))}; + return Buffer; +}(); + +BufferSource0.connect(BiquadFilter0); + +BufferSource0.buffer=function(){ + var length=21989; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0))}; + return Buffer; +}(); + +BufferSource0.stop(0.04184641852043569); + +</script> diff --git a/dom/media/test/crashtests/876252.html b/dom/media/test/crashtests/876252.html new file mode 100644 index 0000000000..cb9cb63ed3 --- /dev/null +++ b/dom/media/test/crashtests/876252.html @@ -0,0 +1,23 @@ +<script> +var Context0= new AudioContext() +var BufferSource4=Context0.createBufferSource(); +BufferSource4.start(0.05386466556228697,0.397192713804543,0.48810303467325866); + +BufferSource4.buffer=function(){ + var length=109076; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(370))}; + return Buffer; +}(); + +BufferSource4.buffer=function(){ + var length=19339; + var Buffer=Context0.createBuffer(1,length,53362); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-0.16235407581552863))}; + return Buffer; +}(); + +BufferSource4.stop(0.46482366253621876); +</script> diff --git a/dom/media/test/crashtests/876834.html b/dom/media/test/crashtests/876834.html new file mode 100644 index 0000000000..f4ca6ee558 --- /dev/null +++ b/dom/media/test/crashtests/876834.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> +new OfflineAudioContext(0, 0, 3229622); +</script> diff --git a/dom/media/test/crashtests/877527.html b/dom/media/test/crashtests/877527.html new file mode 100644 index 0000000000..c639d501b7 --- /dev/null +++ b/dom/media/test/crashtests/877527.html @@ -0,0 +1,37 @@ +<script> +try { o1 = new window.AudioContext(2, 5, 44100); } catch(e) { } +try { o2 = o1.createChannelMerger(1); } catch(e) { } +try { o3 = o1.createDelay(10); } catch(e) { } +try { o4 = o1.createBuffer(2, 2048, 8000); } catch(e) { } +try { o5 = o1.createPanner(); } catch(e) { } +try { o6 = o1.createBufferSource(); } catch(e) { } +try { o7 = (function() { +var buf = o1.createBuffer(1, 50000, o1.sampleRate); +for(var j=0; j<1; ++j) { +for(var i=0; i<50000; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (9.8));} +} +return buf; +})(); } catch(e) { } +try { o6.buffer = o7; } catch(e) { } +try { o6.connect(o5); } catch(e) { } +try { o5.connect(o1.destination); } catch(e) { } +try { o1.listener.speedOfSound = 0.0000019073486328125; } catch(e) { } +try { o6.loop = true; } catch(e) { } +try { o8 = (function() { +var buf = o1.createBuffer(2, 1000, o1.sampleRate); +for(var j=0; j<2; ++j) { +for(var i=0; i<1000; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (1));} +} +return buf; +})(); } catch(e) { } +try { o6.buffer = o7; } catch(e) { } +try { o6.connect(o5); } catch(e) { } +try { o5.connect(o1.destination); } catch(e) { } +try { o6.loopEnd = 1.4901161193847656e-8; } catch(e) { } +try { o6.connect(o1.destination); } catch(e) { } +try { o6.buffer = o8; } catch(e) { } +try { o5.setPosition(0.36, o1.destination.context.destination.channelCountMode, o1.destination.context.destination.channelInterpretation) } catch(e) { } +try { o2.channelCountMode = 'explicit'; } catch(e) { } +try { o1.listener.speedOfSound = 4; } catch(e) { } +try { o1.startRendering(); } catch(e) { } +</script> diff --git a/dom/media/test/crashtests/877820.html b/dom/media/test/crashtests/877820.html new file mode 100644 index 0000000000..6d65c1e9d9 --- /dev/null +++ b/dom/media/test/crashtests/877820.html @@ -0,0 +1,4 @@ +<script> +o1 = new window.OfflineAudioContext(2, 5, 0.39); +window.location.reload(); +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/878014.html b/dom/media/test/crashtests/878014.html new file mode 100644 index 0000000000..4a0ca6dce2 --- /dev/null +++ b/dom/media/test/crashtests/878014.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +try { o2 = new window.AudioContext(1, 15, 44100); } catch(e) { } +try { o5 = o2.createDelay(0.1); } catch(e) { } +try { o6 = o2.createPanner(); } catch(e) { } +try { o8 = o2.createBufferSource(); } catch(e) { } +try { o9 = (function() { +var buf = o2.createBuffer(1, 12134, o2.sampleRate); +for(var j=0; j<1; ++j) { +for(var i=0; i<12134; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (-127));} +} +return buf; +})(); } catch(e) { } +try { o8.buffer = o9; } catch(e) { } +try { o8.connect(o6); } catch(e) { } +try { o6.connect(o5, 0, 0) } catch(e) { } +try { o8.buffer = (function() { +var buf = o2.createBuffer(1, 5409, o2.sampleRate); +for(var j=0; j<1; ++j) { +for(var i=0; i<5409; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (-1));} +} +return buf; +})(); } catch(e) { } +setTimeout(function() { try { o5.delayTime.setValueAtTime(0.78, 121560862.56366833); } catch(e) { } setTimeout(done, 0); },128) +try { o5.delayTime.value = 0.4283; } catch(e) { } + +function done() { + document.documentElement.removeAttribute("class"); +} +</script> diff --git a/dom/media/test/crashtests/878328.html b/dom/media/test/crashtests/878328.html new file mode 100644 index 0000000000..ec7b39871b --- /dev/null +++ b/dom/media/test/crashtests/878328.html @@ -0,0 +1,5 @@ +<script> +o1 = new window.AudioContext(2, 7, 44100); +o5 = o1.createDelay(1); +o5.delayTime.setValueCurveAtTime(new Float32Array(3), 1.7976931348623157e+308, 0.64); +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/878407.html b/dom/media/test/crashtests/878407.html new file mode 100644 index 0000000000..ae2918dc85 --- /dev/null +++ b/dom/media/test/crashtests/878407.html @@ -0,0 +1,11 @@ +<script> +o1 = new window.AudioContext(); +o4 = o1.createDelay(0.4468); +o6 = o1.createPanner(); +o7 = (function() {var buf = o1.createBuffer(1, 1000, o1.sampleRate);for(var j=0; j<1; ++j) {for(var i=0; i<1000; ++i) {buf.getChannelData(j)[i] = Math.sin(i * (-15));}}return buf;})(); +o8 = o1.createBufferSource(); +o8.buffer = o7; +o8.connect(o6); +o6.connect(o4) +o4.delayTime.setValueAtTime(0.6019893103749466289898, 0.26) +</script> diff --git a/dom/media/test/crashtests/878478.html b/dom/media/test/crashtests/878478.html new file mode 100644 index 0000000000..89a47ddb55 --- /dev/null +++ b/dom/media/test/crashtests/878478.html @@ -0,0 +1,30 @@ +<script> +var Context0= new AudioContext() +var BufferSource0=Context0.createBufferSource(); + +BufferSource0.loop=true; + +BufferSource0.buffer=function(){ + var channels=3; + var length=97252; + var Buffer=Context0.createBuffer(channels,length,Context0.sampleRate); + for(var y=0;y<channels;y++){ + var bufferData= Buffer.getChannelData(y); + for (var i = 0; i < length; ++i) { bufferData[i] = 1;} + }; + return Buffer; +}(); + +BufferSource0.playbackRate.cancelScheduledValues(0.4); + +BufferSource0.loopEnd=5e-324; + +BufferSource0.buffer=function(){ + var length=26590; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = 1}; + return Buffer; +}(); + +</script> diff --git a/dom/media/test/crashtests/880129.html b/dom/media/test/crashtests/880129.html new file mode 100644 index 0000000000..775e9d80ba --- /dev/null +++ b/dom/media/test/crashtests/880129.html @@ -0,0 +1,9 @@ +<script> +try { o1 = new window.AudioContext(3, 2, 44100); } catch(e) { } +try { o6 = o1.createBufferSource(); } catch(e) { } +try { o15 = o1.createAnalyser(); } catch(e) { } +try { o15.fftSize = 32; } catch(e) { } +try { o6.connect(o15,0,0) } catch(e) { } +try { o27 = o1.createPanner(); } catch(e) { } +try { o6.buffer = function(){var buffer = o1.createBuffer(2, 1148, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1148; i++) {buffer.getChannelData(c)[i] = 0;}}return buffer;}() } catch(e) { } +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/880202.html b/dom/media/test/crashtests/880202.html new file mode 100644 index 0000000000..dc0fade9db --- /dev/null +++ b/dom/media/test/crashtests/880202.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var Context0= new window.OfflineAudioContext(15,12119,44100) +var BufferSource1=Context0.createBufferSource(); +var Gain0=Context0.createGain(); +var Panner0=Context0.createPanner(); +Context0.listener.setPosition(29,135,158); + +BufferSource1.connect(Gain0); + +BufferSource1.buffer=function(){ + var channels=3; + var length=53325; + var Buffer=Context0.createBuffer(channels,length,Context0.sampleRate); + for(var y=0;y<channels;y++){ + var bufferData= Buffer.getChannelData(y); + for (var i = 0; i < length; ++i) { + bufferData[i] = i*(270); + } + }; + return Buffer; +}(); + +Gain0.connect(Panner0); +Panner0.panningModel="equalpower"; + + +setTimeout(function(){ +document.documentElement.removeAttribute("class"); +},500) + +</script> diff --git a/dom/media/test/crashtests/880342-1.html b/dom/media/test/crashtests/880342-1.html new file mode 100644 index 0000000000..7d1aa0c3e3 --- /dev/null +++ b/dom/media/test/crashtests/880342-1.html @@ -0,0 +1,208 @@ +<script> +try { o1 = new window.AudioContext(2, 10, 1019159); } catch(e) { } +try { o2 = o1.createBufferSource(); } catch(e) { } +try { o3 = o1.createConvolver(); } catch(e) { } +try { o4 = o1.createChannelMerger(2); } catch(e) { } +try { o5 = o1.createAnalyser(); } catch(e) { } +try { o6 = o1.createPanner(); } catch(e) { } +try { o7 = o1.createDynamicsCompressor(); } catch(e) { } +try { o8 = o1.createWaveShaper(); } catch(e) { } +try { o9 = o1.createChannelSplitter(6); } catch(e) { } +try { o10 = o1.createScriptProcessor(8192, 2, 3); } catch(e) { } +try { o1.listener.setPosition(-144115188075855870, 0, -5e-324) } catch(e) { } +try { o3.buffer = function(){var buffer = o1.createBuffer(2, 741, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<741; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); } catch(e) { } +try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { } +try { o3.connect(o5,0,0) } catch(e) { } +try { o9.channelCountMode = 'max'; } catch(e) { } +try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { } +try { o7.channelInterpretation = 'speakers'; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1887, 63346);for(var c=0; c<2; c++) {for(var i=0; i<1887; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.playbackRate.exponentialRampToValueAtTime(3.819464020334534, 32) } catch(e) { } +try { o5.connect(o9,2,0) } catch(e) { } +try { o9.channelCountMode = 'explicit'; } catch(e) { } +try { o1.listener.speedOfSound = 72057594037927940; } catch(e) { } +try { o1.destination.channelCountMode = 'explicit'; } catch(e) { } +try { o7.threshold.value = 0.0646435404346891590021684237399313133209944; } catch(e) { } +try { o7.connect(o10,0,0) } catch(e) { } +try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { } +try { o3.connect(o4,0,0) } catch(e) { } +try { o7.release.value = 23.030355486273447; } catch(e) { } +try { o9.connect(o9,4,0) } catch(e) { } +try { o6.channelCountMode = 'max'; } catch(e) { } +try { o7.threshold.value = 0.6395867363641939418172910336579661816358566284179687500; } catch(e) { } +try { o2.connect(o9,3,0) } catch(e) { } +try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { } +try { o7.connect(o10,0,0) } catch(e) { } +try { o5.connect(o5,0,0) } catch(e) { } +try { o2.playbackRate.value = 5e-324; } catch(e) { } +try { o4.channelInterpretation = 'discrete'; } catch(e) { } +try { o2.playbackRate.value = 1.2211628339508704; } catch(e) { } +try { o2.channelCountMode = 'clamped-max'; } catch(e) { } +try { o1.listener.dopplerFactor = 32; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(22, 1811, 21177);for(var c=0; c<22; c++) {for(var i=0; i<1811; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o5.channelCountMode = 'max'; } catch(e) { } +try { o1.listener.speedOfSound = 1024; } catch(e) { } +try { o2.playbackRate.value = 9.999999999999998e-91; } catch(e) { } +try { o1.listener.speedOfSound = 16; } catch(e) { } +try { o8.curve = new Float32Array(256); } catch(e) { } +try { o1.listener.setVelocity(0, 128, 4) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(23, 483, o1.sampleRate);for(var c=0; c<23; c++) {for(var i=0; i<483; i++) {buffer.getChannelData(c)[i] = 0.026;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 945, 43803);for(var c=0; c<3; c++) {for(var i=0; i<945; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(19, 997, 23781);for(var c=0; c<19; c++) {for(var i=0; i<997; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.listener.speedOfSound = -8; } catch(e) { } +try { o3.channelCountMode = 'explicit'; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1740, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1740; i++) {buffer.getChannelData(c)[i] = 8;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1057, 30602);for(var c=0; c<1; c++) {for(var i=0; i<1057; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 78, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<78; i++) {buffer.getChannelData(c)[i] = 10;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.listener.setPosition(9.999999999999995e-275, 0.0001, 2) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 863, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<863; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o8.channelCount = 3; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1821, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1821; i++) {buffer.getChannelData(c)[i] = 0.3655;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o6.connect(o6,0,0) } catch(e) { } +try { o1.listener.setOrientation(128, -0.479527496, 128, 1, 1, -16) } catch(e) { } +try { o4.connect(o8,0,0) } catch(e) { } +try { o7.reduction.exponentialRampToValueAtTime(1.7976931348623157e+308, 2048) } catch(e) { } +try { o1.listener.dopplerFactor = 0.5754; } catch(e) { } +try { o7.release.value = 0; } catch(e) { } +try { o5.getFloatFrequencyData(new Float32Array(2048)) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 120, 42419);for(var c=0; c<2; c++) {for(var i=0; i<120; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o3.buffer = function(){var buffer = o1.createBuffer(1, 1620, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<1620; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}(); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 823, 77239);for(var c=0; c<1; c++) {for(var i=0; i<823; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { } +try { o1.listener.setPosition(2048, 0.000522899209, -4503599627370495) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 84028, 75483);for(var c=0; c<3; c++) {for(var i=0; i<84028; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.playbackRate.setTargetAtTime(5e-324, 9.999999999999998e-104, 0) } catch(e) { } +try { o7.attack.value = 9.999999999999994e-236; } catch(e) { } +try { o1.listener.dopplerFactor = 1024; } catch(e) { } +try { o3.connect(o7,0,0) } catch(e) { } +try { o2.playbackRate.linearRampToValueAtTime(0, 0) } catch(e) { } +try { o4.connect(o10,0,0) } catch(e) { } +try { o1.listener.setVelocity(0.682, 32, 1e-76) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 201, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<201; i++) {buffer.getChannelData(c)[i] = 4;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.connect(o6,0,0) } catch(e) { } +try { o5.getByteFrequencyData(new Uint8Array(32)) } catch(e) { } +try { o3.connect(o6,0,0) } catch(e) { } +try { o7.channelCount = 1; } catch(e) { } +try { o2.playbackRate.setValueCurveAtTime(new Float32Array(512), 8, 1) } catch(e) { } +try { o3.connect(o4,0,1) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1996, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1996; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.listener.dopplerFactor = 2; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 983, 60517);for(var c=0; c<2; c++) {for(var i=0; i<983; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o6.coneInnerAngle = 360; } catch(e) { } +try { o8.curve = new Float32Array(512); } catch(e) { } +try { o1.listener.setPosition(32, 16, 9.999999999999995e-280) } catch(e) { } +try { o6.connect(o6,0,0) } catch(e) { } +try { o1.listener.setPosition(2097151, 512, 5e-324) } catch(e) { } +try { o7.channelCountMode = 'clamped-max'; } catch(e) { } +try { o1.destination.channelCountMode = 'max'; } catch(e) { } +try { o1.listener.setPosition(0, 1000000, 64) } catch(e) { } +try { o4.connect(o5,0,0) } catch(e) { } +try { o9.connect(o4,0,1) } catch(e) { } +try { o7.attack.setValueCurveAtTime(new Float32Array(8), 256, 2048) } catch(e) { } +try { o9.channelCountMode = 'explicit'; } catch(e) { } +try { o1.listener.dopplerFactor = 5e-324; } catch(e) { } +try { o4.connect(o10,0,0) } catch(e) { } +try { o7.ratio.cancelScheduledValues(1) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1590, 9733);for(var c=0; c<1; c++) {for(var i=0; i<1590; i++) {buffer.getChannelData(c)[i] = 2;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.destination.channelCount = 2; } catch(e) { } +try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { } +try { o5.channelCountMode = 'max'; } catch(e) { } +try { o9.channelCountMode = 'clamped-max'; } catch(e) { } +try { o7.attack.setTargetAtTime(0.11499421161482907549622467513472656719386577606201171875000, 1, 1.7976931348623157e+308) } catch(e) { } +try { o2.playbackRate.value = 1; } catch(e) { } +try { o3.normalize = false; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1866, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1866; i++) {buffer.getChannelData(c)[i] = 0.174908422905697580329587026426452212035655975341797;}}return buffer;}(); } catch(e) { } +try { o2.playbackRate.value = 1.0; } catch(e) { } +try { o7.threshold.value = 100; } catch(e) { } +try { o1.listener.setVelocity(961.4441892370145, 9.999999999999999e-157, 1) } catch(e) { } +try { o5.getByteFrequencyData(new Uint8Array(2)) } catch(e) { } +try { o6.connect(o10,0,0) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1176, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1176; i++) {buffer.getChannelData(c)[i] = 9.753993292300242;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.playbackRate.linearRampToValueAtTime(0.8687, 32) } catch(e) { } +try { o5.channelCountMode = 'explicit'; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(13, 703, 12723);for(var c=0; c<13; c++) {for(var i=0; i<703; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.loopEnd = 1.7976931348623157e+308; } catch(e) { } +try { o3.connect(o7,0,0) } catch(e) { } +try { o1.destination.channelCountMode = 'clamped-max'; } catch(e) { } +try { o1.destination.channelInterpretation = 'discrete'; } catch(e) { } +try { o2.connect(o10,0,0) } catch(e) { } +try { o3.normalize = false; } catch(e) { } +try { /* switch-case did not return anything. */ } catch(e) { } +try { o10.connect(o8,0,0) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 132, 88507);for(var c=0; c<3; c++) {for(var i=0; i<132; i++) {buffer.getChannelData(c)[i] = 1.7976931348623157e+308;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.loopStart = 1.7976931348623157e+308; } catch(e) { } +try { o1.listener.dopplerFactor = 1024; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 40, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<40; i++) {buffer.getChannelData(c)[i] = 1.3785770335346594;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o4.connect(o9,5,0) } catch(e) { } +try { o8.curve = new Float32Array(16); } catch(e) { } +try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { } +try { o6.channelCountMode = 'explicit'; } catch(e) { } +try { o2.playbackRate.value = 0; } catch(e) { } +try { o4.channelCount = 3; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 220, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<220; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.destination.channelInterpretation = 'discrete'; } catch(e) { } +try { o1.listener.setPosition(70368744177664, 2048, 1.7976931348623157e+308) } catch(e) { } +try { o2.playbackRate.setValueCurveAtTime(new Float32Array(128), 1.7976931348623157e+308, 0.75) } catch(e) { } +try { o5.fftSize = 118; } catch(e) { } +try { o5.minDecibels = 1; } catch(e) { } +try { o7.release.value = 5e-324; } catch(e) { } +try { o2.channelCount = 3; } catch(e) { } +try { o2.channelCountMode = 'clamped-max'; } catch(e) { } +try { o3.normalize = false; } catch(e) { } +try { o1.listener.speedOfSound = 2; } catch(e) { } +try { o2.loopStart = 32; } catch(e) { } +try { o3.channelCountMode = 'max'; } catch(e) { } +try { o1.listener.setPosition(0.37976588317653304, -274877906943, 64) } catch(e) { } +try { o8.curve = new Float32Array(4); } catch(e) { } +try { o1.listener.setVelocity(16, -1024, 0) } catch(e) { } +try { o5.minDecibels = 10000; } catch(e) { } +try { o2.playbackRate.value = 5e-324; } catch(e) { } +try { o3.connect(o9,1,0) } catch(e) { } +try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { } +try { o5.connect(o8,0,0) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(3, 554, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<554; i++) {buffer.getChannelData(c)[i] = 0.000001;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o7.release.value = 0; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1289, 29583);for(var c=0; c<1; c++) {for(var i=0; i<1289; i++) {buffer.getChannelData(c)[i] = 5e-324;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.listener.setOrientation(1024, 64, 16, 1024, 1, 68719476735) } catch(e) { } +try { o1.listener.setOrientation(512, 10000000, 274877906944, 9.999999999999998e-113, -8, 3.6191134923595274) } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1453, 15258);for(var c=0; c<1; c++) {for(var i=0; i<1453; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o8.channelInterpretation = 'discrete'; } catch(e) { } +try { o5.channelInterpretation = 'discrete'; } catch(e) { } +try { o7.connect(o9,4,0) } catch(e) { } +try { o8.connect(o10,0,0) } catch(e) { } +try { o1.listener.setPosition(18014398509481984, 16, 0.505129302503804056279079759406158700585365295410156250000) } catch(e) { } +try { o10.connect(o9,5,0) } catch(e) { } +try { o2.loop = false; } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(11, 1673, o1.sampleRate);for(var c=0; c<11; c++) {for(var i=0; i<1673; i++) {buffer.getChannelData(c)[i] = 0;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 577, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<577; i++) {buffer.getChannelData(c)[i] = 0.5611;}}return buffer;}() } catch(e) { } +try { o2.connect(o1.destination); } catch(e) { } +try { o1.listener.setOrientation(64, 2049, 5e-324, 0.1777, 2, 7) } catch(e) { } +try { o5.maxDecibels = 128; } catch(e) { } +try { o2.start(0) } catch(e) { } +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/880342-2.html b/dom/media/test/crashtests/880342-2.html new file mode 100644 index 0000000000..a8ef88ae70 --- /dev/null +++ b/dom/media/test/crashtests/880342-2.html @@ -0,0 +1,8 @@ +<script> +try { o1 = new window.AudioContext(2, 10, 1019159); } catch(e) { } +try { o2 = o1.createBufferSource(); } catch(e) { } +try { o3 = o1.createConvolver(); } catch(e) { } +try { o3.buffer = function(){var buffer = o1.createBuffer(2, 741, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<741; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); } catch(e) { } +try { o2.buffer = function(){var buffer = o1.createBuffer(2, 120, 42419);for(var c=0; c<2; c++) {for(var i=0; i<120; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { } +try { o3.buffer = function(){var buffer = o1.createBuffer(1, 1620, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<1620; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}(); } catch(e) { } +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/880384.html b/dom/media/test/crashtests/880384.html new file mode 100644 index 0000000000..798fbf133d --- /dev/null +++ b/dom/media/test/crashtests/880384.html @@ -0,0 +1,8 @@ +<script> +o1 = new window.AudioContext(3, 256, 44100); +o2 = o1.createBufferSource(); +o3 = o1.createConvolver(); +o3.normalize = false; +o3.buffer = function(){var buffer = o1.createBuffer(2, 1051, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1051; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); +o3.buffer = function(){var buffer = o1.createBuffer(2, 1112, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1112; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); +</script> diff --git a/dom/media/test/crashtests/880404.html b/dom/media/test/crashtests/880404.html new file mode 100644 index 0000000000..bf34932388 --- /dev/null +++ b/dom/media/test/crashtests/880404.html @@ -0,0 +1,6 @@ +<script> +o1 = new window.OfflineAudioContext(2, 32, 44100); +o12 = o1.createConvolver(); +o12.buffer = function(){var buffer = o1.createBuffer(1, 78, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<78; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); +o1.startRendering(); +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/880724.html b/dom/media/test/crashtests/880724.html new file mode 100644 index 0000000000..b57b5f9964 --- /dev/null +++ b/dom/media/test/crashtests/880724.html @@ -0,0 +1,13 @@ +<script> +o1 = new window.AudioContext(); +o5 = o1.createConvolver(); +o5.buffer = function() { + var buffer = o1.createBuffer(2, 3, 33120); + for(var c=0; c<2; c++) { + for(var i=0; i<3; i++) { + buffer.getChannelData(c)[i] = -1; + } + } + return buffer; +}(); +</script>
\ No newline at end of file diff --git a/dom/media/test/crashtests/881775.html b/dom/media/test/crashtests/881775.html new file mode 100644 index 0000000000..d55c45d17e --- /dev/null +++ b/dom/media/test/crashtests/881775.html @@ -0,0 +1,25 @@ +<script> +o1 = new window.AudioContext(2, 16, 44100); +o2 = o1.createBufferSource(); +o12 = o1.createBiquadFilter(); +o1.destination.channelCountMode = 'max'; +o2.buffer = function(){ + var buffer = o1.createBuffer(2, 326, 77632); + for(var c=0; c<2; c++) { + for(var i=0; i<326; i++) { + buffer.getChannelData(c)[i] = -1; + } + } + return buffer; +}(); +o2.connect(o1.destination); +o2.buffer = function(){ + var buffer = o1.createBuffer(3, 405, o1.sampleRate); + for(var c=0; c<3; c++) { + for(var i=0; i<405; i++) { + buffer.getChannelData(c)[i] = 1; + } + } + return buffer; +}(); +</script> diff --git a/dom/media/test/crashtests/882956.html b/dom/media/test/crashtests/882956.html new file mode 100644 index 0000000000..ae7b441f99 --- /dev/null +++ b/dom/media/test/crashtests/882956.html @@ -0,0 +1,15 @@ +<script> +o1 = new window.AudioContext(1, 2048, 44100); +o2 = o1.createBufferSource(); +o1.destination.channelCountMode = 'max'; +o2.connect(o1.destination); +o2.buffer = function(){ + var buffer = o1.createBuffer(30, 442, 94933); + for(var c=0; c<30; c++) { + for(var i=0; i<442; i++) { + buffer.getChannelData(c)[i] = 1; + } + } + return buffer; +}(); +</script> diff --git a/dom/media/test/crashtests/884459.html b/dom/media/test/crashtests/884459.html new file mode 100644 index 0000000000..e321d569f2 --- /dev/null +++ b/dom/media/test/crashtests/884459.html @@ -0,0 +1,12 @@ +<script> +var Context0= new window.OfflineAudioContext(14,191531,44100) +var BufferSource1=Context0.createBufferSource(); + +setInterval(function(){ +BufferSource1.playbackRate.setTargetAtTime(0xC8F461D3EE6B2,(Context0.currentTime+0.0677539280615747),0.826130285160616); +BufferSource1.playbackRate.setValueAtTime(35467.63924283907536607336193,0); +},1) + +Context0.startRendering(); + +</script> diff --git a/dom/media/test/crashtests/889042.html b/dom/media/test/crashtests/889042.html new file mode 100644 index 0000000000..9f74979c58 --- /dev/null +++ b/dom/media/test/crashtests/889042.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + (new AudioContext()).createConvolver().buffer = null; +</script> diff --git a/dom/media/test/crashtests/907986-1.html b/dom/media/test/crashtests/907986-1.html new file mode 100644 index 0000000000..320b8eadd0 --- /dev/null +++ b/dom/media/test/crashtests/907986-1.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 100, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +// zero front vector +context.listener.setOrientation(0, 0, 0, 6.311749985202524e+307, 0, 0); +var panner = context.createPanner(); +panner.setPosition(6.311749985202524e+307, 4, 6.311749985202524e+307); +panner.connect(context.destination); +var source = context.createOscillator(); +source.connect(panner); +source.start(0); +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/907986-2.html b/dom/media/test/crashtests/907986-2.html new file mode 100644 index 0000000000..e0626ba2c2 --- /dev/null +++ b/dom/media/test/crashtests/907986-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 100, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +// zero up vector +context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0, 0, 0); +var panner = context.createPanner(); +panner.setPosition(1, 2, 3); +panner.connect(context.destination); +var source = context.createOscillator(); +source.connect(panner); +source.start(0); +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/907986-3.html b/dom/media/test/crashtests/907986-3.html new file mode 100644 index 0000000000..75b756c363 --- /dev/null +++ b/dom/media/test/crashtests/907986-3.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 100, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +// linearly dependent +context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307); +var panner = context.createPanner(); +panner.setPosition(1, 2, 3); +panner.connect(context.destination); +var source = context.createOscillator(); +source.connect(panner); +source.start(0); +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/907986-4.html b/dom/media/test/crashtests/907986-4.html new file mode 100644 index 0000000000..a73500efca --- /dev/null +++ b/dom/media/test/crashtests/907986-4.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 100, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +var panner = context.createPanner(); +panner.setPosition(0, 3, 0); // directly overhead +panner.connect(context.destination); +var source = context.createOscillator(); +source.connect(panner); +source.start(0); +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/910171-1.html b/dom/media/test/crashtests/910171-1.html new file mode 100644 index 0000000000..9f3ec831be --- /dev/null +++ b/dom/media/test/crashtests/910171-1.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 4096, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +var delay = context.createDelay(); +delay.connect(context.destination); +delay.delayTime.value = 1.0; +var buffer = context.createBuffer(1, 2048, context.sampleRate); +var source = context.createBufferSource(); +source.buffer = buffer; +source.connect(delay); +source.start(); +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/920987.html b/dom/media/test/crashtests/920987.html new file mode 100644 index 0000000000..6e8b2992a6 --- /dev/null +++ b/dom/media/test/crashtests/920987.html @@ -0,0 +1,6 @@ +<script> +var ctx = new AudioContext(); +var buffer = ctx.createBuffer(1, 1000, ctx.sampleRate); +var array = new Float32Array(900); +buffer.copyToChannel(array, 0, 0xfffffff8); +</script> diff --git a/dom/media/test/crashtests/925619-1.html b/dom/media/test/crashtests/925619-1.html new file mode 100644 index 0000000000..146c531f9b --- /dev/null +++ b/dom/media/test/crashtests/925619-1.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +// 1024 > 89478.5 * 48000 - (1 << 32) +var context = new window.OfflineAudioContext(1, 1024, 48000); +context.oncomplete = function(e) { + document.documentElement.removeAttribute("class"); +}; +var buffer = context.createBuffer(1, 2048, context.sampleRate); +var source = context.createBufferSource(); +source.buffer = buffer; +source.start(89478.5); // 89478.5 is a little greater than 2^32 / 48000. +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/925619-2.html b/dom/media/test/crashtests/925619-2.html new file mode 100644 index 0000000000..e734b8bcad --- /dev/null +++ b/dom/media/test/crashtests/925619-2.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 2048, 48000); +// 1024 > 89478.5 * 48000 - (1 << 32) +var buffer = context.createBuffer(1, 1024, context.sampleRate); +var source = context.createBufferSource(); +source.buffer = buffer; +source.onended = function(e) { + document.documentElement.removeAttribute("class"); +}; +source.start(0); +source.stop(89478.5); // 89478.5 is a little greater than 2^32 / 48000. +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/926619.html b/dom/media/test/crashtests/926619.html new file mode 100644 index 0000000000..2ead02af4e --- /dev/null +++ b/dom/media/test/crashtests/926619.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +function boom() +{ + var ac = new window.AudioContext(); + + var _ChannelMergerNode = ac.createChannelMerger(4); + + var _MediaStreamAudioDestinationNode = ac.createMediaStreamDestination(); + var _MediaStream = _MediaStreamAudioDestinationNode.stream; + var _MediaStreamAudioSourceNode = ac.createMediaStreamSource(_MediaStream); + + _ChannelMergerNode.connect(_MediaStreamAudioDestinationNode, 0, 0); + _MediaStreamAudioSourceNode.connect(_ChannelMergerNode, 0, 0); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/933151.html b/dom/media/test/crashtests/933151.html new file mode 100644 index 0000000000..3d45f7af38 --- /dev/null +++ b/dom/media/test/crashtests/933151.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + var ac = new window.AudioContext(); + var buffer = ac.createBuffer(1, 24313, 47250); + buffer.copyFromChannel(buffer.getChannelData(0), ''); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/dom/media/test/crashtests/933156.html b/dom/media/test/crashtests/933156.html new file mode 100644 index 0000000000..b89445a43d --- /dev/null +++ b/dom/media/test/crashtests/933156.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +function boom() +{ + var ac = new window.AudioContext(); + + var panner = ac.createPanner(); + panner.setPosition(8, 0.7643051305237005, 0.10292575673733972); + + var oscillator = ac.createOscillator(); + oscillator.connect(panner); + oscillator.start(0); + + setTimeout(function() { + panner.panningModel = 'equalpower'; + oscillator.stop(0); + document.documentElement.removeAttribute("class"); + }, 0.5); +} +boom(); +</script> +</html> diff --git a/dom/media/test/crashtests/944851.html b/dom/media/test/crashtests/944851.html new file mode 100644 index 0000000000..4f663accc4 --- /dev/null +++ b/dom/media/test/crashtests/944851.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +var ac = new AudioContext(1, 1354, 44100); +var shaper = ac.createWaveShaper(); +var biquad = ac.createBiquadFilter(); +shaper.connect(biquad.frequency); +biquad.getFrequencyResponse(new Float32Array(55785), + new Float32Array(62876), + new Float32Array(45111)); + +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/952756.html b/dom/media/test/crashtests/952756.html new file mode 100644 index 0000000000..ffced2e400 --- /dev/null +++ b/dom/media/test/crashtests/952756.html @@ -0,0 +1,19 @@ +<script> +var Context0= new AudioContext() +var BufferSource0=Context0.createBufferSource(); +BufferSource0.buffer=function(){ + var length=35887; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0.39765272522345185))}; + return Buffer; +}(); +BufferSource0.start(0.01932738965842873,0.33345631847623736,0.3893404237460345); +BufferSource0.buffer=function(){ + var length=15952; + var Buffer=Context0.createBuffer(1,length,Context0.sampleRate); + var bufferData= Buffer.getChannelData(0); + for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(85))}; + return Buffer; +}(); +</script> diff --git a/dom/media/test/crashtests/986901.html b/dom/media/test/crashtests/986901.html new file mode 100644 index 0000000000..343df2c0ed --- /dev/null +++ b/dom/media/test/crashtests/986901.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + var ac = new window.AudioContext(); + var delay1 = ac.createDelay(0.02); + var delay2 = ac.createDelay(0.002); + var source = ac.createOscillator(); + source.start(0); + source.connect(delay1, 0, 0); + delay2.connect(delay1, 0, 0); + delay1.connect(delay2, 0, 0); +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/990794.html b/dom/media/test/crashtests/990794.html new file mode 100644 index 0000000000..8b40088b1a --- /dev/null +++ b/dom/media/test/crashtests/990794.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<script> +var ctx = new AudioContext(); +var source = ctx.createOscillator(); +source.start(0); + +function appendMerger(src) { + const inputCount = 18; + + var merger = ctx.createChannelMerger(32); + + for (var i = 0; i < inputCount; ++i) { + src.connect(merger, 0, i); + } + + return merger; +} + +for (var i = 0; i < 6; ++i) { + source = appendMerger(source); +} +</script> diff --git a/dom/media/test/crashtests/995289.html b/dom/media/test/crashtests/995289.html new file mode 100644 index 0000000000..c988f41fa8 --- /dev/null +++ b/dom/media/test/crashtests/995289.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<script> +var r0=new AudioContext(); +var r5=r0.createOscillator(); +var r6=r0.createPeriodicWave(new Float32Array(1),new Float32Array(1)); +r5.frequency.value = 4294967295; +r5.start(0); +r5.setPeriodicWave(r6); +</script> diff --git a/dom/media/test/crashtests/analyser-channels-1.html b/dom/media/test/crashtests/analyser-channels-1.html new file mode 100644 index 0000000000..2f3133cf13 --- /dev/null +++ b/dom/media/test/crashtests/analyser-channels-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> + var context = new window.OfflineAudioContext(1, 256, 48000); + var analyser = context.createAnalyser(); + analyser.channelCount = 2; + analyser.channelCountMode = "explicit"; + analyser.fftSize = 32; + var source = context.createOscillator(); + source.connect(analyser); + source.start(0); + context.startRendering(). + then(function() { + document.documentElement.removeAttribute("class"); + }); +</script> diff --git a/dom/media/test/crashtests/audiocontext-after-unload-1.html b/dom/media/test/crashtests/audiocontext-after-unload-1.html new file mode 100644 index 0000000000..9b4f1181d2 --- /dev/null +++ b/dom/media/test/crashtests/audiocontext-after-unload-1.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<title>Test for bug 1646601</title> +<script> +document.addEventListener('DOMContentLoaded', async () => { + const frame = document.createElement('iframe'); + document.body.appendChild(frame); + frame.srcdoc = '<html></html>'; + await new Promise(resolve => frame.onload = resolve); + const subwin = frame.contentWindow; + const subcontext = subwin.AudioContext; + // Construct an AudioContext while the subdocument is fully active to start + // a MediaTrackGraph. + new subcontext(); + // Unload the subdocument and wait for completion. + // This shuts down the MediaTrackGraph. + subwin.location.reload(); + await new Promise(resolve => frame.onload = resolve); + // Test that a new AudioContext on the inactive subdocument does not attempt + // to use the shut-down MediaTrackGraph. + try { new subcontext() } catch {} + document.documentElement.removeAttribute('class'); +}); +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/audiocontext-double-suspend.html b/dom/media/test/crashtests/audiocontext-double-suspend.html new file mode 100644 index 0000000000..98399549bd --- /dev/null +++ b/dom/media/test/crashtests/audiocontext-double-suspend.html @@ -0,0 +1,5 @@ +<script> +var ac = new AudioContext(); +ac.resume(); +ac.resume(); +</script> diff --git a/dom/media/test/crashtests/audioworkletnode-after-unload-1.html b/dom/media/test/crashtests/audioworkletnode-after-unload-1.html new file mode 100644 index 0000000000..7da8d1161a --- /dev/null +++ b/dom/media/test/crashtests/audioworkletnode-after-unload-1.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<title>Test for bug 1634200 and bug 1655544</title> +<script> +document.addEventListener('DOMContentLoaded', async () => { + const frame = document.createElement('iframe'); + document.body.appendChild(frame); + frame.srcdoc = '<html></html>'; + await new Promise(resolve => frame.onload = resolve); + + const subwin = frame.contentWindow; + const ctx = new subwin.AudioContext(); + const url = URL.createObjectURL( + new Blob([`registerProcessor("noop", + class extends AudioWorkletProcessor {})`]), + {type: "application/javascript"}); + await ctx.audioWorklet.addModule(url); + + frame.remove(); + new subwin.AudioWorkletNode(ctx, 'noop') + + document.documentElement.removeAttribute('class'); +}); +</script> +</head> +</html> diff --git a/dom/media/test/crashtests/buffer-source-duration-1.html b/dom/media/test/crashtests/buffer-source-duration-1.html new file mode 100644 index 0000000000..df8d7a37d5 --- /dev/null +++ b/dom/media/test/crashtests/buffer-source-duration-1.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +const rate = 44100; +var context = new window.OfflineAudioContext(1, 512, rate); +var buffer = context.createBuffer(1, 128, rate); +var source = context.createBufferSource(); +source.buffer = buffer; +source.start(0, 0, 86400); +context.startRendering(). + then(function() { + document.documentElement.removeAttribute("class"); + }); +</script> diff --git a/dom/media/test/crashtests/buffer-source-ended-1.html b/dom/media/test/crashtests/buffer-source-ended-1.html new file mode 100644 index 0000000000..de8546316c --- /dev/null +++ b/dom/media/test/crashtests/buffer-source-ended-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new AudioContext(); + +var source = context.createBufferSource(); +source.buffer = context.createBuffer(1, 2.0 * context.sampleRate, context.sampleRate); +source.onended = function(e) { + document.documentElement.removeAttribute("class"); +} +source.start(0.0, 1.0); +setTimeout( + function() { + source.buffer = context.createBuffer(1, 1, context.sampleRate); + }, 0); +</script> diff --git a/dom/media/test/crashtests/buffer-source-resampling-start-1.html b/dom/media/test/crashtests/buffer-source-resampling-start-1.html new file mode 100644 index 0000000000..55db8591ed --- /dev/null +++ b/dom/media/test/crashtests/buffer-source-resampling-start-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +const rate = 44101; // not divisible by 2 +var context = new window.OfflineAudioContext(1, 512, rate); +var buffer = context.createBuffer(1, 128, rate); +buffer.getChannelData(0)[0] = 1.0; +var source = context.createBufferSource(); +source.buffer = buffer; +source.playbackRate.value = rate / (Math.pow(2, 30) * 1.0000001); +source.start(512 / rate); +context.startRendering(). + then(function() { + document.documentElement.removeAttribute("class"); + }); +</script> diff --git a/dom/media/test/crashtests/buffer-source-slow-resampling-1.html b/dom/media/test/crashtests/buffer-source-slow-resampling-1.html new file mode 100644 index 0000000000..5d8a50442b --- /dev/null +++ b/dom/media/test/crashtests/buffer-source-slow-resampling-1.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +const blockSize = 128; +// The sample rate is a prime number so that the resampler is not expected to +// simplify in/out fractions. +const rate = 44101; +var context = new window.OfflineAudioContext(1, 3 * blockSize, rate); +// Non-zero buffer, so it can't be optimized away. +var buffer = context.createBuffer(1, 128, rate); +buffer.getChannelData(0)[0] = 1.0; +var source = context.createBufferSource(); +source.buffer = buffer; +source.loop = true; +// Initialize the resampler with a slow input rate. +// With the current (Mar 2017) implementation, very slow rates give the +// resampler a very large denominator. +source.playbackRate.setValueAtTime(rate / 0x7fffffff, 0.0); +// Change to a moderate input rate. +// With the current implementation, skip_frac_num increases by den_rate for +// each output sample and so one block before the change in playback rate is +// enough for high skip_frac_num at the time of the change. +const changeBlock = 1; +const changeBlockSeconds = changeBlock * blockSize / rate; +// With the current speex_resampler_set_rate_frac() implementation, the +// moderate resampler denominator is still large enough to trigger overflow of +// 32-bit unsigned integer arithmetic. +source.playbackRate.setValueAtTime(rate / (rate + 1), changeBlockSeconds); +source.start(0); +context.startRendering(). + then(function() { + document.documentElement.removeAttribute("class"); + }); +</script> diff --git a/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4 b/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4 Binary files differnew file mode 100644 index 0000000000..92bf3722f2 --- /dev/null +++ b/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4 diff --git a/dom/media/test/crashtests/convolver-memory-report-1.html b/dom/media/test/crashtests/convolver-memory-report-1.html new file mode 100644 index 0000000000..a49a281d1c --- /dev/null +++ b/dom/media/test/crashtests/convolver-memory-report-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Bug 1481745: Exercise ConvolverNode memory reporting</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script> +let context = new AudioContext(); +let response = new AudioBuffer({length: 128, + sampleRate: context.sampleRate}); +response.getChannelData(0)[response.length - 1] = 1; +let convolver = new ConvolverNode(context, + {disableNormalization: true, + buffer: response}); +convolver.connect(context.destination); +let osc = new OscillatorNode(context); +osc.connect(convolver); +osc.start(); +osc.stop(128/context.sampleRate); +osc.onended = (e) => { + SpecialPowers.getMemoryReports(); + document.documentElement.removeAttribute("class"); +}; + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/copyFromChannel-2.html b/dom/media/test/crashtests/copyFromChannel-2.html new file mode 100644 index 0000000000..8d3d5a2124 --- /dev/null +++ b/dom/media/test/crashtests/copyFromChannel-2.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <title>Crashtest for bug 1548816</title> + <script> +let cx = new OfflineAudioContext({numberOfChannels: 1, + length: 1, sampleRate: 44100}); +let buffer = new AudioBuffer({numberOfChannels: 13, + length: 22050, sampleRate: 44100}); +buffer.getChannelData(12)[0] = 1.0; +let o2248 = new AudioBufferSourceNode(cx, {buffer: buffer}); +let array = new Float32Array(52428); +buffer.copyFromChannel(array, 12); + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/cors.webm b/dom/media/test/crashtests/cors.webm Binary files differnew file mode 100644 index 0000000000..72b0297233 --- /dev/null +++ b/dom/media/test/crashtests/cors.webm diff --git a/dom/media/test/crashtests/cors.webm^headers^ b/dom/media/test/crashtests/cors.webm^headers^ new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/dom/media/test/crashtests/cors.webm^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list new file mode 100644 index 0000000000..f3e5683733 --- /dev/null +++ b/dom/media/test/crashtests/crashtests.list @@ -0,0 +1,142 @@ +load 0-timescale.html # bug 1229166 +skip-if(Android) pref(media.autoplay.default,0) load 459439-1.html # bug 888557 +load 466607-1.html +load 466945-1.html +load 468763-1.html +load 474744-1.html +HTTP load 481136-1.html # needs to be HTTP to recognize the ogg as an audio file? +load 492286-1.xhtml +load 493915-1.html +pref(media.autoplay.default,0) load 495794-1.html +load 497734-1.xhtml +load 497734-2.html +load 576612-1.html +load 752784-1.html +skip-if(Android) load 789075-1.html # bug 1374405 for android +skip-if(Android&&AndroidVersion=='22') HTTP load 795892-1.html # bug 1358718 +load 844563.html +load 846612.html +skip-if(verify&&isDebugBuild&>kWidget) load 852838.html +load 865004.html +load 865537-1.html +load 865550.html +skip-if(verify&&isDebugBuild&>kWidget) load 868504.html +skip-if(verify&&isDebugBuild&>kWidget) load 874869.html +skip-if(verify&&isDebugBuild&>kWidget) load 874915.html +skip-if(verify&&isDebugBuild&>kWidget) load 874934.html +skip-if(verify&&isDebugBuild&>kWidget) load 874952.html +skip-if(verify&&isDebugBuild&>kWidget) load 875144.html +skip-if(verify&&isDebugBuild&>kWidget) load 875596.html +load 875911.html +load 876024-1.html +skip-if(verify&&isDebugBuild&>kWidget) load 876024-2.html +skip-if(verify&&isDebugBuild&>kWidget) load 876118.html +load 876207.html +load 876215.html +skip-if(verify&&isDebugBuild&>kWidget) load 876249.html +skip-if(verify&&isDebugBuild&>kWidget) load 876252.html +load 876834.html +load 877527.html +load 877820.html +load 878014.html +load 878328.html +skip-if(verify&&isDebugBuild&>kWidget) load 878407.html +skip-if(verify&&isDebugBuild&>kWidget) load 878478.html +load 880129.html +load 880202.html +load 880342-1.html +load 880342-2.html +load 880384.html +load 880404.html +skip-if(verify&&isDebugBuild&>kWidget) load 880724.html +load 881775.html +load 882956.html +load 884459.html +skip-if(verify&&isDebugBuild&>kWidget) load 889042.html +load 907986-1.html +load 907986-2.html +load 907986-3.html +load 907986-4.html +load 910171-1.html +skip-if(verify&&isDebugBuild&>kWidget) load 920987.html +load 925619-1.html +load 925619-2.html +skip-if(verify&&isDebugBuild&>kWidget) load 926619.html +skip-if(verify&&isDebugBuild&>kWidget) load 933151.html +skip-if(verify&&isDebugBuild&>kWidget) load 933156.html +load 944851.html +skip-if(verify&&isDebugBuild&>kWidget) load 952756.html +skip-if(verify&&isDebugBuild&>kWidget) load 986901.html +skip-if(verify&&isDebugBuild&>kWidget) load 990794.html +skip-if(verify&&isDebugBuild&>kWidget) load 995289.html +skip-if(verify&&isDebugBuild&>kWidget) load 1012609.html +load 1015662.html +skip-if(Android) test-pref(media.navigator.permission.disabled,true) load 1028458.html # bug 1048863 +skip-if(verify&&isDebugBuild&>kWidget) load 1041466.html +skip-if(verify&&isDebugBuild&>kWidget) load 1045650.html +load 1080986.html +skip-if(Android&&AndroidVersion=='21') load 1180881.html # bug 1409365 +load 1197935.html +skip-if(verify&&isDebugBuild&>kWidget) load 1122218.html +load 1127188.html +skip-if(verify&&isDebugBuild&>kWidget) load 1157994.html +load 1158427.html +skip-if(verify&&isDebugBuild&>kWidget) load 1185176.html +load 1185192.html +skip-if(Android) load 1257700.html # bug 1575666 +load 1267263.html +load 1270303.html +skip-if(verify&&isDebugBuild&>kWidget) load 1368490.html +skip-if(verify&&isDebugBuild&>kWidget) load 1291702.html +load 1378826.html +load 1384248.html +load 1389304.html +load 1393272.webm +load 1411322.html +load 1450845.html +load 1489160.html +load disconnect-wrong-destination.html +load analyser-channels-1.html +load audiocontext-after-unload-1.html +skip-if(verify&&isDebugBuild&>kWidget) load audiocontext-double-suspend.html +skip-if(Android) load audioworkletnode-after-unload-1.html # Needs secure context +load buffer-source-duration-1.html +skip-if(verify&&isDebugBuild&>kWidget) load buffer-source-ended-1.html +load buffer-source-resampling-start-1.html +load buffer-source-slow-resampling-1.html +load convolver-memory-report-1.html +load copyFromChannel-2.html +load empty-buffer-source.html +skip-if(verify&&isDebugBuild&>kWidget) HTTP load media-element-source-seek-1.html +skip-if(verify&&isDebugBuild&>kWidget) load offline-buffer-source-ended-1.html +load oscillator-ended-1.html +load oscillator-ended-2.html +skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876 +# This needs to run at the end to avoid leaking busted state into other tests. +skip-if(Android) load 691096-1.html # Bug 1365451 +load 1236639.html +test-pref(media.navigator.permission.disabled,true) test-pref(media.devices.insecure.enabled,true) test-pref(media.getusermedia.insecure.enabled,true) load 1388372.html +load 1494073.html +skip-if(Android) load 1526044.html # Bug 1528391 +skip-if(Android&&AndroidVersion<21) load encrypted-track-with-bad-sample-description-index.mp4 # Bug 1533211, unkip after bug 1550912 +load encrypted-track-without-tenc.mp4 # Bug 1533215 +asserts-if(Android,0-1) load encrypted-track-with-sample-missing-cenc-aux.mp4 # Bug 1533625, bug 1588967 +load 1538727.html +load empty-samples.webm # Bug 1540580 +test-pref(media.autoplay.block-webaudio,false) load 1545133.html +load track-with-zero-dimensions.mp4 # Bug 1542539 +load 1560215.html +skip-if(Android) load 1547784.html # Skip on Android as clearkey is not supported +load 1547899.html +load 1569645.html +load 1575271.html +load 1577184.html +pref(media.autoplay.default,0) load 1587248.html +load 1594466.html +load 1601385.html +load 1601422.html +load 1604941.html +pref(media.autoplay.default,0) load 1673525.html +skip-if(!winWidget) load 1608286.html +load channel-count-in-metadata-different-than-in-content.mp4 # Bug 1584959 +load mp4_box_emptyrange.mp4 # Bug 1667480 diff --git a/dom/media/test/crashtests/disconnect-wrong-destination.html b/dom/media/test/crashtests/disconnect-wrong-destination.html new file mode 100644 index 0000000000..515ca3c877 --- /dev/null +++ b/dom/media/test/crashtests/disconnect-wrong-destination.html @@ -0,0 +1,13 @@ +<script> + var oc = new OfflineAudioContext(1, 1, 44100); + var splitter = oc.createChannelSplitter(2); + var merger0 = oc.createChannelMerger(2); + var merger1 = oc.createChannelMerger(2); + splitter.connect(merger0, 0); + splitter.connect(merger0, 1); + splitter.connect(merger1, 0); + splitter.connect(merger1, 1); + + splitter.disconnect(merger0, 0); + splitter.disconnect(merger1, 1); +</script> diff --git a/dom/media/test/crashtests/doppler-1.html b/dom/media/test/crashtests/doppler-1.html new file mode 100644 index 0000000000..2af3c8f460 --- /dev/null +++ b/dom/media/test/crashtests/doppler-1.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.AudioContext(); +var source = context.createBufferSource(); +source.buffer = context.createBuffer(1, 1, context.sampleRate); +source.onended = + function(e) { + setTimeout( + function() { + var panner = context.createPanner(); + source.connect(panner); + panner.setVelocity(1.0, 0.0, 0.0); + setTimeout( + function() { + document.documentElement.removeAttribute("class"); + }, + 0); + }, + 0); + }; +source.start(0); +</script> diff --git a/dom/media/test/crashtests/empty-buffer-source.html b/dom/media/test/crashtests/empty-buffer-source.html new file mode 100644 index 0000000000..2ce48a9ec1 --- /dev/null +++ b/dom/media/test/crashtests/empty-buffer-source.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Bug 1636540: AudioBufferSourceNode with empty buffer</title> + <script> +const offline = new OfflineAudioContext({length: 128, sampleRate: 16384}); +const buffer = new AudioBuffer({length: 1, sampleRate: 21725}); +const node = new AudioBufferSourceNode(offline, {buffer: buffer}); +node.start(5/offline.sampleRate); +offline.startRendering().then( + () => document.documentElement.removeAttribute("class")); + </script> +</head> +</html> diff --git a/dom/media/test/crashtests/empty-samples.webm b/dom/media/test/crashtests/empty-samples.webm new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/media/test/crashtests/empty-samples.webm diff --git a/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4 b/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4 Binary files differnew file mode 100644 index 0000000000..32303f0357 --- /dev/null +++ b/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4 diff --git a/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4 b/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4 Binary files differnew file mode 100644 index 0000000000..875c5dca76 --- /dev/null +++ b/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4 diff --git a/dom/media/test/crashtests/encrypted-track-without-tenc.mp4 b/dom/media/test/crashtests/encrypted-track-without-tenc.mp4 Binary files differnew file mode 100644 index 0000000000..188faebf1b --- /dev/null +++ b/dom/media/test/crashtests/encrypted-track-without-tenc.mp4 diff --git a/dom/media/test/crashtests/media-element-source-seek-1.html b/dom/media/test/crashtests/media-element-source-seek-1.html new file mode 100644 index 0000000000..5c3aed5ae7 --- /dev/null +++ b/dom/media/test/crashtests/media-element-source-seek-1.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var audioElement = document.createElement("audio"); +audioElement.autoplay = true; +audioElement.src = "sound.ogg"; +audioElement.onplaying = + function() { + audioElement.onplaying = null; + setTimeout( + function() { + audioElement.onseeked = + function() { + // Note we reset 'src' to release decoder resources and cubeb + // streams to prevent OOM or OpenCubeb() failures. + audioElement.src = ""; + document.documentElement.removeAttribute("class"); + }; + audioElement.currentTime = 0; + }, 100); + }; + +var context = new window.AudioContext(); +var source = context.createMediaElementSource(audioElement); +source.connect(context.destination); +</script> +</html> diff --git a/dom/media/test/crashtests/mp4_box_emptyrange.mp4 b/dom/media/test/crashtests/mp4_box_emptyrange.mp4 Binary files differnew file mode 100644 index 0000000000..83057533a0 --- /dev/null +++ b/dom/media/test/crashtests/mp4_box_emptyrange.mp4 diff --git a/dom/media/test/crashtests/offline-buffer-source-ended-1.html b/dom/media/test/crashtests/offline-buffer-source-ended-1.html new file mode 100644 index 0000000000..0631021126 --- /dev/null +++ b/dom/media/test/crashtests/offline-buffer-source-ended-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +var context = new window.OfflineAudioContext(1, 12001, 12000); + +var source = context.createBufferSource(); +source.buffer = context.createBuffer(1, 12000, context.sampleRate); +source.onended = function(e) { + document.documentElement.removeAttribute("class"); +} +source.connect(context.destination); +source.start(0); + +context.startRendering(); +</script> diff --git a/dom/media/test/crashtests/oscillator-ended-1.html b/dom/media/test/crashtests/oscillator-ended-1.html new file mode 100644 index 0000000000..831111261c --- /dev/null +++ b/dom/media/test/crashtests/oscillator-ended-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +function createContext() { + var context = new window.AudioContext(); + var source = context.createOscillator(); + source.onended = function(e) { + document.documentElement.removeAttribute("class"); + }; + source.connect(context.destination); + source.start(0.49); + source.stop(0.5); +} +createContext(); +</script> diff --git a/dom/media/test/crashtests/oscillator-ended-2.html b/dom/media/test/crashtests/oscillator-ended-2.html new file mode 100644 index 0000000000..ee9b8cf300 --- /dev/null +++ b/dom/media/test/crashtests/oscillator-ended-2.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> +function createContext() { + var context = new window.AudioContext(); + var source = context.createOscillator(); + source.onended = function(e) { + document.documentElement.removeAttribute("class"); + }; + source.connect(context.destination); + source.start(60); + source.stop(0.5); +} +createContext(); +</script> diff --git a/dom/media/test/crashtests/sound.ogg b/dom/media/test/crashtests/sound.ogg Binary files differnew file mode 100644 index 0000000000..edda4e9128 --- /dev/null +++ b/dom/media/test/crashtests/sound.ogg diff --git a/dom/media/test/crashtests/track-with-zero-dimensions.mp4 b/dom/media/test/crashtests/track-with-zero-dimensions.mp4 Binary files differnew file mode 100644 index 0000000000..3f4a1317f3 --- /dev/null +++ b/dom/media/test/crashtests/track-with-zero-dimensions.mp4 diff --git a/dom/media/test/crashtests/video-crash.webm b/dom/media/test/crashtests/video-crash.webm Binary files differnew file mode 100644 index 0000000000..9532113d87 --- /dev/null +++ b/dom/media/test/crashtests/video-crash.webm diff --git a/dom/media/test/crashtests/video-replay-after-audio-end.html b/dom/media/test/crashtests/video-replay-after-audio-end.html new file mode 100644 index 0000000000..9ffd6078de --- /dev/null +++ b/dom/media/test/crashtests/video-replay-after-audio-end.html @@ -0,0 +1,43 @@ +<html class="reftest-wait"> +<head> + <title> Bug 1242774 : video crashed if pause and play again after audio track ends </title> +</head> +<body> +<script type="text/javascript"> +function assert(value, msg) { + if (!value) { + dump("### Error : " + msg + "\n"); + } +} + +var AUDIO_END_TIME = 4.5; +var video = document.createElement('video'); +video.src = "video-crash.webm"; +video.play(); + +video.ontimeupdate = function () { + assert(AUDIO_END_TIME < video.duration, + "AUDIO_END_TIME should be smaller than the duration!"); + + if (video.currentTime > AUDIO_END_TIME) { + dump("### Pause video during silent part.\n"); + video.ontimeupdate = null; + video.pause(); + } + + video.onpause = function () { + video.onpause = null; + setTimeout(function() { + dump("### Re-play after pausing during silent part.\n"); + video.play(); + video.onended = function () { + video.onended = null; + dump("### Video is ended.\n"); + document.documentElement.removeAttribute("class"); + } + }, 1000); + } +} +</script> +</body> +</html>
\ No newline at end of file diff --git a/dom/media/test/dash/dash-manifest-garbled-webm.mpd b/dom/media/test/dash/dash-manifest-garbled-webm.mpd new file mode 100644 index 0000000000..aa78ded3ec --- /dev/null +++ b/dom/media/test/dash/dash-manifest-garbled-webm.mpd @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<MPD + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="urn:mpeg:DASH:schema:MPD:2011" + xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011" + type="static" + mediaPresentationDuration="PT3.958S" + minBufferTime="PT1S" + profiles="urn:webm:dash:profile:webm-on-demand:2012"> + <BaseURL>./</BaseURL> + <Period id="0" start="PT0S" duration="PT3.958S" > + <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true"> + <Representation id="0" bandwidth="54207" width="320" height="180"> + <BaseURL>garbled.webm</BaseURL> + <SegmentBase indexRange="35090-35123"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + <Representation id="1" bandwidth="78006" width="428" height="240"> + <BaseURL>dash-webm-video-428x240.webm</BaseURL> + <SegmentBase indexRange="50173-50206"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + </AdaptationSet> + <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1"> + <Representation id="2" bandwidth="57264"> + <BaseURL>dash-webm-audio-128k.webm</BaseURL> + <SegmentBase indexRange="41927-41946"> + <Initialization range="0-4521" /> + </SegmentBase> + </Representation> + </AdaptationSet> + </Period> +</MPD> diff --git a/dom/media/test/dash/dash-manifest-garbled.mpd b/dom/media/test/dash/dash-manifest-garbled.mpd new file mode 100644 index 0000000000..ac8eadbddc --- /dev/null +++ b/dom/media/test/dash/dash-manifest-garbled.mpd @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxNUEQgbWVkaWFQcmVzZW50YXRpb25EdXJhdGlvbj0iUFQxOS41MVMiIG1pbkJ1ZmZlclRpbWU9IlBUMVMiIHByb2ZpbGVzPSJ1cm46d2VibTpkYXNoOnByb2ZpbGU6d2VibS1vbi1kZW1hbmQ6MjAxMiIgdHlwZT0ic3RhdGljIiB4bWxucz0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiPjxCYXNlVVJMPmh0dHA6Ly93d3cuZ29vZ2xlLmNvbTwvQmFzZVVSTD48UGVyaW9kIGR1cmF0aW9uPSJQVDE5LjUxUyIgaWQ9IjAiIHN0YXJ0PSJQVDBTIj48QWRhcHRhdGlvblNldCBhdWRpb1NhbXBsaW5nUmF0ZT0iNDgwMDAiIGNvZGVjcz0idm9yYmlzIiBpZD0iMSIgbGFuZz0iZW5nIiBtaW1lVHlwZT0iYXVkaW8vd2VibSIgc3Vic2VnbWVudFN0YXJ0c1dpdGhTQVA9IjEiPjxSZXByZXNlbnRhdGlvbiBiYW5kd2lkdGg9IjIwMTA5IiBpZD0iMiI+PEJhc2VVUkwvPjxTZWdtZW50QmFzZSBpbmRleFJhbmdlPSIzMTk3ODAtMzIwNjEyIj48SW5pdGlhbGl6YXRpb24gcmFuZ2U9IjAtMjA4NzAiLz48L1NlZ21lbnRCYXNlPjwvUmVwcmVzZW50YXRpb24+PC9BZGFwdGF0aW9uU2V0PjwvUGVyaW9kPjwvTVBEPg diff --git a/dom/media/test/dash/dash-manifest-sjs.mpd b/dom/media/test/dash/dash-manifest-sjs.mpd new file mode 100644 index 0000000000..c7ecba3c69 --- /dev/null +++ b/dom/media/test/dash/dash-manifest-sjs.mpd @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<MPD + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="urn:mpeg:DASH:schema:MPD:2011" + xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011" + type="static" + mediaPresentationDuration="PT3.958S" + minBufferTime="PT1S" + profiles="urn:webm:dash:profile:webm-on-demand:2012"> + <BaseURL>./dash_detect_stream_switch.sjs?name=</BaseURL> + <Period id="0" start="PT0S" duration="PT3.958S" > + <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true"> + <Representation id="0" bandwidth="54207" width="320" height="180"> + <BaseURL>dash-webm-video-320x180.webm</BaseURL> + <SegmentBase indexRange="35090-35123"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + <Representation id="1" bandwidth="78006" width="428" height="240"> + <BaseURL>dash-webm-video-428x240.webm</BaseURL> + <SegmentBase indexRange="50173-50206"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + </AdaptationSet> + <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1"> + <Representation id="2" bandwidth="57264"> + <BaseURL>dash-webm-audio-128k.webm</BaseURL> + <SegmentBase indexRange="41927-41946"> + <Initialization range="0-4521" /> + </SegmentBase> + </Representation> + </AdaptationSet> + </Period> +</MPD> diff --git a/dom/media/test/dash/dash-manifest.mpd b/dom/media/test/dash/dash-manifest.mpd new file mode 100644 index 0000000000..98c7a90480 --- /dev/null +++ b/dom/media/test/dash/dash-manifest.mpd @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<MPD + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="urn:mpeg:DASH:schema:MPD:2011" + xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011" + type="static" + mediaPresentationDuration="PT3.958S" + minBufferTime="PT1S" + profiles="urn:webm:dash:profile:webm-on-demand:2012"> + <BaseURL>./</BaseURL> + <Period id="0" start="PT0S" duration="PT3.958S" > + <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true"> + <Representation id="0" bandwidth="54207" width="320" height="180"> + <BaseURL>dash-webm-video-320x180.webm</BaseURL> + <SegmentBase indexRange="35090-35123"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + <Representation id="1" bandwidth="78006" width="428" height="240"> + <BaseURL>dash-webm-video-428x240.webm</BaseURL> + <SegmentBase indexRange="50173-50206"> + <Initialization range="0-228" /> + </SegmentBase> + </Representation> + </AdaptationSet> + <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1"> + <Representation id="2" bandwidth="57264"> + <BaseURL>dash-webm-audio-128k.webm</BaseURL> + <SegmentBase indexRange="41927-41946"> + <Initialization range="0-4521" /> + </SegmentBase> + </Representation> + </AdaptationSet> + </Period> +</MPD> diff --git a/dom/media/test/dash/dash-webm-audio-128k.webm b/dom/media/test/dash/dash-webm-audio-128k.webm Binary files differnew file mode 100644 index 0000000000..f56c042053 --- /dev/null +++ b/dom/media/test/dash/dash-webm-audio-128k.webm diff --git a/dom/media/test/dash/dash-webm-video-320x180.webm b/dom/media/test/dash/dash-webm-video-320x180.webm Binary files differnew file mode 100644 index 0000000000..282e6a2cc3 --- /dev/null +++ b/dom/media/test/dash/dash-webm-video-320x180.webm diff --git a/dom/media/test/dash/dash-webm-video-428x240.webm b/dom/media/test/dash/dash-webm-video-428x240.webm Binary files differnew file mode 100644 index 0000000000..23f2c89616 --- /dev/null +++ b/dom/media/test/dash/dash-webm-video-428x240.webm diff --git a/dom/media/test/dash/garbled.webm b/dom/media/test/dash/garbled.webm new file mode 100644 index 0000000000..ac8eadbddc --- /dev/null +++ b/dom/media/test/dash/garbled.webm @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxNUEQgbWVkaWFQcmVzZW50YXRpb25EdXJhdGlvbj0iUFQxOS41MVMiIG1pbkJ1ZmZlclRpbWU9IlBUMVMiIHByb2ZpbGVzPSJ1cm46d2VibTpkYXNoOnByb2ZpbGU6d2VibS1vbi1kZW1hbmQ6MjAxMiIgdHlwZT0ic3RhdGljIiB4bWxucz0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiPjxCYXNlVVJMPmh0dHA6Ly93d3cuZ29vZ2xlLmNvbTwvQmFzZVVSTD48UGVyaW9kIGR1cmF0aW9uPSJQVDE5LjUxUyIgaWQ9IjAiIHN0YXJ0PSJQVDBTIj48QWRhcHRhdGlvblNldCBhdWRpb1NhbXBsaW5nUmF0ZT0iNDgwMDAiIGNvZGVjcz0idm9yYmlzIiBpZD0iMSIgbGFuZz0iZW5nIiBtaW1lVHlwZT0iYXVkaW8vd2VibSIgc3Vic2VnbWVudFN0YXJ0c1dpdGhTQVA9IjEiPjxSZXByZXNlbnRhdGlvbiBiYW5kd2lkdGg9IjIwMTA5IiBpZD0iMiI+PEJhc2VVUkwvPjxTZWdtZW50QmFzZSBpbmRleFJhbmdlPSIzMTk3ODAtMzIwNjEyIj48SW5pdGlhbGl6YXRpb24gcmFuZ2U9IjAtMjA4NzAiLz48L1NlZ21lbnRCYXNlPjwvUmVwcmVzZW50YXRpb24+PC9BZGFwdGF0aW9uU2V0PjwvUGVyaW9kPjwvTVBEPg diff --git a/dom/media/test/dash_detect_stream_switch.sjs b/dom/media/test/dash_detect_stream_switch.sjs new file mode 100644 index 0000000000..a8abf6f2e5 --- /dev/null +++ b/dom/media/test/dash_detect_stream_switch.sjs @@ -0,0 +1,114 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* dash_detect_stream_switch.sjs + * + * Parses requests for DASH manifests and ensures stream switching takes place + * by verifying the subsegments downloaded and the streams they belong to. + * If unexpected subsegments (byte ranges) are requested, the script will + * will respond with a 404. + */ + +var DEBUG = false; + +function parseQuery(request, key) { + var params = request.queryString.split('&'); + if (DEBUG) { + dump("DASH-SJS: request params = \"" + params + "\"\n"); + } + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") === 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key === "") + return p; + } + return false; +} + +function handleRequest(request, response) +{ + try { + var name = parseQuery(request, "name"); + var range = request.hasHeader("Range") ? request.getHeader("Range") + : undefined; + + // Should not get request for 1st subsegment from 2nd stream, nor 2nd + // subsegment from 1st stream. + if (name == "dash-webm-video-320x180.webm" && range == "bytes=25514-32767" || + name == "dash-webm-video-428x240.webm" && range == "bytes=228-35852") + { + throw "Should not request " + name + " with byte-range " + range; + } else { + var rangeSplit = range.split("="); + if (rangeSplit.length != 2) { + throw "DASH-SJS: ERROR: invalid number of tokens (" + rangeSplit.length + + ") delimited by \'=\' in \'Range\' header."; + } + var offsets = rangeSplit[1].split("-"); + if (offsets.length != 2) { + throw "DASH-SJS: ERROR: invalid number of tokens (" + offsets.length + + ") delimited by \'-\' in \'Range\' header."; + } + var startOffset = parseInt(offsets[0]); + var endOffset = parseInt(offsets[1]); + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + + var paths = "tests/dom/media/test/" + name; + var split = paths.split("/"); + for (var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + + fis.init(file, -1, -1, false); + // Exception: start offset should be within file bounds. + if (startOffset > file.fileSize) { + throw "Starting offset [" + startOffset + "] is after end of file [" + + file.fileSize + "]."; + } + // End offset may be too large in the MPD. Real world HTTP servers just + // return what data they can; do the same here - reduce the end offset. + if (endOffset >= file.fileSize) { + if (DEBUG) { + dump("DASH-SJS: reducing endOffset [" + endOffset + "] to fileSize [" + + (file.fileSize-1) + "]\n"); + } + endOffset = file.fileSize-1; + } + fis.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, startOffset); + bis.setInputStream(fis); + + var byteLengthToRead = endOffset + 1 - startOffset; + var totalBytesExpected = byteLengthToRead + startOffset; + if (DEBUG) { + dump("DASH-SJS: byteLengthToRead = " + byteLengthToRead + + " byteLengthToRead+startOffset = " + totalBytesExpected + + " fileSize = " + file.fileSize + "\n"); + } + + var bytes = bis.readBytes(byteLengthToRead); + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "application/dash+xml", false); + var contentRange = "bytes " + startOffset + "-" + endOffset + "/" + + file.fileSize; + response.setHeader("Content-Range", contentRange, false); + response.write(bytes, bytes.length); + bis.close(); + } + } catch (e) { + dump ("DASH-SJS-ERROR: " + e + "\n"); + response.setStatusLine(request.httpVersion, 404, "Not found"); + } +} diff --git a/dom/media/test/detodos-recorder-test.opus b/dom/media/test/detodos-recorder-test.opus Binary files differnew file mode 100644 index 0000000000..88b2eab0f8 --- /dev/null +++ b/dom/media/test/detodos-recorder-test.opus diff --git a/dom/media/test/detodos-recorder-test.opus^headers^ b/dom/media/test/detodos-recorder-test.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/detodos-recorder-test.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/detodos-short.opus b/dom/media/test/detodos-short.opus Binary files differnew file mode 100644 index 0000000000..8bda283fc5 --- /dev/null +++ b/dom/media/test/detodos-short.opus diff --git a/dom/media/test/detodos-short.opus^headers^ b/dom/media/test/detodos-short.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/detodos-short.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/detodos-short.webm b/dom/media/test/detodos-short.webm Binary files differnew file mode 100644 index 0000000000..45af2675a6 --- /dev/null +++ b/dom/media/test/detodos-short.webm diff --git a/dom/media/test/detodos-short.webm^headers^ b/dom/media/test/detodos-short.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/detodos-short.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/detodos.opus b/dom/media/test/detodos.opus Binary files differnew file mode 100644 index 0000000000..6c7ba88a66 --- /dev/null +++ b/dom/media/test/detodos.opus diff --git a/dom/media/test/detodos.opus^headers^ b/dom/media/test/detodos.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/detodos.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/detodos.webm b/dom/media/test/detodos.webm Binary files differnew file mode 100644 index 0000000000..39cfa7f537 --- /dev/null +++ b/dom/media/test/detodos.webm diff --git a/dom/media/test/detodos.webm^headers^ b/dom/media/test/detodos.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/detodos.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/dirac.ogg b/dom/media/test/dirac.ogg Binary files differnew file mode 100644 index 0000000000..2986cf1e80 --- /dev/null +++ b/dom/media/test/dirac.ogg diff --git a/dom/media/test/dirac.ogg^headers^ b/dom/media/test/dirac.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/dirac.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/dynamic_resource.sjs b/dom/media/test/dynamic_resource.sjs new file mode 100644 index 0000000000..7731612184 --- /dev/null +++ b/dom/media/test/dynamic_resource.sjs @@ -0,0 +1,48 @@ +function parseQuery(request, key) { + var params = request.queryString.split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +// Return resource1 file content for the first request with a given key. +// All subsequent requests return resource2. Both must be video/ogg. +function handleRequest(request, response) +{ + var key = parseQuery(request, "key"); + var resource1 = parseQuery(request, "res1"); + var resource2 = parseQuery(request, "res2"); + + var resource = getState(key) == "2" ? resource2 : resource1; + setState(key, "2"); + + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/" + resource; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + dump("file=" + file + "\n"); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/dom/media/test/eme.js b/dom/media/test/eme.js new file mode 100644 index 0000000000..fffe71607c --- /dev/null +++ b/dom/media/test/eme.js @@ -0,0 +1,496 @@ +/* import-globals-from manifest.js */ + +const CLEARKEY_KEYSYSTEM = "org.w3.clearkey"; + +const gCencMediaKeySystemConfig = [ + { + initDataTypes: ["cenc"], + videoCapabilities: [{ contentType: "video/mp4" }], + audioCapabilities: [{ contentType: "audio/mp4" }], + }, +]; + +function bail(message) { + return function(err) { + if (err) { + message += "; " + String(err); + } + ok(false, message); + if (err) { + info(String(err)); + } + SimpleTest.finish(); + }; +} + +function ArrayBufferToString(arr) { + var str = ""; + var view = new Uint8Array(arr); + for (var i = 0; i < view.length; i++) { + str += String.fromCharCode(view[i]); + } + return str; +} + +function StringToArrayBuffer(str) { + var arr = new ArrayBuffer(str.length); + var view = new Uint8Array(arr); + for (var i = 0; i < str.length; i++) { + view[i] = str.charCodeAt(i); + } + return arr; +} + +function StringToHex(str) { + var res = ""; + for (var i = 0; i < str.length; ++i) { + res += ("0" + str.charCodeAt(i).toString(16)).slice(-2); + } + return res; +} + +function Base64ToHex(str) { + var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/")); + var res = ""; + for (var i = 0; i < bin.length; i++) { + res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2); + } + return res; +} + +function HexToBase64(hex) { + var bin = ""; + for (var i = 0; i < hex.length; i += 2) { + bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return window + .btoa(bin) + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function TimeRangesToString(trs) { + var l = trs.length; + if (l === 0) { + return "-"; + } + var s = ""; + var i = 0; + for (;;) { + s += trs.start(i) + "-" + trs.end(i); + if (++i === l) { + return s; + } + s += ","; + } +} + +function SourceBufferToString(sb) { + return ( + "SourceBuffer{" + + "AppendMode=" + + (sb.AppendMode || "-") + + ", updating=" + + (sb.updating ? "true" : "false") + + ", buffered=" + + TimeRangesToString(sb.buffered) + + ", audioTracks=" + + (sb.audioTracks ? sb.audioTracks.length : "-") + + ", videoTracks=" + + (sb.videoTracks ? sb.videoTracks.length : "-") + + "}" + ); +} + +function SourceBufferListToString(sbl) { + return "SourceBufferList[" + sbl.map(SourceBufferToString).join(", ") + "]"; +} + +function GenerateClearKeyLicense(licenseRequest, keyStore) { + var msgStr = ArrayBufferToString(licenseRequest); + var msg = JSON.parse(msgStr); + + var keys = []; + for (var i = 0; i < msg.kids.length; i++) { + var id64 = msg.kids[i]; + var idHex = Base64ToHex(msg.kids[i]).toLowerCase(); + var key = keyStore[idHex]; + + if (key) { + keys.push({ + kty: "oct", + kid: id64, + k: HexToBase64(key), + }); + } + } + + return new TextEncoder().encode( + JSON.stringify({ + keys, + type: msg.type || "temporary", + }) + ); +} + +function UpdateSessionFunc(test, token, sessionType, resolve, reject) { + return function(ev) { + var license = GenerateClearKeyLicense(ev.message, test.keys); + Log( + token, + "sending update message to CDM: " + new TextDecoder().decode(license) + ); + ev.target + .update(license) + .then(function() { + Log(token, "MediaKeySession update ok!"); + resolve(ev.target); + }) + .catch(function(reason) { + reject(`${token} MediaKeySession update failed: ${reason}`); + }); + }; +} + +function MaybeCrossOriginURI(test, uri) { + if (test.crossOrigin) { + return "https://example.com:443/tests/dom/media/test/allowed.sjs?" + uri; + } + return uri; +} + +function AppendTrack(test, ms, track, token) { + return new Promise(function(resolve, reject) { + var sb; + var curFragment = 0; + var fragments = track.fragments; + var fragmentFile; + + function addNextFragment() { + if (curFragment >= fragments.length) { + Log(token, track.name + ": end of track"); + resolve(); + return; + } + + fragmentFile = MaybeCrossOriginURI(test, fragments[curFragment++]); + + var req = new XMLHttpRequest(); + req.open("GET", fragmentFile); + req.responseType = "arraybuffer"; + + req.addEventListener("load", function() { + Log( + token, + track.name + ": fetch of " + fragmentFile + " complete, appending" + ); + sb.appendBuffer(new Uint8Array(req.response)); + }); + + req.addEventListener("error", function() { + reject(`${token} - ${track.name}: error fetching ${fragmentFile}`); + }); + req.addEventListener("abort", function() { + reject(`${token} - ${track.name}: aborted fetching ${fragmentFile}`); + }); + + Log( + token, + track.name + + ": addNextFragment() fetching next fragment " + + fragmentFile + ); + req.send(null); + } + + Log(token, track.name + ": addSourceBuffer(" + track.type + ")"); + sb = ms.addSourceBuffer(track.type); + sb.addEventListener("updateend", function() { + Log( + token, + track.name + + ": updateend for " + + fragmentFile + + ", " + + SourceBufferToString(sb) + ); + addNextFragment(); + }); + + addNextFragment(); + }); +} + +//Returns a promise that is resolved when the media element is ready to have +//its play() function called; when it's loaded MSE fragments. +function LoadTest(test, elem, token, endOfStream = true) { + if (!test.tracks) { + ok(false, token + " test does not have a tracks list"); + return Promise.reject(); + } + + var ms = new MediaSource(); + elem.src = URL.createObjectURL(ms); + elem.crossOrigin = test.crossOrigin || false; + + return new Promise(function(resolve, reject) { + ms.addEventListener( + "sourceopen", + function() { + Log(token, "sourceopen"); + Promise.all( + test.tracks.map(function(track) { + return AppendTrack(test, ms, track, token); + }) + ) + .then(function() { + Log(token, "Tracks loaded, calling MediaSource.endOfStream()"); + if (endOfStream) { + ms.endOfStream(); + } + resolve(); + }) + .catch(reject); + }, + { once: true } + ); + }); +} + +function EMEPromise() { + var self = this; + self.promise = new Promise(function(resolve, reject) { + self.resolve = resolve; + self.reject = reject; + }); +} + +/* + * Create a new MediaKeys object. + * Return a promise which will be resolved with a new MediaKeys object, + * or will be rejected with a string that describes the failure. + */ +function CreateMediaKeys(v, test, token) { + let p = new EMEPromise(); + + function streamType(type) { + var x = test.tracks.find(o => o.name == type); + return x ? x.type : undefined; + } + + function onencrypted(ev) { + var options = { initDataTypes: [ev.initDataType] }; + if (streamType("video")) { + options.videoCapabilities = [{ contentType: streamType("video") }]; + } + if (streamType("audio")) { + options.audioCapabilities = [{ contentType: streamType("audio") }]; + } + navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, [options]).then( + keySystemAccess => { + keySystemAccess + .createMediaKeys() + .then(p.resolve, () => + p.reject(`${token} Failed to create MediaKeys object.`) + ); + }, + () => p.reject(`${token} Failed to request key system access.`) + ); + } + + v.addEventListener("encrypted", onencrypted, { once: true }); + return p.promise; +} + +/* + * Create a new MediaKeys object and provide it to the media element. + * Return a promise which will be resolved if succeeded, or will be rejected + * with a string that describes the failure. + */ +function CreateAndSetMediaKeys(v, test, token) { + let p = new EMEPromise(); + + CreateMediaKeys(v, test, token).then(mediaKeys => { + v.setMediaKeys(mediaKeys).then(p.resolve, () => + p.reject(`${token} Failed to set MediaKeys on <video> element.`) + ); + }, p.reject); + + return p.promise; +} + +/* + * Collect the init data from 'encrypted' events. + * Return a promise which will be resolved with the init data when collection + * is completed (specified by test.sessionCount). + */ +function LoadInitData(v, test, token) { + let p = new EMEPromise(); + let initDataQueue = []; + + // Call SimpleTest._originalSetTimeout() to bypass the flaky timeout checker. + let timer = SimpleTest._originalSetTimeout.call( + window, + () => { + p.reject(`${token} Timed out in waiting for the init data.`); + }, + 60000 + ); + + function onencrypted(ev) { + initDataQueue.push(ev); + Log( + token, + `got encrypted(${ev.initDataType}, ` + + `${StringToHex(ArrayBufferToString(ev.initData))}) event.` + ); + if (test.sessionCount == initDataQueue.length) { + p.resolve(initDataQueue); + clearTimeout(timer); + } + } + + v.addEventListener("encrypted", onencrypted); + return p.promise; +} + +/* + * Generate a license request and update the session. + * Return a promsise which will be resolved with the updated session + * or rejected with a string that describes the failure. + */ +function MakeRequest(test, token, ev, session, sessionType) { + sessionType = sessionType || "temporary"; + let p = new EMEPromise(); + let str = + `session[${session.sessionId}].generateRequest(` + + `${ev.initDataType}, ${StringToHex(ArrayBufferToString(ev.initData))})`; + + session.addEventListener( + "message", + UpdateSessionFunc(test, token, sessionType, p.resolve, p.reject) + ); + + Log(token, str); + session.generateRequest(ev.initDataType, ev.initData).catch(reason => { + // Reject the promise if generateRequest() failed. + // Otherwise it will be resolved in UpdateSessionFunc(). + p.reject(`${token}: ${str} failed; ${reason}`); + }); + + return p.promise; +} + +/* + * Process the init data by calling MakeRequest(). + * Return a promise which will be resolved with the updated sessions + * when all init data are processed or rejected if any failure. + */ +function ProcessInitData(v, test, token, initData, sessionType) { + return Promise.all( + initData.map(ev => { + let session = v.mediaKeys.createSession(sessionType); + return MakeRequest(test, token, ev, session, sessionType); + }) + ); +} + +/* + * Clean up the |v| element. + */ +function CleanUpMedia(v) { + v.setMediaKeys(null); + v.remove(); + v.removeAttribute("src"); + v.load(); +} + +/* + * Close all sessions and clean up the |v| element. + */ +function CloseSessions(v, sessions) { + return Promise.all(sessions.map(s => s.close())).then(CleanUpMedia(v)); +} + +/* + * Set up media keys and source buffers for the media element. + * Return a promise resolved when all key sessions are updated or rejected + * if any failure. + */ +function SetupEME(v, test, token) { + let p = new EMEPromise(); + + v.onerror = function() { + p.reject(`${token} got an error event.`); + }; + + Promise.all([ + LoadInitData(v, test, token), + CreateAndSetMediaKeys(v, test, token), + LoadTest(test, v, token), + ]) + .then(values => { + let initData = values[0]; + return ProcessInitData(v, test, token, initData); + }) + .then(p.resolve, p.reject); + + return p.promise; +} + +function SetupEMEPref(callback) { + var prefs = [ + ["media.mediasource.enabled", true], + ["media.mediasource.webm.enabled", true], + ]; + + if ( + SpecialPowers.Services.appinfo.name == "B2G" || + !manifestVideo().canPlayType("video/mp4") + ) { + // XXX remove once we have mp4 PlatformDecoderModules on all platforms. + prefs.push(["media.use-blank-decoder", true]); + } + + SpecialPowers.pushPrefEnv({ set: prefs }, callback); +} + +function fetchWithXHR(uri, onLoadFunction) { + var p = new Promise(function(resolve, reject) { + var 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(); + }); + + if (onLoadFunction) { + p.then(onLoadFunction); + } + + return p; +} + +function once(target, name, cb) { + var p = new Promise(function(resolve, reject) { + target.addEventListener( + name, + function(arg) { + resolve(arg); + }, + { once: true } + ); + }); + if (cb) { + p.then(cb); + } + return p; +} diff --git a/dom/media/test/empty_size.mp3 b/dom/media/test/empty_size.mp3 Binary files differnew file mode 100644 index 0000000000..0c208a2959 --- /dev/null +++ b/dom/media/test/empty_size.mp3 diff --git a/dom/media/test/file_access_controls.html b/dom/media/test/file_access_controls.html new file mode 100644 index 0000000000..2f7bc360ed --- /dev/null +++ b/dom/media/test/file_access_controls.html @@ -0,0 +1,160 @@ +<html> +<head> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="setTimeout(load, 0);"> +<script> + +// Page URL: http://example.org/tests/dom/media/test/file_access_controls.html + +var gResource = getPlayableVideo(gSmallTests).name; + +var gTests = [ + { + // Test 0 + url: "redirect.sjs?domain=example.com&file="+ gResource, + result: "error", + description: "Won't load when redirected to different domain", + },{ + // Test 1 + url: "redirect.sjs?domain=example.com&allowed&file=" + gResource, + result: "loadeddata", + description: "Can load when redirected to different domain with allow-origin", + },{ + // Test 2 + url: "redirect.sjs?domain=test1.example.org&file=" + gResource, + result: "error", + description: "Won't load when redirected to subdomain", + },{ + // Test 3 + url: "redirect.sjs?domain=test1.example.org&allowed&file=" + gResource, + result: "loadeddata", + description: "Can load when redirected to subdomain with allow-origin", + },{ + // Test 4 + url: "redirect.sjs?domain=example.org&file=" + gResource, + result: "loadeddata", + description: "Can load when redirected to same domain", + },{ + // Test 5 + url: "http://example.org/tests/dom/media/test/" + gResource, + result: "loadeddata", + description: "Can load from same domain" + },{ + // Test 6 + url: "http://example.org:8000/tests/dom/media/test/" + gResource, + result: "error", + description: "Won't load from different port on same domain" + },{ + // Test 7 + url: "http://example.org:8000/tests/dom/media/test/allowed.sjs?" + gResource, + result: "loadeddata", + description: "Can load from different port on same domain with allow-origin", + },{ + // Test 8 + url: "http://example.com/tests/dom/media/test/" + gResource, + result: "error", + description: "Won't load cross domain", + },{ + // Test 9 + url: "http://example.com/tests/dom/media/test/allowed.sjs?" + gResource, + result: "loadeddata", + description: "Can load cross domain with allow-origin", + },{ + // Test 10 + url: "http://test1.example.org/tests/dom/media/test/allowed.sjs?" + gResource, + result: "loadeddata", + description: "Can load from subdomain with allow-origin", + },{ + // Test 11 + url: "http://test1.example.org/tests/dom/media/test/" + gResource, + result: "error", + description: "Won't load from subdomain", + } +]; + +var gTestNum = 0; +var gVideo = null; +var gTestedRemoved = false; + +function eventHandler(event) { + //dump((gTestNum - 1) + ": " + event.type + "\n"); + var video = event.target; + opener.postMessage({"result": (event.type == video.expectedResult), + "message": video.testDescription + (gTestedRemoved ? " (element not in document)" : " (element in document)")}, + "http://mochi.test:8888"); + // Make sure any extra events cause an error + video.expectedResult = "<none>"; + nextTest(); +} + +function createVideo() { + var v = document.createElement('video'); + v.addEventListener('loadeddata', eventHandler); + v.addEventListener('error', eventHandler); + v.crossOrigin = 'anonymous'; + return v; +} + +function load() { + opener.postMessage({"result": (window.location.href == "http://example.org/tests/dom/media/test/file_access_controls.html"), + "message": "We must be on a example.org:80"}, + "http://mochi.test:8888"); + + nextTest(); +} + +function nextTest() { + //dump("nextTest() called, gTestNum="+gTestNum+" gTestedRemoved="+gTestedRemoved+"\n"); + if (gTestNum == gTests.length) { + //dump("gTestNum == gTests.length\n"); + if (!gTestedRemoved) { + // Repeat all tests with element removed from doc, should get same result. + gTestedRemoved = true; + gTestNum = 0; + } else { + //dump("Exiting...\n"); + // We're done, exit the test. + done(); + window.close(); + return; + } + } + + if (gVideo) { + gVideo.remove(); + gVideo.removeAttribute("src"); + gVideo.load(); + } + + gVideo = null; + SpecialPowers.forceGC(); + + gVideo = createVideo(); + gVideo.expectedResult = gTests[gTestNum].result; + gVideo.testDescription = gTests[gTestNum].description; + // Uniquify the resource URL to ensure that the resources loaded by earlier or subsequent tests + // don't overlap with the resources we load here, which are loaded with non-default preferences set. + // We also want to make sure that an HTTP fetch actually happens for each testcase. + var url = gTests[gTestNum].url; + var random = Math.floor(Math.random()*1000000000); + url += (url.search(/\?/) < 0 ? "?" : "&") + "rand=" + random; + gVideo.src = url; + //dump("Starting test " + gTestNum + " at " + gVideo.src + " expecting:" + gVideo.expectedResult + "\n"); + if (!gTestedRemoved) { + document.body.appendChild(gVideo); + // Will cause load() to be invoked. + } else { + gVideo.load(); + } + gTestNum++; +} + +function done() { + opener.postMessage({"done": "true"}, "http://mochi.test:8888"); +} + +</script> +</body> +</html> + diff --git a/dom/media/test/file_eme_createMediaKeys.html b/dom/media/test/file_eme_createMediaKeys.html new file mode 100644 index 0000000000..3ff782b1bf --- /dev/null +++ b/dom/media/test/file_eme_createMediaKeys.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<head> +<title>Eme createMediaKeys helper page</title> +<script> +// This script waits for a message then attempts to requestMediaKeySystemAccess +// then createMediaKeys. On success posts 'successCreatingMediaKeys' to the +// source of the message, on failure posts 'failureCreatingMediaKeys' and a +// description of the failure to the source of the message. + +async function createMediaKeys() { + const clearKeyOptions = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + }, + ]; + + let access = await navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + clearKeyOptions + ); + + return access.createMediaKeys(); +} +function setupMessageListener() { + window.onmessage = async event => { + // We don't bother checking the message data since it should always be + // telling us to create media keys. + try { + let keys = await createMediaKeys(); + if (!keys) { + event.source.postMessage("failureCreatingMediaKeys no keys", "*"); + return; + } + event.source.postMessage("successCreatingMediaKeys", "*"); + } catch (e) { + event.source.postMessage(`failureCreatingMediaKeys ${e}`, "*"); + } + }; +} +window.onload = setupMessageListener; +</script> +</head> +<body> +</body> +</html> diff --git a/dom/media/test/flac-noheader-s16.flac b/dom/media/test/flac-noheader-s16.flac Binary files differnew file mode 100644 index 0000000000..01152142a9 --- /dev/null +++ b/dom/media/test/flac-noheader-s16.flac diff --git a/dom/media/test/flac-noheader-s16.flac^headers^ b/dom/media/test/flac-noheader-s16.flac^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/flac-noheader-s16.flac^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/flac-s24.flac b/dom/media/test/flac-s24.flac Binary files differnew file mode 100644 index 0000000000..1ba5e27a15 --- /dev/null +++ b/dom/media/test/flac-s24.flac diff --git a/dom/media/test/flac-s24.flac^headers^ b/dom/media/test/flac-s24.flac^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/flac-s24.flac^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/flac-sample-cenc.mp4 b/dom/media/test/flac-sample-cenc.mp4 Binary files differnew file mode 100644 index 0000000000..c89190387a --- /dev/null +++ b/dom/media/test/flac-sample-cenc.mp4 diff --git a/dom/media/test/flac-sample-cenc.mp4^headers^ b/dom/media/test/flac-sample-cenc.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/flac-sample-cenc.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/flac-sample.mp4 b/dom/media/test/flac-sample.mp4 Binary files differnew file mode 100644 index 0000000000..d39c94a7b2 --- /dev/null +++ b/dom/media/test/flac-sample.mp4 diff --git a/dom/media/test/flac-sample.mp4^headers^ b/dom/media/test/flac-sample.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/flac-sample.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/fragment_noplay.js b/dom/media/test/fragment_noplay.js new file mode 100644 index 0000000000..247641763f --- /dev/null +++ b/dom/media/test/fragment_noplay.js @@ -0,0 +1,19 @@ +function test_fragment_noplay(v, start, end, is, ok, finish) { + function onLoadedMetadata() { + var s = start == null ? 0 : start; + var e = end == null ? v.duration : end; + var a = s - 0.15; + var b = s + 0.15; + ok( + v.currentTime >= a && v.currentTime <= b, + "loadedmetadata currentTime is " + a + " < " + v.currentTime + " < " + b + ); + ok( + v.mozFragmentEnd == e, + "mozFragmentEnd (" + v.mozFragmentEnd + ") == end Time (" + e + ")" + ); + finish(); + } + + v.addEventListener("loadedmetadata", onLoadedMetadata); +} diff --git a/dom/media/test/fragment_play.js b/dom/media/test/fragment_play.js new file mode 100644 index 0000000000..ec0fe7952a --- /dev/null +++ b/dom/media/test/fragment_play.js @@ -0,0 +1,92 @@ +function test_fragment_play(v, start, end, is, ok, finish) { + var completed = false; + var loadedMetadataRaised = false; + var seekedRaised = false; + var pausedRaised = false; + + function onLoadedMetadata() { + var s = start == null ? 0 : start; + var e = end == null ? v.duration : end; + ok( + v.currentTime == s, + "loadedmetadata currentTime is " + v.currentTime + " != " + s + ); + ok( + v.mozFragmentEnd == e, + "mozFragmentEnd (" + v.mozFragmentEnd + ") == end Time (" + e + ")" + ); + loadedMetadataRaised = true; + v.play(); + } + + function onSeeked() { + if (completed) { + return; + } + + var s = start == null ? 0 : start; + ok( + v.currentTime - s < 0.1, + "seeked currentTime is " + + v.currentTime + + " != " + + s + + " (fuzzy compare +-0.1)" + ); + + seekedRaised = true; + } + + function onTimeUpdate() { + if (completed) { + return; + } + + v._lastTimeUpdate = v.currentTime; + } + + function onPause() { + if (completed) { + return; + } + + var e = end == null ? v.duration : end; + var a = e - 0.05; + var b = e + 0.05; + ok( + v.currentTime >= a && v.currentTime <= b, + "paused currentTime is " + + a + + " < " + + v.currentTime + + " < " + + b + + " ? " + + v._lastTimeUpdate + ); + pausedRaised = true; + v.play(); + } + + function onEnded() { + if (completed) { + return; + } + + completed = true; + ok(loadedMetadataRaised, "loadedmetadata event"); + if (start) { + ok(seekedRaised, "seeked event"); + } + if (end) { + ok(pausedRaised, "paused event: " + end + " " + v.duration); + } + finish(); + } + + v.addEventListener("ended", onEnded); + v.addEventListener("loadedmetadata", onLoadedMetadata); + v.addEventListener("seeked", onSeeked); + v.addEventListener("pause", onPause); + v.addEventListener("timeupdate", onTimeUpdate); +} diff --git a/dom/media/test/gUM_support.js b/dom/media/test/gUM_support.js new file mode 100644 index 0000000000..2be885abf4 --- /dev/null +++ b/dom/media/test/gUM_support.js @@ -0,0 +1,106 @@ +// Support script for test that use getUserMedia. This allows explicit +// configuration of prefs which affect gUM. See also +// `testing/mochitest/runtests.py` for how the harness configures values. + +// Setup preconditions for tests using getUserMedia. This functions helps +// manage different prefs that affect gUM calls in tests and makes explicit +// the expected state before test runs. +async function pushGetUserMediaTestPrefs({ + fakeAudio = false, + fakeVideo = false, + loopbackAudio = false, + loopbackVideo = false, +}) { + // Make sure we have sensical arguments + if (!fakeAudio && !loopbackAudio) { + throw new Error( + "pushGetUserMediaTestPrefs: Should have fake or loopback audio!" + ); + } else if (fakeAudio && loopbackAudio) { + throw new Error( + "pushGetUserMediaTestPrefs: Should not have both fake and loopback audio!" + ); + } + if (!fakeVideo && !loopbackVideo) { + throw new Error( + "pushGetUserMediaTestPrefs: Should have fake or loopback video!" + ); + } else if (fakeVideo && loopbackVideo) { + throw new Error( + "pushGetUserMediaTestPrefs: Should not have both fake and loopback video!" + ); + } + + let testPrefs = []; + if (fakeAudio) { + // Unset the loopback device so it doesn't take precedence + testPrefs.push(["media.audio_loopback_dev", ""]); + // Setup fake streams pref + testPrefs.push(["media.navigator.streams.fake", true]); + } + if (loopbackAudio) { + // If audio loopback is requested we expect the test harness to have set + // the loopback device pref, make sure it's set + let audioLoopDev = SpecialPowers.getCharPref( + "media.audio_loopback_dev", + "" + ); + if (!audioLoopDev) { + throw new Error( + "pushGetUserMediaTestPrefs: Loopback audio requested but " + + "media.audio_loopback_dev does not appear to be set!" + ); + } + } + if (fakeVideo) { + // Unset the loopback device so it doesn't take precedence + testPrefs.push(["media.video_loopback_dev", ""]); + // Setup fake streams pref + testPrefs.push(["media.navigator.streams.fake", true]); + } + if (loopbackVideo) { + // If video loopback is requested we expect the test harness to have set + // the loopback device pref, make sure it's set + let videoLoopDev = SpecialPowers.getCharPref( + "media.video_loopback_dev", + "" + ); + if (!videoLoopDev) { + throw new Error( + "pushGetUserMediaTestPrefs: Loopback video requested but " + + "media.video_loopback_dev does not appear to be set!" + ); + } + } + if (loopbackAudio || loopbackVideo) { + // Prevent gUM permission prompt. Since loopback devices are considered + // real devices we need to set prefs so the gUM prompt isn't presented. + testPrefs.push(["media.navigator.permission.disabled", true]); + } + return SpecialPowers.pushPrefEnv({ set: testPrefs }); +} + +// Setup preconditions for tests using getUserMedia. This function will +// configure prefs to select loopback device(s) if it can find loopback device +// names already set in the prefs. If no loopback device name can be found then +// prefs are setup such that a fake device is used. +async function setupGetUserMediaTestPrefs() { + let prefRequests = {}; + let audioLoopDev = SpecialPowers.getCharPref("media.audio_loopback_dev", ""); + if (audioLoopDev) { + prefRequests.fakeAudio = false; + prefRequests.loopbackAudio = true; + } else { + prefRequests.fakeAudio = true; + prefRequests.loopbackAudio = false; + } + let videoLoopDev = SpecialPowers.getCharPref("media.video_loopback_dev", ""); + if (videoLoopDev) { + prefRequests.fakeVideo = false; + prefRequests.loopbackVideo = true; + } else { + prefRequests.fakeVideo = true; + prefRequests.loopbackVideo = false; + } + return pushGetUserMediaTestPrefs(prefRequests); +} diff --git a/dom/media/test/gizmo-frag.mp4 b/dom/media/test/gizmo-frag.mp4 Binary files differnew file mode 100644 index 0000000000..f6980663c2 --- /dev/null +++ b/dom/media/test/gizmo-frag.mp4 diff --git a/dom/media/test/gizmo-noaudio.mp4 b/dom/media/test/gizmo-noaudio.mp4 Binary files differnew file mode 100644 index 0000000000..24732a4064 --- /dev/null +++ b/dom/media/test/gizmo-noaudio.mp4 diff --git a/dom/media/test/gizmo-noaudio.mp4^headers^ b/dom/media/test/gizmo-noaudio.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/gizmo-noaudio.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/gizmo-noaudio.webm b/dom/media/test/gizmo-noaudio.webm Binary files differnew file mode 100644 index 0000000000..9f412cb6e3 --- /dev/null +++ b/dom/media/test/gizmo-noaudio.webm diff --git a/dom/media/test/gizmo-noaudio.webm^headers^ b/dom/media/test/gizmo-noaudio.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/gizmo-noaudio.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/gizmo-short.mp4 b/dom/media/test/gizmo-short.mp4 Binary files differnew file mode 100644 index 0000000000..f8caec741e --- /dev/null +++ b/dom/media/test/gizmo-short.mp4 diff --git a/dom/media/test/gizmo-short.mp4^headers^ b/dom/media/test/gizmo-short.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/gizmo-short.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/gizmo.mp4 b/dom/media/test/gizmo.mp4 Binary files differnew file mode 100644 index 0000000000..87efad5ade --- /dev/null +++ b/dom/media/test/gizmo.mp4 diff --git a/dom/media/test/gizmo.mp4^headers^ b/dom/media/test/gizmo.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/gizmo.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/gizmo.webm b/dom/media/test/gizmo.webm Binary files differnew file mode 100644 index 0000000000..518531a93f --- /dev/null +++ b/dom/media/test/gizmo.webm diff --git a/dom/media/test/gizmo.webm^headers^ b/dom/media/test/gizmo.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/gizmo.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/gzipped_mp4.sjs b/dom/media/test/gzipped_mp4.sjs new file mode 100644 index 0000000000..132b87e851 --- /dev/null +++ b/dom/media/test/gzipped_mp4.sjs @@ -0,0 +1,27 @@ +function getGzippedFileBytes()
+{
+ var file;
+ getObjectState("SERVER_ROOT", function(serverRoot) {
+ file = serverRoot.getFile("tests/dom/media/test/short.mp4.gz");
+ });
+ var fileInputStream =
+ Components.classes['@mozilla.org/network/file-input-stream;1']
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ var binaryInputStream =
+ Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ fileInputStream.init(file, -1, -1, 0);
+ binaryInputStream.setInputStream(fileInputStream);
+ return binaryInputStream.readBytes(binaryInputStream.available());
+}
+
+function handleRequest(request, response)
+{
+ var bytes = getGzippedFileBytes();
+ response.setHeader("Content-Length", String(bytes.length), false);
+ response.setHeader("Content-Type", "video/mp4", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(bytes, bytes.length);
+}
diff --git a/dom/media/test/hls/400x300_prog_index.m3u8 b/dom/media/test/hls/400x300_prog_index.m3u8 new file mode 100644 index 0000000000..3252eb178f --- /dev/null +++ b/dom/media/test/hls/400x300_prog_index.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:9.97667, +400x300_seg0.ts +#EXTINF:9.97667, +400x300_seg1.ts +#EXT-X-ENDLIST diff --git a/dom/media/test/hls/400x300_prog_index_5s.m3u8 b/dom/media/test/hls/400x300_prog_index_5s.m3u8 new file mode 100644 index 0000000000..8e9d19f764 --- /dev/null +++ b/dom/media/test/hls/400x300_prog_index_5s.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-TARGETDURATION:4.00 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:4.00, +400x300_seg0_5s.ts +#EXT-X-ENDLIST diff --git a/dom/media/test/hls/400x300_seg0.ts b/dom/media/test/hls/400x300_seg0.ts Binary files differnew file mode 100644 index 0000000000..b17b0a88ff --- /dev/null +++ b/dom/media/test/hls/400x300_seg0.ts diff --git a/dom/media/test/hls/400x300_seg0_5s.ts b/dom/media/test/hls/400x300_seg0_5s.ts Binary files differnew file mode 100644 index 0000000000..9504d5d889 --- /dev/null +++ b/dom/media/test/hls/400x300_seg0_5s.ts diff --git a/dom/media/test/hls/400x300_seg1.ts b/dom/media/test/hls/400x300_seg1.ts Binary files differnew file mode 100644 index 0000000000..d751091e68 --- /dev/null +++ b/dom/media/test/hls/400x300_seg1.ts diff --git a/dom/media/test/hls/416x243_prog_index_5s.m3u8 b/dom/media/test/hls/416x243_prog_index_5s.m3u8 new file mode 100644 index 0000000000..ae518e7890 --- /dev/null +++ b/dom/media/test/hls/416x243_prog_index_5s.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-TARGETDURATION:4.04 +#EXT-X-VERSION:4 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:4.04, +416x243_seg0_5s.ts +#EXT-X-ENDLIST diff --git a/dom/media/test/hls/416x243_seg0_5s.ts b/dom/media/test/hls/416x243_seg0_5s.ts Binary files differnew file mode 100644 index 0000000000..48e5473276 --- /dev/null +++ b/dom/media/test/hls/416x243_seg0_5s.ts diff --git a/dom/media/test/hls/640x480_prog_index.m3u8 b/dom/media/test/hls/640x480_prog_index.m3u8 new file mode 100644 index 0000000000..ac1212e2e7 --- /dev/null +++ b/dom/media/test/hls/640x480_prog_index.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:9.97667, +640x480_seg0.ts +#EXTINF:9.97667, +640x480_seg1.ts +#EXT-X-ENDLIST diff --git a/dom/media/test/hls/640x480_seg0.ts b/dom/media/test/hls/640x480_seg0.ts Binary files differnew file mode 100644 index 0000000000..9bf0f0454a --- /dev/null +++ b/dom/media/test/hls/640x480_seg0.ts diff --git a/dom/media/test/hls/640x480_seg1.ts b/dom/media/test/hls/640x480_seg1.ts Binary files differnew file mode 100644 index 0000000000..c1ed938f44 --- /dev/null +++ b/dom/media/test/hls/640x480_seg1.ts diff --git a/dom/media/test/hls/960x720_prog_index.m3u8 b/dom/media/test/hls/960x720_prog_index.m3u8 new file mode 100644 index 0000000000..8ff18b089c --- /dev/null +++ b/dom/media/test/hls/960x720_prog_index.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:9.97667, +960x720_seg0.ts +#EXTINF:9.97667, +960x720_seg1.ts +#EXT-X-ENDLIST diff --git a/dom/media/test/hls/960x720_seg0.ts b/dom/media/test/hls/960x720_seg0.ts Binary files differnew file mode 100644 index 0000000000..031bfe30d6 --- /dev/null +++ b/dom/media/test/hls/960x720_seg0.ts diff --git a/dom/media/test/hls/960x720_seg1.ts b/dom/media/test/hls/960x720_seg1.ts Binary files differnew file mode 100644 index 0000000000..63f15cb0cb --- /dev/null +++ b/dom/media/test/hls/960x720_seg1.ts diff --git a/dom/media/test/hls/bipbop_16x9_single.m3u8 b/dom/media/test/hls/bipbop_16x9_single.m3u8 new file mode 100644 index 0000000000..dce6a76c7b --- /dev/null +++ b/dom/media/test/hls/bipbop_16x9_single.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U + +#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d" +416x243_prog_index_5s.m3u8 + diff --git a/dom/media/test/hls/bipbop_4x3_single.m3u8 b/dom/media/test/hls/bipbop_4x3_single.m3u8 new file mode 100644 index 0000000000..8f354ff011 --- /dev/null +++ b/dom/media/test/hls/bipbop_4x3_single.m3u8 @@ -0,0 +1,4 @@ +#EXTM3U + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=232370,CODECS="mp4a.40.2, avc1.4d4015" +400x300_prog_index_5s.m3u8
\ No newline at end of file diff --git a/dom/media/test/hls/bipbop_4x3_variant.m3u8 b/dom/media/test/hls/bipbop_4x3_variant.m3u8 new file mode 100644 index 0000000000..8a9a100dba --- /dev/null +++ b/dom/media/test/hls/bipbop_4x3_variant.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=232370,CODECS="mp4a.40.2, avc1.4d4015" +400x300_prog_index.m3u8 + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=649879,CODECS="mp4a.40.2, avc1.4d401e" +640x480_prog_index.m3u8 + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=649879,CODECS="mp4a.40.2, avc1.4d401e" +960x720_prog_index.m3u8 diff --git a/dom/media/test/huge-id3.mp3 b/dom/media/test/huge-id3.mp3 Binary files differnew file mode 100644 index 0000000000..41cb93d805 --- /dev/null +++ b/dom/media/test/huge-id3.mp3 diff --git a/dom/media/test/huge-id3.mp3^headers^ b/dom/media/test/huge-id3.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/huge-id3.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/id3tags.mp3 b/dom/media/test/id3tags.mp3 Binary files differnew file mode 100644 index 0000000000..bad506cf18 --- /dev/null +++ b/dom/media/test/id3tags.mp3 diff --git a/dom/media/test/id3tags.mp3^headers^ b/dom/media/test/id3tags.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/id3tags.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-cmap-s0c0.opus b/dom/media/test/invalid-cmap-s0c0.opus Binary files differnew file mode 100644 index 0000000000..0b99587865 --- /dev/null +++ b/dom/media/test/invalid-cmap-s0c0.opus diff --git a/dom/media/test/invalid-cmap-s0c0.opus^headers^ b/dom/media/test/invalid-cmap-s0c0.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-cmap-s0c0.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-cmap-s0c2.opus b/dom/media/test/invalid-cmap-s0c2.opus Binary files differnew file mode 100644 index 0000000000..a921894fee --- /dev/null +++ b/dom/media/test/invalid-cmap-s0c2.opus diff --git a/dom/media/test/invalid-cmap-s0c2.opus^headers^ b/dom/media/test/invalid-cmap-s0c2.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-cmap-s0c2.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-cmap-s1c2.opus b/dom/media/test/invalid-cmap-s1c2.opus Binary files differnew file mode 100644 index 0000000000..95a84f523c --- /dev/null +++ b/dom/media/test/invalid-cmap-s1c2.opus diff --git a/dom/media/test/invalid-cmap-s1c2.opus^headers^ b/dom/media/test/invalid-cmap-s1c2.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-cmap-s1c2.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-cmap-short.opus b/dom/media/test/invalid-cmap-short.opus Binary files differnew file mode 100644 index 0000000000..fcd7eb506a --- /dev/null +++ b/dom/media/test/invalid-cmap-short.opus diff --git a/dom/media/test/invalid-cmap-short.opus^headers^ b/dom/media/test/invalid-cmap-short.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-cmap-short.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-discard_on_multi_blocks.webm b/dom/media/test/invalid-discard_on_multi_blocks.webm Binary files differnew file mode 100644 index 0000000000..f39ab5bcb8 --- /dev/null +++ b/dom/media/test/invalid-discard_on_multi_blocks.webm diff --git a/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^ b/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-excess_discard.webm b/dom/media/test/invalid-excess_discard.webm Binary files differnew file mode 100644 index 0000000000..5b34aca1a7 --- /dev/null +++ b/dom/media/test/invalid-excess_discard.webm diff --git a/dom/media/test/invalid-excess_discard.webm^headers^ b/dom/media/test/invalid-excess_discard.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-excess_discard.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-excess_neg_discard.webm b/dom/media/test/invalid-excess_neg_discard.webm Binary files differnew file mode 100644 index 0000000000..2bfad6ed21 --- /dev/null +++ b/dom/media/test/invalid-excess_neg_discard.webm diff --git a/dom/media/test/invalid-excess_neg_discard.webm^headers^ b/dom/media/test/invalid-excess_neg_discard.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-excess_neg_discard.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m0c0.opus b/dom/media/test/invalid-m0c0.opus Binary files differnew file mode 100644 index 0000000000..86555f60eb --- /dev/null +++ b/dom/media/test/invalid-m0c0.opus diff --git a/dom/media/test/invalid-m0c0.opus^headers^ b/dom/media/test/invalid-m0c0.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m0c0.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m0c3.opus b/dom/media/test/invalid-m0c3.opus Binary files differnew file mode 100644 index 0000000000..2c681a8c03 --- /dev/null +++ b/dom/media/test/invalid-m0c3.opus diff --git a/dom/media/test/invalid-m0c3.opus^headers^ b/dom/media/test/invalid-m0c3.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m0c3.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m1c0.opus b/dom/media/test/invalid-m1c0.opus Binary files differnew file mode 100644 index 0000000000..c7728f79ee --- /dev/null +++ b/dom/media/test/invalid-m1c0.opus diff --git a/dom/media/test/invalid-m1c0.opus^headers^ b/dom/media/test/invalid-m1c0.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m1c0.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m1c9.opus b/dom/media/test/invalid-m1c9.opus Binary files differnew file mode 100644 index 0000000000..1ef6e9f9cf --- /dev/null +++ b/dom/media/test/invalid-m1c9.opus diff --git a/dom/media/test/invalid-m1c9.opus^headers^ b/dom/media/test/invalid-m1c9.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m1c9.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m2c0.opus b/dom/media/test/invalid-m2c0.opus Binary files differnew file mode 100644 index 0000000000..5c3f97e2ab --- /dev/null +++ b/dom/media/test/invalid-m2c0.opus diff --git a/dom/media/test/invalid-m2c0.opus^headers^ b/dom/media/test/invalid-m2c0.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m2c0.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-m2c1.opus b/dom/media/test/invalid-m2c1.opus Binary files differnew file mode 100644 index 0000000000..5ecb95ee25 --- /dev/null +++ b/dom/media/test/invalid-m2c1.opus diff --git a/dom/media/test/invalid-m2c1.opus^headers^ b/dom/media/test/invalid-m2c1.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-m2c1.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-neg_discard.webm b/dom/media/test/invalid-neg_discard.webm Binary files differnew file mode 100644 index 0000000000..3f665c0b59 --- /dev/null +++ b/dom/media/test/invalid-neg_discard.webm diff --git a/dom/media/test/invalid-neg_discard.webm^headers^ b/dom/media/test/invalid-neg_discard.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-neg_discard.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/invalid-preskip.webm b/dom/media/test/invalid-preskip.webm Binary files differnew file mode 100644 index 0000000000..99b4f2ca71 --- /dev/null +++ b/dom/media/test/invalid-preskip.webm diff --git a/dom/media/test/invalid-preskip.webm^headers^ b/dom/media/test/invalid-preskip.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/invalid-preskip.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/make-headers.sh b/dom/media/test/make-headers.sh new file mode 100644 index 0000000000..35d9bd90f8 --- /dev/null +++ b/dom/media/test/make-headers.sh @@ -0,0 +1,18 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Script to generate ^header^ files for all media files we use. +# This is to ensure that our media files are not cached by necko, +# so that our detection as to whether the server supports byte range +# requests is not interferred with by Necko's cache. See bug 977398 +# for details. Necko will fix this in bug 977314. + +FILES=(`ls *.ogg *.ogv *.webm *.mp3 *.opus *.mp4 *.m4s *.wav`) + +rm -f *.ogg^headers^ *.ogv^headers^ *.webm^headers^ *.mp3^headers^ *.opus^headers^ *.mp4^headers^ *.m4s^headers^ *.wav^headers^ + +for i in "${FILES[@]}" +do + echo "Cache-Control: no-store" >> $i^headers^ +done diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js new file mode 100644 index 0000000000..0d8d5b9300 --- /dev/null +++ b/dom/media/test/manifest.js @@ -0,0 +1,2266 @@ +// In each list of tests below, test file types that are not supported should +// be ignored. To make sure tests respect that, we include a file of type +// "bogus/duh" in each list. + +// Make sure to not touch navigator in here, since we want to push prefs that +// will affect the APIs it exposes, but the set of exposed APIs is determined +// when Navigator.prototype is created. So if we touch navigator before pushing +// the prefs, the APIs it exposes will not take those prefs into account. We +// work around this by using a navigator object from a different global for our +// UA string testing. +var gManifestNavigatorSource = document.documentElement.appendChild( + document.createElement("iframe") +); +gManifestNavigatorSource.style.display = "none"; +function manifestNavigator() { + return gManifestNavigatorSource.contentWindow.navigator; +} + +// Similarly, use a <video> element from a different global for canPlayType or +// other feature testing. If we used one from our global and did so before our +// prefs are pushed, then we'd instantiate HTMLMediaElement.prototype before the +// prefs are pushed and APIs we expect to be on that object would not be there. +function manifestVideo() { + return gManifestNavigatorSource.contentDocument.createElement("video"); +} + +// Need to get the server url composed with ip:port instead of mochi.test. +// Since we will provide the url to Exoplayer which cannot recognize the domain +// name "mochi.test". +let serverUrl = SpecialPowers.Services.prefs.getCharPref( + "media.hls.server.url" +); +var gHLSTests = [ + { + name: serverUrl + "/bipbop_4x3_variant.m3u8", + type: "audio/x-mpegurl", + duration: 20.0, + }, +]; + +// These are small test files, good for just seeing if something loads. We +// really only need one test file per backend here. +var gSmallTests = [ + { name: "small-shot.ogg", type: "audio/ogg", duration: 0.276 }, + { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 }, + { name: "small-shot.mp3", type: "audio/mpeg", duration: 0.27 }, + { name: "small-shot-mp3.mp4", type: "audio/mp4; codecs=mp3", duration: 0.34 }, + { name: "small-shot.flac", type: "audio/flac", duration: 0.197 }, + { name: "r11025_s16_c1-short.wav", type: "audio/x-wav", duration: 0.37 }, + { + name: "320x240.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 0.266, + contentDuration: 0.133, + }, + { + name: "seek-short.webm", + type: "video/webm", + width: 320, + height: 240, + duration: 0.23, + }, + { + name: "vp9-short.webm", + type: "video/webm", + width: 320, + height: 240, + duration: 0.2, + }, + { + name: "detodos-short.opus", + type: "audio/ogg; codecs=opus", + duration: 0.22, + }, + { + name: "gizmo-short.mp4", + type: "video/mp4", + width: 560, + height: 320, + duration: 0.27, + }, + { name: "flac-s24.flac", type: "audio/flac", duration: 4.04 }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +var gFrameCountTests = [ + { name: "bipbop.mp4", type: "video/mp4", totalFrameCount: 297 }, + { name: "gizmo.mp4", type: "video/mp4", totalFrameCount: 166 }, + { name: "seek-short.webm", type: "video/webm", totalFrameCount: 8 }, + { name: "seek.webm", type: "video/webm", totalFrameCount: 120 }, + { name: "320x240.ogv", type: "video/ogg", totalFrameCount: 8 }, + { name: "av1.mp4", type: "video/mp4", totalFrameCount: 24 }, +]; + +gSmallTests = gSmallTests.concat([ + { name: "sample.3gp", type: "video/3gpp", duration: 4.933 }, + { name: "sample.3g2", type: "video/3gpp2", duration: 4.933 }, +]); + +// Used by test_bug654550.html, for videoStats preference +var gVideoTests = [ + { + name: "320x240.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 0.266, + }, + { + name: "seek-short.webm", + type: "video/webm", + width: 320, + height: 240, + duration: 0.23, + }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// Temp hack for trackIDs and captureStream() -- bug 1215769 +var gLongerTests = [ + { + name: "seek.webm", + type: "video/webm", + width: 320, + height: 240, + duration: 3.966, + }, + { + name: "gizmo.mp4", + type: "video/mp4", + width: 560, + height: 320, + duration: 5.56, + }, +]; + +// Used by test_progress to ensure we get the correct progress information +// during resource download. +var gProgressTests = [ + { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0, size: 11069 }, + { name: "big-short.wav", type: "audio/x-wav", duration: 1.11, size: 12366 }, + { name: "seek-short.ogv", type: "video/ogg", duration: 1.03, size: 79921 }, + { + name: "320x240.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 0.266, + size: 28942, + }, + { name: "seek-short.webm", type: "video/webm", duration: 0.23, size: 19267 }, + { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27, size: 29905 }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// Used by test_played.html +var gPlayedTests = [ + { name: "big-short.wav", type: "audio/x-wav", duration: 1.11 }, + { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 }, + { name: "seek-short.webm", type: "video/webm", duration: 0.23 }, + { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27 }, + { name: "owl-short.mp3", type: "audio/mpeg", duration: 0.52 }, + { name: "very-short.mp3", type: "audio/mpeg", duration: 0.07 }, + // Disable vbr.mp3 to see if it reduces the error of AUDCLNT_E_CPUUSAGE_EXCEEDED. + // See bug 1110922 comment 26. + //{ name:"vbr.mp3", type:"audio/mpeg", duration:10.0 }, + { name: "bug495794.ogg", type: "audio/ogg", duration: 0.3 }, +]; + +if ( + manifestNavigator().userAgent.includes("Windows") && + manifestVideo().canPlayType('video/mp4; codecs="avc1.42E01E"') +) { + gPlayedTests = gPlayedTests.concat( + { name: "red-46x48.mp4", type: "video/mp4", duration: 1.0 }, + { name: "red-48x46.mp4", type: "video/mp4", duration: 1.0 } + ); +} + +// Used by test_mozLoadFrom. Need one test file per decoder backend, plus +// anything for testing clone-specific bugs. +var cloneKey = Math.floor(Math.random() * 100000000); +var gCloneTests = [ + // short-video is more like 1s, so if you load this twice you'll get an unexpected duration + { + name: + "dynamic_resource.sjs?key=" + + cloneKey + + "&res1=320x240.ogv&res2=short-video.ogv", + type: "video/ogg", + duration: 0.266, + }, +]; + +// Used by test_play_twice. Need one test file per decoder backend, plus +// anything for testing bugs that occur when replying a played file. +var gReplayTests = gSmallTests.concat([ + { name: "bug533822.ogg", type: "audio/ogg" }, +]); + +// Used by test_paused_after_ended. Need one test file per decoder backend, plus +// anything for testing bugs that occur when replying a played file. +var gPausedAfterEndedTests = gSmallTests.concat([ + { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0 }, + { name: "small-shot.ogg", type: "video/ogg", duration: 0.276 }, +]); + +// Test the mozHasAudio property, and APIs that detect different kinds of +// tracks +var gTrackTests = [ + { + name: "big-short.wav", + type: "audio/x-wav", + duration: 1.11, + size: 12366, + hasAudio: true, + hasVideo: false, + }, + { + name: "320x240.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 0.266, + size: 28942, + hasAudio: false, + hasVideo: true, + }, + { + name: "short-video.ogv", + type: "video/ogg", + duration: 1.081, + hasAudio: true, + hasVideo: true, + }, + { + name: "seek-short.webm", + type: "video/webm", + duration: 0.23, + size: 19267, + hasAudio: false, + hasVideo: true, + }, + { + name: "flac-s24.flac", + type: "audio/flac", + duration: 4.04, + hasAudio: true, + hasVideo: false, + }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +var gClosingConnectionsTest = [ + { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 }, +]; + +// Used by any media recorder test. Need one test file per decoder backend +// currently supported by the media encoder. +var gMediaRecorderTests = [ + // Duration should be greater than 500ms because we will record 2 + // time slices (250ms per slice) + { + name: "detodos-recorder-test.opus", + type: "audio/ogg; codecs=opus", + duration: 0.62, + }, +]; + +// Used by video media recorder tests +var gMediaRecorderVideoTests = [ + { + name: "seek-short.webm", + type: "video/webm", + width: 320, + height: 240, + duration: 0.23, + }, +]; + +// These are files that we want to make sure we can play through. We can +// also check metadata. Put files of the same type together in this list so if +// something crashes we have some idea of which backend is responsible. +// Used by test_playback, which expects no error event and one ended event. +var gPlayTests = [ + // Test playback of a WebM file with vp9 video + { name: "vp9cake-short.webm", type: "video/webm", duration: 1.0 }, + // 8-bit samples + { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0 }, + // 8-bit samples, file is truncated + { name: "r11025_u8_c1_trunc.wav", type: "audio/x-wav", duration: 1.8 }, + // file has trailing non-PCM data + { name: "r11025_s16_c1_trailing.wav", type: "audio/x-wav", duration: 1.0 }, + // file with list chunk + { name: "r16000_u8_c1_list.wav", type: "audio/x-wav", duration: 4.2 }, + // file with 2 extra bytes of metadata + { + name: "16bit_wave_extrametadata.wav", + type: "audio/x-wav", + duration: 1.108, + }, + // IEEE float wave file + { name: "wavedata_float.wav", type: "audio/x-wav", duration: 1.0 }, + // 24-bit samples + { name: "wavedata_s24.wav", type: "audio/x-wav", duration: 1.0 }, + // aLaw compressed wave file + { name: "wavedata_alaw.wav", type: "audio/x-wav", duration: 1.0 }, + // uLaw compressed wave file + { name: "wavedata_ulaw.wav", type: "audio/x-wav", duration: 1.0 }, + // Data length 0xFFFFFFFF + { name: "bug1301226.wav", type: "audio/x-wav", duration: 0.003673 }, + // Data length 0xFFFFFFFF and odd chunk lengths. + { name: "bug1301226-odd.wav", type: "audio/x-wav", duration: 0.003673 }, + + // Ogg stream without eof marker + { name: "bug461281.ogg", type: "application/ogg", duration: 2.208 }, + + // oggz-chop stream + { name: "bug482461.ogv", type: "video/ogg", duration: 4.34 }, + // Theora only oggz-chop stream + { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 }, + // With first frame a "duplicate" (empty) frame. + { + name: "bug500311.ogv", + type: "video/ogg", + duration: 1.96, + contentDuration: 1.958, + }, + // Small audio file + { name: "small-shot.ogg", type: "audio/ogg", duration: 0.276 }, + // More audio in file than video. + { name: "short-video.ogv", type: "video/ogg", duration: 1.081 }, + // First Theora data packet is zero bytes. + { name: "bug504613.ogv", type: "video/ogg", duration: Number.NaN }, + // Multiple audio streams. + { name: "bug516323.ogv", type: "video/ogg", duration: 4.208 }, + // oggz-chop with non-keyframe as first frame + { + name: "bug556821.ogv", + type: "video/ogg", + duration: 2.936, + contentDuration: 2.903, + }, + + // Encoded with vorbis beta1, includes unusually sized codebooks + { name: "beta-phrasebook.ogg", type: "audio/ogg", duration: 4.01 }, + // Small file, only 1 frame with audio only. + { name: "bug520493.ogg", type: "audio/ogg", duration: 0.458 }, + // Small file with vorbis comments with 0 length values and names. + { name: "bug520500.ogg", type: "audio/ogg", duration: 0.123 }, + + // Various weirdly formed Ogg files + { + name: "bug499519.ogv", + type: "video/ogg", + duration: 0.24, + contentDuration: 0.22, + }, + { name: "bug506094.ogv", type: "video/ogg", duration: 0 }, + { name: "bug498855-1.ogv", type: "video/ogg", duration: 0.24 }, + { name: "bug498855-2.ogv", type: "video/ogg", duration: 0.24 }, + { name: "bug498855-3.ogv", type: "video/ogg", duration: 0.24 }, + { + name: "bug504644.ogv", + type: "video/ogg", + duration: 1.6, + contentDuration: 1.52, + }, + { + name: "chain.ogv", + type: "video/ogg", + duration: Number.NaN, + contentDuration: 0.266, + }, + { + name: "bug523816.ogv", + type: "video/ogg", + duration: 0.766, + contentDuration: 0, + }, + { name: "bug495129.ogv", type: "video/ogg", duration: 2.41 }, + { + name: "bug498380.ogv", + type: "video/ogg", + duration: 0.7663, + contentDuration: 0, + }, + { name: "bug495794.ogg", type: "audio/ogg", duration: 0.3 }, + { name: "bug557094.ogv", type: "video/ogg", duration: 0.24 }, + { name: "multiple-bos.ogg", type: "video/ogg", duration: 0.431 }, + { name: "audio-overhang.ogg", type: "video/ogg", duration: 2.3 }, + { name: "video-overhang.ogg", type: "video/ogg", duration: 3.966 }, + + // bug461281.ogg with the middle second chopped out. + { name: "audio-gaps.ogg", type: "audio/ogg", duration: 2.208 }, + + // Test playback/metadata work after a redirect + { + name: "redirect.sjs?domain=mochi.test:8888&file=320x240.ogv", + type: "video/ogg", + duration: 0.266, + }, + + // Test playback of a webm file + { name: "seek-short.webm", type: "video/webm", duration: 0.23 }, + + // Test playback of a webm file with 'matroska' doctype + { name: "bug1377278.webm", type: "video/webm", duration: 4.0 }, + + // Test playback of a WebM file with non-zero start time. + { name: "split.webm", type: "video/webm", duration: 1.967 }, + + // Test playback of a WebM file with resolution changes. + { name: "resolution-change.webm", type: "video/webm", duration: 6.533 }, + + // A really short, low sample rate, single channel file. This tests whether + // we can handle playing files when only push very little audio data to the + // hardware. + { name: "spacestorm-1000Hz-100ms.ogg", type: "audio/ogg", duration: 0.099 }, + + // Opus data in an ogg container + { + name: "detodos-short.opus", + type: "audio/ogg; codecs=opus", + duration: 0.22, + contentDuration: 0.2135, + }, + // Opus data in a webm container + { + name: "detodos-short.webm", + type: "audio/webm; codecs=opus", + duration: 0.26, + contentDuration: 0.2535, + }, + // Opus in webm channel mapping=2 sample file + { + name: "opus-mapping2.webm", + type: "audio/webm; codecs=opus", + duration: 10.01, + contentDuration: 9.99, + }, + { name: "bug1066943.webm", type: "audio/webm; codecs=opus", duration: 1.383 }, + + // Multichannel Opus in an ogg container + { name: "test-1-mono.opus", type: "audio/ogg; codecs=opus", duration: 1.044 }, + { + name: "test-2-stereo.opus", + type: "audio/ogg; codecs=opus", + duration: 2.925, + }, + { name: "test-3-LCR.opus", type: "audio/ogg; codecs=opus", duration: 4.214 }, + { name: "test-4-quad.opus", type: "audio/ogg; codecs=opus", duration: 6.234 }, + { name: "test-5-5.0.opus", type: "audio/ogg; codecs=opus", duration: 7.558 }, + { name: "test-6-5.1.opus", type: "audio/ogg; codecs=opus", duration: 10.333 }, + { name: "test-7-6.1.opus", type: "audio/ogg; codecs=opus", duration: 11.69 }, + { name: "test-8-7.1.opus", type: "audio/ogg; codecs=opus", duration: 13.478 }, + + { + name: "gizmo-short.mp4", + type: "video/mp4", + duration: 0.27, + contentDuration: 0.267, + }, + // Test playback of a MP4 file with a non-zero start time (and audio starting + // a second later). + { name: "bipbop-lateaudio.mp4", type: "video/mp4" }, + // Ambisonics AAC, requires AAC extradata to be set when creating decoder (see bug 1431169) + // Also test 4.0 decoding. + { name: "ambisonics.mp4", type: "audio/mp4", duration: 16.48 }, + // Opus in MP4 channel mapping=0 sample file (content shorter due to preskip) + { + name: "opus-sample.mp4", + type: "audio/mp4; codecs=opus", + duration: 10.92, + contentDuration: 10.09, + }, + // Opus in MP4 channel mapping=2 sample file + { name: "opus-mapping2.mp4", type: "audio/mp4; codecs=opus", duration: 10.0 }, + + { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 }, + { name: "small-shot.mp3", type: "audio/mpeg", duration: 0.27 }, + { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 }, + // owl.mp3 as above, but with something funny going on in the ID3v2 tag + // that caused DirectShow to fail. + { name: "owl-funny-id3.mp3", type: "audio/mpeg", duration: 3.343 }, + // owl.mp3 as above, but with something even funnier going on in the ID3v2 tag + // that caused DirectShow to fail. + { name: "owl-funnier-id3.mp3", type: "audio/mpeg", duration: 3.343 }, + // One second of silence with ~140KB of ID3 tags. Usually when the first MP3 + // frame is at such a high offset into the file, MP3FrameParser will give up + // and report that the stream is not MP3. However, it does not count ID3 tags + // in that offset. This test case makes sure that ID3 exclusion holds. + { name: "huge-id3.mp3", type: "audio/mpeg", duration: 1.0 }, + // A truncated VBR MP3 with just enough frames to keep most decoders happy. + // The Xing header reports the length of the file to be around 10 seconds, but + // there is really only one second worth of data. We want MP3FrameParser to + // trust the header, so this should be reported as 10 seconds. + { + name: "vbr-head.mp3", + type: "audio/mpeg", + duration: 10.0, + contentDuration: 1.019, + }, + + // A flac file where the STREAMINFO block was removed. + // It is necessary to parse the file to find an audio frame instead. + { name: "flac-noheader-s16.flac", type: "audio/flac", duration: 4.0 }, + { name: "flac-s24.flac", type: "audio/flac", duration: 4.04 }, + { + name: "flac-sample.mp4", + type: "audio/mp4; codecs=flac", + duration: 4.95, + contentDuration: 5.03, + }, + // Ogg with theora video and flac audio. + { + name: "A4.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 3.13, + }, + + // Invalid file + { name: "bogus.duh", type: "bogus/duh", duration: Number.NaN }, +]; + +const win32 = + SpecialPowers.Services.appinfo.OS == "WINNT" && + !SpecialPowers.Services.appinfo.is64Bit; +if (!win32) { + gPlayTests.push({ name: "av1.mp4", type: "video/mp4", duration: 1.0 }); +} + +var gSeekToNextFrameTests = [ + // Test playback of a WebM file with vp9 video + { name: "vp9-short.webm", type: "video/webm", duration: 0.2 }, + { name: "vp9cake-short.webm", type: "video/webm", duration: 1.0 }, + // oggz-chop stream + { name: "bug482461.ogv", type: "video/ogg", duration: 4.34 }, + // Theora only oggz-chop stream + { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 }, + // With first frame a "duplicate" (empty) frame. + { name: "bug500311.ogv", type: "video/ogg", duration: 1.96 }, + + // More audio in file than video. + { name: "short-video.ogv", type: "video/ogg", duration: 1.081 }, + // First Theora data packet is zero bytes. + { name: "bug504613.ogv", type: "video/ogg", duration: Number.NaN }, + // Multiple audio streams. + { name: "bug516323.ogv", type: "video/ogg", duration: 4.208 }, + // oggz-chop with non-keyframe as first frame + { name: "bug556821.ogv", type: "video/ogg", duration: 2.936 }, + // Various weirdly formed Ogg files + { name: "bug498855-1.ogv", type: "video/ogg", duration: 0.24 }, + { name: "bug498855-2.ogv", type: "video/ogg", duration: 0.24 }, + { name: "bug498855-3.ogv", type: "video/ogg", duration: 0.24 }, + { name: "bug504644.ogv", type: "video/ogg", duration: 1.6 }, + + { name: "bug523816.ogv", type: "video/ogg", duration: 0.766 }, + + { name: "bug498380.ogv", type: "video/ogg", duration: 0.2 }, + { name: "bug557094.ogv", type: "video/ogg", duration: 0.24 }, + { name: "multiple-bos.ogg", type: "video/ogg", duration: 0.431 }, + // Test playback/metadata work after a redirect + { + name: "redirect.sjs?domain=mochi.test:8888&file=320x240.ogv", + type: "video/ogg", + duration: 0.266, + }, + // Test playback of a webm file + { name: "seek-short.webm", type: "video/webm", duration: 0.23 }, + // Test playback of a WebM file with non-zero start time. + { name: "split.webm", type: "video/webm", duration: 1.967 }, + + { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27 }, + + // Test playback of a MP4 file with a non-zero start time (and audio starting + // a second later). + { name: "bipbop-lateaudio.mp4", type: "video/mp4" }, +]; + +// A file for each type we can support. +var gSnifferTests = [ + { name: "big.wav", type: "audio/x-wav", duration: 9.278982, size: 102444 }, + { + name: "320x240.ogv", + type: "video/ogg", + width: 320, + height: 240, + duration: 0.233, + size: 28942, + }, + { name: "seek.webm", type: "video/webm", duration: 3.966, size: 215529 }, + { name: "gizmo.mp4", type: "video/mp4", duration: 5.56, size: 383631 }, + // A mp3 file with id3 tags. + { name: "id3tags.mp3", type: "audio/mpeg", duration: 0.28, size: 3530 }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// Files that contain resolution changes +var gResolutionChangeTests = [ + { name: "resolution-change.webm", type: "video/webm", duration: 6.533 }, +]; + +// Files we must reject as invalid. +var gInvalidTests = [ + { name: "invalid-m0c0.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-m0c3.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-m1c0.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-m1c9.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-m2c0.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-m2c1.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-cmap-short.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-cmap-s0c0.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-cmap-s0c2.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-cmap-s1c2.opus", type: "audio/ogg; codecs=opus" }, + { name: "invalid-preskip.webm", type: "audio/webm; codecs=opus" }, +]; + +var gInvalidPlayTests = [ + { name: "invalid-excess_discard.webm", type: "audio/webm; codecs=opus" }, + { name: "invalid-excess_neg_discard.webm", type: "audio/webm; codecs=opus" }, + { name: "invalid-neg_discard.webm", type: "audio/webm; codecs=opus" }, + { + name: "invalid-discard_on_multi_blocks.webm", + type: "audio/webm; codecs=opus", + }, +]; + +// Files to check different cases of ogg skeleton information. +// sample-fisbone-skeleton4.ogv +// - Skeleton v4, w/ Content-Type,Role,Name,Language,Title for both theora/vorbis +// sample-fisbone-wrong-header.ogv +// - Skeleton v4, wrong message field sequence for vorbis +// multiple-bos-more-header-fields.ogg +// - Skeleton v3, w/ Content-Type,Role,Name,Language,Title for both theora/vorbis +// seek-short.ogv +// - No skeleton, but theora +// audio-gaps-short.ogg +// - No skeleton, but vorbis +var gMultitrackInfoOggPlayList = [ + { name: "sample-fisbone-skeleton4.ogv", type: "video/ogg", duration: 1.0 }, + { name: "sample-fisbone-wrong-header.ogv", type: "video/ogg", duration: 1.0 }, + { + name: "multiple-bos-more-header-fileds.ogg", + type: "video/ogg", + duration: 0.431, + }, + { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 }, + { name: "audio-gaps-short.ogg", type: "audio/ogg", duration: 0.5 }, +]; +// Pre-parsed results of gMultitrackInfoOggPlayList. +var gOggTrackInfoResults = { + "sample-fisbone-skeleton4.ogv": { + audio_id: " audio_1", + audio_kind: "main", + audio_language: " en-US", + audio_label: " Audio track for test", + video_id: " video_1", + video_kind: "main", + video_language: " fr", + video_label: " Video track for test", + }, + "sample-fisbone-wrong-header.ogv": { + audio_id: "1", + audio_kind: "main", + audio_language: "", + audio_label: "", + video_id: " video_1", + video_kind: "main", + video_language: " fr", + video_label: " Video track for test", + }, + "multiple-bos-more-header-fileds.ogg": { + audio_id: "1", + audio_kind: "main", + audio_language: "", + audio_label: "", + video_id: "2", + video_kind: "main", + video_language: "", + video_label: "", + }, + "seek-short.ogv": { + video_id: "2", + video_kind: "main", + video_language: "", + video_label: "", + }, + "audio-gaps-short.ogg": { + audio_id: "1", + audio_kind: "main", + audio_language: "", + audio_label: "", + }, +}; + +// Returns a promise that resolves to a function that converts +// relative paths to absolute, to test loading files from file: URIs. +// Optionally checks whether the file actually exists on disk at the location +// we've specified. +function makeAbsolutePathConverter() { + const url = SimpleTest.getTestFileURL("chromeHelper.js"); + const script = SpecialPowers.loadChromeScript(url); + return new Promise((resolve, reject) => { + script.addMessageListener("media-test:cwd", cwd => { + if (!cwd) { + ok(false, "Failed to find path to test files"); + } + + resolve((path, mustExist) => { + // android mochitest doesn't support file:// + if (manifestNavigator().appVersion.includes("Android")) { + return path; + } + + const { Ci, Cc } = SpecialPowers; + var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + f.initWithPath(cwd); + var split = path.split("/"); + for (var i = 0; i < split.length; ++i) { + f.append(split[i]); + } + if (mustExist && !f.exists()) { + ok(false, "We expected '" + path + "' to exist, but it doesn't!"); + } + return f.path; + }); + }); + script.sendAsyncMessage("media-test:getcwd"); + }); +} + +// Returns true if two TimeRanges are equal, false otherwise +function range_equals(r1, r2) { + if (r1.length != r2.length) { + return false; + } + for (var i = 0; i < r1.length; i++) { + if (r1.start(i) != r2.start(i) || r1.end(i) != r2.end(i)) { + return false; + } + } + return true; +} + +// These are URIs to files that we use to check that we don't leak any state +// or other information such that script can determine stuff about a user's +// environment. Used by test_info_leak. +function makeInfoLeakTests() { + return makeAbsolutePathConverter().then(fileUriToSrc => [ + { + type: "video/ogg", + src: fileUriToSrc("tests/dom/media/test/320x240.ogv", true), + }, + { + type: "video/ogg", + src: fileUriToSrc("tests/dom/media/test/404.ogv", false), + }, + { + type: "audio/x-wav", + src: fileUriToSrc("tests/dom/media/test/r11025_s16_c1.wav", true), + }, + { + type: "audio/x-wav", + src: fileUriToSrc("tests/dom/media/test/404.wav", false), + }, + { + type: "audio/ogg", + src: fileUriToSrc("tests/dom/media/test/bug461281.ogg", true), + }, + { + type: "audio/ogg", + src: fileUriToSrc("tests/dom/media/test/404.ogg", false), + }, + { + type: "video/webm", + src: fileUriToSrc("tests/dom/media/test/seek.webm", true), + }, + { + type: "video/webm", + src: fileUriToSrc("tests/dom/media/test/404.webm", false), + }, + { + type: "video/ogg", + src: "http://localhost/404.ogv", + }, + { + type: "audio/x-wav", + src: "http://localhost/404.wav", + }, + { + type: "video/webm", + src: "http://localhost/404.webm", + }, + { + type: "video/ogg", + src: "http://example.com/tests/dom/media/test/test_info_leak.html", + }, + { + type: "audio/ogg", + src: "http://example.com/tests/dom/media/test/test_info_leak.html", + }, + ]); +} + +// These are files that must fire an error during load or playback, and do not +// cause a crash. Used by test_playback_errors, which expects one error event +// and no ended event. Put files of the same type together in this list so if +// something crashes we have some idea of which backend is responsible. +var gErrorTests = [ + { name: "bogus.wav", type: "audio/x-wav" }, + { name: "bogus.ogv", type: "video/ogg" }, + { name: "448636.ogv", type: "video/ogg" }, + { name: "bug504843.ogv", type: "video/ogg" }, + { name: "bug501279.ogg", type: "audio/ogg" }, + { name: "bug603918.webm", type: "video/webm" }, + { name: "bug604067.webm", type: "video/webm" }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// These files would get error after receiving "loadedmetadata", we would like +// to check duration in "onerror" and make sure the duration is still available. +var gDurationTests = [ + { name: "bug603918.webm", duration: 6.076 }, + { name: "bug604067.webm", duration: 6.076 }, +]; + +// These are files that have nontrivial duration and are useful for seeking within. +var gSeekTests = [ + { name: "r11025_s16_c1.wav", type: "audio/x-wav", duration: 1.0 }, + { name: "audio.wav", type: "audio/x-wav", duration: 0.031247 }, + { name: "seek.ogv", type: "video/ogg", duration: 3.966 }, + { name: "320x240.ogv", type: "video/ogg", duration: 0.266 }, + { name: "seek.webm", type: "video/webm", duration: 3.966 }, + { name: "sine.webm", type: "audio/webm", duration: 4.001 }, + { name: "bug516323.indexed.ogv", type: "video/ogg", duration: 4.208333 }, + { name: "split.webm", type: "video/webm", duration: 1.967 }, + { name: "detodos.opus", type: "audio/ogg; codecs=opus", duration: 2.9135 }, + { name: "gizmo.mp4", type: "video/mp4", duration: 5.56 }, + { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 }, + { name: "bogus.duh", type: "bogus/duh", duration: 123 }, + + // Bug 1242338: hit a numerical problem while seeking to the duration. + { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 }, +]; + +var gFastSeekTests = [ + { + name: "gizmo.mp4", + type: "video/mp4", + keyframes: [0, 1.0, 2.0, 3.0, 4.0, 5.0], + }, + // Note: Not all keyframes in the file are actually referenced in the Cues in this file. + { name: "seek.webm", type: "video/webm", keyframes: [0, 0.8, 1.6, 2.4, 3.2] }, + // Note: the sync points are the points on both the audio and video streams + // before the keyframes. You can't just assume that the keyframes are the sync + // points, as the audio required for that sync point may be before the keyframe. + { + name: "bug516323.indexed.ogv", + type: "video/ogg", + keyframes: [0, 0.46, 3.06], + }, +]; + +// These files are WebMs without cues. They're seekable within their buffered +// ranges. If work renders WebMs fully seekable these files should be moved +// into gSeekTests +var gCuelessWebMTests = [ + { name: "no-cues.webm", type: "video/webm", duration: 3.967 }, +]; + +// These are files that are non seekable, due to problems with the media, +// for example broken or missing indexes. +var gUnseekableTests = [{ name: "bogus.duh", type: "bogus/duh" }]; + +var androidVersion = -1; // non-Android platforms +if ( + manifestNavigator().userAgent.includes("Mobile") || + manifestNavigator().userAgent.includes("Tablet") +) { + androidVersion = SpecialPowers.Services.sysinfo.getProperty("version"); +} + +function getAndroidVersion() { + return androidVersion; +} + +// These are files suitable for using with a "new Audio" constructor. +var gAudioTests = [ + { name: "r11025_s16_c1.wav", type: "audio/x-wav", duration: 1.0 }, + { name: "sound.ogg", type: "audio/ogg" }, + { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 }, + { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 }, + { name: "bogus.duh", type: "bogus/duh", duration: 123 }, + { name: "empty_size.mp3", type: "audio/mpeg", duration: 2.235 }, +]; + +// These files ensure our handling of 404 errors is consistent across the +// various backends. +var g404Tests = [ + { name: "404.wav", type: "audio/x-wav" }, + { name: "404.ogv", type: "video/ogg" }, + { name: "404.oga", type: "audio/ogg" }, + { name: "404.webm", type: "video/webm" }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// These are files suitable for testing various decoder failures that are +// expected to fire MEDIA_ERR_DECODE. Used by test_decode_error, which expects +// an error and emptied event, and no loadedmetadata or ended event. +var gDecodeErrorTests = [ + // Valid files with unsupported codecs + { name: "r11025_msadpcm_c1.wav", type: "audio/x-wav" }, + { name: "dirac.ogg", type: "video/ogg" }, + // Invalid files + { name: "bogus.wav", type: "audio/x-wav" }, + { name: "bogus.ogv", type: "video/ogg" }, + + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// These are files that are used for media fragments tests +var gFragmentTests = [ + { name: "big.wav", type: "audio/x-wav", duration: 9.278982, size: 102444 }, +]; + +// Used by test_chaining.html. The |links| attributes is the number of links in +// this file that we should be able to play. +var gChainingTests = [ + // Vorbis and Opus chained file. They have user comments |index=n| where `n` + // is the index of this segment in the file, 0 indexed. + { name: "chain.ogg", type: "audio/ogg", links: 4 }, + { name: "chain.opus", type: "audio/ogg; codec=opus", links: 4 }, + // Those files are chained files with a different number of channels in each + // part. This is not supported and should stop playing after the first part. + { name: "variable-channel.ogg", type: "audio/ogg", links: 1 }, + { name: "variable-channel.opus", type: "audio/ogg; codec=opus", links: 1 }, + // Those files are chained files with a different sample rate in each + // part. This is not supported and should stop playing after the first part. + { name: "variable-samplerate.ogg", type: "audio/ogg", links: 1 }, + // Opus decoding in Firefox outputs 48 kHz PCM despite having a different + // original sample rate, so we can safely play Opus chained media that have + // different samplerate accross links. + { name: "variable-samplerate.opus", type: "audio/ogg; codec=opus", links: 2 }, + // A chained video file. We don't support those, so only one link should be + // reported. + { name: "chained-video.ogv", type: "video/ogg", links: 1 }, + // A file that consist in 4 links of audio, then another link that has video. + // We should stop right after the 4 audio links. + { name: "chained-audio-video.ogg", type: "video/ogg", links: 4 }, + // An opus file that has two links, with a different preskip value for each + // link. We should be able to play both links. + { name: "variable-preskip.opus", type: "audio/ogg; codec=opus", links: 2 }, + { name: "bogus.duh", type: "bogus/duh" }, +]; + +// Videos with an aspect ratio. Used for testing that displaying frames +// on a canvas works correctly in the case of non-standard aspect ratios. +// See bug 874897 for an example. +var gAspectRatioTests = [ + { name: "VID_0001.ogg", type: "video/ogg", duration: 19.966 }, +]; + +// These are files with non-trivial tag sets. +// Used by test_metadata.html. +var gMetadataTests = [ + // Ogg Vorbis files + { + name: "short-video.ogv", + tags: { + TITLE: "Lepidoptera", + ARTIST: "Epoq", + ALBUM: "Kahvi Collective", + DATE: "2002", + COMMENT: "http://www.kahvi.org", + }, + }, + { + name: "bug516323.ogv", + tags: { + GENRE: "Open Movie", + ENCODER: "Audacity", + TITLE: "Elephants Dream", + ARTIST: "Silvia Pfeiffer", + COMMENTS: "Audio Description", + }, + }, + { + name: "bug516323.indexed.ogv", + tags: { + GENRE: "Open Movie", + ENCODER: "Audacity", + TITLE: "Elephants Dream", + ARTIST: "Silvia Pfeiffer", + COMMENTS: "Audio Description", + }, + }, + { + name: "detodos.opus", + tags: { + title: "De todos. Para todos.", + artist: "Mozilla.org", + }, + }, + { name: "sound.ogg", tags: {} }, + { + name: "small-shot.ogg", + tags: { + title: "Pew SFX", + }, + }, + { + name: "badtags.ogg", + tags: { + // We list only the valid tags here, and verify + // the invalid ones are filtered out. + title: "Invalid comments test file", + empty: "", + "": "empty", + "{- [(`!@\"#$%^&')] -}": "valid tag name, surprisingly", + // The file also includes the following invalid tags. + // "A description with no separator is a common problem.", + // "雨":"Likely, but an invalid key (non-ascii).", + // "not\nval\x1fid":"invalid tag name", + // "not~valid":"this isn't a valid name either", + // "not-utf-8":"invalid sequences: \xff\xfe\xfa\xfb\0eol" + }, + }, + { + name: "wave_metadata.wav", + tags: { + name: "Track Title", + artist: "Artist Name", + comments: "Comments", + }, + }, + { + name: "wave_metadata_utf8.wav", + tags: { + name: "歌曲名稱", + artist: "作曲者", + comments: "註解", + }, + }, + { + name: "wave_metadata_unknown_tag.wav", + tags: { + name: "Track Title", + comments: "Comments", + }, + }, + { + name: "wave_metadata_bad_len.wav", + tags: { + name: "Track Title", + artist: "Artist Name", + comments: "Comments", + }, + }, + { + name: "wave_metadata_bad_no_null.wav", + tags: { + name: "Track Title", + artist: "Artist Name", + comments: "Comments!!", + }, + }, + { + name: "wave_metadata_bad_utf8.wav", + tags: { + name: "歌曲名稱", + comments: "註解", + }, + }, + { name: "wavedata_u8.wav", tags: {} }, +]; + +// Now Fennec doesn't support flac, so only test it on non-android platforms. +if (getAndroidVersion() < 0) { + gMetadataTests = gMetadataTests.concat([ + { + name: "flac-s24.flac", + tags: { + ALBUM: "Seascapes", + TITLE: "(La Mer) - II. Jeux de vagues. Allegro", + COMPOSER: "Debussy, Claude", + TRACKNUMBER: "2/9", + DISCNUMBER: "1/1", + encoder: "Lavf57.41.100", + }, + }, + ]); +} + +// Test files for Encrypted Media Extensions +var gEMETests = [ + { + name: "vp9 in mp4", + tracks: [ + { + name: "video", + type: 'video/mp4; codecs="vp9.0"', + fragments: ["short-vp9-encrypted-video.mp4"], + }, + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: ["short-aac-encrypted-audio.mp4"], + }, + ], + keys: { + "2cdb0ed6119853e7850671c3e9906c3c": "808B9ADAC384DE1E4F56140F4AD76194", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 0.47, + }, + { + name: "video-only with 2 keys", + tracks: [ + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop-cenc-videoinit.mp4", + "bipbop-cenc-video1.m4s", + "bipbop-cenc-video2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333", + "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 1.6, + }, + { + name: "video-only with 2 keys, CORS", + tracks: [ + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop-cenc-videoinit.mp4", + "bipbop-cenc-video1.m4s", + "bipbop-cenc-video2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333", + "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444", + }, + sessionType: "temporary", + sessionCount: 1, + crossOrigin: true, + duration: 1.6, + }, + { + name: "audio&video tracks, both with all keys", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop-cenc-audioinit.mp4", + "bipbop-cenc-audio1.m4s", + "bipbop-cenc-audio2.m4s", + "bipbop-cenc-audio3.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop-cenc-videoinit.mp4", + "bipbop-cenc-video1.m4s", + "bipbop-cenc-video2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333", + "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "audio&video tracks, both with all keys, CORS", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop-cenc-audioinit.mp4", + "bipbop-cenc-audio1.m4s", + "bipbop-cenc-audio2.m4s", + "bipbop-cenc-audio3.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop-cenc-videoinit.mp4", + "bipbop-cenc-video1.m4s", + "bipbop-cenc-video2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333", + "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444", + }, + sessionType: "temporary", + sessionCount: 2, + crossOrigin: true, + duration: 1.6, + }, + { + name: "400x300 audio&video tracks, each with its key", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_300_215kbps-cenc-audio-key1-init.mp4", + "bipbop_300_215kbps-cenc-audio-key1-1.m4s", + "bipbop_300_215kbps-cenc-audio-key1-2.m4s", + "bipbop_300_215kbps-cenc-audio-key1-3.m4s", + "bipbop_300_215kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_300_215kbps-cenc-video-key1-init.mp4", + "bipbop_300_215kbps-cenc-video-key1-1.m4s", + "bipbop_300_215kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "640x480@624kbps audio&video tracks, each with its key", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_624kbps-cenc-audio-key1-init.mp4", + "bipbop_480_624kbps-cenc-audio-key1-1.m4s", + "bipbop_480_624kbps-cenc-audio-key1-2.m4s", + "bipbop_480_624kbps-cenc-audio-key1-3.m4s", + "bipbop_480_624kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_624kbps-cenc-video-key1-init.mp4", + "bipbop_480_624kbps-cenc-video-key1-1.m4s", + "bipbop_480_624kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "640x480@959kbps audio&video tracks, each with its key", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_959kbps-cenc-audio-key1-init.mp4", + "bipbop_480_959kbps-cenc-audio-key1-1.m4s", + "bipbop_480_959kbps-cenc-audio-key1-2.m4s", + "bipbop_480_959kbps-cenc-audio-key1-3.m4s", + "bipbop_480_959kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_959kbps-cenc-video-key1-init.mp4", + "bipbop_480_959kbps-cenc-video-key1-1.m4s", + "bipbop_480_959kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "640x480 then 400x300, same key (1st) per track", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_624kbps-cenc-audio-key1-init.mp4", + "bipbop_480_624kbps-cenc-audio-key1-1.m4s", + "bipbop_480_624kbps-cenc-audio-key1-2.m4s", + "bipbop_480_624kbps-cenc-audio-key1-3.m4s", + "bipbop_480_624kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_624kbps-cenc-video-key1-init.mp4", + "bipbop_480_624kbps-cenc-video-key1-1.m4s", + "bipbop_300_215kbps-cenc-video-key1-init.mp4", + "bipbop_300_215kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "640x480 then 400x300, same key (2nd) per track", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_624kbps-cenc-audio-key2-init.mp4", + "bipbop_480_624kbps-cenc-audio-key2-1.m4s", + "bipbop_480_624kbps-cenc-audio-key2-2.m4s", + "bipbop_480_624kbps-cenc-audio-key2-3.m4s", + "bipbop_480_624kbps-cenc-audio-key2-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_624kbps-cenc-video-key2-init.mp4", + "bipbop_480_624kbps-cenc-video-key2-1.m4s", + "bipbop_300_215kbps-cenc-video-key2-init.mp4", + "bipbop_300_215kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + "7e571d047e571d047e571d047e571d22": "7e5744447e5744447e5744447e574422", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "640x480 with 1st keys then 400x300 with 2nd keys", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_624kbps-cenc-audio-key1-init.mp4", + "bipbop_480_624kbps-cenc-audio-key1-1.m4s", + "bipbop_480_624kbps-cenc-audio-key1-2.m4s", + "bipbop_480_624kbps-cenc-audio-key1-3.m4s", + "bipbop_480_624kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_624kbps-cenc-video-key1-init.mp4", + "bipbop_480_624kbps-cenc-video-key1-1.m4s", + "bipbop_300_215kbps-cenc-video-key2-init.mp4", + "bipbop_300_215kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "400x300 with 1st keys then 640x480 with 2nd keys", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_300_215kbps-cenc-audio-key1-init.mp4", + "bipbop_300_215kbps-cenc-audio-key1-1.m4s", + "bipbop_300_215kbps-cenc-audio-key1-2.m4s", + "bipbop_300_215kbps-cenc-audio-key1-3.m4s", + "bipbop_300_215kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_300_215kbps-cenc-video-key1-init.mp4", + "bipbop_300_215kbps-cenc-video-key1-1.m4s", + "bipbop_480_624kbps-cenc-video-key2-init.mp4", + "bipbop_480_624kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "640x480@959kbps with 1st keys then 640x480@624kbps with 2nd keys", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_959kbps-cenc-audio-key1-init.mp4", + "bipbop_480_959kbps-cenc-audio-key1-1.m4s", + "bipbop_480_959kbps-cenc-audio-key1-2.m4s", + "bipbop_480_959kbps-cenc-audio-key1-3.m4s", + "bipbop_480_959kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_959kbps-cenc-video-key1-init.mp4", + "bipbop_480_959kbps-cenc-video-key1-1.m4s", + "bipbop_480_624kbps-cenc-video-key2-init.mp4", + "bipbop_480_624kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "640x480@624kbps with 1st keys then 640x480@959kbps with 2nd keys", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_480_624kbps-cenc-audio-key1-init.mp4", + "bipbop_480_624kbps-cenc-audio-key1-1.m4s", + "bipbop_480_624kbps-cenc-audio-key1-2.m4s", + "bipbop_480_624kbps-cenc-audio-key1-3.m4s", + "bipbop_480_624kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_480_624kbps-cenc-video-key1-init.mp4", + "bipbop_480_624kbps-cenc-video-key1-1.m4s", + "bipbop_480_959kbps-cenc-video-key2-init.mp4", + "bipbop_480_959kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "400x300 with presentation size 533x300", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_300wp_227kbps-cenc-audio-key1-init.mp4", + "bipbop_300wp_227kbps-cenc-audio-key1-1.m4s", + "bipbop_300wp_227kbps-cenc-audio-key1-2.m4s", + "bipbop_300wp_227kbps-cenc-audio-key1-3.m4s", + "bipbop_300wp_227kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_300wp_227kbps-cenc-video-key1-init.mp4", + "bipbop_300wp_227kbps-cenc-video-key1-1.m4s", + "bipbop_300wp_227kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "400x300 as-is then 400x300 presented as 533x300", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_300_215kbps-cenc-audio-key1-init.mp4", + "bipbop_300_215kbps-cenc-audio-key1-1.m4s", + "bipbop_300_215kbps-cenc-audio-key1-2.m4s", + "bipbop_300_215kbps-cenc-audio-key1-3.m4s", + "bipbop_300_215kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_300_215kbps-cenc-video-key1-init.mp4", + "bipbop_300_215kbps-cenc-video-key1-1.m4s", + "bipbop_300wp_227kbps-cenc-video-key1-init.mp4", + "bipbop_300wp_227kbps-cenc-video-key1-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "400x225", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_225w_175kbps-cenc-audio-key1-init.mp4", + "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-2.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-3.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_225w_175kbps-cenc-video-key1-init.mp4", + "bipbop_225w_175kbps-cenc-video-key1-1.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "640x360", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_360w_253kbps-cenc-audio-key1-init.mp4", + "bipbop_360w_253kbps-cenc-audio-key1-1.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-2.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-3.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_360w_253kbps-cenc-video-key1-init.mp4", + "bipbop_360w_253kbps-cenc-video-key1-1.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "400x225 then 640x360", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_225w_175kbps-cenc-audio-key1-init.mp4", + "bipbop_225w_175kbps-cenc-audio-key1-1.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-2.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-3.m4s", + "bipbop_225w_175kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_225w_175kbps-cenc-video-key1-init.mp4", + "bipbop_225w_175kbps-cenc-video-key1-1.m4s", + "bipbop_360w_253kbps-cenc-video-key2-init.mp4", + "bipbop_360w_253kbps-cenc-video-key2-1.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + name: "640x360 then 640x480", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="mp4a.40.2"', + fragments: [ + "bipbop_360w_253kbps-cenc-audio-key1-init.mp4", + "bipbop_360w_253kbps-cenc-audio-key1-1.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-2.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-3.m4s", + "bipbop_360w_253kbps-cenc-audio-key1-4.m4s", + ], + }, + { + name: "video", + type: 'video/mp4; codecs="avc1.64000d"', + fragments: [ + "bipbop_360w_253kbps-cenc-video-key1-init.mp4", + "bipbop_360w_253kbps-cenc-video-key1-1.m4s", + "bipbop_480_624kbps-cenc-video-key2-init.mp4", + "bipbop_480_624kbps-cenc-video-key2-2.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311", + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312", + }, + sessionType: "temporary", + sessionCount: 3, + duration: 1.6, + }, + { + // File generated with shaka packager: + // packager-osx --enable_raw_key_encryption --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 --segment_duration 1 --clear_lead 0 in=test-flac.mp4,stream=audio,output=flac-sample-cenc.mp4 + name: "flac in mp4 clearkey", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="flac"', + fragments: ["flac-sample-cenc.mp4"], + }, + ], + keys: { + // "keyid" : "key" + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 2.05, + }, + { + // File generated with shaka packager: + // packager-osx --enable_raw_key_encryption --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 --segment_duration 1 --clear_lead 0 in=test-opus.mp4,stream=audio,output=opus-sample-cenc.mp4 + name: "opus in mp4 clearkey", + tracks: [ + { + name: "audio", + type: 'audio/mp4; codecs="opus"', + fragments: ["opus-sample-cenc.mp4"], + }, + ], + keys: { + // "keyid" : "key" + "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 1.98, + }, + { + name: "WebM vorbis audio & vp8 video clearkey", + tracks: [ + { + name: "audio", + type: 'audio/webm; codecs="vorbis"', + fragments: ["bipbop_360w_253kbps-clearkey-audio.webm"], + }, + { + name: "video", + type: 'video/webm; codecs="vp8"', + fragments: ["bipbop_360w_253kbps-clearkey-video-vp8.webm"], + }, + ], + keys: { + // "keyid" : "key" + f1f3ee1790527e9de47217d43835f76a: "97b9ddc459c8d5ff23c1f2754c95abe8", + "8b5df745ad84145b5617c33116e35a67": "bddfd35dd9be033ee73bc18bc1885056", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "WebM vorbis audio & vp9 video clearkey", + tracks: [ + { + name: "audio", + type: 'audio/webm; codecs="vorbis"', + fragments: ["bipbop_360w_253kbps-clearkey-audio.webm"], + }, + { + name: "video", + type: 'video/webm; codecs="vp9"', + fragments: ["bipbop_360w_253kbps-clearkey-video-vp9.webm"], + }, + ], + keys: { + // "keyid" : "key" + f1f3ee1790527e9de47217d43835f76a: "97b9ddc459c8d5ff23c1f2754c95abe8", + eedf63a94fa7c398ee094f123a4ee709: "973b679a746c82f3acdb856b30e9378e", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 1.6, + }, + { + name: "WebM vorbis audio & vp9 video clearkey with subsample encryption", + tracks: [ + { + name: "audio", + type: 'audio/webm; codecs="vorbis"', + fragments: ["sintel-short-clearkey-subsample-encrypted-audio.webm"], + }, + { + name: "video", + type: 'video/webm; codecs="vp9"', + fragments: ["sintel-short-clearkey-subsample-encrypted-video.webm"], + }, + ], + keys: { + // "keyid" : "key" + "2cdb0ed6119853e7850671c3e9906c3c": "808B9ADAC384DE1E4F56140F4AD76194", + }, + sessionType: "temporary", + sessionCount: 2, + duration: 2.0, + }, + { + // Files adapted from testcase for bug 1560092. See bug 1630381 for a + // detailed explanation on how they were adapted. + name: "avc3 h264 video in mp4 using clearkey cenc encryption", + tracks: [ + { + name: "video", + type: 'video/mp4; codecs="avc3.640015"', + fragments: [ + "big-buck-bunny-cenc-avc3-init.mp4", + "big-buck-bunny-cenc-avc3-1.m4s", + ], + }, + ], + keys: { + // "keyid" : "key" + "10000000100010001000100000000001": "3A2A1B68DD2BD9B2EEB25E84C4776668", + }, + sessionType: "temporary", + sessionCount: 1, + duration: 2.08, + }, +]; + +var gEMENonMSEFailTests = [ + { + name: "short-cenc.mp4", + audioType: 'audio/mp4; codecs="mp4a.40.2"', + videoType: 'video/mp4; codecs="avc1.64000d"', + duration: 0.47, + }, +]; + +// These are files that are used for video decode suspend in +// background tabs tests. +var gDecodeSuspendTests = [ + { name: "gizmo.mp4", type: "video/mp4", duration: 5.56 }, + { name: "gizmo-noaudio.mp4", type: "video/mp4", duration: 5.56 }, + { name: "gizmo.webm", type: 'video/webm; codecs="vp9,opus"', duration: 5.56 }, + { + name: "gizmo-noaudio.webm", + type: 'video/webm; codecs="vp9"', + duration: 5.56, + }, +]; + +function checkMetadata(msg, e, test) { + if (test.width) { + is(e.videoWidth, test.width, msg + " video width"); + } + if (test.height) { + is(e.videoHeight, test.height, msg + " video height"); + } + if (test.duration) { + ok( + Math.abs(e.duration - test.duration) < 0.1, + msg + " duration (" + e.duration + ") should be around " + test.duration + ); + } + is( + !!test.keys, + SpecialPowers.do_lookupGetter(e, "isEncrypted").apply(e), + msg + " isEncrypted should be true if we have decryption keys" + ); +} + +// Returns the first test from candidates array which we can play with the +// installed video backends. +function getPlayableVideo(candidates) { + var resources = getPlayableVideos(candidates); + if (resources.length > 0) { + return resources[0]; + } + return null; +} + +function getPlayableVideos(candidates) { + var v = manifestVideo(); + return candidates.filter(function(x) { + return /^video/.test(x.type) && v.canPlayType(x.type); + }); +} + +function getPlayableAudio(candidates) { + var v = manifestVideo(); + var resources = candidates.filter(function(x) { + return /^audio/.test(x.type) && v.canPlayType(x.type); + }); + if (resources.length > 0) { + return resources[0]; + } + return null; +} + +// Returns the type of element that should be created for the given mimetype. +function getMajorMimeType(mimetype) { + if (/^video/.test(mimetype)) { + return "video"; + } + return "audio"; +} + +// Force releasing decoder to avoid timeout in waiting for decoding resource. +function removeNodeAndSource(n) { + n.remove(); + // reset |srcObject| first since it takes precedence over |src|. + n.srcObject = null; + n.removeAttribute("src"); + n.load(); + while (n.firstChild) { + n.firstChild.remove(); + } +} + +function once(target, name, cb) { + var p = new Promise(function(resolve, reject) { + target.addEventListener( + name, + function() { + resolve(); + }, + { once: true } + ); + }); + if (cb) { + p.then(cb); + } + return p; +} + +/** + * @param {HTMLMediaElement} video target of interest. + * @param {string} eventName the event to wait on. + * @returns {Promise} A promise that is resolved when event happens. + */ +function nextEvent(video, eventName) { + return new Promise(function(resolve, reject) { + let f = function(event) { + video.removeEventListener(eventName, f); + resolve(event); + }; + video.addEventListener(eventName, f); + }); +} + +function TimeStamp(token) { + function pad(x) { + return x < 10 ? "0" + x : x; + } + var now = new Date(); + var ms = now.getMilliseconds(); + var time = + "[" + + pad(now.getHours()) + + ":" + + pad(now.getMinutes()) + + ":" + + pad(now.getSeconds()) + + "." + + ms + + "]" + + // eslint-disable-next-line no-nested-ternary + (ms < 10 ? " " : ms < 100 ? " " : ""); + return token ? time + " " + token : time; +} + +function Log(token, msg) { + info(TimeStamp(token) + " " + msg); +} + +// Number of tests to run in parallel. +var PARALLEL_TESTS = 2; + +// Prefs to set before running tests. Use this to improve coverage of +// conditions that might not otherwise be encountered on the test data. +var gTestPrefs = [ + ["media.recorder.max_memory", 1024], + ["media.audio-max-decode-error", 0], + ["media.video-max-decode-error", 0], +]; + +// When true, we'll loop forever on whatever test we run. Use this to debug +// intermittent test failures. +const DEBUG_TEST_LOOP_FOREVER = false; + +// Manages a run of media tests. Runs them in chunks in order to limit +// the number of media elements/threads running in parallel. This limits peak +// memory use, particularly on Linux x86 where thread stacks use 10MB of +// virtual address space. +// Usage: +// 1. Create a new MediaTestManager object. +// 2. Create a test startTest function. This takes a test object and a token, +// and performs anything necessary to start the test. The test object is an +// element in one of the g*Tests above. Your startTest function must call +// MediaTestManager.start(token) if it starts a test. The test object is +// guaranteed to be playable by our supported decoders; you don't need to +// check canPlayType. +// 3. When your tests finishes, call MediaTestManager.finished(), passing +// the token back to the manager. The manager may either start the next run +// or end the mochitest if all the tests are done. +function MediaTestManager() { + // Set a very large timeout to prevent Mochitest timeout. + // Instead MediaTestManager will manage timeout of each test. + SimpleTest.requestLongerTimeout(1000); + + // Return how many seconds elapsed since |begin|. + function elapsedTime(begin) { + var end = new Date(); + return (end.getTime() - begin.getTime()) / 1000; + } + // Sets up a MediaTestManager to runs through the 'tests' array, which needs + // to be one of, or have the same fields as, the g*Test arrays of tests. Uses + // the user supplied 'startTest' function to initialize the test. This + // function must accept two arguments, the test entry from the 'tests' array, + // and a token. Call MediaTestManager.started(token) if you start the test, + // and MediaTestManager.finished(token) when the test finishes. You don't have + // to start every test, but if you call started() you *must* call finish() + // else you'll timeout. + this.runTests = function(tests, startTest) { + this.startTime = new Date(); + SimpleTest.info( + "Started " + + this.startTime + + " (" + + this.startTime.getTime() / 1000 + + "s)" + ); + this.testNum = 0; + this.tests = tests; + this.startTest = startTest; + this.tokens = []; + this.isShutdown = false; + this.numTestsRunning = 0; + this.handlers = {}; + this.timers = {}; + + // Always wait for explicit finish. + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({ set: gTestPrefs }, () => { + this.nextTest(); + }); + + SimpleTest.registerCleanupFunction(() => { + if (this.tokens.length > 0) { + info("Test timed out. Remaining tests=" + this.tokens); + } + for (var token of this.tokens) { + var handler = this.handlers[token]; + if (handler && handler.ontimeout) { + handler.ontimeout(); + } + } + }); + }; + + // Registers that the test corresponding to 'token' has been started. + // Don't call more than once per token. + this.started = function(token, handler) { + this.tokens.push(token); + this.numTestsRunning++; + this.handlers[token] = handler; + + var onTimeout = async () => { + ok(false, "Test timed out!"); + info(`${token} timed out!`); + await dumpDebugInfoForToken(token); + this.finished(token); + }; + // Default timeout to 180s for each test. + // Call SimpleTest._originalSetTimeout() to bypass the flaky timeout checker. + this.timers[token] = SimpleTest._originalSetTimeout.call( + window, + onTimeout, + 180000 + ); + + is( + this.numTestsRunning, + this.tokens.length, + "[started " + + token + + " t=" + + elapsedTime(this.startTime) + + "] Length of array should match number of running tests" + ); + }; + + // Registers that the test corresponding to 'token' has finished. Call when + // you've finished your test. If all tests are complete this will finish the + // run, otherwise it may start up the next run. It's ok to call multiple times + // per token. + this.finished = function(token) { + var i = this.tokens.indexOf(token); + if (i != -1) { + // Remove the element from the list of running tests. + this.tokens.splice(i, 1); + } + + if (this.timers[token]) { + // Cancel the timer when the test finishes. + clearTimeout(this.timers[token]); + this.timers[token] = null; + } + + info("[finished " + token + "] remaining= " + this.tokens); + this.numTestsRunning--; + is( + this.numTestsRunning, + this.tokens.length, + "[finished " + + token + + " t=" + + elapsedTime(this.startTime) + + "] Length of array should match number of running tests" + ); + if (this.tokens.length < PARALLEL_TESTS) { + this.nextTest(); + } + }; + + // Starts the next batch of tests, or finishes if they're all done. + // Don't call this directly, call finished(token) when you're done. + this.nextTest = function() { + while ( + this.testNum < this.tests.length && + this.tokens.length < PARALLEL_TESTS + ) { + var test = this.tests[this.testNum]; + var token = (test.name ? test.name + "-" : "") + this.testNum; + this.testNum++; + + if (DEBUG_TEST_LOOP_FOREVER && this.testNum == this.tests.length) { + this.testNum = 0; + } + + // Ensure we can play the resource type. + if ( + test.type && + !document.createElement("video").canPlayType(test.type) + ) { + continue; + } + + // Do the init. This should start the test. + this.startTest(test, token); + } + + if ( + this.testNum == this.tests.length && + !DEBUG_TEST_LOOP_FOREVER && + this.tokens.length == 0 && + !this.isShutdown + ) { + this.isShutdown = true; + if (this.onFinished) { + this.onFinished(); + } + var onCleanup = () => { + var end = new Date(); + SimpleTest.info( + "Finished at " + end + " (" + end.getTime() / 1000 + "s)" + ); + SimpleTest.info("Running time: " + elapsedTime(this.startTime) + "s"); + SimpleTest.finish(); + }; + mediaTestCleanup(onCleanup); + } + }; +} + +// Ensures we've got no active video or audio elements in the document, and +// forces a GC to release the address space reserved by the decoders' threads' +// stacks. +function mediaTestCleanup(callback) { + var V = document.getElementsByTagName("video"); + for (let i = 0; i < V.length; i++) { + removeNodeAndSource(V[i]); + V[i] = null; + } + var A = document.getElementsByTagName("audio"); + for (let i = 0; i < A.length; i++) { + removeNodeAndSource(A[i]); + A[i] = null; + } + SpecialPowers.exactGC(callback); +} + +async function dumpDebugInfoForToken(token) { + for (let v of document.getElementsByTagName("video")) { + if (token === v.token) { + info(JSON.stringify(await SpecialPowers.wrap(v).mozRequestDebugInfo())); + return; + } + } + for (let a of document.getElementsByTagName("audio")) { + if (token === a.token) { + info(JSON.stringify(await SpecialPowers.wrap(a).mozRequestDebugInfo())); + return; + } + } +} + +// Could be undefined in a page opened by the parent test page +// like file_access_controls.html. +if ("SimpleTest" in window) { + SimpleTest.requestFlakyTimeout("untriaged"); + + // Register timeout function to dump debugging logs. + SimpleTest.registerTimeoutFunction(async function() { + for (const v of document.getElementsByTagName("video")) { + SimpleTest.info( + JSON.stringify(await SpecialPowers.wrap(v).mozRequestDebugInfo()) + ); + } + for (const a of document.getElementsByTagName("audio")) { + SimpleTest.info( + JSON.stringify(await SpecialPowers.wrap(a).mozRequestDebugInfo()) + ); + } + }); +} diff --git a/dom/media/test/midflight-redirect.sjs b/dom/media/test/midflight-redirect.sjs new file mode 100644 index 0000000000..d4dae537a0 --- /dev/null +++ b/dom/media/test/midflight-redirect.sjs @@ -0,0 +1,78 @@ +function parseQuery(query, key) { + for (let p of query.split('&')) { + if (p == key) { + return true; + } + if (p.startsWith(key + "=")) { + return p.substring(key.length + 1); + } + } +} + +// Return the first few bytes in a short byte range response. When Firefox +// requests subsequent bytes in a second range request, respond with a +// redirect. Requests after the first redirected are serviced as expected. +function handleRequest(request, response) +{ + var query = request.queryString; + var resource = parseQuery(query, "resource"); + var type = parseQuery(query, "type") || "application/octet-stream"; + var redirected = parseQuery(query, "redirected") || false; + var useCors = parseQuery(query, "cors") || false; + + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/" + resource; + var split = paths.split("/"); + for (var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + let [from, to] = request.getHeader("range").split("=")[1].split("-").map(s => parseInt(s)); + + if (!redirected && from > 0) { + var origin = request.host == "mochi.test" ? "example.org" : "mochi.test:8888"; + response.setStatusLine(request.httpVersion, 303, "See Other"); + let url = "http://" + origin + + "/tests/dom/media/test/midflight-redirect.sjs?redirected&" + query; + response.setHeader("Location", url); + response.setHeader("Content-Type", "text/html"); + return; + } + + if (isNaN(to)) { + to = bytes.length - 1; + } + + if (from == 0 && !redirected) { + to = parseInt(parseQuery(query, "redirectAt")) || Math.floor(bytes.length / 4); + } + to = Math.min(to, bytes.length - 1); + + // Note: 'to' is the first index *excluded*, so we need (to + 1) + // in the substring end here. + byterange = bytes.substring(from, to + 1); + + let contentRange = "bytes " + from + "-" + to + "/" + bytes.length; + let contentLength = byterange.length.toString(); + + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + response.setHeader("Content-Range", contentRange); + response.setHeader("Content-Length", contentLength, false); + response.setHeader("Content-Type", type, false); + response.setHeader("Accept-Ranges", "bytes", false); + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + if (redirected && useCors) { + response.setHeader("Access-Control-Allow-Origin", "*"); + } + response.write(byterange, byterange.length); + bis.close(); +} diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini new file mode 100644 index 0000000000..6561f1e614 --- /dev/null +++ b/dom/media/test/mochitest.ini @@ -0,0 +1,1172 @@ +# Media tests should be backend independent, i.e., not conditioned on ogg, +# wave etc. (The only exception is the can_play_type tests, which +# necessarily depend on the backend(s) configured.) As far as possible, each +# test should work with any resource type. This makes it easy to add new +# backends and reduces the amount of test duplication. + +# For each supported backend, resources that can be played by that backend +# should be added to the lists in manifest.js. Media tests that aren't +# testing for a bug in handling a specific resource type should pick one of +# the lists in manifest.js and run the test for each resource in the list +# that is supported in the current build (the canPlayType API is useful for +# this). + +# To test whether a valid resource can simply be played through correctly, +# and optionally that its metadata is read correctly, just add it to +# gPlayTests in manifest.js. To test whether an invalid resource correctly +# throws an error (and does not cause a crash or hang), just add it to +# gErrorTests in manifest.js. + +# To test for a specific bug in handling a specific resource type, make the +# test first check canPlayType for the type, and if it's not supported, just +# do ok(true, "Type not supported") and stop the test. + +[DEFAULT] +subsuite = media +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536604 +support-files = + 16bit_wave_extrametadata.wav + 16bit_wave_extrametadata.wav^headers^ + 320x240.ogv + 320x240.ogv^headers^ + 448636.ogv + 448636.ogv^headers^ + A4.ogv + A4.ogv^headers^ + VID_0001.ogg + VID_0001.ogg^headers^ + allowed.sjs + ambisonics.mp4 + ambisonics.mp4^headers^ + audio-gaps.ogg + audio-gaps.ogg^headers^ + audio-gaps-short.ogg + audio-gaps-short.ogg^headers^ + audio-overhang.ogg + audio-overhang.ogg^headers^ + audio.wav + audio.wav^headers^ + av1.mp4 + av1.mp4^headers^ + background_video.js + badtags.ogg + badtags.ogg^headers^ + bear-640x360-v_frag-cenc-key_rotation.mp4 + bear-640x360-a_frag-cenc-key_rotation.mp4 + beta-phrasebook.ogg + beta-phrasebook.ogg^headers^ + big.wav + big.wav^headers^ + big-buck-bunny-cenc-avc3-1.m4s + big-buck-bunny-cenc-avc3-1.m4s^headers^ + big-buck-bunny-cenc-avc3-init.mp4 + big-buck-bunny-cenc-avc3-init.mp4^headers^ + big-short.wav + big-short.wav^headers^ + bipbop.mp4 + bipbop-cenc-audio1.m4s + bipbop-cenc-audio1.m4s^headers^ + bipbop-cenc-audio2.m4s + bipbop-cenc-audio2.m4s^headers^ + bipbop-cenc-audio3.m4s + bipbop-cenc-audio3.m4s^headers^ + bipbop-cenc-audioinit.mp4 + bipbop-cenc-audioinit.mp4^headers^ + bipbop-cenc-video1.m4s + bipbop-cenc-video1.m4s^headers^ + bipbop-cenc-video2.m4s + bipbop-cenc-video2.m4s^headers^ + bipbop-cenc-videoinit.mp4 + bipbop-cenc-videoinit.mp4^headers^ + bipbop-cenc-video-10s.mp4 + bipbop-cenc-video-10s.mp4^headers^ + bipbop-clearkey-keyrotation-clear-lead-audio.mp4 + bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^ + bipbop-clearkey-keyrotation-clear-lead-video.mp4 + bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^ + bipbop_225w_175kbps.mp4 + bipbop_225w_175kbps.mp4^headers^ + bipbop_225w_175kbps-cenc-audio-key1-1.m4s + bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key1-2.m4s + bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key1-3.m4s + bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key1-4.m4s + bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key1-init.mp4 + bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_225w_175kbps-cenc-audio-key2-1.m4s + bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key2-2.m4s + bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key2-3.m4s + bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key2-4.m4s + bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_225w_175kbps-cenc-audio-key2-init.mp4 + bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_225w_175kbps-cenc-video-key1-1.m4s + bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^ + bipbop_225w_175kbps-cenc-video-key1-init.mp4 + bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^ + bipbop_225w_175kbps-cenc-video-key2-1.m4s + bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^ + bipbop_225w_175kbps-cenc-video-key2-init.mp4 + bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^ + bipbop_300_215kbps-cenc-audio-key1-1.m4s + bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key1-2.m4s + bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key1-3.m4s + bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key1-4.m4s + bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key1-init.mp4 + bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_300_215kbps-cenc-audio-key2-1.m4s + bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key2-2.m4s + bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key2-3.m4s + bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key2-4.m4s + bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_300_215kbps-cenc-audio-key2-init.mp4 + bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_300_215kbps-cenc-video-key1-1.m4s + bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^ + bipbop_300_215kbps-cenc-video-key1-2.m4s + bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^ + bipbop_300_215kbps-cenc-video-key1-init.mp4 + bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^ + bipbop_300_215kbps-cenc-video-key2-1.m4s + bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^ + bipbop_300_215kbps-cenc-video-key2-2.m4s + bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^ + bipbop_300_215kbps-cenc-video-key2-init.mp4 + bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^ + bipbop_300wp_227kbps-cenc-audio-key1-1.m4s + bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key1-2.m4s + bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key1-3.m4s + bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key1-4.m4s + bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key1-init.mp4 + bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_300wp_227kbps-cenc-audio-key2-1.m4s + bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key2-2.m4s + bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key2-3.m4s + bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key2-4.m4s + bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_300wp_227kbps-cenc-audio-key2-init.mp4 + bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_300wp_227kbps-cenc-video-key1-1.m4s + bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^ + bipbop_300wp_227kbps-cenc-video-key1-2.m4s + bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^ + bipbop_300wp_227kbps-cenc-video-key1-init.mp4 + bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^ + bipbop_300wp_227kbps-cenc-video-key2-1.m4s + bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^ + bipbop_300wp_227kbps-cenc-video-key2-2.m4s + bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^ + bipbop_300wp_227kbps-cenc-video-key2-init.mp4 + bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^ + bipbop_360w_253kbps-cenc-audio-key1-1.m4s + bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key1-2.m4s + bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key1-3.m4s + bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key1-4.m4s + bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key1-init.mp4 + bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_360w_253kbps-cenc-audio-key2-1.m4s + bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key2-2.m4s + bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key2-3.m4s + bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key2-4.m4s + bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_360w_253kbps-cenc-audio-key2-init.mp4 + bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_360w_253kbps-cenc-video-key1-1.m4s + bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^ + bipbop_360w_253kbps-cenc-video-key1-init.mp4 + bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^ + bipbop_360w_253kbps-cenc-video-key2-1.m4s + bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^ + bipbop_360w_253kbps-cenc-video-key2-init.mp4 + bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^ + bipbop_360w_253kbps-clearkey-audio.webm + bipbop_360w_253kbps-clearkey-audio.webm^headers^ + bipbop_360w_253kbps-clearkey-video-vp8.webm + bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^ + bipbop_360w_253kbps-clearkey-video-vp9.webm + bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^ + bipbop_480_624kbps-cenc-audio-key1-1.m4s + bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key1-2.m4s + bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key1-3.m4s + bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key1-4.m4s + bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key1-init.mp4 + bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_480_624kbps-cenc-audio-key2-1.m4s + bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key2-2.m4s + bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key2-3.m4s + bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key2-4.m4s + bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_480_624kbps-cenc-audio-key2-init.mp4 + bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_480_624kbps-cenc-video-key1-1.m4s + bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^ + bipbop_480_624kbps-cenc-video-key1-2.m4s + bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^ + bipbop_480_624kbps-cenc-video-key1-init.mp4 + bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^ + bipbop_480_624kbps-cenc-video-key2-1.m4s + bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^ + bipbop_480_624kbps-cenc-video-key2-2.m4s + bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^ + bipbop_480_624kbps-cenc-video-key2-init.mp4 + bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^ + bipbop_480_959kbps-cenc-audio-key1-1.m4s + bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key1-2.m4s + bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key1-3.m4s + bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key1-4.m4s + bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key1-init.mp4 + bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_480_959kbps-cenc-audio-key2-1.m4s + bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key2-2.m4s + bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key2-3.m4s + bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key2-4.m4s + bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_480_959kbps-cenc-audio-key2-init.mp4 + bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_480_959kbps-cenc-video-key1-1.m4s + bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^ + bipbop_480_959kbps-cenc-video-key1-2.m4s + bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^ + bipbop_480_959kbps-cenc-video-key1-init.mp4 + bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^ + bipbop_480_959kbps-cenc-video-key2-1.m4s + bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^ + bipbop_480_959kbps-cenc-video-key2-2.m4s + bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^ + bipbop_480_959kbps-cenc-video-key2-init.mp4 + bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^ + bipbop_480wp_663kbps-cenc-audio-key1-1.m4s + bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key1-2.m4s + bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key1-3.m4s + bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key1-4.m4s + bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key1-init.mp4 + bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_480wp_663kbps-cenc-audio-key2-1.m4s + bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key2-2.m4s + bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key2-3.m4s + bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key2-4.m4s + bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_480wp_663kbps-cenc-audio-key2-init.mp4 + bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_480wp_663kbps-cenc-video-key1-1.m4s + bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^ + bipbop_480wp_663kbps-cenc-video-key1-2.m4s + bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^ + bipbop_480wp_663kbps-cenc-video-key1-init.mp4 + bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^ + bipbop_480wp_663kbps-cenc-video-key2-1.m4s + bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^ + bipbop_480wp_663kbps-cenc-video-key2-2.m4s + bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^ + bipbop_480wp_663kbps-cenc-video-key2-init.mp4 + bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^ + bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s + bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s + bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s + bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s + bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4 + bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^ + bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s + bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s + bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s + bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s + bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^ + bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4 + bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^ + bipbop_480wp_1001kbps-cenc-video-key1-1.m4s + bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^ + bipbop_480wp_1001kbps-cenc-video-key1-2.m4s + bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^ + bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 + bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^ + bipbop_480wp_1001kbps-cenc-video-key2-1.m4s + bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^ + bipbop_480wp_1001kbps-cenc-video-key2-2.m4s + bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^ + bipbop_480wp_1001kbps-cenc-video-key2-init.mp4 + bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^ + bipbop-lateaudio.mp4 + bipbop-lateaudio.mp4^headers^ + black100x100-aspect3to2.ogv + black100x100-aspect3to2.ogv^headers^ + bogus.duh + bogus.ogv + bogus.ogv^headers^ + bogus.wav + bogus.wav^headers^ + bug461281.ogg + bug461281.ogg^headers^ + bug482461-theora.ogv + bug482461-theora.ogv^headers^ + bug482461.ogv + bug482461.ogv^headers^ + bug495129.ogv + bug495129.ogv^headers^ + bug495794.ogg + bug495794.ogg^headers^ + bug498380.ogv + bug498380.ogv^headers^ + bug498855-1.ogv + bug498855-1.ogv^headers^ + bug498855-2.ogv + bug498855-2.ogv^headers^ + bug498855-3.ogv + bug498855-3.ogv^headers^ + bug499519.ogv + bug499519.ogv^headers^ + bug500311.ogv + bug500311.ogv^headers^ + bug501279.ogg + bug501279.ogg^headers^ + bug504613.ogv + bug504613.ogv^headers^ + bug504644.ogv + bug504644.ogv^headers^ + bug504843.ogv + bug504843.ogv^headers^ + bug506094.ogv + bug506094.ogv^headers^ + bug516323.indexed.ogv + bug516323.indexed.ogv^headers^ + bug516323.ogv + bug516323.ogv^headers^ + bug520493.ogg + bug520493.ogg^headers^ + bug520500.ogg + bug520500.ogg^headers^ + bug520908.ogv + bug520908.ogv^headers^ + bug523816.ogv + bug523816.ogv^headers^ + bug533822.ogg + bug533822.ogg^headers^ + bug556821.ogv + bug556821.ogv^headers^ + bug557094.ogv + bug557094.ogv^headers^ + bug603918.webm + bug603918.webm^headers^ + bug604067.webm + bug604067.webm^headers^ + bug1066943.webm + bug1066943.webm^headers^ + bug1301226.wav + bug1301226.wav^headers^ + bug1301226-odd.wav + bug1301226-odd.wav^headers^ + bug1377278.webm + bug1377278.webm^headers^ + bunny.webm + can_play_type_dash.js + can_play_type_ogg.js + can_play_type_wave.js + can_play_type_webm.js + cancellable_request.sjs + chain.ogg + chain.ogg^headers^ + chain.ogv + chain.ogv^headers^ + chain.opus + chain.opus^headers^ + chained-audio-video.ogg + chained-audio-video.ogg^headers^ + chained-video.ogv + chained-video.ogv^headers^ + chromeHelper.js + cloneElementVisually_helpers.js + contentType.sjs + detodos.opus + detodos.opus^headers^ + detodos.webm + detodos.webm^headers^ + detodos-short.webm + detodos-short.webm^headers^ + detodos-recorder-test.opus + detodos-recorder-test.opus^headers^ + detodos-short.opus + detodos-short.opus^headers^ + dirac.ogg + dirac.ogg^headers^ + dynamic_resource.sjs + eme.js + empty_size.mp3 + file_access_controls.html + file_eme_createMediaKeys.html + flac-s24.flac + flac-s24.flac^headers^ + flac-noheader-s16.flac + flac-noheader-s16.flac^headers^ + flac-sample.mp4 + flac-sample.mp4^headers^ + flac-sample-cenc.mp4 + flac-sample-cenc.mp4^headers^ + fragment_noplay.js + fragment_play.js + gizmo.mp4 + gizmo.mp4^headers^ + gizmo-noaudio.mp4 + gizmo-noaudio.mp4^headers^ + gizmo-short.mp4 + gizmo-short.mp4^headers^ + gizmo.webm + gizmo.webm^headers^ + gizmo-noaudio.webm + gizmo-noaudio.webm^headers^ + gUM_support.js + gzipped_mp4.sjs + huge-id3.mp3 + huge-id3.mp3^headers^ + id3tags.mp3 + id3tags.mp3^headers^ + invalid-cmap-s0c0.opus + invalid-cmap-s0c0.opus^headers^ + invalid-cmap-s0c2.opus + invalid-cmap-s0c2.opus^headers^ + invalid-cmap-s1c2.opus + invalid-cmap-s1c2.opus^headers^ + invalid-cmap-short.opus + invalid-cmap-short.opus^headers^ + invalid-discard_on_multi_blocks.webm + invalid-discard_on_multi_blocks.webm^headers^ + invalid-excess_discard.webm + invalid-excess_discard.webm^headers^ + invalid-excess_neg_discard.webm + invalid-excess_neg_discard.webm^headers^ + invalid-m0c0.opus + invalid-m0c0.opus^headers^ + invalid-m0c3.opus + invalid-m0c3.opus^headers^ + invalid-m1c0.opus + invalid-m1c0.opus^headers^ + invalid-m1c9.opus + invalid-m1c9.opus^headers^ + invalid-m2c0.opus + invalid-m2c0.opus^headers^ + invalid-m2c1.opus + invalid-m2c1.opus^headers^ + invalid-neg_discard.webm + invalid-neg_discard.webm^headers^ + invalid-preskip.webm + invalid-preskip.webm^headers^ + manifest.js + midflight-redirect.sjs + multiple-bos.ogg + multiple-bos.ogg^headers^ + multiple-bos-more-header-fileds.ogg + multiple-bos-more-header-fileds.ogg^headers^ + multi_id3v2.mp3 + no-cues.webm + no-cues.webm^headers^ + notags.mp3 + notags.mp3^headers^ + opus-mapping2.mp4 + opus-mapping2.mp4^headers^ + opus-mapping2.webm + opus-mapping2.webm^headers^ + opus-sample.mp4 + opus-sample.mp4^headers^ + opus-sample-cenc.mp4 + opus-sample-cenc.mp4^headers^ + owl-funnier-id3.mp3 + owl-funnier-id3.mp3^headers^ + owl-funny-id3.mp3 + owl-funny-id3.mp3^headers^ + owl.mp3 + owl.mp3^headers^ + owl-short.mp3 + owl-short.mp3^headers^ + pixel_aspect_ratio.mp4 + play_promise.js + poster-test.jpg + r11025_msadpcm_c1.wav + r11025_msadpcm_c1.wav^headers^ + r11025_s16_c1.wav + r11025_s16_c1.wav^headers^ + r11025_s16_c1_trailing.wav + r11025_s16_c1_trailing.wav^headers^ + r11025_s16_c1-short.wav + r11025_s16_c1-short.wav^headers^ + r11025_u8_c1.wav + r11025_u8_c1.wav^headers^ + r11025_u8_c1_trunc.wav + r11025_u8_c1_trunc.wav^headers^ + r16000_u8_c1_list.wav + r16000_u8_c1_list.wav^headers^ + reactivate_helper.html + red-46x48.mp4 + red-46x48.mp4^headers^ + red-48x46.mp4 + red-48x46.mp4^headers^ + redirect.sjs + referer.sjs + resolution-change.webm + resolution-change.webm^headers^ + sample.3gp + sample.3g2 + sample-encrypted-sgpdstbl-sbgptraf.mp4 + sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^ + sample-fisbone-skeleton4.ogv + sample-fisbone-skeleton4.ogv^headers^ + sample-fisbone-wrong-header.ogv + sample-fisbone-wrong-header.ogv^headers^ + seek.ogv + seek.ogv^headers^ + seek-short.ogv + seek-short.ogv^headers^ + seek.webm + seek.webm^headers^ + seek-short.webm + seek-short.webm^headers^ + seek_support.js + seekLies.sjs + seek_with_sound.ogg^headers^ + short-cenc.mp4 + sine.webm + sine.webm^headers^ + sintel-short-clearkey-subsample-encrypted-audio.webm + sintel-short-clearkey-subsample-encrypted-audio.webm^headers^ + sintel-short-clearkey-subsample-encrypted-video.webm + sintel-short-clearkey-subsample-encrypted-video.webm^headers^ + short.mp4 + short.mp4.gz + short.mp4^headers^ + short-aac-encrypted-audio.mp4 + short-aac-encrypted-audio.mp4^headers^ + short-audio-fragmented-cenc-without-pssh.mp4 + short-audio-fragmented-cenc-without-pssh.mp4^headers^ + short-video.ogv + short-video.ogv^headers^ + short-vp9-encrypted-video.mp4 + short-vp9-encrypted-video.mp4^headers^ + small-shot-mp3.mp4 + small-shot-mp3.mp4^headers^ + small-shot.m4a + small-shot.mp3 + small-shot.mp3^headers^ + small-shot.ogg + small-shot.ogg^headers^ + small-shot.flac + sound.ogg + sound.ogg^headers^ + spacestorm-1000Hz-100ms.ogg + spacestorm-1000Hz-100ms.ogg^headers^ + split.webm + split.webm^headers^ + street.mp4 + street.mp4^headers^ + test-1-mono.opus + test-1-mono.opus^headers^ + test-2-stereo.opus + test-2-stereo.opus^headers^ + test-3-LCR.opus + test-3-LCR.opus^headers^ + test-4-quad.opus + test-4-quad.opus^headers^ + test-5-5.0.opus + test-5-5.0.opus^headers^ + test-6-5.1.opus + test-6-5.1.opus^headers^ + test-7-6.1.opus + test-7-6.1.opus^headers^ + test-8-7.1.opus + test-8-7.1.opus^headers^ + test-stereo-phase-inversion-180.opus + test-stereo-phase-inversion-180.opus^headers^ + variable-channel.ogg + variable-channel.ogg^headers^ + variable-channel.opus + variable-channel.opus^headers^ + variable-preskip.opus + variable-preskip.opus^headers^ + variable-samplerate.ogg + variable-samplerate.ogg^headers^ + variable-samplerate.opus + variable-samplerate.opus^headers^ + vbr-head.mp3 + vbr-head.mp3^headers^ + vbr.mp3 + vbr.mp3^headers^ + very-short.mp3 + video-overhang.ogg + video-overhang.ogg^headers^ + vp9-superframes.webm + vp9-superframes.webm^headers^ + vp9.webm + vp9.webm^headers^ + vp9-short.webm + vp9-short.webm^headers^ + vp9cake.webm + vp9cake.webm^headers^ + vp9cake-short.webm + vp9cake-short.webm^headers^ + wave_metadata.wav + wave_metadata.wav^headers^ + wave_metadata_bad_len.wav + wave_metadata_bad_len.wav^headers^ + wave_metadata_bad_no_null.wav + wave_metadata_bad_no_null.wav^headers^ + wave_metadata_bad_utf8.wav + wave_metadata_bad_utf8.wav^headers^ + wave_metadata_unknown_tag.wav + wave_metadata_unknown_tag.wav^headers^ + wave_metadata_utf8.wav + wave_metadata_utf8.wav^headers^ + wavedata_alaw.wav + wavedata_alaw.wav^headers^ + wavedata_float.wav + wavedata_float.wav^headers^ + wavedata_s24.wav + wavedata_s24.wav^headers^ + wavedata_s16.wav + wavedata_s16.wav^headers^ + wavedata_u8.wav + wavedata_u8.wav^headers^ + wavedata_ulaw.wav + wavedata_ulaw.wav^headers^ + !/dom/canvas/test/captureStream_common.js + !/dom/html/test/reflect.js + !/dom/media/webrtc/tests/mochitests/head.js + hls/bipbop_16x9_single.m3u8 + hls/bipbop_4x3_single.m3u8 + hls/bipbop_4x3_variant.m3u8 + hls/400x300_prog_index.m3u8 + hls/400x300_prog_index_5s.m3u8 + hls/416x243_prog_index_5s.m3u8 + hls/640x480_prog_index.m3u8 + hls/960x720_prog_index.m3u8 + hls/400x300_seg0.ts + hls/400x300_seg0_5s.ts + hls/400x300_seg1.ts + hls/416x243_seg0_5s.ts + hls/640x480_seg0.ts + hls/640x480_seg1.ts + hls/960x720_seg0.ts + hls/960x720_seg1.ts + +[test_access_control.html] +[test_arraybuffer.html] +[test_aspectratio_mp4.html] +[test_audio1.html] +[test_audio2.html] +[test_audioDocumentTitle.html] +skip-if = true # bug 475110 - disabled since we don't play Wave files standalone +[test_buffered.html] +[test_bug448534.html] +[test_bug463162.xhtml] +[test_bug465498.html] +[test_bug495145.html] +skip-if = os == "win" #Bug 1404373 +[test_bug495300.html] +[test_bug654550.html] +[test_bug686942.html] +[test_bug726904.html] +[test_bug874897.html] +[test_bug879717.html] +skip-if = toolkit == 'android' # bug 1285441, android(bug 1232305) +tags=capturestream +[test_bug895305.html] +skip-if = (android_version == '25' && debug) # android(bug 1232305) +[test_bug919265.html] +skip-if = (android_version == '25' && debug) # android(bug 1232305) +[test_bug1113600.html] +tags=capturestream +[test_bug1242338.html] +[test_bug1248229.html] +tags=capturestream +[test_bug1512958.html] +tags=mtg capturestream +[test_bug1553262.html] +tags=mtg capturestream +[test_can_play_type.html] +skip-if = (android_version == '25' && debug) # android(bug 1232305) +[test_can_play_type_mpeg.html] +[test_can_play_type_no_ogg.html] +skip-if = (android_version == '25' && debug) # android(bug 1232305) +[test_can_play_type_ogg.html] +skip-if = (android_version == '25' && debug) # android(bug 1232305) +[test_chaining.html] +[test_clone_media_element.html] +skip-if = toolkit == 'android' # bug 1108558, android(bug 1232305) +[test_closing_connections.html] +[test_constants.html] +[test_controls.html] +[test_cueless_webm_seek-1.html] +[test_cueless_webm_seek-2.html] +[test_cueless_webm_seek-3.html] +[test_currentTime.html] +[test_decode_error.html] +[test_decode_error_crossorigin.html] +[test_decoder_disable.html] +[test_defaultMuted.html] +[test_delay_load.html] +[test_duration_after_error.html] +[test_eme_autoplay.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_pssh_in_moof.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_session_callable_value.html] +skip-if = verify && debug && (os == 'linux') +scheme=https +[test_eme_canvas_blocked.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_createMediaKeys_iframes.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_detach_media_keys.html] +skip-if = toolkit == 'android' || (verify && debug && (os == 'linux' || os == 'win')) # bug 1149374 +scheme=https +[test_eme_detach_reattach_same_mediakeys_during_playback.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_initDataTypes.html] +skip-if = toolkit == 'android' || (verify && debug && (os == 'linux' || os == 'mac')) # bug 1149374 +scheme=https +[test_eme_missing_pssh.html] +skip-if = toolkit == 'android' || (verify && debug && (os == 'mac')) # bug 1149374 +scheme=https +[test_eme_non_mse_fails.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_request_notifications.html] +skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) # bug 1149374 +scheme=https +[test_eme_playback.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_requestKeySystemAccess.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_requestMediaKeySystemAccess_with_app_approval.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_sample_groups_playback.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_setMediaKeys_before_attach_MediaSource.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_stream_capture_blocked_case1.html] +tags=mtg capturestream +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_stream_capture_blocked_case2.html] +tags=mtg capturestream +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_stream_capture_blocked_case3.html] +tags=mtg capturestream +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_unsetMediaKeys_then_capture.html] +skip-if = xorigin || toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_waitingforkey.html] +skip-if = xorigin || toolkit == 'android' # bug 1149374 +scheme=https +[test_eme_getstatusforpolicy.html] +skip-if = toolkit == 'android' # bug 1149374 +scheme=https +[test_empty_resource.html] +[test_error_in_video_document.html] +[test_error_on_404.html] +[test_fastSeek.html] +[test_fastSeek-forwards.html] +[test_imagecapture.html] +scheme=https +[test_info_leak.html] +[test_invalid_reject.html] +[test_invalid_reject_play.html] +[test_invalid_seek.html] +[test_load.html] +[test_load_candidates.html] +[test_load_same_resource.html] +[test_load_source.html] +[test_load_source_empty_type.html] +[test_loop.html] +[test_looping_eventsOrder.html] +[test_media_selection.html] +[test_media_sniffer.html] +[test_mediacapabilities_resistfingerprinting.html] +[test_mediarecorder_avoid_recursion.html] +skip-if = os == 'win' && !debug +scheme=https +tags=mtg +[test_mediarecorder_bitrate.html] +skip-if = toolkit == 'android' # bug 1297432, android(bug 1232305) +tags=mtg +[test_mediarecorder_creation.html] +tags=mtg capturestream +[test_mediarecorder_creation_fail.html] +tags=mtg +[test_mediarecorder_fires_start_event_once_when_erroring.html] +tags=mtg +[test_mediarecorder_onerror_pause.html] +scheme=https +tags=mtg +[test_mediarecorder_pause_resume_video.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_mediarecorder_playback_can_repeat.html] +tags=mtg +[test_mediarecorder_principals.html] +skip-if = (os == 'win' && os_version == '10.0' && webrender) # Bug 1453375 +tags=mtg +[test_mediarecorder_record_4ch_audiocontext.html] +tags=mtg +skip-if = os == "linux" && bits == 64 #Bug 1598101 +[test_mediarecorder_record_addtracked_stream.html] +skip-if = toolkit == 'android' # Bug 1408241 +tags=mtg capturestream +[test_mediarecorder_record_audiocontext.html] +tags=mtg +[test_mediarecorder_record_audiocontext_mlk.html] +tags=mtg +[test_mediarecorder_record_audionode.html] +tags=mtg +[test_mediarecorder_record_canvas_captureStream.html] +skip-if = toolkit == 'android' # android(bug 1232305) +tags=mtg +[test_mediarecorder_record_changing_video_resolution.html] +skip-if = toolkit == 'android' # android(bug 1232305) +tags=mtg +[test_mediarecorder_record_upsize_resolution.html] +skip-if = toolkit == 'android' # android(bug 1232305) +tags=mtg +[test_mediarecorder_record_downsize_resolution.html] +skip-if = toolkit == 'android' # android(bug 1232305) +tags=mtg +[test_mediarecorder_record_gum_video_timeslice.html] +scheme=https +tags=mtg +[test_mediarecorder_record_gum_video_timeslice_mixed.html] +scheme=https +tags=mtg +[test_mediarecorder_record_immediate_stop.html] +tags=mtg capturestream +[test_mediarecorder_record_no_timeslice.html] +tags=mtg capturestream +[test_mediarecorder_record_session.html] +tags=mtg capturestream +[test_mediarecorder_record_startstopstart.html] +tags=mtg +[test_mediarecorder_record_timeslice.html] +tags=mtg capturestream +[test_mediarecorder_reload_crash.html] +tags=mtg capturestream +[test_mediarecorder_state_transition.html] +tags=mtg capturestream +[test_mediarecorder_state_event_order.html] +tags=mtg capturestream +[test_mediarecorder_webm_support.html] +tags=mtg +[test_mediarecorder_record_getdata_afterstart.html] +tags=mtg capturestream +[test_mediatrack_consuming_mediaresource.html] +[test_mediatrack_consuming_mediastream.html] +scheme=https +tags=mtg +[test_mediatrack_events.html] +scheme=https +[test_mediatrack_parsing_ogg.html] +[test_mediatrack_replay_from_end.html] +[test_metadata.html] +[test_midflight_redirect_blocked.html] +[test_mixed_principals.html] +skip-if = toolkit == 'android' # bug 1309814, android(bug 1232305) +[test_mozHasAudio.html] +[test_mp3_with_multiple_ID3v2.html] +[test_multiple_mediastreamtracks.html] +scheme=https +[test_networkState.html] +[test_new_audio.html] +[test_no_load_event.html] +[test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html] +[test_paused.html] +[test_paused_after_ended.html] +[test_play_events.html] +[test_play_events_2.html] +[test_play_promise_1.html] +tags=promise-play +[test_play_promise_2.html] +tags=promise-play +[test_play_promise_3.html] +tags=promise-play +[test_play_promise_4.html] +tags=promise-play +[test_play_promise_5.html] +tags=promise-play +[test_play_promise_6.html] +tags=promise-play +[test_play_promise_7.html] +tags=promise-play +[test_play_promise_8.html] +tags=promise-play +[test_play_promise_9.html] +tags=promise-play +[test_play_promise_10.html] +tags=promise-play +[test_play_promise_11.html] +tags=promise-play +[test_play_promise_12.html] +tags=promise-play +[test_play_promise_13.html] +tags=promise-play +[test_play_promise_14.html] +tags=promise-play +[test_play_promise_15.html] +tags=promise-play +[test_play_promise_16.html] +tags=promise-play +[test_play_promise_17.html] +tags=promise-play +[test_play_promise_18.html] +tags=promise-play +[test_play_twice.html] +skip-if = appname == "seamonkey" # Seamonkey: Bug 598252, bug 1307337, bug 1143695 +[test_playback.html] +skip-if = toolkit == 'android' || (debug && os == "mac") # bug 1316177, 1484451 +[test_playback_errors.html] +[test_playback_rate.html] +[test_playback_rate_playpause.html] +[test_playback_reactivate.html] +[test_played.html] +skip-if = toolkit == 'android' && is_emulator # Times out on android-em, Bug 1613946 +[test_preload_actions.html] +[test_preload_attribute.html] +[test_preload_suspend.html] +[test_preserve_playbackrate_after_ui_play.html] +[test_progress.html] +[test_reactivate.html] +skip-if = true # see bug 1319725 +[test_readyState.html] +[test_referer.html] +skip-if = android_version == '25' && debug # android(bug 1232305) +[test_replay_metadata.html] +[test_reset_events_async.html] +[test_reset_src.html] +skip-if = (verify && debug && os == 'win') +[test_video_dimensions.html] +[test_resolution_change.html] +tags=capturestream +[test_resume.html] +skip-if = true # bug 1021673 +[test_seamless_looping.html] +[test_seek_negative.html] +[test_seek_nosrc.html] +[test_seek_out_of_range.html] +skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305) +[test_seek_promise_bug1344357.html] +skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305) +[test_seek-1.html] +skip-if = toolkit == 'android' # bug 1322806, android(bug 1232305) +[test_seek-2.html] +skip-if = toolkit == 'android' # bug 1309778, android(bug 1232305) +[test_seek-3.html] +skip-if = toolkit == 'android' # bug 1321082, android(bug 1232305) +[test_seek-4.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seek-5.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seek-6.html] +skip-if = toolkit == 'android' # bug 1336629, bug 1324482, android(bug 1232305) +[test_seek-7.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seek-8.html] +skip-if = toolkit == 'android' # bug 1310584, android(bug 1232305) +[test_seek-9.html] +skip-if = toolkit == 'android' # bug 1332019, android(bug 1232305) +[test_seek-10.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seek-11.html] +skip-if = toolkit == 'android' # bug 1323133, android(bug 1232305) +[test_seek-12.html] +skip-if = toolkit == 'android' # bug 1321081, android(bug 1232305) +[test_seek-13.html] +skip-if = toolkit == 'android' # bug 1299174, android(bug 1232305) +[test_seek-14.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seekable1.html] +skip-if = toolkit == 'android' # android(bug 1232305) +[test_seekLies.html] +[test_seekToNextFrame.html] +skip-if = toolkit == 'android' # bug 1329391, android(bug 1232305) +tags=seektonextframe +[test_seek_duration.html] +[test_source.html] +[test_source_null.html] +[test_source_write.html] +[test_standalone.html] +[test_streams_capture_origin.html] +tags=mtg capturestream +[test_streams_element_capture.html] +skip-if = true # bug 1372457 # bug 1557901 # bug 1554808 +tags=mtg capturestream +[test_streams_element_capture_mediatrack.html] +tags=mtg capturestream +[test_streams_element_capture_playback.html] +tags=mtg capturestream +[test_streams_element_capture_reset.html] +tags=mtg capturestream +[test_streams_element_capture_twice.html] +tags=mtg capturestream +[test_streams_firstframe.html] +tags=mtg capturestream +[test_streams_gc.html] +tags=mtg capturestream +[test_streams_individual_pause.html] +scheme=https +tags=mtg +[test_streams_srcObject.html] +skip-if = toolkit == 'android' # bug 1300443, android(bug 1232305) +tags=mtg capturestream +[test_streams_tracks.html] +skip-if = toolkit == 'android' # android(bug 1232305) +tags=mtg capturestream +[test_suspend_media_by_inactive_docshell.html] +[test_timeupdate_small_files.html] +[test_unseekable.html] +[test_video_to_canvas.html] +skip-if = toolkit == 'android' # android(bug 1232305), bugs 1320418,1347953,1347954,1348140,1348386 +[test_video_in_audio_element.html] +[test_video_stats_resistfingerprinting.html] +tags = resistfingerprinting +[test_videoDocumentTitle.html] +[test_VideoPlaybackQuality.html] +[test_VideoPlaybackQuality_disabled.html] +[test_volume.html] +[test_vp9_superframes.html] +skip-if = os == 'mac' && os_version == '10.14' # mac due to bug 1545737 +# The tests below contain backend-specific tests. Write backend independent +# tests rather than adding to this list. +[test_can_play_type_webm.html] +[test_can_play_type_wave.html] +[test_fragment_noplay.html] +[test_fragment_play.html] +[test_background_video_cancel_suspend_taint.html] +skip-if = toolkit == 'android' # bug 1346705 +tags = suspend +[test_background_video_cancel_suspend_visible.html] +tags = suspend +[test_background_video_no_suspend_disabled.html] +tags = suspend +[test_background_video_no_suspend_short_vid.html] +tags = suspend +[test_background_video_no_suspend_not_in_tree.html] +tags = suspend +[test_background_video_resume_after_end_show_last_frame.html] +skip-if = toolkit == 'android' # bug 1346705 +tags = suspend +[test_background_video_resume_looping_video_without_audio.html] +tags = suspend +[test_background_video_suspend.html] +skip-if = os == 'android' #Bug 1304480 +tags = suspend +[test_background_video_suspend_ends.html] +tags = suspend +[test_background_video_tainted_by_capturestream.html] +tags = suspend +[test_background_video_tainted_by_createimagebitmap.html] +tags = suspend +[test_background_video_tainted_by_drawimage.html] +skip-if = toolkit == 'android' # bug 1346705 +tags = suspend +[test_background_video_drawimage_with_suspended_video.html] +skip-if = toolkit == 'android' # bug 1346705 +tags = suspend +[test_background_video_ended_event.html] +skip-if = toolkit == 'android' # bug 1346705 +tags = suspend + +[test_temporary_file_blob_video_plays.html] +skip-if = toolkit == 'android' || (os == 'win' && processor == 'aarch64') # bug 1533534 # android(bug 1232305) +[test_videoPlaybackQuality_totalFrames.html] +skip-if = os == 'win' || (os == 'mac' && os_version == '10.14') # bug 1374189, mac due to bug 1544938 + +[test_video_gzip_encoding.html] + +[test_playback_hls.html] +# HLS is only supported on Fennec with API level >= 16 +# TODO: This test is similar to test_playback.html, will remove the +# redundant code once test_playback.html is enabled on Fennec. +skip-if = toolkit != 'android' +tags = hls + +[test_hls_player_independency.html] +# There's a limit for creating decoder when API lever < 18(Bug 1278574) +# We could skip the test in that case as we cannot play 2 video at a time. +skip-if = toolkit != 'android' || android_version < '18' +tags = hls + +[test_bug1431810_opus_downmix_to_mono.html] + +[test_cloneElementVisually_paused.html] +tags = cloneelementvisually +[test_cloneElementVisually_mediastream.html] +tags = cloneelementvisually +[test_cloneElementVisually_mediastream_multitrack.html] +tags = cloneelementvisually +[test_cloneElementVisually_resource_change.html] +tags = cloneelementvisually +[test_cloneElementVisually_no_suspend.html] +tags = cloneelementvisually +[test_cloneElementVisually_poster.html] +tags = cloneelementvisually +[test_cloneElementVisually_ended_video.html] +tags = cloneelementvisually diff --git a/dom/media/test/multi_id3v2.mp3 b/dom/media/test/multi_id3v2.mp3 Binary files differnew file mode 100644 index 0000000000..253f19a9b6 --- /dev/null +++ b/dom/media/test/multi_id3v2.mp3 diff --git a/dom/media/test/multiple-bos-more-header-fileds.ogg b/dom/media/test/multiple-bos-more-header-fileds.ogg Binary files differnew file mode 100644 index 0000000000..c9721cb98e --- /dev/null +++ b/dom/media/test/multiple-bos-more-header-fileds.ogg diff --git a/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^ b/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/multiple-bos.ogg b/dom/media/test/multiple-bos.ogg Binary files differnew file mode 100644 index 0000000000..193200868e --- /dev/null +++ b/dom/media/test/multiple-bos.ogg diff --git a/dom/media/test/multiple-bos.ogg^headers^ b/dom/media/test/multiple-bos.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/multiple-bos.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/no-cues.webm b/dom/media/test/no-cues.webm Binary files differnew file mode 100644 index 0000000000..8ed761099e --- /dev/null +++ b/dom/media/test/no-cues.webm diff --git a/dom/media/test/no-cues.webm^headers^ b/dom/media/test/no-cues.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/no-cues.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/notags.mp3 b/dom/media/test/notags.mp3 Binary files differnew file mode 100644 index 0000000000..7f298131aa --- /dev/null +++ b/dom/media/test/notags.mp3 diff --git a/dom/media/test/notags.mp3^headers^ b/dom/media/test/notags.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/notags.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/opus-mapping2.mp4 b/dom/media/test/opus-mapping2.mp4 Binary files differnew file mode 100644 index 0000000000..72401a9c0b --- /dev/null +++ b/dom/media/test/opus-mapping2.mp4 diff --git a/dom/media/test/opus-mapping2.mp4^headers^ b/dom/media/test/opus-mapping2.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/opus-mapping2.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/opus-mapping2.webm b/dom/media/test/opus-mapping2.webm Binary files differnew file mode 100644 index 0000000000..4379f2534a --- /dev/null +++ b/dom/media/test/opus-mapping2.webm diff --git a/dom/media/test/opus-mapping2.webm^headers^ b/dom/media/test/opus-mapping2.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/opus-mapping2.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/opus-sample-cenc.mp4 b/dom/media/test/opus-sample-cenc.mp4 Binary files differnew file mode 100644 index 0000000000..22bb787540 --- /dev/null +++ b/dom/media/test/opus-sample-cenc.mp4 diff --git a/dom/media/test/opus-sample-cenc.mp4^headers^ b/dom/media/test/opus-sample-cenc.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/opus-sample-cenc.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/opus-sample.mp4 b/dom/media/test/opus-sample.mp4 Binary files differnew file mode 100644 index 0000000000..80329ce14b --- /dev/null +++ b/dom/media/test/opus-sample.mp4 diff --git a/dom/media/test/opus-sample.mp4^headers^ b/dom/media/test/opus-sample.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/opus-sample.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/owl-funnier-id3.mp3 b/dom/media/test/owl-funnier-id3.mp3 Binary files differnew file mode 100644 index 0000000000..05ec507530 --- /dev/null +++ b/dom/media/test/owl-funnier-id3.mp3 diff --git a/dom/media/test/owl-funnier-id3.mp3^headers^ b/dom/media/test/owl-funnier-id3.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/owl-funnier-id3.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/owl-funny-id3.mp3 b/dom/media/test/owl-funny-id3.mp3 Binary files differnew file mode 100644 index 0000000000..6533755a32 --- /dev/null +++ b/dom/media/test/owl-funny-id3.mp3 diff --git a/dom/media/test/owl-funny-id3.mp3^headers^ b/dom/media/test/owl-funny-id3.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/owl-funny-id3.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/owl-short.mp3 b/dom/media/test/owl-short.mp3 Binary files differnew file mode 100644 index 0000000000..9b31531f22 --- /dev/null +++ b/dom/media/test/owl-short.mp3 diff --git a/dom/media/test/owl-short.mp3^headers^ b/dom/media/test/owl-short.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/owl-short.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/owl.mp3 b/dom/media/test/owl.mp3 Binary files differnew file mode 100644 index 0000000000..9fafa32f93 --- /dev/null +++ b/dom/media/test/owl.mp3 diff --git a/dom/media/test/owl.mp3^headers^ b/dom/media/test/owl.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/owl.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/pixel_aspect_ratio.mp4 b/dom/media/test/pixel_aspect_ratio.mp4 Binary files differnew file mode 100644 index 0000000000..fce12cc03e --- /dev/null +++ b/dom/media/test/pixel_aspect_ratio.mp4 diff --git a/dom/media/test/play_promise.js b/dom/media/test/play_promise.js new file mode 100644 index 0000000000..7051fedc19 --- /dev/null +++ b/dom/media/test/play_promise.js @@ -0,0 +1,3 @@ +function getNotSupportedFile(name) { + return name + ".bad"; +} diff --git a/dom/media/test/poster-test.jpg b/dom/media/test/poster-test.jpg Binary files differnew file mode 100644 index 0000000000..595a5315f8 --- /dev/null +++ b/dom/media/test/poster-test.jpg diff --git a/dom/media/test/r11025_msadpcm_c1.wav b/dom/media/test/r11025_msadpcm_c1.wav Binary files differnew file mode 100644 index 0000000000..2e883ba5ed --- /dev/null +++ b/dom/media/test/r11025_msadpcm_c1.wav diff --git a/dom/media/test/r11025_msadpcm_c1.wav^headers^ b/dom/media/test/r11025_msadpcm_c1.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_msadpcm_c1.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r11025_s16_c1-short.wav b/dom/media/test/r11025_s16_c1-short.wav Binary files differnew file mode 100644 index 0000000000..e08d5bbdc0 --- /dev/null +++ b/dom/media/test/r11025_s16_c1-short.wav diff --git a/dom/media/test/r11025_s16_c1-short.wav^headers^ b/dom/media/test/r11025_s16_c1-short.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_s16_c1-short.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r11025_s16_c1.wav b/dom/media/test/r11025_s16_c1.wav Binary files differnew file mode 100644 index 0000000000..ab2e08befb --- /dev/null +++ b/dom/media/test/r11025_s16_c1.wav diff --git a/dom/media/test/r11025_s16_c1.wav^headers^ b/dom/media/test/r11025_s16_c1.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_s16_c1.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r11025_s16_c1_trailing.wav b/dom/media/test/r11025_s16_c1_trailing.wav Binary files differnew file mode 100644 index 0000000000..af53beaf25 --- /dev/null +++ b/dom/media/test/r11025_s16_c1_trailing.wav diff --git a/dom/media/test/r11025_s16_c1_trailing.wav^headers^ b/dom/media/test/r11025_s16_c1_trailing.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_s16_c1_trailing.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r11025_u8_c1.wav b/dom/media/test/r11025_u8_c1.wav Binary files differnew file mode 100644 index 0000000000..97dc453b9e --- /dev/null +++ b/dom/media/test/r11025_u8_c1.wav diff --git a/dom/media/test/r11025_u8_c1.wav^headers^ b/dom/media/test/r11025_u8_c1.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_u8_c1.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r11025_u8_c1_trunc.wav b/dom/media/test/r11025_u8_c1_trunc.wav Binary files differnew file mode 100644 index 0000000000..4d2db39777 --- /dev/null +++ b/dom/media/test/r11025_u8_c1_trunc.wav diff --git a/dom/media/test/r11025_u8_c1_trunc.wav^headers^ b/dom/media/test/r11025_u8_c1_trunc.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r11025_u8_c1_trunc.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/r16000_u8_c1_list.wav b/dom/media/test/r16000_u8_c1_list.wav Binary files differnew file mode 100644 index 0000000000..afde32e9a3 --- /dev/null +++ b/dom/media/test/r16000_u8_c1_list.wav diff --git a/dom/media/test/r16000_u8_c1_list.wav^headers^ b/dom/media/test/r16000_u8_c1_list.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/r16000_u8_c1_list.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/reactivate_helper.html b/dom/media/test/reactivate_helper.html new file mode 100644 index 0000000000..1834131559 --- /dev/null +++ b/dom/media/test/reactivate_helper.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<body> +<script> +var loadsWaiting = 0; +var elements = []; + +function checkAllLoaded() { + --loadsWaiting; + if (loadsWaiting == 0) { + parent.loadedAll(elements); + } +} + +function loadedData(event) { + var e = event.target; + parent.ok(!elements.includes(e), "Element already loaded: " + e._name); + parent.info("Loaded " + e._name); + elements.push(e); + // Reset "onerror" handler to avoid triggering another error in removeNodeAndSource(). + e.onerror = null; + checkAllLoaded(); + +} + +function error(event) { + var e = event.target; + parent.info("Error " + e._name); + // Don't wait for the element encounting errors. + checkAllLoaded(); +} + +for (var i = 0; i < parent.gSmallTests.length; ++i) { + var test = parent.gSmallTests[i]; + var elemType = /^audio/.test(test.type) ? "audio" : "video"; + // Associate these elements with the subframe's document + var e = document.createElement(elemType); + e.preload = "metadata"; + if (e.canPlayType(test.type)) { + e.src = test.name; + e._name = test.name; + e.onloadeddata = loadedData; + e.onerror = error; + e.load(); + ++loadsWaiting; + parent.info("Loading " + e._name); + } +} + +if (loadsWaiting == 0) { + parent.todo(false, "Can't play anything"); +} else { + parent.SimpleTest.waitForExplicitFinish(); +} +</script> +</body> +</html> diff --git a/dom/media/test/red-46x48.mp4 b/dom/media/test/red-46x48.mp4 Binary files differnew file mode 100644 index 0000000000..0760cc1c16 --- /dev/null +++ b/dom/media/test/red-46x48.mp4 diff --git a/dom/media/test/red-46x48.mp4^headers^ b/dom/media/test/red-46x48.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/red-46x48.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/red-48x46.mp4 b/dom/media/test/red-48x46.mp4 Binary files differnew file mode 100644 index 0000000000..d83de4027d --- /dev/null +++ b/dom/media/test/red-48x46.mp4 diff --git a/dom/media/test/red-48x46.mp4^headers^ b/dom/media/test/red-48x46.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/red-48x46.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/redirect.sjs b/dom/media/test/redirect.sjs new file mode 100644 index 0000000000..1653e8efc0 --- /dev/null +++ b/dom/media/test/redirect.sjs @@ -0,0 +1,26 @@ +function parseQuery(request, key) { + var params = request.queryString.split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +// Return file content for the first request with a given key. +// All subsequent requests return a redirect to a different-origin resource. +function handleRequest(request, response) +{ + var domain = parseQuery(request, "domain"); + var file = parseQuery(request, "file"); + var allowed = parseQuery(request, "allowed"); + + response.setStatusLine(request.httpVersion, 303, "See Other"); + response.setHeader("Location", "http://" + domain + "/tests/dom/media/test/" + (allowed ? "allowed.sjs?" : "") + file); + response.setHeader("Content-Type", "text/html"); +} diff --git a/dom/media/test/referer.sjs b/dom/media/test/referer.sjs new file mode 100644 index 0000000000..69c27afe9a --- /dev/null +++ b/dom/media/test/referer.sjs @@ -0,0 +1,45 @@ +function parseQuery(request, key) { + var params = request.queryString.split('&'); + for (var j = 0; j < params.length; ++j) { + var p = params[j]; + if (p == key) + return true; + if (p.indexOf(key + "=") == 0) + return p.substring(key.length + 1); + if (p.indexOf("=") < 0 && key == "") + return p; + } + return false; +} + +function handleRequest(request, response) +{ + var referer = request.hasHeader("Referer") ? request.getHeader("Referer") + : undefined; + if (referer == "http://mochi.test:8888/tests/dom/media/test/test_referer.html") { + var name = parseQuery(request, "name"); + var type = parseQuery(request, "type"); + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/" + name; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", type, false); + response.write(bytes, bytes.length); + bis.close(); + } + else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + } +} diff --git a/dom/media/test/reftest/av1hdr2020.mp4 b/dom/media/test/reftest/av1hdr2020.mp4 Binary files differnew file mode 100644 index 0000000000..295bec8a3c --- /dev/null +++ b/dom/media/test/reftest/av1hdr2020.mp4 diff --git a/dom/media/test/reftest/av1hdr2020.png b/dom/media/test/reftest/av1hdr2020.png Binary files differnew file mode 100644 index 0000000000..c5d3344a80 --- /dev/null +++ b/dom/media/test/reftest/av1hdr2020.png diff --git a/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html new file mode 100644 index 0000000000..575acb107d --- /dev/null +++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" +src="data:image/png;base64,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" +> diff --git a/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html new file mode 100644 index 0000000000..600b04a4f0 --- /dev/null +++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "../bipbop_300_215kbps.mp4"; + video.play(); + video.addEventListener("ended", function() { + document.documentElement.removeAttribute('class'); + }, {once: true}); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/generateREF.html b/dom/media/test/reftest/generateREF.html new file mode 100644 index 0000000000..7249ef2579 --- /dev/null +++ b/dom/media/test/reftest/generateREF.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<head> +<script type="application/javascript"> +</script> +</head> +<body> +<p id="out"></p> +<video id="v1" style="position:absolute; left:0; top:0"></video> +<canvas id="canvas"></canvas> +<script type="application/javascript"> +/* READ ME first. +The script is trying to make a reftest sample for reftest. +HOW TO USE: +1. Choose the first or last frame you want to generate. And set +window.onload function to dumpFirstFrame/dumpLastFrame. +2. Set the video.src in dumpFirstFrame/dumpLastFrame. +3. Run the script on browser. +4. Copy the base64 image url to your xxx-ref.html(short.mp4.firstframe-ref.html). +You might hit security error if the video.src cross origin. +Enable "media.seekToNextFrame.enabled" for the seekToNextFrame function +or using nightly, the seekToNextFrame() ensure the ended event fired. +*/ + +//window.onload = function() { setTimeout(dumpFirstFrame, 0); }; +//window.onload = function() { setTimeout(dumpLastFrame, 0); }; +window.onload = function() { setTimeout(function(){dumpNthFrame(15);}, 0); }; + +function drawVideoToInnerHTML(v) { + var canvas = document.getElementById("canvas"); + canvas.width = v.videoWidth; + canvas.height = v.videoHeight; + var ctx = canvas.getContext("2d"); + ctx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight); + var dataURL = canvas.toDataURL(); + document.getElementById("out").innerHTML=dataURL; +} + +function dumpFirstFrame() { + var video = document.getElementById("v1"); + video.src = "short.mp4"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + drawVideoToInnerHTML(video); + }); +} + +function dumpNthFrame(n) { + var video = document.getElementById("v1"); + video.src = "street.mp4"; + video.preload = "metadata"; + + function checkNthFrame() { + console.log((15-n+1)+"th Frame time is " + video.currentTime); + n--; + if (n == 0) { + drawVideoToInnerHTML(video); + } else { + video.seekToNextFrame(); + } + } + video.addEventListener("loadeddata", checkNthFrame); + video.addEventListener("seeked", checkNthFrame); +} + +function dumpLastFrame() { + var video = document.getElementById("v1"); + video.src = "short.mp4"; + video.preload = "metadata"; + video.seenEnded = false; + // Seek to the end + video.addEventListener("loadeddata", function() { + video.currentTime = video.duration; + video.onseeked = () => { + video.onseeked = null; + callSeekToNextFrame(); + }; + }); + + function callSeekToNextFrame() { + video.seekToNextFrame().then( + () => { + if (!video.seenEnded) + callSeekToNextFrame(); + }, + () => { + // Reach the end, do nothing. + } + ); + } + + video.addEventListener("ended", function() { + video.seenEnded = true; + drawVideoToInnerHTML(video); + }); +} +</script> +</body> +</html> diff --git a/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html b/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html new file mode 100644 index 0000000000..28a93cc268 --- /dev/null +++ b/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" +src=" + +data:image/png;base64,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 +" +> diff --git a/dom/media/test/reftest/gizmo.mp4.seek.html b/dom/media/test/reftest/gizmo.mp4.seek.html new file mode 100644 index 0000000000..e4c1fe9515 --- /dev/null +++ b/dom/media/test/reftest/gizmo.mp4.seek.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<!--This testing should match the 55th frame of gizmo.mp4. The +55th frame's time is 1.8s, so seek to a time which is a little +greater than 1.8s, the display frame should be the 55th frame. +--> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "../gizmo.mp4"; + video.preload = "metadata"; + + video.currentTime = 1.801; + + video.addEventListener("seeked", function() { + // Since the our media pipeline send the frame to imageBridge, then fire + // seeked event, the target frame may not be shown on the screen. + // So using canvas to access the target frame in the imageContainer in + // videoElement. + var canvas = document.getElementById("canvas"); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + var ctx = canvas.getContext("2d"); + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +<canvas id="canvas" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html b/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html new file mode 100644 index 0000000000..5e9a8e9b4c --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);" + src="vp9hdr2020.png" +> diff --git a/dom/media/test/reftest/image-10bits-rendering-720-90-video.html b/dom/media/test/reftest/image-10bits-rendering-720-90-video.html new file mode 100644 index 0000000000..890aee1c50 --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-720-90-video.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "vp9hdr2020.webm"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"></video> +</body> +</html> diff --git a/dom/media/test/reftest/image-10bits-rendering-720-ref.html b/dom/media/test/reftest/image-10bits-rendering-720-ref.html new file mode 100644 index 0000000000..1ae393031a --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-720-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" + src="vp9hdr2020.png" +> diff --git a/dom/media/test/reftest/image-10bits-rendering-720-video.html b/dom/media/test/reftest/image-10bits-rendering-720-video.html new file mode 100644 index 0000000000..93d2651ffc --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-720-video.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "vp9hdr2020.webm"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/image-10bits-rendering-720.video.html b/dom/media/test/reftest/image-10bits-rendering-720.video.html new file mode 100644 index 0000000000..93d2651ffc --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-720.video.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "vp9hdr2020.webm"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/image-10bits-rendering-90-ref.html b/dom/media/test/reftest/image-10bits-rendering-90-ref.html new file mode 100644 index 0000000000..38f032f0fd --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-90-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);" + src="av1hdr2020.png" +> diff --git a/dom/media/test/reftest/image-10bits-rendering-90-video.html b/dom/media/test/reftest/image-10bits-rendering-90-video.html new file mode 100644 index 0000000000..d8a4eca5a9 --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-90-video.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "av1hdr2020.mp4"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"></video> +</body> +</html> diff --git a/dom/media/test/reftest/image-10bits-rendering-ref.html b/dom/media/test/reftest/image-10bits-rendering-ref.html new file mode 100644 index 0000000000..de7fbbfc95 --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" + src="av1hdr2020.png" +> diff --git a/dom/media/test/reftest/image-10bits-rendering-video.html b/dom/media/test/reftest/image-10bits-rendering-video.html new file mode 100644 index 0000000000..e6b3fda335 --- /dev/null +++ b/dom/media/test/reftest/image-10bits-rendering-video.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "av1hdr2020.mp4"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/reftest.list b/dom/media/test/reftest/reftest.list new file mode 100644 index 0000000000..5aeaec96e6 --- /dev/null +++ b/dom/media/test/reftest/reftest.list @@ -0,0 +1,8 @@ +skip-if(Android) fuzzy-if(OSX,0-80,0-76800) fuzzy-if(winWidget,0-62,0-76799) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-70,0-600) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html +skip-if(Android) fuzzy-if(OSX,0-87,0-76797) fuzzy-if(winWidget,0-60,0-76797) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-60,0-1800) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html +skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-57,0-4281) fuzzy-if(OSX,55-80,4173-4417) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html +skip-if(Android) fuzzy-if(OSX,0-25,0-175921) fuzzy-if(winWidget,0-71,0-179198) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI)),0-255,0-179500) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html +skip-if(Android) skip-if(MinGW) skip-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) fuzzy(0-10,0-778236) == image-10bits-rendering-video.html image-10bits-rendering-ref.html +skip-if(Android) skip-if(MinGW) skip-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) fuzzy(0-10,0-778536) == image-10bits-rendering-90-video.html image-10bits-rendering-90-ref.html +skip-if(Android) fuzzy(0-26,0-567562) == image-10bits-rendering-720-video.html image-10bits-rendering-720-ref.html +skip-if(Android) fuzzy(0-27,0-573249) == image-10bits-rendering-720-90-video.html image-10bits-rendering-720-90-ref.html diff --git a/dom/media/test/reftest/short.mp4.firstframe-ref.html b/dom/media/test/reftest/short.mp4.firstframe-ref.html new file mode 100644 index 0000000000..d80f2f985f --- /dev/null +++ b/dom/media/test/reftest/short.mp4.firstframe-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAADwCAYAAABxLb1rAAAHv0lEQVR4nO3dQWyfdR3H8R8d7XBjMDYcYzqCQY0SkWhQAomJJ2/GgyeNiTExHk3URE9e1INGEz3NA5CYETwQFi8YEvAgGg6LogFDIojbEBTGpsyNrWNt9/HQso0Btus2vu2+r1/yypN/+/x/+fbyzpP/0z4dGSMAHY3qAQCqCCDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSCl/jNGsmFi/vUVk5mZHDk2Rl4ZI5kaOTo25MC4/NT5h8b894+PTTk+NpXPz+omgJSaGyOZGDm48PrfY2R6YmRmzMvk9cm67afOPzpGpsfIsbExx8bG8vlZ3QSQFeFPYyQ3fyzJwSRHkiQ5+lqS55P9u5P163P0De+ZWlA/O6uXALICrEk+cEvy9W8nOZTk1Zxe/5yP4JVX5vgb3jOVjCtWwOysZgJIqSNjazJ5c+75zleTnMib12tJppOJLTnziu/IGHl1BczP6iaAlNoz1iSbbl+48nvlLQJ4/IwATp56nwByIQggpXbc+enk4d+edcV3IslskrnTX558YwDhQhBAaj3yaPLyoTMCOLsQwDkB5KITQEo8dP2G5N4dycwZkft/a921mRlryufm0iKA1Lh3R7L/2aXFTwC5SASQd9TOmzYnD9y99PC9vq7aeNbvAcL5E0DeWQ/cnTz3hACyIgggF8XJhePcmMihMfLzz9yW/PHhcw+fAHIRCSAX3sTEqeOJMZKP3568/Nfk8D4BZEURQC6eDRvy+599N8nR5YfvVACvyrHqn4dLjgBycWzfnnz/B8nJg0mOCSArkgByQcyNtTk6Ru6/447krrvy+hNdTi5atpnMPwFmkbXh7IchwPkTQM7fmg2ZHVPJ+29LnnoqOXDgHC7tZhYIIO88AWRZXr/L++jnPpvHv/TF5F9/yann+C2y5k4eTQ6eTHb/Lfd97fPJkX1LCOC6+RsqcAEJIMv30VuTx36XHNif0w8xWOJ6+kD+8OVv5Cs3XZ0c3iOAlBBAluWRa65Knt+7pNYdS5LZhU8D9+1JfrUrGZuSqffmnmu3Jc/sE0BKCCDLci4BPHPNfuub+cV1W5KxKRkb5wP47D8EkBICyLLs2rIteekcbnbs+HVy5xfetM9Da7cmTzwngJQQQJZlyQGcnsnuH/4key+/KU+PG07vMTF/fGjt1uRJV4DUEECW5b5t25KDbxHAuePzHns8+d6PkhuvSybffp+dm69N/u4mCDUEkGV52wBOH85/d96TJ2+9PQ9es3XRfQSQSgLIsjy45erkxb2Z/yuOY8lju5Mf/zT54KeSsXn+H54vwc7Nm5I9S3gwqgByEQggy3IqgIefyZO7duTPn7wzj37oI5kdIxlXJxNrl7SPAFJJAFmWE2Mkt3w42bg+WbP8fQSQSgLIssyMkVxx+XnvI4BUEkBKLTmA69ct+XNFWCoBpNQvr3t3sm/v4gGcmhJALjgBpNT929+TvPjC4gFcAbNy6RFASu163w3J/hcFkBICSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSKkHbtyevPySAFJCACklgFQSQEr9ZvMnkj2L92924rLMXFY/L5cWAaTUI5tuS/YtUr+T0zk4RrJ2snxeLi0CyIpy8qzX02MqM2NdXn3TuVML6mdm9RJAVrQT412ZfsvQrV1QPyOrlwCyqswuqJ6DS4MAsqoIIBeSAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0Nb/AJRYBp4E6qsBAAAAAElFTkSuQmCC" +> diff --git a/dom/media/test/reftest/short.mp4.firstframe.html b/dom/media/test/reftest/short.mp4.firstframe.html new file mode 100644 index 0000000000..759bdc5ede --- /dev/null +++ b/dom/media/test/reftest/short.mp4.firstframe.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "../short.mp4"; + video.preload = "metadata"; + video.addEventListener("loadeddata", function() { + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/short.mp4.lastframe-ref.html b/dom/media/test/reftest/short.mp4.lastframe-ref.html new file mode 100644 index 0000000000..7474b9039e --- /dev/null +++ b/dom/media/test/reftest/short.mp4.lastframe-ref.html @@ -0,0 +1,4 @@ +<!DOCTYPE HTML> +<img style="position:absolute; left:0; top:0" +src="data:image/png;base64,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" +> diff --git a/dom/media/test/reftest/short.mp4.lastframe.html b/dom/media/test/reftest/short.mp4.lastframe.html new file mode 100644 index 0000000000..abd27c5c8e --- /dev/null +++ b/dom/media/test/reftest/short.mp4.lastframe.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function doTest() { + var video = document.getElementById("v1"); + video.src = "../short.mp4"; + video.preload = "metadata"; + video.seenEnded = false; + // Seek to the end + video.addEventListener("loadeddata", function() { + video.currentTime = video.duration; + video.onseeked = () => { + video.onseeked = null; + callSeekToNextFrame(); + }; + }); + + function callSeekToNextFrame() { + video.seekToNextFrame().then( + () => { + if (!video.seenEnded) + callSeekToNextFrame(); + }, + () => { + // Reach the end, do nothing. + } + ); + } + + video.addEventListener("ended", function() { + video.seenEnded = true; + document.documentElement.removeAttribute('class'); + }); +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</head> +<body> +<video id="v1" style="position:absolute; left:0; top:0"></video> +</body> +</html> diff --git a/dom/media/test/reftest/vp9hdr2020.png b/dom/media/test/reftest/vp9hdr2020.png Binary files differnew file mode 100644 index 0000000000..afb68d9e0a --- /dev/null +++ b/dom/media/test/reftest/vp9hdr2020.png diff --git a/dom/media/test/reftest/vp9hdr2020.webm b/dom/media/test/reftest/vp9hdr2020.webm Binary files differnew file mode 100644 index 0000000000..516f62093a --- /dev/null +++ b/dom/media/test/reftest/vp9hdr2020.webm diff --git a/dom/media/test/resolution-change.webm b/dom/media/test/resolution-change.webm Binary files differnew file mode 100644 index 0000000000..29aad93b96 --- /dev/null +++ b/dom/media/test/resolution-change.webm diff --git a/dom/media/test/resolution-change.webm^headers^ b/dom/media/test/resolution-change.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/resolution-change.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4 b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4 Binary files differnew file mode 100644 index 0000000000..720339bdc2 --- /dev/null +++ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4 diff --git a/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sample-fisbone-skeleton4.ogv b/dom/media/test/sample-fisbone-skeleton4.ogv Binary files differnew file mode 100644 index 0000000000..8afe0be7a4 --- /dev/null +++ b/dom/media/test/sample-fisbone-skeleton4.ogv diff --git a/dom/media/test/sample-fisbone-skeleton4.ogv^headers^ b/dom/media/test/sample-fisbone-skeleton4.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sample-fisbone-skeleton4.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sample-fisbone-wrong-header.ogv b/dom/media/test/sample-fisbone-wrong-header.ogv Binary files differnew file mode 100644 index 0000000000..46c3933da5 --- /dev/null +++ b/dom/media/test/sample-fisbone-wrong-header.ogv diff --git a/dom/media/test/sample-fisbone-wrong-header.ogv^headers^ b/dom/media/test/sample-fisbone-wrong-header.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sample-fisbone-wrong-header.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sample.3g2 b/dom/media/test/sample.3g2 Binary files differnew file mode 100644 index 0000000000..769cb01dbd --- /dev/null +++ b/dom/media/test/sample.3g2 diff --git a/dom/media/test/sample.3gp b/dom/media/test/sample.3gp Binary files differnew file mode 100644 index 0000000000..4a3d8ea66f --- /dev/null +++ b/dom/media/test/sample.3gp diff --git a/dom/media/test/seek-short.ogv b/dom/media/test/seek-short.ogv Binary files differnew file mode 100644 index 0000000000..a5ca6951d0 --- /dev/null +++ b/dom/media/test/seek-short.ogv diff --git a/dom/media/test/seek-short.ogv^headers^ b/dom/media/test/seek-short.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/seek-short.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/seek-short.webm b/dom/media/test/seek-short.webm Binary files differnew file mode 100644 index 0000000000..36abd1570e --- /dev/null +++ b/dom/media/test/seek-short.webm diff --git a/dom/media/test/seek-short.webm^headers^ b/dom/media/test/seek-short.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/seek-short.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/seek.ogv b/dom/media/test/seek.ogv Binary files differnew file mode 100644 index 0000000000..ac7ece3519 --- /dev/null +++ b/dom/media/test/seek.ogv diff --git a/dom/media/test/seek.ogv^headers^ b/dom/media/test/seek.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/seek.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/seek.webm b/dom/media/test/seek.webm Binary files differnew file mode 100644 index 0000000000..72b0297233 --- /dev/null +++ b/dom/media/test/seek.webm diff --git a/dom/media/test/seek.webm^headers^ b/dom/media/test/seek.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/seek.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/seekLies.sjs b/dom/media/test/seekLies.sjs new file mode 100644 index 0000000000..3277950b08 --- /dev/null +++ b/dom/media/test/seekLies.sjs @@ -0,0 +1,23 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsIFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/test/seek.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.setHeader("Accept-Ranges", "bytes", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/dom/media/test/seek_support.js b/dom/media/test/seek_support.js new file mode 100644 index 0000000000..b100572a5a --- /dev/null +++ b/dom/media/test/seek_support.js @@ -0,0 +1,61 @@ +// This file expects manifest.js to be included in the same scope. +/* import-globals-from manifest.js */ +// This file expects SEEK_TEST_NUMBER to be defined by the test. +/* global SEEK_TEST_NUMBER */ +var manager = new MediaTestManager(); + +function createTestArray() { + var tests = []; + var tmpVid = document.createElement("video"); + + for (var testNum = 0; testNum < gSeekTests.length; testNum++) { + var test = gSeekTests[testNum]; + if (!tmpVid.canPlayType(test.type)) { + continue; + } + + var t = {}; + t.name = test.name; + t.type = test.type; + t.duration = test.duration; + t.number = SEEK_TEST_NUMBER; + tests.push(t); + } + return tests; +} + +function startTest(test, token) { + var video = document.createElement("video"); + video.token = token += "-seek" + test.number + ".js"; + manager.started(video.token); + video.src = test.name; + video.preload = "metadata"; + document.body.appendChild(video); + var name = test.name + " seek test " + test.number; + var localIs = (function(n) { + return function(a, b, msg) { + is(a, b, n + ": " + msg); + }; + })(name); + var localOk = (function(n) { + return function(a, msg) { + ok(a, n + ": " + msg); + }; + })(name); + var localFinish = (function(v, m) { + return function() { + v.onerror = null; + removeNodeAndSource(v); + dump("SEEK-TEST: Finished " + name + " token: " + v.token + "\n"); + m.finished(v.token); + }; + })(video, manager); + dump("SEEK-TEST: Started " + name + "\n"); + window["test_seek" + test.number]( + video, + test.duration / 2, + localIs, + localOk, + localFinish + ); +} diff --git a/dom/media/test/seek_with_sound.ogg b/dom/media/test/seek_with_sound.ogg Binary files differnew file mode 100644 index 0000000000..c86d9946bd --- /dev/null +++ b/dom/media/test/seek_with_sound.ogg diff --git a/dom/media/test/seek_with_sound.ogg^headers^ b/dom/media/test/seek_with_sound.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/seek_with_sound.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/short-aac-encrypted-audio.mp4 b/dom/media/test/short-aac-encrypted-audio.mp4 Binary files differnew file mode 100644 index 0000000000..c1ba8d37e4 --- /dev/null +++ b/dom/media/test/short-aac-encrypted-audio.mp4 diff --git a/dom/media/test/short-aac-encrypted-audio.mp4^headers^ b/dom/media/test/short-aac-encrypted-audio.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/short-aac-encrypted-audio.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4 b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4 Binary files differnew file mode 100644 index 0000000000..bc58623999 --- /dev/null +++ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4 diff --git a/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/short-cenc-pssh-in-moof.mp4 b/dom/media/test/short-cenc-pssh-in-moof.mp4 Binary files differnew file mode 100644 index 0000000000..aa44c3b9a1 --- /dev/null +++ b/dom/media/test/short-cenc-pssh-in-moof.mp4 diff --git a/dom/media/test/short-cenc.mp4 b/dom/media/test/short-cenc.mp4 Binary files differnew file mode 100644 index 0000000000..aa44c3b9a1 --- /dev/null +++ b/dom/media/test/short-cenc.mp4 diff --git a/dom/media/test/short-cenc.xml b/dom/media/test/short-cenc.xml new file mode 100644 index 0000000000..9658c3e32f --- /dev/null +++ b/dom/media/test/short-cenc.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + This XML file describes the encryption applied to short-cenc.mp4. To generate + short-cenc, run the following command: + + MP4Box -crypt short-cenc.xml -out short-cenc.mp4 short.mp4 +--> + +<GPACDRM type="CENC AES-CTR"> + + <DRMInfo type="pssh" version="1"> + <!-- + SystemID specified in + https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html + --> + <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" /> + <!-- Number of KeyIDs = 2 --> + <BS bits="32" value="2" /> + <!-- KeyID --> + <BS ID128="0x7e571d017e571d017e571d017e571d01" /> + <BS ID128="0x7e571d027e571d027e571d027e571d02" /> + </DRMInfo> + + <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d017e571d017e571d017e571d01" + value="0x7e5711117e5711117e5711117e571111" /> + </CrypTrack> + + <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc" + first_IV="0x00000000000000000000000000000000"> + <key KID="0x7e571d027e571d027e571d027e571d02" + value="0x7e5722227e5722227e5722227e572222" /> + </CrypTrack> + +</GPACDRM> diff --git a/dom/media/test/short-video.ogv b/dom/media/test/short-video.ogv Binary files differnew file mode 100644 index 0000000000..68dee3cf2b --- /dev/null +++ b/dom/media/test/short-video.ogv diff --git a/dom/media/test/short-video.ogv^headers^ b/dom/media/test/short-video.ogv^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/short-video.ogv^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/short-vp9-encrypted-video.mp4 b/dom/media/test/short-vp9-encrypted-video.mp4 Binary files differnew file mode 100644 index 0000000000..23d1083d18 --- /dev/null +++ b/dom/media/test/short-vp9-encrypted-video.mp4 diff --git a/dom/media/test/short-vp9-encrypted-video.mp4^headers^ b/dom/media/test/short-vp9-encrypted-video.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/short-vp9-encrypted-video.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/short.mp4 b/dom/media/test/short.mp4 Binary files differnew file mode 100644 index 0000000000..802da047bc --- /dev/null +++ b/dom/media/test/short.mp4 diff --git a/dom/media/test/short.mp4.gz b/dom/media/test/short.mp4.gz Binary files differnew file mode 100644 index 0000000000..efb95e38e3 --- /dev/null +++ b/dom/media/test/short.mp4.gz diff --git a/dom/media/test/short.mp4^headers^ b/dom/media/test/short.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/short.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sine.webm b/dom/media/test/sine.webm Binary files differnew file mode 100644 index 0000000000..3913ffa874 --- /dev/null +++ b/dom/media/test/sine.webm diff --git a/dom/media/test/sine.webm^headers^ b/dom/media/test/sine.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sine.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm Binary files differnew file mode 100644 index 0000000000..7497096ba5 --- /dev/null +++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm Binary files differnew file mode 100644 index 0000000000..0f7609439d --- /dev/null +++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/small-shot-mp3.mp4 b/dom/media/test/small-shot-mp3.mp4 Binary files differnew file mode 100644 index 0000000000..61fe0ac719 --- /dev/null +++ b/dom/media/test/small-shot-mp3.mp4 diff --git a/dom/media/test/small-shot-mp3.mp4^headers^ b/dom/media/test/small-shot-mp3.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/small-shot-mp3.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/small-shot.flac b/dom/media/test/small-shot.flac Binary files differnew file mode 100644 index 0000000000..0da7c9044e --- /dev/null +++ b/dom/media/test/small-shot.flac diff --git a/dom/media/test/small-shot.m4a b/dom/media/test/small-shot.m4a Binary files differnew file mode 100644 index 0000000000..51a23c5b49 --- /dev/null +++ b/dom/media/test/small-shot.m4a diff --git a/dom/media/test/small-shot.mp3 b/dom/media/test/small-shot.mp3 Binary files differnew file mode 100644 index 0000000000..f9397a5106 --- /dev/null +++ b/dom/media/test/small-shot.mp3 diff --git a/dom/media/test/small-shot.mp3^headers^ b/dom/media/test/small-shot.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/small-shot.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/small-shot.ogg b/dom/media/test/small-shot.ogg Binary files differnew file mode 100644 index 0000000000..1a41623f81 --- /dev/null +++ b/dom/media/test/small-shot.ogg diff --git a/dom/media/test/small-shot.ogg^headers^ b/dom/media/test/small-shot.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/small-shot.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/sound.ogg b/dom/media/test/sound.ogg Binary files differnew file mode 100644 index 0000000000..edda4e9128 --- /dev/null +++ b/dom/media/test/sound.ogg diff --git a/dom/media/test/sound.ogg^headers^ b/dom/media/test/sound.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/sound.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/spacestorm-1000Hz-100ms.ogg b/dom/media/test/spacestorm-1000Hz-100ms.ogg Binary files differnew file mode 100644 index 0000000000..994041e1b0 --- /dev/null +++ b/dom/media/test/spacestorm-1000Hz-100ms.ogg diff --git a/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^ b/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/split.webm b/dom/media/test/split.webm Binary files differnew file mode 100644 index 0000000000..9207017fb6 --- /dev/null +++ b/dom/media/test/split.webm diff --git a/dom/media/test/split.webm^headers^ b/dom/media/test/split.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/split.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/street.mp4 b/dom/media/test/street.mp4 Binary files differnew file mode 100644 index 0000000000..837d23b383 --- /dev/null +++ b/dom/media/test/street.mp4 diff --git a/dom/media/test/street.mp4^headers^ b/dom/media/test/street.mp4^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/street.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-1-mono.opus b/dom/media/test/test-1-mono.opus Binary files differnew file mode 100644 index 0000000000..d5198e9ceb --- /dev/null +++ b/dom/media/test/test-1-mono.opus diff --git a/dom/media/test/test-1-mono.opus^headers^ b/dom/media/test/test-1-mono.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-1-mono.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-2-stereo.opus b/dom/media/test/test-2-stereo.opus Binary files differnew file mode 100644 index 0000000000..7115cac243 --- /dev/null +++ b/dom/media/test/test-2-stereo.opus diff --git a/dom/media/test/test-2-stereo.opus^headers^ b/dom/media/test/test-2-stereo.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-2-stereo.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-3-LCR.opus b/dom/media/test/test-3-LCR.opus Binary files differnew file mode 100644 index 0000000000..145536f3e7 --- /dev/null +++ b/dom/media/test/test-3-LCR.opus diff --git a/dom/media/test/test-3-LCR.opus^headers^ b/dom/media/test/test-3-LCR.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-3-LCR.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-4-quad.opus b/dom/media/test/test-4-quad.opus Binary files differnew file mode 100644 index 0000000000..731b867b2e --- /dev/null +++ b/dom/media/test/test-4-quad.opus diff --git a/dom/media/test/test-4-quad.opus^headers^ b/dom/media/test/test-4-quad.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-4-quad.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-5-5.0.opus b/dom/media/test/test-5-5.0.opus Binary files differnew file mode 100644 index 0000000000..7eb2faa812 --- /dev/null +++ b/dom/media/test/test-5-5.0.opus diff --git a/dom/media/test/test-5-5.0.opus^headers^ b/dom/media/test/test-5-5.0.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-5-5.0.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-6-5.1.opus b/dom/media/test/test-6-5.1.opus Binary files differnew file mode 100644 index 0000000000..526515eb7b --- /dev/null +++ b/dom/media/test/test-6-5.1.opus diff --git a/dom/media/test/test-6-5.1.opus^headers^ b/dom/media/test/test-6-5.1.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-6-5.1.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-7-6.1.opus b/dom/media/test/test-7-6.1.opus Binary files differnew file mode 100644 index 0000000000..8b6a7ce329 --- /dev/null +++ b/dom/media/test/test-7-6.1.opus diff --git a/dom/media/test/test-7-6.1.opus^headers^ b/dom/media/test/test-7-6.1.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-7-6.1.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-8-7.1.opus b/dom/media/test/test-8-7.1.opus Binary files differnew file mode 100644 index 0000000000..e56176a30f --- /dev/null +++ b/dom/media/test/test-8-7.1.opus diff --git a/dom/media/test/test-8-7.1.opus^headers^ b/dom/media/test/test-8-7.1.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-8-7.1.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test-stereo-phase-inversion-180.opus b/dom/media/test/test-stereo-phase-inversion-180.opus Binary files differnew file mode 100644 index 0000000000..ce70290002 --- /dev/null +++ b/dom/media/test/test-stereo-phase-inversion-180.opus diff --git a/dom/media/test/test-stereo-phase-inversion-180.opus^headers^ b/dom/media/test/test-stereo-phase-inversion-180.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/test-stereo-phase-inversion-180.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/test_VideoPlaybackQuality.html b/dom/media/test/test_VideoPlaybackQuality.html new file mode 100644 index 0000000000..67636f9ea6 --- /dev/null +++ b/dom/media/test/test_VideoPlaybackQuality.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test basic functionality of VideoPlaybackQuality</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test() { + var video = document.createElement("video"); + ok(video.getVideoPlaybackQuality, "getVideoPlaybackQuality should be exposed with pref set"); + + var vpq = video.getVideoPlaybackQuality(); + ok(vpq, "getVideoPlaybackQuality should return an object"); + ok(vpq.creationTime <= performance.now(), "creationTime should be in the past"); + is(vpq.totalVideoFrames, 0, "totalVideoFrames should be 0"); + is(vpq.droppedVideoFrames, 0, "droppedVideoFrames should be 0"); + + var vpq2 = video.getVideoPlaybackQuality(); + ok(vpq !== vpq2, "getVideoPlaybackQuality should return a new object"); + ok(vpq.creationTime <= vpq2.creationTime, "VideoPlaybackQuality objects should have increasing creationTime"); + + var audio = document.createElement("audio"); + ok(!audio.getVideoPlaybackQuality, "getVideoPlaybackQuality should not be available on Audio elements"); + + video.src = "seek.webm"; + video.play(); + video.addEventListener("ended", function () { + vpq = video.getVideoPlaybackQuality(); + ok(vpq.creationTime <= performance.now(), "creationTime should be in the past"); + ok(vpq.totalVideoFrames > 0, "totalVideoFrames should be > 0"); + ok(vpq.droppedVideoFrames >= 0, "droppedVideoFrames should be >= 0"); + ok(vpq.droppedVideoFrames <= vpq.totalVideoFrames, "droppedVideoFrames should be <= totalVideoFrames"); + + SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", false]]}, function () { + vpq = video.getVideoPlaybackQuality(); + is(vpq.creationTime, 0, "creationTime should be 0"); + is(vpq.totalVideoFrames, 0, "totalVideoFrames should be 0"); + is(vpq.droppedVideoFrames, 0, "droppedVideoFrames should be 0"); + + SimpleTest.finish(); + }); + }); +} + +addLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": + [ + ["media.mediasource.enabled", true], + ] + }, test); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_VideoPlaybackQuality_disabled.html b/dom/media/test/test_VideoPlaybackQuality_disabled.html new file mode 100644 index 0000000000..1c41b79d8b --- /dev/null +++ b/dom/media/test/test_VideoPlaybackQuality_disabled.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test basic functionality of VideoPlaybackQuality</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test() { + var video = document.createElement("video"); + ok(!video.getVideoPlaybackQuality, "getVideoPlaybackQuality should be hidden behind a pref"); + var accessThrows = false; + try { + video.getVideoPlaybackQuality(); + } catch (e) { + accessThrows = true; + } + ok(accessThrows, "getVideoPlaybackQuality should be hidden behind a pref"); + SimpleTest.finish(); +} + +addLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": + [ + ["media.mediasource.enabled", false], + ] + }, test); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_access_control.html b/dom/media/test/test_access_control.html new file mode 100644 index 0000000000..816563f9f5 --- /dev/null +++ b/dom/media/test/test_access_control.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=451958 +--> +<head> + <title>Test for Bug 451958</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=451958">Mozilla Bug 451958</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 451958 **/ + +function run() { + window.open("http://example.org:80/tests/dom/media/test/file_access_controls.html", "", "width=500,height=500"); +} + +function done() { + mediaTestCleanup(); + SimpleTest.finish(); +} + +addLoadEvent(run); +SimpleTest.waitForExplicitFinish(); + + +window.addEventListener("message", receiveMessage); + +function receiveMessage(event) +{ + if (event.origin !== "http://example.org") { + ok(false, "Received message from wrong domain"); + return; + } + + if (event.data.done == "true") { + done(); + return; + } + + ok(event.data.result, event.data.message); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_arraybuffer.html b/dom/media/test/test_arraybuffer.html new file mode 100644 index 0000000000..9ef84c53dc --- /dev/null +++ b/dom/media/test/test_arraybuffer.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1462967 +--> +<head> + <title>Test for Bug 1457661</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1457661">Mozilla Bug 1457661</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Test for Bug 1457661; Ensure that readyState properly move to 4 when using arrayBuffer obtained from XMLHttpRequest + +var manager = new MediaTestManager; + +function getBlob(url, callback) { + const req = new XMLHttpRequest(); + req.addEventListener("load", function() { + callback(req.response); + }); + req.open("GET", url, true); + req.responseType = "arraybuffer"; + req.send(); +} + +function startTest(test, token) { + manager.started(token); + // Fetch the media resource using XHR so we can be sure the entire + // resource is loaded before we test buffered ranges. This ensures + // we have deterministic behaviour. + getBlob(test.name, function(arr) { + const v = document.createElement("video"); + const events = [ "suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause", "durationchange", "seeking", "seeked" ]; + function logEvent(e) { + info(test.name + ": got " + e.type + " event"); + } + events.forEach(function(e) { + v.addEventListener(e, logEvent); + }); + once(v, "stalled", function(e) { + // Resource fetch algorithm in local mode should never fire stalled event. + // https://html.spec.whatwg.org/multipage/media.html#concept-media-load-resource + ok(false, test.name + ": got stalled"); + removeNodeAndSource(v); + manager.finished(token); + }); + once(v, "canplaythrough", function(e) { + ok(true, test.name + ": got canplaythrough"); + is(v.readyState, v.HAVE_ENOUGH_DATA, test.name + ": readyState is HAVE_ENOUGH_DATA"); + removeNodeAndSource(v); + manager.finished(token); + }); + const blob = new Blob([arr], {type:test.type}); + const blobUrl = URL.createObjectURL(blob); + + document.body.appendChild(v); + v.preload = "auto"; + v.src = blobUrl; + v.load(); + }); +} + +SimpleTest.waitForExplicitFinish(); +// Note: No need to set media test prefs, since we're using XHR to fetch +// media data. +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_aspectratio_mp4.html b/dom/media/test/test_aspectratio_mp4.html new file mode 100644 index 0000000000..5e01875439 --- /dev/null +++ b/dom/media/test/test_aspectratio_mp4.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=975978 +--> + +<head> + <title>Media test: default video size</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=975978">Mozilla Bug 975978</a> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// MP4 video with display size is difference to decode frame size. +// The display size is recorded in TrackHeaderBox 'tkhd' of this mp4 video. +var resource = + { name:"pixel_aspect_ratio.mp4", type:"video/mp4", width:525, height:288 }; + +var v = document.createElement("video"); +v.onloadedmetadata = function() { + is(v.videoWidth, resource.width, "Intrinsic width should match video width"); + is(v.videoHeight, resource.height, "Intrinsic height should match video height"); + SimpleTest.finish(); +} +v.addEventListener("error", function(ev) { + if (v.readyState < v.HAVE_METADATA) { + info("Video element returns with readyState " + v.readyState + " error.code " + v.error.code); + todo(false, "This platform doesn't support to retrieve MP4 metadata."); + SimpleTest.finish(); + } +}); + +v.src = resource.name; +v.preload = "auto"; + +document.body.appendChild(v); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_audio1.html b/dom/media/test/test_audio1.html new file mode 100644 index 0000000000..a5d9f12ab9 --- /dev/null +++ b/dom/media/test/test_audio1.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: Audio Constructor Test 1</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var a1 = new Audio(); + if (!a1.canPlayType(test.type)) + return; + manager.started(token); + + is(a1.getAttribute("preload"), "auto", "preload:auto automatically set"); + is(a1.src, "", "Src set?"); + a1 = null; + manager.finished(token); +} + +manager.runTests(gAudioTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_audio2.html b/dom/media/test/test_audio2.html new file mode 100644 index 0000000000..fde9adc791 --- /dev/null +++ b/dom/media/test/test_audio2.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: Audio Constructor Test 2</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tmpAudio = new Audio(); + +var manager = new MediaTestManager; + +function startTest(test, token) { + if (!tmpAudio.canPlayType(test.type)) + return; + manager.started(token); + var a1 = new Audio(test.name); + is(a1.getAttribute("preload"), "auto", "Preload automatically set to auto"); + ok(a1.src.endsWith("/" + test.name), "src OK"); + manager.finished(token); +} + +manager.runTests(gAudioTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_audioDocumentTitle.html b/dom/media/test/test_audioDocumentTitle.html new file mode 100644 index 0000000000..2913debb39 --- /dev/null +++ b/dom/media/test/test_audioDocumentTitle.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=463830 +--> +<head> + <title>Test for Bug 463830</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463830">Mozilla Bug 463830</a> +<p id="display"></p> +<iframe id="i"></iframe> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 463830 **/ + +var gTests = [ + { file: "r11025_s16_c1.wav", title: "r11025_s16_c1.wav" } +]; + +var gTestNum = 0; + +addLoadEvent(runTest); + +var title; +var i = document.getElementById("i"); + +function runTest() { + if (gTestNum == gTests.length) { + SimpleTest.finish(); + return; + } + if (gTestNum == 0) { + i.addEventListener("load", function() { + is(i.contentDocument.title, title, "Doc title incorrect"); + setTimeout(runTest, 0); + }); + } + + title = gTests[gTestNum].title; + i.src = gTests[gTestNum].file; + gTestNum++; +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_background_video_cancel_suspend_taint.html b/dom/media/test/test_background_video_cancel_suspend_taint.html new file mode 100644 index 0000000000..26691f3f43 --- /dev/null +++ b/dom/media/test/test_background_video_cancel_suspend_taint.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Suspend Cancels (Element Taint)</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +/** + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode resumes. + */ +function testSuspendTimerCanceledWhenTainted(video) { + function ended() { + video.removeEventListener("mozcancelvideosuspendtimer", canceled); + ok(false, `${video.token} ended before suspend cancels`); + this.ended_resolve(); + } + + function canceled() { + video.removeEventListener("ended", ended); + ok(true, `${video.token} suspend cancels`); + this.canceled_resolve(); + } + + let p = Promise.race([ + new Promise((resolve) => { + video.ended_resolve = resolve; + video.addEventListener('ended', ended, { 'once': true }); + }), + new Promise((resolve) => { + video.canceled_resolve = resolve; + video.addEventListener('mozcancelvideosuspendtimer', canceled, { 'once': true }); + }) + ]); + + Log(video.token, "Mark tainted"); + + let c = document.createElement('canvas'); + let g = c.getContext('2d'); + g.drawImage(video, 0, 0, c.width, c.height); + ok(video.hasSuspendTaint(), 'video used with drawImage is tainted.'); + + return p; +} + +startTest({ + desc: 'Test Background Video Suspend Cancels (Element Taint)', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 10000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilVisible(v) + .then(() => waitUntilPlaying(v)) + .then(() => testSuspendTimerStartedWhenHidden(v)) + .then(() => testSuspendTimerCanceledWhenTainted(v)) + .then(() => { manager.finished(token); }); + } +}); +</script> diff --git a/dom/media/test/test_background_video_cancel_suspend_visible.html b/dom/media/test/test_background_video_cancel_suspend_visible.html new file mode 100644 index 0000000000..e947b27734 --- /dev/null +++ b/dom/media/test/test_background_video_cancel_suspend_visible.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Suspend Cancels (Visibility)</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +/** + * Check that making the element visible before suspend timer delay causes the + * the suspend timer to be canceled. + * @param {HTMLMediaElement} video Video element under test. + * @returns {Promise} Promise that is resolved when video decode resumes. + */ +function testSuspendTimerCanceledWhenShown(video) { + function ended() { + video.removeEventListener("mozcancelvideosuspendtimer", canceled); + ok(false, `${video.token} ended before suspend cancels`); + this.ended_resolve(); + } + + function canceled() { + video.removeEventListener("ended", ended); + ok(true, `${video.token} suspend cancels`); + this.canceled_resolve(); + } + + let p = Promise.race([ + new Promise((resolve) => { + video.ended_resolve = resolve; + video.addEventListener('ended', ended, { 'once': true }); + }), + new Promise((resolve) => { + video.canceled_resolve = resolve; + video.addEventListener('mozcancelvideosuspendtimer', canceled, { 'once': true }); + }) + ]); + + Log(video.token, "Set visible"); + video.setVisible(true); + + return p; +} + +startTest({ + desc: 'Test Background Video Suspend Cancels (Visibility)', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 10000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilPlaying(v) + .then(() => testSuspendTimerStartedWhenHidden(v)) + .then(() => testSuspendTimerCanceledWhenShown(v)) + .then(() => { + ok(true, `${v.token} finished`); + manager.finished(token); + }); + } +}); diff --git a/dom/media/test/test_background_video_drawimage_with_suspended_video.html b/dom/media/test/test_background_video_drawimage_with_suspended_video.html new file mode 100644 index 0000000000..32bda51dbf --- /dev/null +++ b/dom/media/test/test_background_video_drawimage_with_suspended_video.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Displays Video Frame via drawImage When Suspended</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<style> +video, canvas { + border: 1px solid black; +} +</style> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +function drawVideoToCanvas(v) { + console.log('drawVideoToCanvas'); + let c = document.createElement('canvas'); + c.width = 4; + c.height = 4; + c.style.width = 64; + c.style.height = 64; + document.body.appendChild(c); + + let gfx = c.getContext('2d'); + if (!gfx) { + throw Error("Unable to obtain context '2d' from canvas"); + } + + gfx.drawImage(v, 0, 0, 4, 4); + let imageData = gfx.getImageData(0, 0, 4, 4); + let pixels = imageData.data; + + // Check that pixels aren't all the same colour. + // Implements by checking against rgb of the first pixel. + let rr = pixels[0], + gg = pixels[1], + bb = pixels[2], + allSame = true; + + for (let i = 0; i < 4*4; i++) { + let r = pixels[4*i+0]; + let g = pixels[4*i+1]; + let b = pixels[4*i+2]; + if (r != rr || g != gg || b != bb) { + allSame = false; + break; + } + } + + ok(!allSame, "Pixels aren't all the same color"); +} + +startTest({ + desc: 'Test Background Video Displays Video Frame via drawImage When Suspended', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 500 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilPlaying(v) + .then(() => testVideoSuspendsWhenHidden(v)) + .then(() => { + drawVideoToCanvas(v); + manager.finished(token); + }); + } +}); +</script> diff --git a/dom/media/test/test_background_video_ended_event.html b/dom/media/test/test_background_video_ended_event.html new file mode 100644 index 0000000000..99e2dd2f18 --- /dev/null +++ b/dom/media/test/test_background_video_ended_event.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Suspends</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +startTest({ + desc: "Test background video doesn't fire the 'ended' event twice.", + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 100 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + let count = 0; + v.addEventListener("ended", function() { + is(++count, 1, `${token} should get only one 'ended' event.`); + }); + + /* + * This test checks that, after a video element had finished its playback, + * resuming video decoder doesn't dispatch 2nd ended event. + * This issue was found in bug 1349097. + */ + waitUntilPlaying(v) + .then(() => testVideoSuspendsWhenHidden(v)) + .then(() => waitUntilEnded(v)) + .then(() => testVideoResumesWhenShown(v)) + .then(() => { + // Wait for a while and finish the test. We should get only one 'ended' event. + setTimeout(function() { + removeNodeAndSource(v); + manager.finished(token); + }, 3000); + }); + } +}); +</script> diff --git a/dom/media/test/test_background_video_no_suspend_disabled.html b/dom/media/test/test_background_video_no_suspend_disabled.html new file mode 100644 index 0000000000..f93977f2df --- /dev/null +++ b/dom/media/test/test_background_video_no_suspend_disabled.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Doesn't Suspend When Feature Disabled</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script> +"use strict"; + +var manager = new MediaTestManager; + +startTest({ + desc: "Test Background Video Doesn't Suspend When Feature Disabled.", + prefs: [ + [ 'media.test.video-suspend', true ], + [ 'media.suspend-bkgnd-video.enabled', false ], + [ 'media.suspend-bkgnd-video.delay-ms', 0 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + /* This test checks that suspend doesn't occur when the feature is disabled */ + waitUntilPlaying(v) + .then(() => checkVideoDoesntSuspend(v)) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); }) + .catch((e) => { + ok(false, 'Test Failed: ' + e.toString()); + manager.finished(token); }); + } +}); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_no_suspend_not_in_tree.html b/dom/media/test/test_background_video_no_suspend_not_in_tree.html new file mode 100644 index 0000000000..f65d363bd6 --- /dev/null +++ b/dom/media/test/test_background_video_no_suspend_not_in_tree.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Doesn't Suspend When Timeout Is Longer Than Video</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script> +"use strict"; + +var manager = new MediaTestManager; + +var MIN_DELAY = 100; + +/** + * @param {string} url video src. + * @returns {HTMLMediaElement} The created video element. + */ +function createVideoNotAppendToDoc(url, token, width, height) { + // Default size of (160, 120) is used by other media tests. + if (width === undefined) { width = 160; } + if (height === undefined) { height = 3*width/4; } + + let v = document.createElement('video'); + v.token = token; + v.width = width; + v.height = height; + v.src = url; + return v; +} + +startTest({ + desc: "Test Background Video Doesn't Suspend When If The Video Is Not In Tree.", + prefs: [ + [ 'media.test.video-suspend', true ], + [ 'media.suspend-bkgnd-video.enabled', true ], + [ 'media.suspend-bkgnd-video.delay-ms', MIN_DELAY ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = createVideoNotAppendToDoc(test.name, token); + manager.started(token); + + /* This test checks that suspend doesn't occur if a video element is not + append to tree. */ + waitUntilPlaying(v) + .then(() => checkVideoDoesntSuspend(v)) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); }) + .catch((e) => { + ok(false, 'Test Failed: ' + e.toString()); + manager.finished(token); }); + } +}); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_no_suspend_short_vid.html b/dom/media/test/test_background_video_no_suspend_short_vid.html new file mode 100644 index 0000000000..35597f6b96 --- /dev/null +++ b/dom/media/test/test_background_video_no_suspend_short_vid.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Doesn't Suspend When Timeout Is Longer Than Video</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script> +"use strict"; + +var manager = new MediaTestManager; + +startTest({ + desc: "Test Background Video Doesn't Suspend When Timeout Is Longer Than Video.", + prefs: [ + [ 'media.test.video-suspend', true ], + [ 'media.suspend-bkgnd-video.enabled', true ], + // Gizmo.mp4 is about 5.6s + [ 'media.suspend-bkgnd-video.delay-ms', 10000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + /* This test checks that suspend doesn't occur when the delay is longer + than the duration of the video that's playing */ + waitUntilPlaying(v) + .then(() => checkVideoDoesntSuspend(v)) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); }) + .catch((e) => { + ok(false, 'Test Failed: ' + e.toString()); + manager.finished(token); }); + } +}); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_resume_after_end_show_last_frame.html b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html new file mode 100644 index 0000000000..767567e116 --- /dev/null +++ b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Suspends</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +function testSameContent(video1, video2) { + if (video1.videoWidth != video2.videoWidth || + video1.videoHeight != video2.videoHeight) { + ok(false, `${video1.token} video1 and video2 have different dimensions.`); + return; + } + + let w = video1.videoWidth; + let h = video1.videoHeight; + let c1 = document.createElement('canvas'); + let c2 = document.createElement('canvas'); + c1.width = w; + c1.height = h; + c2.width = w; + c2.height = h; + + let gfx1 = c1.getContext('2d'); + let gfx2 = c2.getContext('2d'); + if (!gfx1 || !gfx2) { + ok(false, "Unable to obtain context '2d' from canvas"); + return; + } + + gfx1.drawImage(video1, 0, 0, w, h); + gfx2.drawImage(video2, 0, 0, w, h); + + // Get content out. + let contentWidth = 4; + let contentHeight = 4; + let imageData1 = gfx1.getImageData(0, 0, contentWidth, contentHeight); + let imageData2 = gfx2.getImageData(0, 0, contentWidth, contentHeight); + let pixels1 = imageData1.data; + let pixels2 = imageData2.data; + + // Check that the content of two video are identical. + for (let i = 0; i < contentWidth*contentHeight; i++) { + let pixelCount = 4 * i; + if (pixels1[pixelCount+0] != pixels2[pixelCount+0] || + pixels1[pixelCount+1] != pixels2[pixelCount+1] || + pixels1[pixelCount+2] != pixels2[pixelCount+2] || + pixels1[pixelCount+3] != pixels2[pixelCount+3]) { + ok(false, `${video1.token} video1 and video2 have different content.`); + return; + } + } + + ok(true, `${video1.token} video1 and video2 have identical content.`); +} + +function waitUntilSeekToLastFrame(video) { + Log(video.token, "Waiting for seeking to the last frame"); + function callSeekToNextFrame() { + video.seekToNextFrame().then( + () => { + if (!video.seenEnded) { + callSeekToNextFrame(); + } + }, + () => { + // When seek reaches the end, the promise is resolved before 'ended' + // is fired. The resolver calls callSeekToNextFrame() to schedule + // another seek and then the 'ended' handler calls finish() to shut + // down the MediaDecoder which will reject the seek promise. So we don't + // raise an error in this case. + ok(video.seenEnded, "seekToNextFrame() failed."); + } + ); + } + + return new Promise(function(resolve, reject) { + video.seenEnded = false; + video.addEventListener("ended", () => { + video.seenEnded = true; + resolve(); + }, true); + callSeekToNextFrame(video); + }); +} + +startTest({ + desc: "Test resume an ended video shows the last frame.", + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 100 ], + [ "media.dormant-on-pause-timeout-ms", -1], + [ "media.decoder.skip-to-next-key-frame.enabled", false] + ], + tests: gDecodeSuspendTests, + + runTest: (test, token) => { + let v = appendVideoToDocWithoutLoad(token); + let vReference = appendVideoToDocWithoutLoad(token+"-ref"); + manager.started(token); + + /* + * This test checks that, after a video element had finished its playback, + * resuming video decoder should seek to the last frame. + * This issue was found in bug 1358057. + */ + waitUntilVisible(v) + .then(() => { + return Promise.all([loadAndWaitUntilLoadedmetadata(v, test.name), loadAndWaitUntilLoadedmetadata(vReference, test.name, "auto")]); + }) + .then(() => waitUntilPlaying(v)) + .then(() => testVideoSuspendsWhenHidden(v)) + .then(() => { + return Promise.all([waitUntilEnded(v), waitUntilSeekToLastFrame(vReference)]); + }) + .then(() => testVideoOnlySeekCompletedWhenShown(v)) + .then(() => { + testSameContent(v, vReference); + removeNodeAndSource(v); + removeNodeAndSource(vReference); + manager.finished(token); + }); + } +}); +</script> diff --git a/dom/media/test/test_background_video_resume_looping_video_without_audio.html b/dom/media/test/test_background_video_resume_looping_video_without_audio.html new file mode 100644 index 0000000000..53b380ce15 --- /dev/null +++ b/dom/media/test/test_background_video_resume_looping_video_without_audio.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Resume suspended looping video which doesn't contain audio track</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="manifest.js"></script> + <script src="background_video.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script class="testbody" type="text/javascript"> +/** + * This test is used to ensure that the looping video (without audio track) which + * has been suspended can continute to playback correctly after we resume video + * decoding. + */ +async function startTest() { + const video = await createVisibleVideo(); + await startVideo(video); + await suspendVideoDecoding(video); + await resumeVideoDecoding(video); + await checkIfVideoIsStillPlaying(video); + endTestAndClearVideo(video); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ 'set': [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + ["media.suspend-bkgnd-video.delay-ms", 0], + ]}, () => { + startTest(); +}); + +/** + * The following are test helper functions. + */ +async function createVisibleVideo() { + let video = document.createElement("video"); + video.src = "gizmo-noaudio.webm"; + video.controls = true; + video.loop = true; + document.body.appendChild(video); + info(`ensure video becomes visible`); + await waitUntilVisible(video); + return video; +} + +async function startVideo(video) { + info(`start playing video`); + const played = video && await video.play().then(() => true, () => false); + ok(played, "video has started playing"); +} + +async function suspendVideoDecoding(video) { + info(`suspend video decoding`); + video.setVisible(false); + await nextVideoSuspends(video); + info(`suspended video decoding`); +} + +async function resumeVideoDecoding(video) { + info(`resume video decoding.`); + video.setVisible(true); + await nextVideoResumes(video); + info(`resumed video decoding`); +} + +async function checkIfVideoIsStillPlaying(video) { + await once(video, "timeupdate"); + ok(!video.paused, "video is still playing after resuming video decoding.") +} + +function endTestAndClearVideo(video) { + removeNodeAndSource(video); + SimpleTest.finish(); +} + +</script> +</body> +</html> diff --git a/dom/media/test/test_background_video_suspend.html b/dom/media/test/test_background_video_suspend.html new file mode 100644 index 0000000000..a6a720ad13 --- /dev/null +++ b/dom/media/test/test_background_video_suspend.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Suspends</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +<script type="text/javascript"> + "use strict"; + + var manager = new MediaTestManager; + + var MIN_DELAY = 100; + + function testDelay(v, start, min) { + let end = performance.now(); + let delay = end - start; + ok(delay > min, `${v.token} suspended with a delay of ${delay} ms`); + } + + async function runTest(test, token) { + let video = appendVideoToDocWithoutLoad(token); + manager.started(token); + + let visible = waitUntilVisible(video); + let ended = nextVideoEnded(video); + let playing = nextVideoPlaying(video); + let resumes = nextVideoResumes(video); + let suspends = nextVideoSuspends(video); + + Log(token, "Waiting until video becomes visible"); + await visible; + + Log(token, "Waiting for metadata loaded"); + await loadAndWaitUntilLoadedmetadata(video, test.name); + + Log(token, "Start playing"); + video.play(); + + Log(token, "Waiting for video playing"); + await playing; + + let start = performance.now(); + + Log(token, "Set hidden"); + video.setVisible(false); + + Log(token, "Waiting for video suspend"); + await suspends; + + testDelay(video, start, MIN_DELAY); + + Log(token, "Set visible"); + video.setVisible(true); + + Log(token, "Waiting for video resume"); + await resumes; + + Log(token, "Waiting for ended"); + await ended; + + ok(video.currentTime >= video.duration, 'current time approximates duration.'); + + removeNodeAndSource(video); + manager.finished(token); + } + + startTest({ + desc: 'Test Background Video Suspends', + prefs: [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + // Use a short delay to ensure video decode suspend happens before end + // of video. + ["media.suspend-bkgnd-video.delay-ms", MIN_DELAY], + ["privacy.reduceTimerPrecision", false] + ], + tests: gDecodeSuspendTests, + runTest + }); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_suspend_ends.html b/dom/media/test/test_background_video_suspend_ends.html new file mode 100644 index 0000000000..c16ffff290 --- /dev/null +++ b/dom/media/test/test_background_video_suspend_ends.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Suspended Video Fires 'ended' Event</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +<script type="text/javascript"> + "use strict"; + + PARALLEL_TESTS = 1; + + var manager = new MediaTestManager; + + async function runTest(test, token) { + let video = appendVideoToDoc(test.name, token); + manager.started(token); + + // This test checks that 'ended' event is received for videos with + // suspended video decoding. This is important for looping video logic + // handling in HTMLMediaElement. + + let ended = nextVideoEnded(video); + let suspends = nextVideoSuspends(video); + + Log(token, "Start playing"); + video.play(); + + Log(token, "Set hidden"); + video.setVisible(false); + + Log(token, "Waiting for video suspend"); + await suspends; + + Log(token, "Waiting for ended"); + await ended; + + ok(video.currentTime >= video.duration, 'current time approximates duration.'); + + manager.finished(token); + } + + startTest({ + desc: "Test Background Suspended Video Fires 'ended' Event", + prefs: [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + // User a short delay to ensure video decode suspend happens before end + // of video. + ["media.suspend-bkgnd-video.delay-ms", 1000] + ], + tests: gDecodeSuspendTests, + runTest + }); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_tainted_by_capturestream.html b/dom/media/test/test_background_video_tainted_by_capturestream.html new file mode 100644 index 0000000000..5cbb40f3f7 --- /dev/null +++ b/dom/media/test/test_background_video_tainted_by_capturestream.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Is Tainted By captureStream</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +function captureVideoAsStream(v) { + v.mozCaptureStream(); +} + +startTest({ + desc: 'Test Background Video Is Tainted By captureStream', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 1000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + ok(true, `${test.name}`); + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilPlaying(v) + .then(() => { + captureVideoAsStream(v); + ok(v.hasSuspendTaint(), "Video is tainted after captured"); + return checkVideoDoesntSuspend(v); + }) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); + }) + .catch((e) => { + ok(false, 'Test failed: ' + e.toString()); + manager.finished(token); + }); + } +}); +</script> diff --git a/dom/media/test/test_background_video_tainted_by_createimagebitmap.html b/dom/media/test/test_background_video_tainted_by_createimagebitmap.html new file mode 100644 index 0000000000..cd884f9144 --- /dev/null +++ b/dom/media/test/test_background_video_tainted_by_createimagebitmap.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Is Tainted By createImageBitmap</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +startTest({ + desc: 'Test Background Video Is Tainted By createImageBitmap', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 1000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + ok(true, `${test.name}`); + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilPlaying(v) + .then(() => createImageBitmap(v)) + .then(() => { + ok(v.hasSuspendTaint(), "Video is tainted after drawing to canvas"); + return checkVideoDoesntSuspend(v); + }) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); + }) + .catch((e) => { + ok(false, 'Test failed: ' + e.toString()); + manager.finished(token); + }); + } +}); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_background_video_tainted_by_drawimage.html b/dom/media/test/test_background_video_tainted_by_drawimage.html new file mode 100644 index 0000000000..d5b8c75f3d --- /dev/null +++ b/dom/media/test/test_background_video_tainted_by_drawimage.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test Background Video Is Tainted By drawImage</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="manifest.js"></script> +<script src="background_video.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script type="text/javascript"> +"use strict"; + +var manager = new MediaTestManager; + +function drawVideoToCanvas(v) { + let w = v.width, + h = v.height, + c = document.createElement('canvas'); + c.width = w; + c.height = h; + document.body.appendChild(c); + + let gfx = c.getContext('2d'); + if (!gfx) { + throw Error("Unable to obtain context '2d' from canvas"); + } + + gfx.drawImage(v, 0, 0, w, h); +} + +startTest({ + desc: 'Test Background Video Is Tainted By drawImage', + prefs: [ + [ "media.test.video-suspend", true ], + [ "media.suspend-bkgnd-video.enabled", true ], + [ "media.suspend-bkgnd-video.delay-ms", 1000 ] + ], + tests: gDecodeSuspendTests, + runTest: (test, token) => { + ok(true, `${test.name}`); + let v = appendVideoToDoc(test.name, token); + manager.started(token); + + waitUntilPlaying(v) + .then(() => { + drawVideoToCanvas(v); + ok(v.hasSuspendTaint(), "Video is tainted after drawing to canvas"); + return checkVideoDoesntSuspend(v); + }) + .then(() => { + ok(true, 'Video ended before decode was suspended'); + manager.finished(token); + }) + .catch((e) => { + ok(false, 'Test failed: ' + e.toString()); + manager.finished(token); + }); + } +}); +</script>
\ No newline at end of file diff --git a/dom/media/test/test_buffered.html b/dom/media/test/test_buffered.html new file mode 100644 index 0000000000..86d8eec28a --- /dev/null +++ b/dom/media/test/test_buffered.html @@ -0,0 +1,117 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462957 +--> +<head> + <title>Test for Bug 462957</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462957">Mozilla Bug 462957</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Test for Bug 462957; HTMLMediaElement.buffered. + +var manager = new MediaTestManager; + +function testBuffered(e) { + var v = e.target; + + // The whole media should be buffered... + var b = v.buffered; + is(b.length, 1, v._name + ": Should be buffered in one range"); + is(b.start(0), 0, v._name + ": First range start should be media start"); + ok(Math.abs(b.end(0) - v.duration) < 0.1, v._name + ": First range end should be media end"); + + // Ensure INDEX_SIZE_ERR is thrown when we access outside the range + var caught = false; + try { + b.start(-1); + } catch (ex) { + caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR; + } + is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under start bounds range"); + + caught = false; + try { + b.end(-1); + } catch (ex) { + caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR; + } + is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under end bounds range"); + + caught = false; + try { + b.start(b.length); + } catch (ex) { + caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR; + } + is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over start bounds range"); + + caught = false; + try { + b.end(b.length); + } catch (ex) { + caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR; + } + is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over end bounds range"); + + removeNodeAndSource(v); + manager.finished(v._token); +} + +function fetch(url, fetched_callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "blob"; + + var loaded = function (event) { + if (xhr.status == 200 || xhr.status == 206) { + ok(true, `${url}: Fetch succeeded, status=${xhr.status}`); + // Request fulfilled. Note sometimes we get 206... Presumably because either + // httpd.js or Necko cached the result. + fetched_callback(window.URL.createObjectURL(xhr.response)); + } else { + ok(false, `${url}: Fetch failed, headers=${xhr.getAllResponseHeaders()}`); + } + }; + + xhr.addEventListener("load", loaded); + xhr.send(); +} + +function startTest(test, token) { + // Fetch the media resource using XHR so we can be sure the entire + // resource is loaded before we test buffered ranges. This ensures + // we have deterministic behaviour. + var onfetched = function(uri) { + var v = document.createElement('video'); + v._token = token; + v.src = uri; + v._name = test.name; + v._test = test; + v.addEventListener("loadeddata", testBuffered, {once: true}); + document.body.appendChild(v); + }; + + manager.started(token); + fetch(test.name, onfetched); +} + +// Note: No need to set media test prefs, since we're using XHR to fetch +// media data. +manager.runTests(gSeekTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug1113600.html b/dom/media/test/test_bug1113600.html new file mode 100644 index 0000000000..37a9cb0adb --- /dev/null +++ b/dom/media/test/test_bug1113600.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that a video element captured to a stream mid-playback can be played to the end</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +PARALLEL_TESTS = 1; +SimpleTest.requestCompleteLog(); +var manager = new MediaTestManager; + +function startTest(test, token) { + var v = document.createElement('video'); + v.style = "background-color:#aca;"; + v.width = 160; + v.height = 120; + + manager.started(token); + + v.src = test.name; + + v.ontimeupdate = function() { + if (v.currentTime < test.duration / 4) { + // Allow some time to pass before starting the capture. + return; + } + v.ontimeupdate = null; + v.mozCaptureStreamUntilEnded(); + info(test.name + " capture started at " + v.currentTime + ". Duration=" + test.duration); + }; + + v.onended = function() { + ok(true, test.name + " ended"); + removeNodeAndSource(v); + manager.finished(token); + }; + + document.body.appendChild(v); + v.play(); +} + +manager.runTests(gSmallTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug1242338.html b/dom/media/test/test_bug1242338.html new file mode 100644 index 0000000000..7b72153a6e --- /dev/null +++ b/dom/media/test/test_bug1242338.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Bug 1242338</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.preload = "metadata"; + video.token = token; + + var handler = { + "ontimeout": function() { + Log(token, "timed out"); + } + }; + manager.started(token, handler); + + video.src = test.name; + video.name = test.name; + + function finish() { + video.finished = true; + video.removeEventListener("loadedmetadata", onLoadedmetadata); + video.removeEventListener("ended", onEnded); + removeNodeAndSource(video); + manager.finished(video.token); + } + + function onLoadedmetadata() { + // seek to the media's duration + var duration = video.duration; + console.log("onloadedmetadata(), duration = " + duration); + video.currentTime = duration; + } + + function onEnded() { + ok(video.ended, test.name + " checking playback has ended"); + ok(!video.finished, test.name + " shouldn't be finished"); + ok(!video.seenEnded, test.name + " shouldn't be ended"); + video.seenEnded = true; + + ok(true, "Seeking to the duration triggers ended event"); + finish(); + } + + video.addEventListener("loadedmetadata", onLoadedmetadata); + video.addEventListener("ended", onEnded); + + document.body.appendChild(video); +} + +manager.runTests(gSeekTests, startTest); + +</script> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/dom/media/test/test_bug1248229.html b/dom/media/test/test_bug1248229.html new file mode 100644 index 0000000000..3165795622 --- /dev/null +++ b/dom/media/test/test_bug1248229.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test garbage collection of captured stream (bug 1248229)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="doTest()"> +<video id="v" src="black100x100-aspect3to2.ogv"></video> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function doTest() { + /* global v */ + window.oak = v.mozCaptureStreamUntilEnded(); + v.mozCaptureStreamUntilEnded(); + v.play(); + + v.onended = function() { + info("Got ended."); + v.onended = null; + SpecialPowers.exactGC(function() { + info("GC completed."); + v.play(); + SimpleTest.finish(); + }); + } +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug1431810_opus_downmix_to_mono.html b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html new file mode 100644 index 0000000000..647ddf0489 --- /dev/null +++ b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html @@ -0,0 +1,139 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: disable phase inversion in opus decoder</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<audio preload=none id="a" controls></audio> +<audio preload=none id="b" controls></audio> +<script class="testbody" type="text/javascript"> +/* + This test makes use of an (stereo) opus file with phase inversion of 180 degrees (right = -left => right + left = 0). + Firstly, the phase inversion is verified on a normal stereo playback. + Secondly, mono playback is forced which results in the phase inversion being disabled (Bug 1431810). +*/ +SimpleTest.waitForExplicitFinish(); + +/* global a, b */ + +function areChannelsInverted(b1, b2) { + for (var i = 0; i < b1.length; i++) { + if (Math.abs(b1[i] + b2[i]) > 9e-2) { + return false; + } + } + return true; +} + +function areChannelsEqual(b1, b2) { + for (var i = 0; i < b1.length; i++) { + if (Math.abs(b1[i] - b2[i]) > 9e-3) { + return false; + } + } + return true; +} + +function isSilent(b) { + for (var i = 0; i < b.length; i++) { + if (b[i] != 0.0) { + return false; + } + } + return true; +} + +function mediaElementWithPhaseInversion(audioContext, mediaElement, success) { + let audio_source = audioContext.createMediaElementSource(mediaElement); + let script_processor = audioContext.createScriptProcessor(); + audio_source.connect(script_processor); + + mediaElement.onplay = () => { + script_processor.onaudioprocess = (e) => { + let right = e.inputBuffer.getChannelData(0); + let left = e.inputBuffer.getChannelData(1); + + // This is leading or trailing silence + // produced by ScriptProcessor. + if (isSilent(right) && isSilent(left)) { + return; + } + + ok(areChannelsInverted(right, left), "Channels must be inverted"); + } + } + + mediaElement.onended = () => { + ok(true, "End of file."); + mediaElement.onended = null; + script_processor.onaudioprocess = null; + success(); + } + + mediaElement.src = "test-stereo-phase-inversion-180.opus"; + // Normal playback channels will by inverted + mediaElement.play(); +} + +function mediaElementWithPhaseInversionDisabled(audioContext, mediaElement, success) { + let audio_source = audioContext.createMediaElementSource(mediaElement); + let script_processor = audioContext.createScriptProcessor(); + audio_source.connect(script_processor); + + mediaElement.onplay = () => { + script_processor.onaudioprocess = (e) => { + let right = e.inputBuffer.getChannelData(0); + let left = e.inputBuffer.getChannelData(1); + + // This is leading or trailing silence + // produced by ScriptProcessor. + if (isSilent(right) && isSilent(left)) { + return; + } + + ok(!areChannelsInverted(right, left), "Channels must not be inverted"); + ok(areChannelsEqual(right, left), "Channels must be equal"); + } + } + + mediaElement.onended = () => { + ok(true, "End of file."); + mediaElement.onended = null; + script_processor.onaudioprocess = null; + success(); + } + + mediaElement.src = "test-stereo-phase-inversion-180.opus"; + + // Downmix to mono will force to disable opus phase inversion + SpecialPowers.pushPrefEnv({"set": [["accessibility.monoaudio.enable", true]]}) + .then(() => { + mediaElement.play(); + }); +} + +let ac = new AudioContext(); + +function testPhaseInversion(mediaElement) { + return new Promise((accept, reject) => { + mediaElementWithPhaseInversion(ac, a, accept); + }); +} + +function testPhaseInversionDisabled(mediaElement) { + return new Promise((accept, reject) => { + mediaElementWithPhaseInversionDisabled(ac, b, accept); + }); +} + +// Start testing +testPhaseInversion(a) +.then( () => testPhaseInversionDisabled(b) ) +.then( () => SimpleTest.finish() ) + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug1512958.html b/dom/media/test/test_bug1512958.html new file mode 100644 index 0000000000..2513515542 --- /dev/null +++ b/dom/media/test/test_bug1512958.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that pausing and resuming a captured media element with audio doesn't stall</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<audio id="a"></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +function dumpEvent({target, type}) { + info(`${target.name} GOT EVENT ${type} currentTime=${target.currentTime} ` + + `paused=${target.paused} ended=${target.ended} ` + + `readyState=${target.readyState}`); +} + +function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const a = document.getElementById('a'); + +const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"]; +for (let ev of events) { + a.addEventListener(ev, dumpEvent); +} + +(async () => { + try { + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("Timeouts for shortcutting test-timeout"); + + const test = getPlayableAudio(gTrackTests.filter(t => t.duration > 2)); + if (!test) { + todo(false, "No playable audio"); + return; + } + + // Start playing and capture + a.src = test.name; + a.name = test.name; + const ac = new AudioContext(); + ac.createMediaElementSource(a); + a.play(); + do { + await new Promise(r => a.ontimeupdate = r); + } while(a.currentTime == 0) + + // Pause to trigger recreating tracks in DecodedStream + a.pause(); + await new Promise(r => a.onpause = r); + + // Resuming should now work. Bug 1512958 would cause a stall because the + // original track wasn't ended and we'd block on it. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1512958#c5 + a.play(); + await new Promise(r => a.onplaying = r); + a.currentTime = test.duration - 1; + await Promise.race([ + new Promise(res => a.onended = res), + wait(30000).then(() => Promise.reject(new Error("Timeout"))), + ]); + } catch(e) { + ok(false, e); + } finally { + SimpleTest.finish(); + } +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug1553262.html b/dom/media/test/test_bug1553262.html new file mode 100644 index 0000000000..19b937f7fe --- /dev/null +++ b/dom/media/test/test_bug1553262.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Bug 1553262</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script> + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + + const video = document.createElement('video') + const source = new AudioContext().createMediaElementSource(video) + const stream = canvas.captureStream() + + const xhr = new XMLHttpRequest() + + context.rect(256, -32768, 16, 16) + video.srcObject = stream + for (let i = 0; i < 16; i++) { + xhr.open('P', '', false) + xhr.send() + } + + video.srcObject = stream + ok(true, "success") +</script> +</body> +</html> diff --git a/dom/media/test/test_bug448534.html b/dom/media/test/test_bug448534.html new file mode 100644 index 0000000000..ed1135d85a --- /dev/null +++ b/dom/media/test/test_bug448534.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=448534 +--> + +<head> + <title>Test for Bug 448534</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448535">Mozilla Bug 448534</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function loaded(event) { + var v = event.target; + info(v.token + ": event=" + event.type); + if (v._finished) + return; + v.play(); +} + +function started(event) { + var v = event.target; + info(v.token + ": event=" + event.type); + // For a short file, it could reach the end before 'play' received. We will + // skip the test for 'paused' would be true when ended. + if (v._finished || v.ended) + return; + ok(!v.paused, v.token + ": Video should not be paused while playing"); + v.remove(); + v._played = true; +} + +function stopped(event) { + var v = event.target; + info(v.token + ": event=" + event.type); + if (v._finished) + return; + v._finished = true; + ok(v.paused, v.token + ": Video should be paused after removing from the Document"); + removeNodeAndSource(v); + manager.finished(v.token); +} + + +function startTest(test, token) { + var v = document.createElement('video'); + v.preload = "metadata"; + v.token = token; + manager.started(token); + v.src = test.name; + v._played = false; + v._finished = false; + v.addEventListener("loadedmetadata", loaded); + v.addEventListener("play", started); + v.addEventListener("pause", stopped); + document.body.appendChild(v); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug463162.xhtml b/dom/media/test/test_bug463162.xhtml new file mode 100644 index 0000000000..a84b3488d1 --- /dev/null +++ b/dom/media/test/test_bug463162.xhtml @@ -0,0 +1,78 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=463162 +--> +<head> + <title>Test for Bug 463162</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463162">Mozilla Bug 463162</a> + +<script class="testbody" type="text/javascript"> +<![CDATA[ + +var gExpectedResult = { + 'a1' : 'error', + 'a2' : 'loaded', + 'a3' : 'loaded', + 'a4' : 'error', +}; + +var gResultCount = 0; + +function onError(event, id) { + is('error', gExpectedResult[id], 'unexpected error loading ' + id); + gResultCount++; + dump('error('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n'); + if (gResultCount == 4) + SimpleTest.finish(); +} + +function onMetaData(id) { + is('loaded', gExpectedResult[id], 'unexpected loadedmetadata loading ' + id); + gResultCount++; + dump('onMetaData('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n'); + if (gResultCount == 4) + SimpleTest.finish(); +} + +]]> +</script> + +<video id="a1" preload="metadata" onloadedmetadata="onMetaData('a1');"><sauce/><source type="bad" src="404" onerror="onError(event, 'a1');"/></video> +<video id="a2" preload="metadata" onloadedmetadata="onMetaData('a2');"><source onerror="onError(event, 'a2');"/></video> +<video id="a3" preload="metadata" onloadedmetadata="onMetaData('a3');"><html:source onerror="onError(event, 'a3');"/></video> +<video id="a4" preload="metadata" onloadedmetadata="onMetaData('a4');"><svg:source/><source onerror="onError(event, 'a4');" type="bad" src="404"/></video> + +<script class="testbody" type="text/javascript"> +<![CDATA[ + +function setSource(id, res) { + var v = document.getElementById(id); + v.firstChild.src = res.name; + v.firstChild.type = res.type; +} + +var t = getPlayableVideo(gSmallTests); + +setSource('a1', t); +setSource('a2', t); +setSource('a3', t); +setSource('a4', t); + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +<pre id="test"> + +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug465498.html b/dom/media/test/test_bug465498.html new file mode 100644 index 0000000000..157a972e77 --- /dev/null +++ b/dom/media/test/test_bug465498.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: Bug 465498 - Seeking after playback ended</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465498">Mozilla Bug 465498</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(e) { + var v = e.target; + info(v._name + " loadedmetadata"); + e.target.play(); +} + +function playbackEnded(e) { + var v = e.target; + info(v._name + " ended"); + if (v._finished) + return; + ok(v.currentTime >= v.duration - 0.1 && v.currentTime <= v.duration + 0.1, + "Checking currentTime at end: " + v.currentTime + " for " + v._name); + ok(v.ended, "Checking playback has ended for " + v._name); + v.pause(); + v.currentTime = 0; + ok(!v.ended, "Checking ended is no longer true for " + v._name); + v._seeked = true; +} + +function seekEnded(e) { + var v = e.target; + info(v._name + " seeked"); + if (v._finished) + return; + ok(v.currentTime == 0, "Checking currentTime after seek: " + + v.currentTime + " for " + v._name); + ok(!v.ended, "Checking ended is false for " + v._name); + v._finished = true; + removeNodeAndSource(v); + manager.finished(v.token); +} + +function seeking(e) { + var v = e.target; + info(v._name + " seeking"); +} + +function initTest(test, token) { + var type = getMajorMimeType(test.type); + var v = document.createElement(type); + if (!v.canPlayType(test.type)) + return; + v.preload = "metadata"; + v.token = token; + manager.started(token); + v._name = test.name; + + var s = document.createElement("source"); + s.type = test.type; + s.src = test.name; + v.appendChild(s); + + v._seeked = false; + v._finished = false; + v.addEventListener("loadedmetadata", startTest); + v.addEventListener("ended", playbackEnded); + v.addEventListener("seeked", seekEnded); + v.addEventListener("seeking", seeking); + document.body.appendChild(v); +} + +manager.runTests(gSmallTests, initTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug495145.html b/dom/media/test/test_bug495145.html new file mode 100644 index 0000000000..3686e10270 --- /dev/null +++ b/dom/media/test/test_bug495145.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=495145 +--> + +<head> + <title>Bug 495145 - pausing while ended shouldn't cause problems</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495145">Mozilla Bug 495145</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function start(e) { + e.target.play(); +} + +function ended1(e) { + var v = e.target; + if (v._finished) + return; + + ++v._endCount; + if (v._endCount == 2) { + ok(true, "Playing after pause while ended works for " + v._name); + v._finished = true; + v.removeEventListener("loadedmetadata", start); + v.removeEventListener("ended", ended1); + removeNodeAndSource(v); + manager.finished(v.token); + return; + } + + v.pause(); + v.play(); +} + +function ended2(e) { + var v = e.target; + if (v._finished) + return; + + v.pause(); + v.currentTime = 0; +} + +function seeked2(e) { + var v = e.target; + if (v._finished) + return; + + ok(v.paused, "Paused after seek after pause while ended for " + v._name); + v._finished = true; + v.removeEventListener("loadedmetadata", start); + v.removeEventListener("ended", ended2); + v.removeEventListener("seeked", seeked2); + removeNodeAndSource(v); + manager.finished(v.token); +} + +function createVideo(test, x, token) { + var v = document.createElement('video'); + v.preload = "metadata"; + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name + "#" + x; + v._endCount = 0; + v._finished = false; + v.addEventListener("loadedmetadata", start); + v.addEventListener("ended", x == 1 ? ended1 : ended2); + if (x == 2) + v.addEventListener("seeked", seeked2); + document.body.appendChild(v); +} + +function startTest(test, token) { + createVideo(test, 1, token + "a"); + createVideo(test, 2, token + "b"); +} + +manager.runTests(gSmallTests, startTest); + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug495300.html b/dom/media/test/test_bug495300.html new file mode 100644 index 0000000000..bf3b921402 --- /dev/null +++ b/dom/media/test/test_bug495300.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=495300 +--> + +<head> + <title>Bug 495300 - seeking to the end should behave as "ended"</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495300">Mozilla Bug 495300</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function filename(uri) { + return uri.substr(uri.lastIndexOf("/")+1); +} + +function mediaEnded(event) { + ok(true, "Got expected 'ended' event: " + filename(event.target.currentSrc)); + + if (event.target._expectedDuration) + ok(Math.abs(event.target.currentTime - event.target._expectedDuration) < 0.1, + "currentTime equals duration: " + filename(event.target.currentSrc)); + + event.target.removeEventListener("ended", mediaEnded); + manager.finished(event.target.token); + removeNodeAndSource(event.target); +} + +function mediaLoadedmetadata(event) { + event.target.currentTime = event.target.duration; + event.target.removeEventListener("loadedmetadata", mediaLoadedmetadata); +} + +function startTest(test, token) { + var elemType = /^audio/.test(test.type) ? "audio" : "video"; + var v1 = document.createElement(elemType); + v1.preload = "auto"; + + v1.src = test.name; + if (test.duration) { + v1._expectedDuration = test.duration; + } + v1.addEventListener("loadedmetadata", mediaLoadedmetadata); + v1.addEventListener("ended", mediaEnded); + v1.load(); + + v1.token = token; + manager.started(token); +} + +manager.runTests(gSeekTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug654550.html b/dom/media/test/test_bug654550.html new file mode 100644 index 0000000000..f57afae2b4 --- /dev/null +++ b/dom/media/test/test_bug654550.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=654550 +--> + +<head> + <title>Test for Bug 654550</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=654550">Mozilla Bug 654550</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + /* Test for Bug 654550 */ + + // Parallel test must be disabled for media.video_stats.enabled is a global setting + // to prevent the setting from changing unexpectedly in the middle of the test. + PARALLEL_TESTS = 1; + SimpleTest.waitForExplicitFinish(); + var manager = new MediaTestManager; + + function checkStats(v, aShouldBeEnabled) { + if (aShouldBeEnabled) { + ok(v.mozParsedFrames != 0, + "At least one value should be different from 0 if stats are enabled"); + } else { + ok(!v.mozParsedFrames, + "mozParsedFrames should be 0 if stats are disabled"); + ok(!v.mozDecodedFrames, + "mozDecodedFrames should be 0 if stats are disabled"); + ok(!v.mozPresentedFrames, + "mozPresentedFrames should be 0 if stats are disabled"); + ok(!v.mozPaintedFrames, + "mozPaintedFrames should be 0 if stats are disabled"); + } + + } + + function ontimeupdate_statsEnabled(event) { + var v = event.target; + v.removeEventListener('timeupdate', ontimeupdate_statsEnabled); + checkStats(v, true); + SpecialPowers.popPrefEnv( + function() { + v.addEventListener("timeupdate", ontimeupdate_statsDisabled); + }); + } + + function ontimeupdate_statsDisabled(event) { + var v = event.target; + v.removeEventListener('timeupdate', ontimeupdate_statsDisabled); + checkStats(v, false); + removeNodeAndSource(v); + manager.finished(v.token); + } + + function startTest(test, token) { + var v = document.createElement('video'); + v.token = token; + v.src = test.name; + // playback may reach the end before pref is changed for the duration is short + // set 'loop' to true to keep playing so that we won't miss 'timeupdate' events + v.loop = true; + manager.started(token); + SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", true]]}, + function() { + v.play(); + v.addEventListener("timeupdate", ontimeupdate_statsEnabled); + }); + } + + SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", false]]}, + function() { + manager.runTests(gVideoTests, startTest); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug686942.html b/dom/media/test/test_bug686942.html new file mode 100644 index 0000000000..66b48d69f0 --- /dev/null +++ b/dom/media/test/test_bug686942.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=686942 +--> + +<head> + <title>Test for Bug 448534</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=686942">Mozilla Bug 686942</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function onloaded(event) { + var v = event.target; + v.removeEventListener("loadedmetadata", onloaded); + v.currentTime = v.duration; + +} + +function checkNotPlaying(v) { + ok(v.currentTime == 0, "Should not be playing after seek to end and back to beginning"); + v._finished = true; + manager.finished(v.token); + removeNodeAndSource(v); +} + +function onseeked(event) { + var v = event.target; + v.removeEventListener("seeked", onseeked); + setTimeout(function() { checkNotPlaying(v); }, 500); +} + +function onended(event) { + var v = event.target; + v.removeEventListener("ended", onended); + if (v._finished) + return; + v.addEventListener("seeked", onseeked); + v.currentTime = 0; +} + +function startTest(test, token) { + var v = document.createElement('video'); + v.preload = "auto"; + v.token = token; + manager.started(token); + v.src = test.name; + v._played = false; + v._finished = false; + v.addEventListener("loadedmetadata", onloaded); + v.addEventListener("ended", onended); + + document.body.appendChild(v); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug726904.html b/dom/media/test/test_bug726904.html new file mode 100644 index 0000000000..bf8e2536ca --- /dev/null +++ b/dom/media/test/test_bug726904.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=726904 +--> + +<head> + <title>Media test: default video size</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="bodyLoaded();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=726904">Mozilla Bug 726904</a> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var v1 = document.createElement("video"), + v2 = document.createElement("video"), + poster = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAAAAACl1GkQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAALJJREFUeNrtwQENAAAAwqD3T20ON6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHg0cq4AATRk8BYAAAAASUVORK5CYII", + resource = getPlayableVideo(gSmallTests); + +function bodyLoaded(){ + // Note: For DASH, width and height would vary once the video started playing, so + // the values would not correlate with those in manifest.js. Since this test has + // no playing, this should not affect the result. + is(v1.videoWidth, resource.width, "Intrinsic width should match video width"); + is(v1.videoHeight, resource.height, "Intrinsic height should match video height"); + is(v2.clientWidth, 400, "clientWidth should be 400"); + is(v2.clientHeight, 400, "clientHeight should be 400"); + SimpleTest.finish(); +} + +if (resource) { + v1.poster = v2.poster = poster; + + v1.src = v2.src = "http://mochi.test:8888/tests/dom/media/test/" + resource.name; + + v1.preload = "auto"; + v2.preload = "none"; + + v1.muted = v2.muted = true; + + document.body.appendChild(v1); + document.body.appendChild(v2); +} else { + todo(false, "No types supported"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug874897.html b/dom/media/test/test_bug874897.html new file mode 100644 index 0000000000..7801487fe3 --- /dev/null +++ b/dom/media/test/test_bug874897.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=874897 +--> + +<head> + <title>Test for Bug 874897</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function loadeddata(e) { + var v = e.target; + ok(v.readyState >= v.HAVE_CURRENT_DATA, + "readyState must be >= HAVE_CURRENT_DATA for " + v._name); + + var canvas = document.createElement("canvas"); + canvas.width = 210; + canvas.height = 120; + document.body.appendChild(canvas); + var ctx = canvas.getContext("2d"); + try { + ctx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, canvas.width, canvas.height); + ok(true, "Shouldn't throw exception while drawing to canvas from video for " + v._name); + } catch (ex) { + ok(false, "Shouldn't throw exception while drawing to canvas from video for " + v._name); + } + + v._finished = true; + v.remove(); + manager.finished(v.token); +} + +function startTest(test, token) { + var type = getMajorMimeType(test.type); + if (type != "video") + return; + + var v = document.createElement('video'); + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name; + v._finished = false; + v.autoplay = true; + v.style.display = "none"; + v.addEventListener("loadeddata", loadeddata); + document.body.appendChild(v); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest); +function beginTest() { + manager.runTests(gAspectRatioTests, startTest); +} + +</script> +</pre> + +</body> +</html> diff --git a/dom/media/test/test_bug879717.html b/dom/media/test/test_bug879717.html new file mode 100644 index 0000000000..c669773d3f --- /dev/null +++ b/dom/media/test/test_bug879717.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 879717, check that a video element can be drawn into a canvas at various states of playback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +var canvas = document.createElement('canvas'); +document.body.appendChild(canvas); + +var checkDrawImage = function(eventName, videoElement) { + var exception = null; + var exceptionName = "nothing"; + try { + var ctx = canvas.getContext('2d'); + ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); + } catch (e) { + exception = e; + exceptionName = e.name; + } + ok(exception === null, + "drawImage shouldn't throw an exception on " + eventName + + " of " + videoElement.testName + ", got " + exceptionName); +}; + +var checkDrawImageEventHandler = function(ev) { + checkDrawImage(ev.type, ev.target); +}; +var startTest = function(media, token) { + manager.started(token); + + // File playback + var v1 = document.createElement("video"); + v1.autoplay = true; + + // Captured file playback + var v2 = document.createElement("video"); + + // Stream playback + var v3 = document.createElement("video"); + v3.autoplay = true; + + v1.gotLoadeddata = false; + v2.gotLoadeddata = false; + v3.gotLoadeddata = false; + + v1.testName = "v1 (" + media.name + ")"; + v2.testName = "v2 (Captured " + media.name + ")"; + v3.testName = "v3 (Stream of " + media.name + ")"; + + checkDrawImage("beforeplay", v1); + checkDrawImage("beforeplay", v2); + checkDrawImage("beforeplay", v3); + + v1.onloadedmetadata = checkDrawImageEventHandler; + v2.onloadedmetadata = checkDrawImageEventHandler; + v3.onloadedmetadata = checkDrawImageEventHandler; + + v1.onplay = checkDrawImageEventHandler; + v2.onplay = checkDrawImageEventHandler; + v3.onplay = checkDrawImageEventHandler; + + function onplaying(ev) { + if (!ev.target.gotPlaying) { + ev.target.gotPlaying = true; + checkDrawImageEventHandler(ev); + } + } + v1.onplaying = onplaying; + v2.onplaying = onplaying; + v3.onplaying = onplaying; + + var onloadeddata = function(ev) { + ev.target.gotLoadeddata = true; + checkDrawImageEventHandler(ev); + }; + + v1.onloadeddata = onloadeddata; + v2.onloadeddata = onloadeddata; + v3.onloadeddata = onloadeddata; + + var checkFinished = function() { + if (!v1.testFinished || !v2.testFinished || !v3.testFinished) { + return; + } + + ok(v1.gotLoadeddata, v1.testName + " should have gotten the 'loadeddata' event callback"); + ok(v2.gotLoadeddata, v2.testName + " should have gotten the 'loadeddata' event callback"); + ok(v3.gotLoadeddata, v3.testName + " should have gotten the 'loadeddata' event callback"); + + manager.finished(token); + }; + + var onended = function(ev) { + checkDrawImageEventHandler(ev); + removeNodeAndSource(ev.target); + ev.target.testFinished = true; + checkFinished(); + }; + + v1.onended = onended; + v2.onended = onended; + v3.onended = onended; + + document.body.appendChild(v1); + document.body.appendChild(v2); + document.body.appendChild(v3); + + v1.src = media.name; + v2.src = media.name; + v2.preload = 'metadata'; + + v2.addEventListener('loadedmetadata', function () { + v3.srcObject = v2.mozCaptureStreamUntilEnded(); + v2.play(); + }); +} + +manager.runTests(getPlayableVideos(gSmallTests), startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug895305.html b/dom/media/test/test_bug895305.html new file mode 100644 index 0000000000..56ed3c775c --- /dev/null +++ b/dom/media/test/test_bug895305.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=895305 +https://bugzilla.mozilla.org/show_bug.cgi?id=905320 +--> +<head> + <meta charset='utf-8'> + <title>Regression test for bug 895305 and 905320 - TextTrack* leaks</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var audio = document.createElement("audio"); + +// Check leaking on TextTrackList objects. +/* global ttl, ttc */ +window.ttl = audio.textTracks; +ttl.addEventListener("click", function(){}); + +// Check leaking on VTTCue objects. +window.ttc = new VTTCue(3, 4, "Test."); +ttc.addEventListener("click", function() {}); + +// Check leaking on TextTrack objects. +audio.addTextTrack("subtitles", "label", "en-CA"); +ttl[0].addEventListener("click", function() {}); + +ok(true); // Need to have at least one assertion for Mochitest to be happy. +SimpleTest.finish(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_bug919265.html b/dom/media/test/test_bug919265.html new file mode 100644 index 0000000000..40eff135e4 --- /dev/null +++ b/dom/media/test/test_bug919265.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=919265 +--> +<head> + <meta charset='utf-8'> + <title>Regression test for bug 919265 - Leak on VTTCue::GetCueAsHTML()</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +// We shouldn't leak upon shutdown. +(new VTTCue(0, 0, "")).getCueAsHTML(); + +// We need to assert something for Mochitest to be happy. +ok(true); +SimpleTest.finish(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type.html b/dom/media/test/test_can_play_type.html new file mode 100644 index 0000000000..a28d137783 --- /dev/null +++ b/dom/media/test/test_can_play_type.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=469247 +--> +<head> + <title>Test for Bug 469247</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill +a Bug 469247</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script type="application/javascript"> + +var v = document.getElementById('v'); + +function check(type, expected) { + is(v.canPlayType(type), expected, type); +} + +// Invalid types +check("foo/bar", ""); +check("", ""); +check("!!!", ""); + +mediaTestCleanup(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type_mpeg.html b/dom/media/test/test_can_play_type_mpeg.html new file mode 100644 index 0000000000..a4b87272c0 --- /dev/null +++ b/dom/media/test/test_can_play_type_mpeg.html @@ -0,0 +1,168 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=799315 +--> +<head> + <title>Test for MP4 and MP3 support</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script> + +function check_mp4(v, enabled) { + function check(type, expected) { + var ex = enabled ? expected : ""; + is(v.canPlayType(type), ex, type + "='" + ex + "'"); + } + + check("video/mp4", "maybe"); + check("video/x-m4v", "maybe"); + check("audio/mp4", "maybe"); + check("audio/x-m4a", "maybe"); + + // Not the MIME type that other browsers respond to, so we won't either. + check("audio/m4a", ""); + check("video/m4v", ""); + + check("audio/aac", "maybe"); + check("audio/aacp", "maybe"); + + // H.264 Constrained Baseline Profile Level 3.0, AAC-LC + check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably"); + + // H.264 Constrained Baseline Profile Level 3.0, mp3 + check("video/mp4; codecs=\"avc1.42E01E, mp3\"", "probably"); + + check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably"); + check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably"); + + // H.264 Main Profile Level 3.0, AAC-LC + check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably"); + // H.264 Main Profile Level 3.1, AAC-LC + check("video/mp4; codecs=\"avc1.4D401F, mp4a.40.2\"", "probably"); + // H.264 Main Profile Level 4.0, AAC-LC + check("video/mp4; codecs=\"avc1.4D4028, mp4a.40.2\"", "probably"); + // H.264 High Profile Level 3.0, AAC-LC + check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably"); + // H.264 High Profile Level 3.1, AAC-LC + check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably"); + + check("video/mp4; codecs=\"avc1.42E01E\"", "probably"); + check("video/mp4; codecs=\"avc1.42001E\"", "probably"); + check("video/mp4; codecs=\"avc1.58A01E\"", "probably"); + check("video/mp4; codecs=\"avc1.4D401E\"", "probably"); + check("video/mp4; codecs=\"avc1.64001F\"", "probably"); + + // AAC-LC + check("audio/mp4; codecs=\"mp4a.40.2\"", "probably"); + check("audio/mp4; codecs=mp4a.40.2", "probably"); + check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably"); + check("audio/x-m4a; codecs=mp4a.40.2", "probably"); + + check("audio/mp4; codecs=\"mp4a.40.2,\"", ""); // Invalid codecs string + + // HE-AAC v1 + check("audio/mp4; codecs=\"mp4a.40.5\"", "probably"); + check("audio/mp4; codecs=mp4a.40.5", "probably"); + check("audio/x-m4a; codecs=\"mp4a.40.5\"", "probably"); + check("audio/x-m4a; codecs=mp4a.40.5", "probably"); + // HE-AAC v2 + check("audio/mp4; codecs=\"mp4a.40.29\"", "probably"); + + // Opus + check("audio/mp4; codecs=\"opus\"", "probably"); + check("audio/mp4; codecs=opus", "probably"); + + // Flac. + var haveFlac = getPref("media.flac.enabled"); + check("audio/mp4; codecs=\"flac\"", haveFlac ? "probably" : ""); + check("audio/mp4; codecs=flac", haveFlac ? "probably" : ""); + + // VP9. + [ "video/mp4; codecs=vp9", + "video/mp4; codecs=\"vp9\"", + "video/mp4; codecs=\"vp9.0\"" + ].forEach((codec) => { + // canPlayType should support VP9 in MP4... + check(codec, "probably"); + if (!IsSupportedAndroid()) { + // VP9 codec is disabled on Android devices with no HW decoder. So skip it + // on Android for now. + ok(MediaSource.isTypeSupported(codec), "VP9 in MP4 should be supported in MSE"); + } + }); + + var haveAV1 = getPref("media.av1.enabled"); + check("video/mp4; codecs=\"av1\"", haveAV1 ? "probably" : ""); +} + +function check_mp3(v, enabled) { + function check(type, expected) { + var ex = enabled ? expected : ""; + is(v.canPlayType(type), ex, type + "='" + ex + "'"); + } + + check("audio/mpeg", "maybe"); + check("audio/mp3", "maybe"); + + check("audio/mpeg; codecs=\"mp3\"", "probably"); + check("audio/mpeg; codecs=mp3", "probably"); + + check("audio/mp3; codecs=\"mp3\"", "probably"); + check("audio/mp3; codecs=mp3", "probably"); +} + +function IsMacOS() { + return navigator.userAgent.includes("Mac OS"); +} + +function IsLinux() { + return navigator.userAgent.includes("Linux"); +} + +function getPref(name) { + return SpecialPowers.getBoolPref(name, false); +} + +function IsSupportedAndroid() { + return getAndroidVersion() >= 14; +} + +function IsJellyBeanOrLater() { + return getAndroidVersion() >= 16; +} + +var haveMp4 = + getPref("media.wmf.enabled") || + IsMacOS() || + (IsSupportedAndroid() && + (IsJellyBeanOrLater() || getPref("media.plugins.enabled"))) || + (IsLinux() && getPref("media.ffmpeg.enabled")); + +check_mp4(document.getElementById('v'), haveMp4); + +var haveMp3 = + getPref("media.wmf.enabled") || + (IsLinux() && getPref("media.ffmpeg.enabled")) || + (IsSupportedAndroid() && + ((IsJellyBeanOrLater() && getPref("media.android-media-codec.enabled")) || + getPref("media.plugins.enabled"))) || + IsMacOS(); + +check_mp3(document.getElementById('v'), haveMp3); + +mediaTestCleanup(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type_no_ogg.html b/dom/media/test/test_can_play_type_no_ogg.html new file mode 100644 index 0000000000..2e63f191d2 --- /dev/null +++ b/dom/media/test/test_can_play_type_no_ogg.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=469247 +--> +<head> + <title>Test for Bug 469247: Ogg backend disabled</title> + <script type="application/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill +a Bug 469247</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script src="can_play_type_ogg.js"></script> +<script> + +SimpleTest.waitForExplicitFinish(); + +function finish() { + mediaTestCleanup(); + SimpleTest.finish(); +} + +SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false]]}, + function() { + check_ogg(document.getElementById('v'), false, finish); + } +); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type_ogg.html b/dom/media/test/test_can_play_type_ogg.html new file mode 100644 index 0000000000..3e44f8ba8c --- /dev/null +++ b/dom/media/test/test_can_play_type_ogg.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=469247 +--> +<head> + <title>Test for Bug 469247: Ogg backend</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill +a Bug 469247</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script src="can_play_type_ogg.js"></script> +<script> + +SimpleTest.waitForExplicitFinish(); + +function finish() { + mediaTestCleanup(); + SimpleTest.finish(); +} + +check_ogg(document.getElementById('v'), true, finish); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type_wave.html b/dom/media/test/test_can_play_type_wave.html new file mode 100644 index 0000000000..e6d4e29d86 --- /dev/null +++ b/dom/media/test/test_can_play_type_wave.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=469247 +--> +<head> + <title>Test for Bug 469247: WAVE backend</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill +a Bug 469247</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script src="can_play_type_wave.js"></script> +<script> +check_wave(document.getElementById('v'), true); + +mediaTestCleanup(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_can_play_type_webm.html b/dom/media/test/test_can_play_type_webm.html new file mode 100644 index 0000000000..6b0e2adfad --- /dev/null +++ b/dom/media/test/test_can_play_type_webm.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=566245 +--> +<head> + <title>Test for Bug 566245: WebM backend</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566245">Mozill +a Bug 566245</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<video id="v"></video> + +<pre id="test"> +<script src="can_play_type_webm.js"></script> +<script> + async function runTest() { + try { + await check_webm(document.getElementById('v'), true); + mediaTestCleanup(); + } catch (e) { + info("Exception " + e.message); + ok(false, "Threw exception " + e.message); + } + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + addLoadEvent(runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_chaining.html b/dom/media/test/test_chaining.html new file mode 100644 index 0000000000..6ebf94c88d --- /dev/null +++ b/dom/media/test/test_chaining.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: chained ogg files.</title> + <meta charset='utf-8'> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function finish_test(element) { + removeNodeAndSource(element); + manager.finished(element.token); +} + +function onended(e) { + var t = e.target; + is(t._metadataCount, t._links, "We should have received "+ t._links + + " metadataloaded event. " + t.src); + + // If we encounter a file that has links with a different numbers of channel, + // we stop the decoding at the end of the first link. Hence, we report normal + // |seekable| and |buffered| values. + if (t._links != 1) { + is(t.seekable.length, 0, "No seekable ranges should be reported." + t.src); + is(t.buffered.length, 0, "No buffered region should be reported." + t.src); + } + + is(t.played.length, 1, "The played region should be continuous." + t.src); + + if (t._links != 1) { + var oldCurrentTime = t.currentTime; + t.currentTime = 0.0; + is(t.currentTime, oldCurrentTime, + "Seeking should be disabled when playing chained media." + t.src); + } + + finish_test(t); +} + +function onmetadataloaded(e) { + var t = e.target; + if (! t._metadataCount) { + t._metadataCount = 0; + } + + if (t._metadataCount > 1 && t._links === 1) { + ok(false, "We should receive only one \"loadedmetadata\" event for a chained file we don't support.") + } + + // We should be able to assert equality here, but somehow it fails (rarely but + // still) on try. Instead, we give it a little slack and assert that the index + // increases monotonically. + ok(t.mozGetMetadata().index >= t._metadataCount || t._links === 1, + "The metadata index value should increase." + t.src); + + ok(t.currentTime >= t._prevCurrentTime, + "The currenttime should be increased correctly in new chained part."); + t._prevCurrentTime = t.currentTime; + + // The files have all a user comment name 'index' that increments at each link + // in the chained media. + t._metadataCount++; + if (!t.playing && !t.ended) { + t.play(); + } +} + +function startTest(test, token) { + var elemType = /^audio/.test(test.type) ? "audio" : "video"; + var element = document.createElement(elemType); + document.body.appendChild(element); + manager.started(token); + element._links= test.links; + element.src = test.name; + element.token = token; + element.controls = true; + element.addEventListener("loadedmetadata", onmetadataloaded); + element.addEventListener("ended", onended); + element.preload = "metadata"; + element._prevCurrentTime = 0; +} + +manager.runTests(gChainingTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_ended_video.html b/dom/media/test/test_cloneElementVisually_ended_video.html new file mode 100644 index 0000000000..331dda2db9 --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_ended_video.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Test that when we start cloning a video that has already ended, the + * clone displays the last frame from the video. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + let ended = waitForEventOnce(originalVideo, "ended"); + await originalVideo.play(); + await ended; + + await withNewClone(originalVideo, async clone => { + await SpecialPowers.wrap(originalVideo).cloneElementVisually(clone); + ok(await assertVideosMatch(originalVideo, clone), + "Visual clone should display final frame."); + }); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_mediastream.html b/dom/media/test/test_cloneElementVisually_mediastream.html new file mode 100644 index 0000000000..5bfcf7673f --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_mediastream.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>MediaStream</h1> + <video id="streamTarget"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Test that we can clone a video that is playing a MediaStream. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + let stream = originalVideo.mozCaptureStream(); + let streamTarget = document.getElementById("streamTarget"); + originalVideo.setAttribute("loop", true); + let playingPromise = waitForEventOnce(originalVideo, "playing"); + await originalVideo.play(); + await playingPromise; + + streamTarget.srcObject = stream; + playingPromise = waitForEventOnce(streamTarget, "playing"); + await streamTarget.play(); + await playingPromise + + await withNewClone(originalVideo, async clone => { + await SpecialPowers.wrap(streamTarget).cloneElementVisually(clone); + + originalVideo.loop = false; + originalVideo.currentTime = originalVideo.duration - 0.1; + await waitForEventOnce(streamTarget, "ended"); + + ok(await assertVideosMatch(originalVideo, streamTarget), + "Should match Original video"); + ok(await assertVideosMatch(streamTarget, clone), + "Should match MediaStream"); + }); + + // Capturing a stream from a video "taints" it which prevents testing + // shutdown decoder behaviour. To avoid interfering with future tests, + // we replace the video. + let newVideo = originalVideo.cloneNode(); + originalVideo.parentNode.replaceChild(newVideo, originalVideo); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html b/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html new file mode 100644 index 0000000000..04d1ed484c --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Originals</h1> + <div id="originalContainer"></div> + <canvas id="canvas"></canvas> + <h1>MediaStream</h1> + <div id="streamTargetContainer"></div> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Test that a clone of a video that is playing a MediaStream properly tracks + * the selected video track. + */ +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.track.enabled", true], + ], + }); + + let originalVideo = document.createElement("video"); + originalVideo.id = "original"; + document.getElementById("originalContainer").appendChild(originalVideo); + + let streamTarget = document.createElement("video"); + document.getElementById("streamTargetContainer").appendChild(streamTarget); + + await setup(); + + let [track1] = originalVideo.mozCaptureStream().getTracks(); + let playingPromise = waitForEventOnce(originalVideo, "playing"); + await originalVideo.play(); + await playingPromise; + + let canvas = document.getElementById("canvas"); + canvas.width = originalVideo.videoWidth / 2; + canvas.height = originalVideo.videoHeight / 2; + let ctx = canvas.getContext("2d"); + let [track2] = canvas.captureStream().getTracks(); + ctx.fillStyle = "green"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + streamTarget.srcObject = new MediaStream([track1, track2]); + playingPromise = waitForEventOnce(streamTarget, "playing"); + await streamTarget.play(); + await playingPromise; + + await withNewClone(originalVideo, async clone => { + SpecialPowers.wrap(streamTarget).cloneElementVisually(clone); + + let selectedTrackIdx = streamTarget.videoTracks.selectedIndex; + streamTarget.videoTracks[++selectedTrackIdx % 2].selected = true; + await waitForEventOnce(streamTarget, "resize"); + + ok(await assertVideosMatch(streamTarget, clone), + "Should match MediaStream"); + }); + + // Capturing a stream from a video "taints" it which prevents testing + // shutdown decoder behaviour. To avoid interfering with future tests, + // we replace the video. + let newVideo = originalVideo.cloneNode(); + originalVideo.parentNode.replaceChild(newVideo, originalVideo); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_no_suspend.html b/dom/media/test/test_cloneElementVisually_no_suspend.html new file mode 100644 index 0000000000..d576bf8ab3 --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_no_suspend.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>MediaStream</h1> + <video id="streamTarget"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Tests that cloning a video prevents the decoder from being suspended + * if the original video stops being visible. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + await setVideoSrc(originalVideo, LONG_VIDEO); + + await originalVideo.play(); + + // Ensure that hiding and pausing this video will cause us to + // try suspending it. + await ensureVideoSuspendable(originalVideo); + + await withNewClone(originalVideo, async clone => { + SpecialPowers.wrap(originalVideo).cloneElementVisually(clone); + + // Go back to the beginning of the video to give us enough time to + // fail to suspend the video when it's being cloned before the + // video ends. + originalVideo.removeAttribute("loop"); + originalVideo.currentTime = 0; + await waitForEventOnce(originalVideo, "seeked"); + + let suspendTimerFired = false; + + let listener = () => { + suspendTimerFired = true; + } + originalVideo.addEventListener("mozstartvideosuspendtimer", listener); + + // Have to do this to access normally-preffed off binding methods for some + // reason. + // See bug 1544257. + SpecialPowers.wrap(originalVideo).setVisible(false); + + await waitForEventOnce(originalVideo, "ended"); + + originalVideo.removeEventListener("mozstartvideosuspendtimer", listener); + + ok(!suspendTimerFired, + "mozstartvideosuspendtimer should not have fired."); + + // Have to do this to access normally-preffed off binding methods for some + // reason. + // See bug 1544257. + SpecialPowers.wrap(originalVideo).setVisible(true); + }); + + await originalVideo.play(); + + // With the clone gone, the original video should be able to suspend now. + await ensureVideoSuspendable(originalVideo); + + await setVideoSrc(originalVideo, TEST_VIDEO_1); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_paused.html b/dom/media/test/test_cloneElementVisually_paused.html new file mode 100644 index 0000000000..1812becdd8 --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_paused.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Test that when we start cloning a paused video, the clone displays + * the first paused frame. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + await withNewClone(originalVideo, async clone => { + await SpecialPowers.wrap(originalVideo).cloneElementVisually(clone); + + ok(await assertVideosMatch(originalVideo, clone), + "Initial paused frame should match."); + }); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_poster.html b/dom/media/test/test_cloneElementVisually_poster.html new file mode 100644 index 0000000000..e7ba00edb0 --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_poster.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Test that when we start cloning a paused video, the clone displays + * the first paused frame. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + const POSTER_URL = "https://example.com:443/tests/dom/media/test/poster-test.jpg"; + originalVideo.setAttribute("poster", POSTER_URL); + + await withNewClone(originalVideo, async clone => { + SpecialPowers.wrap(originalVideo).cloneElementVisually(clone); + await originalVideo.play(); + await waitForEventOnce(originalVideo, "timeupdate"); + originalVideo.pause(); + await waitForEventOnce(originalVideo, "pause"); + + ok(await assertVideosMatch(originalVideo, clone), + "Video with a poster should clone properly."); + }); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_cloneElementVisually_resource_change.html b/dom/media/test/test_cloneElementVisually_resource_change.html new file mode 100644 index 0000000000..3a66906ea2 --- /dev/null +++ b/dom/media/test/test_cloneElementVisually_resource_change.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test cloneElementVisually</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<div id="content"> + <h1>Original</h1> + <video id="original"></video> + <h1>MediaStream</h1> + <video id="streamTarget"></video> + <h1>Clone</h1> +</div> +<div id="results"> + <h1>Results</h1> + <canvas id="left"></canvas> + <canvas id="right"></canvas> +</div> + +<script type="application/javascript"> + +/* import-globals-from cloneElementVisually_helpers.js */ + +/** + * Tests that cloning survives changes to the underlying video resource. + */ +add_task(async () => { + await setup(); + + let originalVideo = document.getElementById("original"); + originalVideo.setAttribute("loop", true); + await originalVideo.play(); + + await withNewClone(originalVideo, async clone => { + SpecialPowers.wrap(originalVideo).cloneElementVisually(clone); + + await waitForEventOnce(originalVideo, "timeupdate"); + + originalVideo.pause(); + await waitForEventOnce(originalVideo, "pause"); + + ok(await assertVideosMatch(originalVideo, clone), + "Initial video should match."); + + await setVideoSrc(originalVideo, TEST_VIDEO_2); + + await originalVideo.play(); + await waitForEventOnce(originalVideo, "timeupdate"); + + originalVideo.pause(); + await waitForEventOnce(originalVideo, "pause"); + + ok(await assertVideosMatch(originalVideo, clone), + "New video should match."); + }); + + await setVideoSrc(originalVideo, TEST_VIDEO_1); +}); + +</script> + +</body> +</html> diff --git a/dom/media/test/test_clone_media_element.html b/dom/media/test/test_clone_media_element.html new file mode 100644 index 0000000000..35e5cd69d0 --- /dev/null +++ b/dom/media/test/test_clone_media_element.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test: cloned media element should continue to play to the end even after the source of the original element is cleared</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// tests must run in sequence otherwise concurrent running test will also +// update media cache which will hide the fact media cache not updated +// after changes in media cache streams. +PARALLEL_TESTS = 1; + +function startTest(test, token) { + manager.started(token); + info("Trying to load " + token); + var v = document.createElement('video'); + v.preload = "metadata"; + v.token = token; + v.src = test.name; + + v.onloadedmetadata = function(evt) { + info(evt.target.token + " metadata loaded."); + evt.target.onloadedmetadata = null; + var clone = evt.target.cloneNode(false); + clone.token = evt.target.token; + clone.play(); + + clone.onloadstart = function(event) { + info("cloned " + event.target.token + " start loading."); + event.target.onloadstart = null; + removeNodeAndSource(v); + } + + clone.onended = function(event) { + ok(true, "cloned " + event.target.token + " ended."); + event.target.onended = null; + removeNodeAndSource(event.target); + manager.finished(event.target.token); + } + } +} + +var manager = new MediaTestManager; +manager.runTests(gSmallTests.concat(gPlayedTests), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_closing_connections.html b/dom/media/test/test_closing_connections.html new file mode 100644 index 0000000000..c5eb565447 --- /dev/null +++ b/dom/media/test/test_closing_connections.html @@ -0,0 +1,58 @@ +hg diff<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=479863 +--> +<head> + <title>Test for Bug 479863 --- loading many connections</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="use_large_cache.js"></script> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<script type="application/javascript"> +window.onload = function() { + ok(true, "loaded metadata for all videos"); + mediaTestCleanup(); + SimpleTest.finish(); +} + +/* With normal per-domain connection limits and a naive implementation, we + won't ever be able to load all these videos because the first 15 (or whatever) + will each take up one HTTP connection (which will be suspended) and then + the others will be blocked by the per-domain HTTP connection limit. We + pass this test by closing the connection for non-buffered videos after + we've got the first frame. +*/ + +var resource = getPlayableVideo(gClosingConnectionsTest); + +SimpleTest.waitForExplicitFinish(); +function beginTest() { + if (resource != null) { + for (var i=0; i<20; ++i) { + var v = document.createElement("video"); + v.src = resource.name; + v.preload = "metadata"; + document.body.appendChild(v); + } + } else { + todo(false, "No types supported"); + } +} +beginTest(); +</script> + +<pre id="test"> +<script type="application/javascript"> +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_constants.html b/dom/media/test/test_constants.html new file mode 100644 index 0000000000..1d4a8da250 --- /dev/null +++ b/dom/media/test/test_constants.html @@ -0,0 +1,228 @@ +<!DOCTYPE HTML> +<html> +<!-- + Adapted from: + http://simon.html5.org/test/html/dom/interfaces/htmlelement/htmlmediaelement/const-unsigned-short/001.htm +--> +<head> + <title>Media test: constants</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<video><source></video><audio><source></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +is(HTMLElement.NETWORK_EMPTY, undefined); +is(HTMLElement.NETWORK_IDLE, undefined); +is(HTMLElement.NETWORK_LOADING, undefined); +is(HTMLElement.NETWORK_NO_SOURCE, undefined); +is(HTMLElement.HAVE_NOTHING, undefined); +is(HTMLElement.HAVE_METADATA, undefined); +is(HTMLElement.HAVE_CURRENT_DATA, undefined); +is(HTMLElement.HAVE_FUTURE_DATA, undefined); +is(HTMLElement.HAVE_ENOUGH_DATA, undefined); +is(HTMLElement.MEDIA_ERR_ABORTED, undefined); +is(HTMLElement.MEDIA_ERR_NETWORK, undefined); +is(HTMLElement.MEDIA_ERR_DECODE, undefined); +is(HTMLElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLMediaElement.NETWORK_EMPTY, 0); +is(HTMLMediaElement.NETWORK_IDLE, 1); +is(HTMLMediaElement.NETWORK_LOADING, 2); +is(HTMLMediaElement.NETWORK_NO_SOURCE, 3); +is(HTMLMediaElement.HAVE_NOTHING, 0); +is(HTMLMediaElement.HAVE_METADATA, 1); +is(HTMLMediaElement.HAVE_CURRENT_DATA, 2); +is(HTMLMediaElement.HAVE_FUTURE_DATA, 3); +is(HTMLMediaElement.HAVE_ENOUGH_DATA, 4); +is(HTMLMediaElement.MEDIA_ERR_ABORTED, undefined); +is(HTMLMediaElement.MEDIA_ERR_NETWORK, undefined); +is(HTMLMediaElement.MEDIA_ERR_DECODE, undefined); +is(HTMLMediaElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLVideoElement.NETWORK_EMPTY, 0); +is(HTMLVideoElement.NETWORK_IDLE, 1); +is(HTMLVideoElement.NETWORK_LOADING, 2); +is(HTMLVideoElement.NETWORK_NO_SOURCE, 3); +is(HTMLVideoElement.HAVE_NOTHING, 0); +is(HTMLVideoElement.HAVE_METADATA, 1); +is(HTMLVideoElement.HAVE_CURRENT_DATA, 2); +is(HTMLVideoElement.HAVE_FUTURE_DATA, 3); +is(HTMLVideoElement.HAVE_ENOUGH_DATA, 4); +is(HTMLVideoElement.MEDIA_ERR_ABORTED, undefined); +is(HTMLVideoElement.MEDIA_ERR_NETWORK, undefined); +is(HTMLVideoElement.MEDIA_ERR_DECODE, undefined); +is(HTMLVideoElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLAudioElement.NETWORK_EMPTY, 0); +is(HTMLAudioElement.NETWORK_IDLE, 1); +is(HTMLAudioElement.NETWORK_LOADING, 2); +is(HTMLAudioElement.NETWORK_NO_SOURCE, 3); +is(HTMLAudioElement.HAVE_NOTHING, 0); +is(HTMLAudioElement.HAVE_METADATA, 1); +is(HTMLAudioElement.HAVE_CURRENT_DATA, 2); +is(HTMLAudioElement.HAVE_FUTURE_DATA, 3); +is(HTMLAudioElement.HAVE_ENOUGH_DATA, 4); +is(HTMLAudioElement.MEDIA_ERR_ABORTED, undefined); +is(HTMLAudioElement.MEDIA_ERR_NETWORK, undefined); +is(HTMLAudioElement.MEDIA_ERR_DECODE, undefined); +is(HTMLAudioElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLSourceElement.NETWORK_EMPTY, undefined); +is(HTMLSourceElement.NETWORK_IDLE, undefined); +is(HTMLSourceElement.NETWORK_LOADING, undefined); +is(HTMLSourceElement.NETWORK_NO_SOURCE, undefined); +is(HTMLSourceElement.HAVE_NOTHING, undefined); +is(HTMLSourceElement.HAVE_METADATA, undefined); +is(HTMLSourceElement.HAVE_CURRENT_DATA, undefined); +is(HTMLSourceElement.HAVE_FUTURE_DATA, undefined); +is(HTMLSourceElement.HAVE_ENOUGH_DATA, undefined); +is(HTMLSourceElement.MEDIA_ERR_ABORTED, undefined); +is(HTMLSourceElement.MEDIA_ERR_NETWORK, undefined); +is(HTMLSourceElement.MEDIA_ERR_DECODE, undefined); +is(HTMLSourceElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(MediaError.NETWORK_EMPTY, undefined); +is(MediaError.NETWORK_IDLE, undefined); +is(MediaError.NETWORK_LOADING, undefined); +is(MediaError.NETWORK_NO_SOURCE, undefined); +is(MediaError.HAVE_NOTHING, undefined); +is(MediaError.HAVE_METADATA, undefined); +is(MediaError.HAVE_CURRENT_DATA, undefined); +is(MediaError.HAVE_FUTURE_DATA, undefined); +is(MediaError.HAVE_ENOUGH_DATA, undefined); +is(MediaError.MEDIA_ERR_ABORTED, 1); +is(MediaError.MEDIA_ERR_NETWORK, 2); +is(MediaError.MEDIA_ERR_DECODE, 3); +is(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 4); +is(document.body.NETWORK_EMPTY, undefined); +is(document.body.NETWORK_IDLE, undefined); +is(document.body.NETWORK_LOADING, undefined); +is(document.body.NETWORK_NO_SOURCE, undefined); +is(document.body.HAVE_NOTHING, undefined); +is(document.body.HAVE_METADATA, undefined); +is(document.body.HAVE_CURRENT_DATA, undefined); +is(document.body.HAVE_FUTURE_DATA, undefined); +is(document.body.HAVE_ENOUGH_DATA, undefined); +is(document.body.MEDIA_ERR_ABORTED, undefined); +is(document.body.MEDIA_ERR_NETWORK, undefined); +is(document.body.MEDIA_ERR_DECODE, undefined); +is(document.body.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(document.getElementsByTagName("video")[0].NETWORK_EMPTY, 0); +is(document.getElementsByTagName("video")[0].NETWORK_IDLE, 1); +is(document.getElementsByTagName("video")[0].NETWORK_LOADING, 2); +is(document.getElementsByTagName("video")[0].NETWORK_NO_SOURCE, 3); +is(document.getElementsByTagName("video")[0].HAVE_NOTHING, 0); +is(document.getElementsByTagName("video")[0].HAVE_METADATA, 1); +is(document.getElementsByTagName("video")[0].HAVE_CURRENT_DATA, 2); +is(document.getElementsByTagName("video")[0].HAVE_FUTURE_DATA, 3); +is(document.getElementsByTagName("video")[0].HAVE_ENOUGH_DATA, 4); +is(document.getElementsByTagName("video")[0].MEDIA_ERR_ABORTED, undefined); +is(document.getElementsByTagName("video")[0].MEDIA_ERR_NETWORK, undefined); +is(document.getElementsByTagName("video")[0].MEDIA_ERR_DECODE, undefined); +is(document.getElementsByTagName("video")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(document.getElementsByTagName("audio")[0].NETWORK_EMPTY, 0); +is(document.getElementsByTagName("audio")[0].NETWORK_IDLE, 1); +is(document.getElementsByTagName("audio")[0].NETWORK_LOADING, 2); +is(document.getElementsByTagName("audio")[0].NETWORK_NO_SOURCE, 3); +is(document.getElementsByTagName("audio")[0].HAVE_NOTHING, 0); +is(document.getElementsByTagName("audio")[0].HAVE_METADATA, 1); +is(document.getElementsByTagName("audio")[0].HAVE_CURRENT_DATA, 2); +is(document.getElementsByTagName("audio")[0].HAVE_FUTURE_DATA, 3); +is(document.getElementsByTagName("audio")[0].HAVE_ENOUGH_DATA, 4); +is(document.getElementsByTagName("audio")[0].MEDIA_ERR_ABORTED, undefined); +is(document.getElementsByTagName("audio")[0].MEDIA_ERR_NETWORK, undefined); +is(document.getElementsByTagName("audio")[0].MEDIA_ERR_DECODE, undefined); +is(document.getElementsByTagName("audio")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(document.getElementsByTagName("source")[0].NETWORK_EMPTY, undefined); +is(document.getElementsByTagName("source")[0].NETWORK_IDLE, undefined); +is(document.getElementsByTagName("source")[0].NETWORK_LOADING, undefined); +is(document.getElementsByTagName("source")[0].NETWORK_NO_SOURCE, undefined); +is(document.getElementsByTagName("source")[0].HAVE_NOTHING, undefined); +is(document.getElementsByTagName("source")[0].HAVE_METADATA, undefined); +is(document.getElementsByTagName("source")[0].HAVE_CURRENT_DATA, undefined); +is(document.getElementsByTagName("source")[0].HAVE_FUTURE_DATA, undefined); +is(document.getElementsByTagName("source")[0].HAVE_ENOUGH_DATA, undefined); +is(document.getElementsByTagName("source")[0].MEDIA_ERR_ABORTED, undefined); +is(document.getElementsByTagName("source")[0].MEDIA_ERR_NETWORK, undefined); +is(document.getElementsByTagName("source")[0].MEDIA_ERR_DECODE, undefined); +is(document.getElementsByTagName("source")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLElement.prototype.NETWORK_EMPTY, undefined); +is(HTMLElement.prototype.NETWORK_IDLE, undefined); +is(HTMLElement.prototype.NETWORK_LOADING, undefined); +is(HTMLElement.prototype.NETWORK_NO_SOURCE, undefined); +is(HTMLElement.prototype.HAVE_NOTHING, undefined); +is(HTMLElement.prototype.HAVE_METADATA, undefined); +is(HTMLElement.prototype.HAVE_CURRENT_DATA, undefined); +is(HTMLElement.prototype.HAVE_FUTURE_DATA, undefined); +is(HTMLElement.prototype.HAVE_ENOUGH_DATA, undefined); +is(HTMLElement.prototype.MEDIA_ERR_ABORTED, undefined); +is(HTMLElement.prototype.MEDIA_ERR_NETWORK, undefined); +is(HTMLElement.prototype.MEDIA_ERR_DECODE, undefined); +is(HTMLElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLMediaElement.prototype.NETWORK_EMPTY, 0, "HTMLMediaElement.prototype.NETWORK_EMPTY"); +is(HTMLMediaElement.prototype.NETWORK_IDLE, 1, "HTMLMediaElement.prototype.NETWORK_IDLE"); +is(HTMLMediaElement.prototype.NETWORK_LOADING, 2, "HTMLMediaElement.prototype.NETWORK_LOADING"); +is(HTMLMediaElement.prototype.NETWORK_NO_SOURCE, 3, "HTMLMediaElement.prototype.NETWORK_NO_SOURCE"); +is(HTMLMediaElement.prototype.HAVE_NOTHING, 0, "HTMLMediaElement.prototype.HAVE_NOTHING"); +is(HTMLMediaElement.prototype.HAVE_METADATA, 1, "HTMLMediaElement.prototype.HAVE_METADATA"); +is(HTMLMediaElement.prototype.HAVE_CURRENT_DATA, 2, "HTMLMediaElement.prototype.HAVE_CURRENT_DATA"); +is(HTMLMediaElement.prototype.HAVE_FUTURE_DATA, 3, "HTMLMediaElement.prototype.HAVE_FUTURE_DATA"); +is(HTMLMediaElement.prototype.HAVE_ENOUGH_DATA, 4, "HTMLMediaElement.prototype.HAVE_ENOUGH_DATA"); +is(HTMLMediaElement.prototype.MEDIA_ERR_ABORTED, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_ABORTED"); +is(HTMLMediaElement.prototype.MEDIA_ERR_NETWORK, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_NETWORK"); +is(HTMLMediaElement.prototype.MEDIA_ERR_DECODE, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_DECODE"); +is(HTMLMediaElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED"); +is(HTMLVideoElement.prototype.NETWORK_EMPTY, 0); +is(HTMLVideoElement.prototype.NETWORK_IDLE, 1); +is(HTMLVideoElement.prototype.NETWORK_LOADING, 2); +is(HTMLVideoElement.prototype.NETWORK_NO_SOURCE, 3); +is(HTMLVideoElement.prototype.HAVE_NOTHING, 0); +is(HTMLVideoElement.prototype.HAVE_METADATA, 1); +is(HTMLVideoElement.prototype.HAVE_CURRENT_DATA, 2); +is(HTMLVideoElement.prototype.HAVE_FUTURE_DATA, 3); +is(HTMLVideoElement.prototype.HAVE_ENOUGH_DATA, 4); +is(HTMLVideoElement.prototype.MEDIA_ERR_ABORTED, undefined); +is(HTMLVideoElement.prototype.MEDIA_ERR_NETWORK, undefined); +is(HTMLVideoElement.prototype.MEDIA_ERR_DECODE, undefined); +is(HTMLVideoElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLAudioElement.prototype.NETWORK_EMPTY, 0); +is(HTMLAudioElement.prototype.NETWORK_IDLE, 1); +is(HTMLAudioElement.prototype.NETWORK_LOADING, 2); +is(HTMLAudioElement.prototype.NETWORK_NO_SOURCE, 3); +is(HTMLAudioElement.prototype.HAVE_NOTHING, 0); +is(HTMLAudioElement.prototype.HAVE_METADATA, 1); +is(HTMLAudioElement.prototype.HAVE_CURRENT_DATA, 2); +is(HTMLAudioElement.prototype.HAVE_FUTURE_DATA, 3); +is(HTMLAudioElement.prototype.HAVE_ENOUGH_DATA, 4); +is(HTMLAudioElement.prototype.MEDIA_ERR_ABORTED, undefined); +is(HTMLAudioElement.prototype.MEDIA_ERR_NETWORK, undefined); +is(HTMLAudioElement.prototype.MEDIA_ERR_DECODE, undefined); +is(HTMLAudioElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(HTMLSourceElement.prototype.NETWORK_EMPTY, undefined); +is(HTMLSourceElement.prototype.NETWORK_IDLE, undefined); +is(HTMLSourceElement.prototype.NETWORK_LOADING, undefined); +is(HTMLSourceElement.prototype.NETWORK_NO_SOURCE, undefined); +is(HTMLSourceElement.prototype.HAVE_NOTHING, undefined); +is(HTMLSourceElement.prototype.HAVE_METADATA, undefined); +is(HTMLSourceElement.prototype.HAVE_CURRENT_DATA, undefined); +is(HTMLSourceElement.prototype.HAVE_FUTURE_DATA, undefined); +is(HTMLSourceElement.prototype.HAVE_ENOUGH_DATA, undefined); +is(HTMLSourceElement.prototype.MEDIA_ERR_ABORTED, undefined); +is(HTMLSourceElement.prototype.MEDIA_ERR_NETWORK, undefined); +is(HTMLSourceElement.prototype.MEDIA_ERR_DECODE, undefined); +is(HTMLSourceElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined); +is(MediaError.prototype.NETWORK_EMPTY, undefined); +is(MediaError.prototype.NETWORK_IDLE, undefined); +is(MediaError.prototype.NETWORK_LOADING, undefined); +is(MediaError.prototype.NETWORK_NO_SOURCE, undefined); +is(MediaError.prototype.HAVE_NOTHING, undefined); +is(MediaError.prototype.HAVE_METADATA, undefined); +is(MediaError.prototype.HAVE_CURRENT_DATA, undefined); +is(MediaError.prototype.HAVE_FUTURE_DATA, undefined); +is(MediaError.prototype.HAVE_ENOUGH_DATA, undefined); +is(MediaError.prototype.MEDIA_ERR_ABORTED, 1); +is(MediaError.prototype.MEDIA_ERR_NETWORK, 2); +is(MediaError.prototype.MEDIA_ERR_DECODE, 3); +is(MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, 4); +ok(document.getElementsByTagName("video")[0].buffered instanceof TimeRanges, "video.buffered must be TimeRanges object"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_controls.html b/dom/media/test/test_controls.html new file mode 100644 index 0000000000..5c9015fdf2 --- /dev/null +++ b/dom/media/test/test_controls.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: controls</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<video id='v1'></video><audio id='a1'></audio> +<video id='v2' controls></video><audio id='a2' controls></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var v2 = document.getElementById('v2'); +var a2 = document.getElementById('a2'); +ok(!v1.controls, "v1.controls should be false by default"); +ok(!a1.controls, "v1.controls should be false by default"); +ok(v2.controls, "v2.controls should be true"); +ok(a2.controls, "v2.controls should be true"); +v2.controls=false; +a2.controls=false; +ok(!v2.controls, "v2.controls should be false"); +ok(!a2.controls, "a2.controls should be false"); +v2.controls=true; +a2.controls=true; +ok(v2.controls, "v2.controls should be true"); +ok(a2.controls, "a2.controls should be true"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_cueless_webm_seek-1.html b/dom/media/test/test_cueless_webm_seek-1.html new file mode 100644 index 0000000000..db58a89665 --- /dev/null +++ b/dom/media/test/test_cueless_webm_seek-1.html @@ -0,0 +1,136 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=657791 +--> +<head> + <title>Test for Bug 657791</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Subset of seek tests for cueless WebMs. When random seeking (rather than just +// in buffered ranges) is implemented for WebM, these tests can be removed and +// the cueless WebM(s) references can be moved to the general test_seek test +// array. +// Test array is defined in manifest.js + +var manager = new MediaTestManager; + +// Exercise functionality as in test_seek-1 +function testWebM1(e) { + var v = e.target; + v.removeEventListener('loadeddata', testWebM1); + + var startPassed = false; + var endPassed = false; + var seekFlagStart = false; + var seekFlagEnd = false; + var readonly = true; + var completed = false; + + ok(v.buffered.length >= 1, "Should have a buffered range"); + var halfBuffered = v.buffered.end(0) / 2; + + function start() { + is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start"); + is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end"); + ok(!completed, "Should not be completed yet"); + ok(!v.seeking, "seeking should default to false"); + try { + v.seeking = true; + readonly = v.seeking === false; + } + catch(ex) { + readonly = "threw exception: " + ex; + } + is(readonly, true, "seeking should be readonly"); + + v.currentTime = halfBuffered; + seekFlagStart = v.seeking; + } + + function seekStarted() { + ok(!completed, "should not be completed yet"); + startPassed = true; + } + + function seekEnded() { + ok(!completed, "should not be completed yet"); + ok(Math.abs(v.currentTime - halfBuffered) < 0.1, + "Video currentTime should be around " + halfBuffered + ": " + v.currentTime + " (seeked)"); + endPassed = true; + seekFlagEnd = v.seeking; + v.play(); + } + + function playbackEnded() { + ok(!completed, "should not be completed yet"); + + completed = true; + ok(startPassed, "seeking event"); + ok(endPassed, "seeked event"); + ok(seekFlagStart, "seeking flag on start should be true"); + ok(!seekFlagEnd, "seeking flag on end should be false"); + removeNodeAndSource(v); + manager.finished(v._token); + } + + once(v, "ended", playbackEnded); + once(v, "seeking", seekStarted); + once(v, "seeked", seekEnded); + + start(); +} + +// Fetch the media resource using XHR so we can be sure the entire +// resource is loaded before we test buffered ranges. This ensures +// we have deterministic behaviour. +function fetch(url, fetched_callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "blob"; + + var loaded = function (event) { + if (xhr.status == 200 || xhr.status == 206) { + // Request fulfilled. Note sometimes we get 206... Presumably because either + // httpd.js or Necko cached the result. + fetched_callback(window.URL.createObjectURL(xhr.response)); + } else { + ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders()); + } + }; + + xhr.addEventListener("load", loaded); + xhr.send(); +} + +function startTest(test, token) { + var onfetched = function(uri) { + var v = document.createElement('video'); + v._token = token; + v.src = uri; + v.addEventListener("loadeddata", testWebM1); + document.body.appendChild(v); + } + manager.started(token); + fetch(test.name, onfetched); +} + +SimpleTest.waitForExplicitFinish(); +manager.runTests(gCuelessWebMTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_cueless_webm_seek-2.html b/dom/media/test/test_cueless_webm_seek-2.html new file mode 100644 index 0000000000..720cc18399 --- /dev/null +++ b/dom/media/test/test_cueless_webm_seek-2.html @@ -0,0 +1,126 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=657791 +--> +<head> + <title>Test for Bug 657791</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Subset of seek tests for cueless WebMs. When random seeking (rather than just +// in buffered ranges) is implemented for WebM, these tests can be removed and +// the cueless WebM(s) references can be moved to the general test_seek test +// array. +// Test array is defined in manifest.js + +var manager = new MediaTestManager; + +// Exercise functionality as in test_seek-2 +function testWebM2(e) { + var v = e.target; + v.removeEventListener('loadeddata', testWebM2); + + var startPassed = false; + var endPassed = false; + var completed = false; + + ok(v.buffered.length >= 1, "Should have a buffered range"); + var halfBuffered = v.buffered.end(0) / 2; + + function start() { + if (completed) + return; + + is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start"); + is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end"); + v.currentTime=halfBuffered; + v.play(); + } + + function seekStarted() { + if (completed) + return; + + startPassed = true; + } + + function seekEnded() { + if (completed) + return; + + endPassed = true; + } + + function playbackEnded() { + if (completed) + return; + + completed = true; + ok(startPassed, "send seeking event"); + ok(endPassed, "send seeked event"); + ok(v.ended, "Checking playback has ended"); + ok(Math.abs(v.currentTime - v.duration) <= 0.1, "Checking currentTime at end: " + v.currentTime); + removeNodeAndSource(v); + manager.finished(v._token); + } + + v.addEventListener("ended", playbackEnded); + v.addEventListener("seeking", seekStarted); + v.addEventListener("seeked", seekEnded); + + start(); +} + +// Fetch the media resource using XHR so we can be sure the entire +// resource is loaded before we test buffered ranges. This ensures +// we have deterministic behaviour. +function fetch(url, fetched_callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "blob"; + + var loaded = function (event) { + if (xhr.status == 200 || xhr.status == 206) { + // Request fulfilled. Note sometimes we get 206... Presumably because either + // httpd.js or Necko cached the result. + fetched_callback(window.URL.createObjectURL(xhr.response)); + } else { + ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders()); + } + }; + + xhr.addEventListener("load", loaded); + xhr.send(); +} + +function startTest(test, token) { + var onfetched = function(uri) { + var v = document.createElement('video'); + v._token = token; + v.src = uri; + v.addEventListener("loadeddata", testWebM2); + document.body.appendChild(v); + } + manager.started(token); + fetch(test.name, onfetched); +} + +SimpleTest.waitForExplicitFinish(); +manager.runTests(gCuelessWebMTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_cueless_webm_seek-3.html b/dom/media/test/test_cueless_webm_seek-3.html new file mode 100644 index 0000000000..d6e3e50d7d --- /dev/null +++ b/dom/media/test/test_cueless_webm_seek-3.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=657791 +--> +<head> + <title>Test for Bug 657791</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Subset of seek tests for cueless WebMs. When random seeking (rather than just +// in buffered ranges) is implemented for WebM, these tests can be removed and +// the cueless WebM(s) references can be moved to the general test_seek test +// array. +// Test array is defined in manifest.js + +var manager = new MediaTestManager; + +// Exercise functionality as in test_seek-3 +function testWebM3(e) { + var v = e.target; + v.removeEventListener('loadeddata', testWebM3); + + var completed = false; + var gotTimeupdate = false; + + ok(v.buffered.length >= 1, "Should have a buffered range"); + var halfBuffered = v.buffered.end(0) / 2; + + function start() { + if (completed) + return; + + is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start"); + is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end"); + v.currentTime=halfBuffered; + } + + function timeupdate() { + gotTimeupdate = true; + v.removeEventListener("timeupdate", timeupdate); + } + + function seekStarted() { + if (completed) + return; + + v.addEventListener("timeupdate", timeupdate); + } + + function seekEnded() { + if (completed) + return; + + var t = v.currentTime; + ok(Math.abs(t - halfBuffered) <= 0.1, "Video currentTime should be around " + halfBuffered + ": " + t); + ok(gotTimeupdate, "Should have got timeupdate between seeking and seekended"); + completed = true; + removeNodeAndSource(v); + manager.finished(v._token); + } + + v.addEventListener("seeking", seekStarted); + v.addEventListener("seeked", seekEnded); + + start() +} + +// Fetch the media resource using XHR so we can be sure the entire +// resource is loaded before we test buffered ranges. This ensures +// we have deterministic behaviour. +function fetch(url, fetched_callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "blob"; + + var loaded = function (event) { + if (xhr.status == 200 || xhr.status == 206) { + // Request fulfilled. Note sometimes we get 206... Presumably because either + // httpd.js or Necko cached the result. + fetched_callback(window.URL.createObjectURL(xhr.response)); + } else { + ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders()); + } + }; + + xhr.addEventListener("load", loaded); + xhr.send(); +} + +function startTest(test, token) { + var onfetched = function(uri) { + var v = document.createElement('video'); + v._token = token; + v.src = uri; + v.addEventListener("loadeddata", testWebM3); + document.body.appendChild(v); + } + manager.started(token); + fetch(test.name, onfetched); +} + +SimpleTest.waitForExplicitFinish(); +manager.runTests(gCuelessWebMTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_currentTime.html b/dom/media/test/test_currentTime.html new file mode 100644 index 0000000000..b38c8c2c53 --- /dev/null +++ b/dom/media/test/test_currentTime.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: currentTime</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<video id='v1'></video><audio id='a1'></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +is(v1.currentTime, 0.0); +is(a1.currentTime, 0.0); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_decode_error.html b/dom/media/test/test_decode_error.html new file mode 100644 index 0000000000..d6d71102f1 --- /dev/null +++ b/dom/media/test/test_decode_error.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: unknown/invalid formats raise decode error</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) { + var ok = function (condition, name) { + SimpleTest.ok(condition, test.name + ": " + name); + } + var is = function (a, b, name) { + SimpleTest.is(a, b, test.name + ": " + name); + } + + var v = document.createElement("video"); + manager.started(token); + v.addEventListener("error", function (event) { + var el = event.currentTarget; + is(event.type, "error", "Expected event of type 'error'"); + ok(el.error, "Element 'error' attr expected to have a value"); + ok(el.error instanceof MediaError, "Element 'error' attr expected to be MediaError"); + if (v.readyState == v.HAVE_NOTHING) { + is(el.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected media not supported error"); + } else { + is(el.error.code, MediaError.MEDIA_ERR_DECODE, "Expected a decode error"); + } + ok(typeof el.error.message === 'string' || el.error.message instanceof String, "Element 'message' attr expected to be a string"); + ok(el.error.message.length > 0, "Element 'message' attr has content"); + el._sawError = true; + manager.finished(token); + }); + + v.addEventListener("loadeddata", function () { + ok(false, "Unexpected loadeddata event"); + manager.finished(token); + }); + + v.autoplay = true; + v.addEventListener("ended", function () { + ok(false, "Unexpected ended event"); + manager.finished(token); + }); + + v.src = test.name; // implicitly starts a load. +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [ + ["media.cache_size", 40000], + ] +}, beginTest); +function beginTest() { + manager.runTests(gDecodeErrorTests, startTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_decode_error_crossorigin.html b/dom/media/test/test_decode_error_crossorigin.html new file mode 100644 index 0000000000..24c1430a5b --- /dev/null +++ b/dom/media/test/test_decode_error_crossorigin.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Invalid formats raise decode errors with default messages for CORS cross-origin media</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +const manager = new MediaTestManager; +let gotErrSrcNotSupported = false; +let gotErrDecode = false; + +function startTest(test, token) { + const is = function(a, b, name) { + SimpleTest.is(a, b, `${test.name}: ${name}`); + }; + const v = document.createElement("video"); + manager.started(token); + v.addEventListener("error", event => { + if (v.readyState == v.HAVE_NOTHING) { + is(v.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, + "Expected code for a load error"); + is(v.error.message, "Failed to open media", + "Expected message for a load error"); + gotErrSrcNotSupported = true; + } else { + is(v.error.code, MediaError.MEDIA_ERR_DECODE, + "Expected code for a decode error"); + is(v.error.message, "Failed to decode media", + "Expected message for a decode error"); + gotErrDecode = true; + } + manager.finished(token); + }); + + v.autoplay = true; + + // CORS-cross-origin URL. + v.src = `http://example.com/tests/dom/media/test/${test.name}`; +} + +gTestPrefs.push(["media.cache_size", 40000]); +manager.onFinished = () => { + ok(gotErrSrcNotSupported, "At least one test led to src-not-supported"); + ok(gotErrDecode, "At least one test led to a decode error"); +}; +manager.runTests(gErrorTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_decoder_disable.html b/dom/media/test/test_decoder_disable.html new file mode 100644 index 0000000000..dd0d2cc51b --- /dev/null +++ b/dom/media/test/test_decoder_disable.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=448600 +--> +<head> + <title>Test for Bug 448600</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448600">Mozilla Bug 448600</a> +<p id="display"></p> + + +<pre id="test"> +<script type="application/javascript"> + +function filename(uri) { + return uri.substr(uri.lastIndexOf("/")+1); +} + +function e(id) { + return document.getElementById(id); +} + +var gLoadError = {}; + +gLoadError.video1 = 0; +gLoadError.video2 = 0; +gLoadError.video3 = 0; + +var gErrorCount = 0; + +SimpleTest.waitForExplicitFinish(); + +function finishTest() { + is(e('video1').currentSrc, + "", + 'video1 currentSrc should be empty when there\'s no playable typed source children'); + is(filename(e('video2').currentSrc), + filename(e('video2').src), + 'video2 currentSrc should match src'); + is(filename(e('video3').currentSrc), + filename(e('video3').src), + 'video3 currentSrc should match src'); + + is(gLoadError.video1, 2, "Expect one error per invalid source child on video1"); + is(gLoadError.video2, 1, "Expect one error on video2"); + is(gLoadError.video3, 1, "Expect one error on video3"); + + SimpleTest.finish(); +} + +function videoError(event, id) { + gLoadError[id]++; + gErrorCount++; + if (gErrorCount >= 4) { + finishTest(); + } +} + +</script> +<!-- We make the resource URIs unique to ensure that they are (re)loaded with the new disable-decoder prefs. --> +<div id="content"> +</div> +<script> +function makeVideos() { + document.getElementById('content').innerHTML = '<video id="video1" preload="metadata"><source type="video/ogg" src="320x240.ogv?decoder_disabled=1" onerror="videoError(event, \'video1\');"/><source type="audio/wave" src="r11025_u8_c1.wav?decoder_disabled=1" id=\'s2\' onerror="videoError(event, \'video1\');"/></video><video id="video2" preload="metadata" src="320x240.ogv?decoder_disabled=2" onerror="videoError(event, \'video2\');"></video><video id="video3" preload="metadata" src="r11025_u8_c1.wav?decoder_disabled=2" onerror="videoError(event, \'video3\');"></video>'; +} + +SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false], ["media.wave.enabled", false]]}, makeVideos); +</script> + +</pre> + +</body> +</html> diff --git a/dom/media/test/test_defaultMuted.html b/dom/media/test/test_defaultMuted.html new file mode 100644 index 0000000000..77bfa3d29a --- /dev/null +++ b/dom/media/test/test_defaultMuted.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: defaultMuted</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="../../../dom/html/test/reflect.js"></script> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=706731">Mozilla Bug 706731</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <video id='v1'></video><audio id='a1'></audio> + <video id='v2' muted></video><audio id='a2' muted></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> + reflectBoolean({ + element: document.createElement("video"), + attribute: { content: "muted", idl: "defaultMuted" }, + }); + + reflectBoolean({ + element: document.createElement("audio"), + attribute: { content: "muted", idl: "defaultMuted" }, + }); + + var v1 = document.getElementById('v1'); + var a1 = document.getElementById('a1'); + var v2 = document.getElementById('v2'); + var a2 = document.getElementById('a2'); + + // Check that muted state correspond to the default value. + is(v1.muted, false, "v1.muted should be false by default"); + is(a1.muted, false, "a1.muted should be false by default"); + is(v2.muted, true, "v2.muted should be true by default"); + is(a2.muted, true, "a2.muted should be true by default"); + + // Changing defaultMuted value should not change current muted state. + v1.defaultMuted = true; + a1.defaultMuted = true; + v2.defaultMuted = false; + a2.defaultMuted = false; + + is(v1.muted, false, "v1.muted should not have changed"); + is(a1.muted, false, "a1.muted should not have changed"); + is(v2.muted, true, "v2.muted should not have changed"); + is(a2.muted, true, "a2.muted should not have changed"); + + mediaTestCleanup(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_delay_load.html b/dom/media/test/test_delay_load.html new file mode 100644 index 0000000000..d10812a7c1 --- /dev/null +++ b/dom/media/test/test_delay_load.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=479711 +--> +<head> + <title>Test for Bug 479711</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> + <script> + + var gRegisteredElements = []; + var testWindows = []; + + function register(v) { + gRegisteredElements.push(v); + } + + function loaded() { + info("onload fired!"); + + for (var i = 0; i < gRegisteredElements.length; ++i) { + var v = gRegisteredElements[i]; + ok(v.readyState >= v.HAVE_CURRENT_DATA, + v._name + ":" + v.id + " is not ready before onload fired (" + v.readyState + ")"); + } + + for (i=0; i<testWindows.length; ++i) { + testWindows[i].close(); + } + + mediaTestCleanup(); + + SimpleTest.finish(); + } + + addLoadEvent(loaded); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479711">Mozilla Bug 479711</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 479711 **/ + +function createVideo(name, type, id) { + var v = document.createElement("video"); + v.preload = "metadata"; + // Make sure each video is a unique resource + v.src = name + "?" + id; + v._name = name; + v.id = id; + register(v); + return v; +} + +var test = getPlayableVideo(gSmallTests); + +// Straightforward add, causing a load. +var v = createVideo(test.name, test.type, "1"); +document.body.appendChild(v); + +// Load, add, then remove. +v = createVideo(test.name, test.type, "1"); +v.load(); +document.body.appendChild(v); +v.remove(); + +// Load and add. +v = createVideo(test.name, test.type, "2"); +v.load(); +document.body.appendChild(v); + +// Load outside of doc. +v = createVideo(test.name, test.type, "3"); +v.load(); + +// Open a new window for the following test. We open it here instead of in +// the event handler to ensure that our document load event doesn't fire while +// window.open is spinning the event loop. +var w = window.open("", "testWindow", "width=400,height=400"); +testWindows.push(w); + +v = createVideo(test.name, test.type, "4"); +v.onloadstart = function(e) { + // Using a new window to do this is a bit annoying, but if we use an iframe here, + // delaying of the iframe's load event might interfere with the firing of our load event + // in some confusing way. So it's simpler just to use another window. + w.document.body.appendChild(v); +}; +v.load(); // load started while in this document, this doc's load will block until + // the video's finished loading (in the other document). + +if (gRegisteredElements.length > 0) { + SimpleTest.waitForExplicitFinish(); +} else { + todo(false, "No types supported"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_duration_after_error.html b/dom/media/test/test_duration_after_error.html new file mode 100644 index 0000000000..ad6bbe414f --- /dev/null +++ b/dom/media/test/test_duration_after_error.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of media files that should have errors</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function checkDuration(name, e, test) { + if (test.duration) { + ok(Math.abs(e.duration - test.duration) < 0.1, + name + " duration (" + e.duration + ") should be around " + test.duration); + } else { + ok(false, "Test doesn't include the duration field!") + } +} + +function startTest(test, token) { + manager.started(token); + + let v = document.createElement('video'); + v._loadedMetadata = false; + let name = test.name; + + v.onloadedmetadata = function() { + v.onloadedmetadata = null; + v._loadedMetadata = true; + ok(v._loadedMetadata , name + " has loaded metadata."); + } + + v.onerror = function() { + v.onerror = null; + ok(v._loadedMetadata , name + " should load metadata before getting error."); + checkDuration(name, v, test); + manager.finished(token); + } + + v.src = name; + document.body.appendChild(v); + v.play(); +} + +manager.runTests(gDurationTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_autoplay.html b/dom/media/test/test_eme_autoplay.html new file mode 100644 index 0000000000..010995c095 --- /dev/null +++ b/dom/media/test/test_eme_autoplay.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="https://example.com:443/tests/dom/media/test/eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +var EMEmanifest = [ + { + name:"bipbop 10s", + tracks: [ + { + name:"video", + type:"video/mp4; codecs=\"avc1.4d4015\"", + fragments:[ "bipbop-cenc-video-10s.mp4", + ] + } + ], + keys: { + "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311", + }, + sessionType:"temporary", + sessionCount:1, + duration:10.01 + }, +]; + +function startTest(test, token) +{ + manager.started(token); + + let v = document.createElement("video"); + v.controls = true; + v.autoplay = true; + + document.body.appendChild(v); + + var eventCounts = { play: 0, playing: 0}; + function ForbiddenEvents(e) { + var video = e.target; + ok(video.readyState >= video.HAVE_FUTURE_DATA, "Must not have received event too early"); + is(eventCounts[e.type], 0, "event should have only be fired once"); + eventCounts[e.type]++; + } + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause", "durationchange", "seeking", "seeked"]; + function logEvent(e) { + info("got " + e.type + " event"); + } + events.forEach(function(e) { + v.addEventListener(e, logEvent); + }); + v.addEventListener("play", ForbiddenEvents); + v.addEventListener("playing", ForbiddenEvents); + + var gotWaitingForKey = 0; + + let waitForKey = new EMEPromise; + v.addEventListener("waitingforkey", function() { + gotWaitingForKey += 1; + waitForKey.resolve(); + }); + + v.addEventListener("loadedmetadata", function() { + ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v), + TimeStamp(token) + " isEncrypted should be true"); + is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content"); + }); + + let finish = new EMEPromise; + v.addEventListener("playing", function() { + ok(true, TimeStamp(token) + " got playing event"); + // We expect only one waitingForKey as we delay until all sessions are ready. + // I.e. one waitingForKey should be fired, after which, we process all sessions, + // so it should not be possible to be blocked by a key after that point. + ok(gotWaitingForKey == 1, "Expected number 1 wait, got: " + gotWaitingForKey); + + finish.resolve(); + }); + + Promise.all([ + LoadInitData(v, test, token), + CreateAndSetMediaKeys(v, test, token), + LoadTest(test, v, token, false /* do not call endOfStream */), + waitForKey.promise]) + .then(values => { + let initData = values[0]; + return ProcessInitData(v, test, token, initData); + }) + .then(sessions => { + Log(token, "Updated all sessions, loading complete"); + finish.promise.then(() => CloseSessions(v, sessions)); + return finish.promise; + }) + .catch(reason => ok(false, reason)) + .then(() => manager.finished(token)); +} + +function beginTest() { + manager.runTests(EMEmanifest, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_canvas_blocked.html b/dom/media/test/test_eme_canvas_blocked.html new file mode 100644 index 0000000000..714fceafb7 --- /dev/null +++ b/dom/media/test/test_eme_canvas_blocked.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) +{ + manager.started(token); + + let v = document.createElement("video"); + v.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this. + + var p1 = new EMEPromise; + v.addEventListener("loadeddata", function(ev) { + var video = ev.target; + var canvas = document.createElement("canvas"); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + document.body.appendChild(canvas); + var ctx = canvas.getContext("2d"); + var threwError = false; + try { + ctx.drawImage(video, 0, 0); + } catch (ex) { + threwError = true; + } + ok(threwError, TimeStamp(token) + " - Should throw an error when trying to draw EME video to canvas."); + p1.resolve(); + }); + + let p2 = SetupEME(v, test, token); + + Promise.all([p1.promise, p2]) + .catch(reason => ok(false, reason)) + .then(() => { + CleanUpMedia(v); + manager.finished(token); + }); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_createMediaKeys_iframes.html b/dom/media/test/test_eme_createMediaKeys_iframes.html new file mode 100644 index 0000000000..2309fd76f4 --- /dev/null +++ b/dom/media/test/test_eme_createMediaKeys_iframes.html @@ -0,0 +1,201 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test creation of MediaKeys in iframes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody"> +// Helper functions. + +// We take navigator explicitly as an argument to avoid ambiguity in fetching +// it. This is to avoid issues with the following +// ``` +// iframe.contentWindow.createMediaKeys = createMediaKeys; +// await iframe.contentWindow.createMediaKeys(); +// ``` +// If we don't pass a navigator, and just use `navigator` in the function, this +// ends up being equivalent to +// ``` +// iframe.contentWindow.createMediaKeys = createMediaKeys; +// await iframe.contentWindow.createMediaKeys(window.navigator); +// ``` +// i.e. the function will use the navigator from the global window for the top +// browsing context, not the iframe's. This would result in the tests not +// correctly testing within the iframe. +async function createMediaKeys(aNavigator) { + const clearKeyOptions = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + }, + ]; + + let access = await aNavigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + clearKeyOptions + ); + + return access.createMediaKeys(); +} +// End helper functions. + +// Setup for the tests. +add_task(async () => { + info("Setting up EME prefs"); + // Wrap EME pref setup in a promise so it plays nice with add_task. + return new Promise(r => { + SetupEMEPref(r); + }); +}); + +// These tests check that the following work using different iframe combinations +// - navigator.requestMediaKeySystem(...) successfully grants access. +// - the resulting MediaKeySystemAccess object's createMediaKeys() creates +// MediaKeys as expected. + +// Same origin iframe, using src attribute, wait for onload. +add_task(async () => { + info( + "Starting same origin iframe, using src attribute, wait for onload test" + ); + let iframe = document.createElement("iframe"); + let iframeLoadPromise = new Promise(r => { + iframe.onload = r; + }); + iframe.src = "file_eme_createMediaKeys.html"; + document.body.appendChild(iframe); + await iframeLoadPromise; + info("iframe loaded"); + + // Setup our handler for when the iframe messages to tell us if it + // created MediaKeys or not. + let iframeMessagePromise = new Promise(r => { + window.onmessage = message => { + is( + message.data, + "successCreatingMediaKeys", + "iframe should have posted us a message saying keys were successfully created" + ); + r(); + }; + }); + // Post a message to the iframe to ask it to try and create media keys. + iframe.contentWindow.postMessage("", "*"); + // Wait until we've got a message back from our iframe. + await iframeMessagePromise; +}); + +// Same origin iframe, call via JS, wait for onload. +add_task(async () => { + info("Starting same origin iframe, call via JS, wait for onload test"); + let iframe = document.createElement("iframe"); + let iframeLoadPromise = new Promise(r => { + iframe.onload = r; + }); + iframe.src = ""; // No src iframes are same origin. + document.body.appendChild(iframe); + await iframeLoadPromise; + info("iframe loaded"); + + try { + iframe.contentWindow.createMediaKeys = createMediaKeys; + let mediaKeys = await iframe.contentWindow.createMediaKeys( + iframe.contentWindow.navigator + ); + ok(mediaKeys, "Should get media keys"); + } catch (e) { + ok( + false, + `Should not get any errors while trying to get media keys, got ${e}` + ); + } +}); + +// Same origin iframe, call via JS, *do not* wait for onload. +// +// Note, sites shouldn't do this, because +// https://bugzilla.mozilla.org/show_bug.cgi?id=543435 +// means not waiting for onload results in weird behavior, however +// https://bugzilla.mozilla.org/show_bug.cgi?id=1675360 +// shows sites doing this in the wild because historically this worked in +// Firefox. +// +// Breaking this test case isn't necessarily against any specifications +// I'm (bryce) aware of, but it will probably break site compat, so be really +// sure you want to before doing so. +add_task(async () => { + info( + "Starting same origin iframe, call via JS, *do not* wait for onload test" + ); + let iframe = document.createElement("iframe"); + let iframeLoadPromise = new Promise(r => { + iframe.onload = r; + }); + iframe.src = ""; // No src iframes are same origin. + document.body.appendChild(iframe); + info("iframe appended (we're not waiting for load)"); + + try { + iframe.contentWindow.createMediaKeys = createMediaKeys; + let mediaKeys = await iframe.contentWindow.createMediaKeys( + iframe.contentWindow.navigator + ); + ok(mediaKeys, "Should get media keys"); + + // We await the load to see if they keys persist through the load. + // This could fail if gecko internally associates the keys with the + // about:blank page that is replaced by the load. + await iframeLoadPromise; + ok(mediaKeys, "Media keys should still exist after the load"); + } catch (e) { + ok( + false, + `Should not get any errors while trying to get media keys, got ${e}` + ); + } +}); + +// Different origin iframe, using src attribute, wait for onload +add_task(async () => { + info( + "Starting different origin iframe, using src attribute, wait for onload test" + ); + let iframe = document.createElement("iframe"); + let iframeLoadPromise = new Promise(r => { + iframe.onload = r; + }); + // Make our iframe cross origin (see build/pgo/server-locations.txt for more + // info the url used). + iframe.src = + "https://w3c-test.org:443/tests/dom/media/test/file_eme_createMediaKeys.html"; + iframe.allow = "encrypted-media"; + document.body.appendChild(iframe); + await iframeLoadPromise; + info("iframe loaded"); + + // Setup our handler for when the iframe messages to tell us if it + // created MediaKeys or not. + let iframeMessagePromise = new Promise(r => { + window.onmessage = message => { + is( + message.data, + "successCreatingMediaKeys", + "iframe should have posted us a message saying keys were successfully created" + ); + r(); + }; + }); + // Post a message to the iframe to ask it to try and create media keys. + iframe.contentWindow.postMessage("", "*"); + // Wait until we've got a message back from our iframe. + await iframeMessagePromise; +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_detach_media_keys.html b/dom/media/test/test_eme_detach_media_keys.html new file mode 100644 index 0000000000..aad035db14 --- /dev/null +++ b/dom/media/test/test_eme_detach_media_keys.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<video id="v" controls></video> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function createAndSet() { + return new Promise(function(resolve, reject) { + var m; + navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig) + .then(function (access) { + return access.createMediaKeys(); + }) + .then(function (mediaKeys) { + m = mediaKeys; + return document.getElementById("v").setMediaKeys(mediaKeys); + }) + .then(function() { + resolve(m); + }); + } +)} + +var m1,m2; + +// Test that if we create and set two MediaKeys on one video element, +// that if the first MediaKeys we set on the media elemnt is still usable +// after the second MediaKeys has been set on the media element. +SetupEMEPref(() => { + createAndSet().then((m) => { + m1 = m; // Stash MediaKeys. + return createAndSet(); + }) + .then((m) => { + m2 = m; + is(document.getElementById("v").mediaKeys, m2, "Should have set MediaKeys on media element"); + ok(document.getElementById("v").mediaKeys != m1, "First MediaKeys should no longer be set on media element"); + var s = m1.createSession("temporary"); + return s.generateRequest("webm", StringToArrayBuffer(atob('YAYeAX5Hfod+V9ANHtANHg=='))); + }) + .then(() => { + ok(true, "Was able to generateRequest using second CDM"); + SimpleTest.finish(); + }, () => { + ok(false, "Was *NOT* able to generateRequest using second CDM"); + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html new file mode 100644 index 0000000000..0e379a0e5a --- /dev/null +++ b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html @@ -0,0 +1,145 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="https://example.com:443/tests/dom/media/test/eme.js"></script> +</head> +<body> +<pre id="test"> +<video id="v" controls></video> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +var EMEmanifest = [ + { + name:"bipbop 10s", + tracks: [ + { + name:"video", + type:"video/mp4; codecs=\"avc1.4d4015\"", + fragments:[ "bipbop-cenc-video-10s.mp4", + ] + } + ], + keys: { + "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311", + }, + sessionType:"temporary", + sessionCount:1, + duration:10.01 + }, +]; + +function sleep(time) { + return new Promise((resolve) => setTimeout(resolve, time)); +} + +// To check if playback can be blocked and resumed correctly after +// detaching original mediakeys and reattach it back. +function startTest(test, token) +{ + manager.started(token); + + var mk_ori; + let finish = new EMEPromise; + + let v = document.getElementById("v"); + let sessions = []; + function onSessionCreated(session) { + sessions.push(session); + } + + function closeSessions() { + let p = new EMEPromise; + Promise.all(sessions.map(s => s.close())) + .then(p.resolve, p.reject); + return p.promise; + } + + function setMediaKeysToElement(mk, solve, reject) { + v.setMediaKeys(mk).then(solve, reject); + } + + function ReattachOriMediaKeys() { + function onOriMediaKeysSetOK() { + ok(true, TimeStamp(token) + " (ENCRYPTED) Set original MediaKeys back OK!"); + } + function onOriMediaKeysSetFailed() { + ok(false, " Failed to set original mediakeys back."); + } + + function onCanPlayAgain(ev) { + Promise.all([closeSessions()]) + .then(() => { + ok(true, " (ENCRYPTED) Playback can be resumed."); + manager.finished(token); + }, () => { + ok(false, TimeStamp(token) + " Sessions are closed incorrectly."); + manager.finished(token); + }); + } + + once(v, "canplay", onCanPlayAgain); + setMediaKeysToElement(mk_ori, onOriMediaKeysSetOK, onOriMediaKeysSetFailed) + } + + function triggerSeek() { + v.currentTime = v.duration / 2; + } + + function onCanPlay(ev) { + function onSetMediaKeysToNullOK() { + ok(true, TimeStamp(token) + " Set MediaKeys to null. OK!"); + + triggerSeek(); + + SimpleTest.requestFlakyTimeout("To reattach mediakeys back again in 5s."); + sleep(5000).then(ReattachOriMediaKeys); + } + function onSetMediaKeysToNullFailed() { + ok(false, TimeStamp(token) + " Set MediaKeys to null. FAILED!"); + } + + SimpleTest.requestFlakyTimeout("To detach mediakeys after receiving 'canplay' event in 2s"); + sleep(2000).then(() => { + setMediaKeysToElement(null, onSetMediaKeysToNullOK, onSetMediaKeysToNullFailed); + }); + } + + once(v, "canplay", onCanPlay); + + var p1 = LoadInitData(v, test, token); + var p2 = CreateAndSetMediaKeys(v, test, token); + var p3 = LoadTest(test, v, token); + Promise.all([p1, p2, p3]) + .then(values => { + let initData = values[0]; + // stash the mediakeys + mk_ori = v.mediaKeys; + initData.map(ev => { + let session = v.mediaKeys.createSession(); + onSessionCreated(session); + MakeRequest(test, token, ev, session); + }); + }) + .then(() => { + return finish.promise; + }) + .catch(reason => ok(false, reason)) +} + +function beginTest() { + manager.runTests(EMEmanifest, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_getstatusforpolicy.html b/dom/media/test/test_eme_getstatusforpolicy.html new file mode 100644 index 0000000000..fd2c4b11be --- /dev/null +++ b/dom/media/test/test_eme_getstatusforpolicy.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<video id="v" controls></video> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function createMediaKeysAndSet() { + return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig) + .then(function (access) { + return access.createMediaKeys(); + }) + .then(function (mediaKeys) { + document.getElementById("v").setMediaKeys(mediaKeys); + return mediaKeys; + }); +} + +function test() { + SetupEMEPref(() => { + createMediaKeysAndSet() + .then((m) => { + let video = document.getElementById("v"); + is(video.mediaKeys, m, "Should have set MediaKeys on media element"); + // getStatusForPolicy() is not suppored by ClearKey key system. + // The promise will always be rejected with NotSupportedError. + return video.mediaKeys.getStatusForPolicy({minHdcpVersion: "hdcp-2.0"}); + }) + .then((mediaKeyStatus) => { + ok(false, "Promise of getStatusForPolicy should not be resolved with clearkey key system"); + }) + // Promise rejected with NotSupportedError as expected. + .catch(reason => is("NotSupportedError", reason.name, + "Promise should be rejected with NotSupportedError.")) + .then(() => SimpleTest.finish()); + }); +} + +SpecialPowers.pushPrefEnv({"set": + [ + ["media.eme.hdcp-policy-check.enabled", true], + ] + }, test); + +</script> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/dom/media/test/test_eme_initDataTypes.html b/dom/media/test/test_eme_initDataTypes.html new file mode 100644 index 0000000000..5dfb873138 --- /dev/null +++ b/dom/media/test/test_eme_initDataTypes.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tests = [ + { + name: "One keyId", + initDataType: 'keyids', + initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}', + expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}', + sessionType: 'temporary', + expectPass: true, + }, + { + name: "Two keyIds", + initDataType: 'keyids', + initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}', + expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}', + sessionType: 'temporary', + expectPass: true, + }, + { + name: "Two keyIds, temporary session", + initDataType: 'keyids', + initData: '{"type":"temporary", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}', + expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}', + sessionType: 'temporary', + expectPass: true, + }, + { + name: "Two keyIds, persistent session, type before kids", + initDataType: 'keyids', + initData: '{"type":"persistent-license", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}', + expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent-license"}', + sessionType: 'persistent-license', + expectPass: false, + }, + { + name: "Invalid keyId", + initDataType: 'keyids', + initData: '{"kids":["0"]}', + sessionType: 'temporary', + expectPass: false, + }, + { + name: "Empty keyId", + initDataType: 'keyids', + initData: '{"kids":[""]}', + sessionType: 'temporary', + expectPass: false, + }, + { + name: "Invalid initData", + initDataType: 'keyids', + initData: 'invalid initData', + sessionType: 'temporary', + expectPass: false, + }, + { + name: "'webm' initDataType", + initDataType: 'webm', + initData: 'YAYeAX5Hfod+V9ANHtANHg==', + expectedRequest: '{"kids":["YAYeAX5Hfod-V9ANHtANHg"],"type":"temporary"}', + sessionType: 'temporary', + expectPass: true, + }, + { + name: "'webm' initDataType with non 16 byte keyid", + initDataType: 'webm', + initData: 'YAYeAX5Hfod', + expectedRequest: '{\"kids\":[\"YAYeAX5Hfoc\"],\"type\":\"temporary\"}', + sessionType: 'temporary', + expectPass: true, + }, +]; + +function PrepareInitData(initDataType, initData) +{ + if (initDataType == "keyids") { + return new TextEncoder().encode(initData); + } else if (initDataType == "webm") { + return StringToArrayBuffer(atob(initData)); + } +} + +function Test(test) { + return new Promise(function(resolve, reject) { + var configs = [{ + initDataTypes: [test.initDataType], + videoCapabilities: [{contentType: 'video/mp4' }], + }]; + navigator.requestMediaKeySystemAccess('org.w3.clearkey', configs) + .then((access) => access.createMediaKeys()) + .then((mediaKeys) => { + var session = mediaKeys.createSession(test.sessionType); + session.addEventListener("message", function(event) { + is(event.messageType, "license-request", "'" + test.name + "' MediaKeyMessage type should be license-request."); + var text = new TextDecoder().decode(event.message); + is(text, test.expectedRequest, "'" + test.name + "' got expected response."); + is(text == test.expectedRequest, test.expectPass, + "'" + test.name + "' expected to " + (test.expectPass ? "pass" : "fail")); + resolve(); + }); + var initData = PrepareInitData(test.initDataType, test.initData); + return session.generateRequest(test.initDataType, initData); + } + ).catch((x) => { + ok(!test.expectPass, "'" + test.name + "' expected to fail."); + resolve(); + }); + }); +} + +function beginTest() { + Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); }); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_missing_pssh.html b/dom/media/test/test_eme_missing_pssh.html new file mode 100644 index 0000000000..d39a7c9821 --- /dev/null +++ b/dom/media/test/test_eme_missing_pssh.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> + </head> + <body> + <audio controls id="audio"></audio> + <pre id="test"> + <script class="testbody" type="text/javascript"> + + // Tests that a fragmented MP4 file without a PSSH, but with valid encrypted + // tracks with valid TENC boxes, is able to load with EME. + // We setup MSE before starting up EME, so that we exercise the "waiting for + // cdm" step in the MediaDecoderStateMachine. + + SimpleTest.waitForExplicitFinish(); + + var pssh = [ + 0x00, 0x00, 0x00, 0x00, + 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh') + 0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0) + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0x00, 0x00, 0x00, 0x01, // KID_count (1) + 0x2f, 0xef, 0x8a, 0xd8, 0x12, 0xdf, 0x42, 0x97, + 0x83, 0xe9, 0xbf, 0x6e, 0x5e, 0x49, 0x3e, 0x53, + 0x00, 0x00, 0x00, 0x00 // Size of Data (0) + ]; + + var audio = document.getElementById("audio"); + + function LoadEME() { + var options = [{ + initDataType: 'cenc', + audioType: 'audio/mp4; codecs="mp4a.40.2"', + }]; + navigator.requestMediaKeySystemAccess("org.w3.clearkey", options) + .then((keySystemAccess) => { + return keySystemAccess.createMediaKeys(); + }, bail("Failed to request key system access.")) + + .then((mediaKeys) => { + audio.setMediaKeys(mediaKeys); + var session = mediaKeys.createSession(); + once(session, "message", (message) => { + is(message.messageType, 'license-request', "Expected a license-request"); + var license = new TextEncoder().encode(JSON.stringify({ + 'keys': [{ + 'kty':'oct', + 'kid':'L--K2BLfQpeD6b9uXkk-Uw', + 'k':HexToBase64('7f412f0575f44f718259beef56ec7771') + }], + 'type': 'temporary' + })); + session.update(license); + }); + session.generateRequest('cenc', new Uint8Array(pssh)); + }); + } + + function DownloadMedia(url, type, mediaSource) { + return new Promise(function(resolve, reject) { + var sourceBuffer = mediaSource.addSourceBuffer(type); + fetchWithXHR(url, (response) => { + once(sourceBuffer, "updateend", resolve); + sourceBuffer.appendBuffer(new Uint8Array(response)); + }); + }); + } + + function LoadMSE() { + var ms = new MediaSource(); + audio.src = URL.createObjectURL(ms); + + once(ms, "sourceopen", ()=>{ + DownloadMedia('short-audio-fragmented-cenc-without-pssh.mp4', 'audio/mp4; codecs="mp4a.40.2"', ms) + .then(() => { ms.endOfStream(); LoadEME();}); + }); + + audio.addEventListener("loadeddata", SimpleTest.finish); + } + + SetupEMEPref(LoadMSE); + + </script> + </pre> + </body> +</html> diff --git a/dom/media/test/test_eme_non_mse_fails.html b/dom/media/test/test_eme_non_mse_fails.html new file mode 100644 index 0000000000..efd87b0a95 --- /dev/null +++ b/dom/media/test/test_eme_non_mse_fails.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1131392 - Test that EME does not work for non-MSE media</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +function DoSetMediaKeys(v, test, token) +{ + var options = [{ + initDataTypes: ["cenc"], + audioCapabilities: [{contentType: test.audioType}], + videoCapabilities: [{contentType: test.videoType}], + }]; + + return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options) + + .then(function(keySystemAccess) { + return keySystemAccess.createMediaKeys(); + }) + + .catch(function() { + ok(false, token + " was not expecting failure (yet)"); + }) + + .then(function(mediaKeys) { + return v.setMediaKeys(mediaKeys); + }); +} + +function TestSetMediaKeys(test, token) +{ + manager.started(token); + + var v = document.createElement("video"); + + v.addEventListener("encrypted", function() { + ok(false, token + " should not fire encrypted event"); + }); + + var loadedMetadata = false; + v.addEventListener("loadedmetadata", function() { + loadedMetadata = true; + }); + + v.addEventListener("error", function() { + ok(true, token + " expected error event"); + ok(loadedMetadata, token + " expected loadedmetadata to have fired"); + manager.finished(token); + }); + + v.src = test.name; +} + +function TestSetSrc(test, token) +{ + manager.started(token); + + var v = document.createElement("video"); + v.addEventListener("error", function(err) { + ok(true, token + " got error setting src on video element, as expected"); + manager.finished(token); + }); + + DoSetMediaKeys(v, test, token) + + .then(function() { + v.src = test.name; + }) + + .catch(function() { + ok(false, token + " got error setting media keys"); + }); +} + +function startTest(test, token) +{ + TestSetMediaKeys(test, token + "_setMediaKeys"); + TestSetSrc(test, token + "_setSrc"); +} + +function beginTest() { + manager.runTests(gEMENonMSEFailTests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_playback.html b/dom/media/test/test_eme_playback.html new file mode 100644 index 0000000000..dc6092e486 --- /dev/null +++ b/dom/media/test/test_eme_playback.html @@ -0,0 +1,193 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="https://example.com:443/tests/dom/media/test/eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +function ArrayBuffersEqual(a, b) { + if (a.byteLength != b.byteLength) { + return false; + } + var ua = new Uint8Array(a); + var ub = new Uint8Array(b); + for (var i = 0; i < ua.length; i++) { + if (ua[i] != ub[i]) { + return false; + } + } + return true; +} + +function KeysChangeFunc(session, keys, token) { + session.keyIdsReceived = []; + for (var keyid in keys) { + Log(token, "Set " + keyid + " to false in session[" + session.sessionId + "].keyIdsReceived"); + session.keyIdsReceived[keyid] = false; + } + return function(ev) { + var s = ev.target; + s.gotKeysChanged = true; + + var keyList = []; + var valueList = []; + var map = s.keyStatuses; + + // Test that accessing keys not known to the CDM has expected behaviour. + var absentKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b, + 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]); + is(map.has(absentKey), false, "Shouldn't have a key that's not in the media"); + is(map.get(absentKey), undefined, "Unknown keys should undefined status"); + + // Verify known keys have expected status. + for (let [key, val] of map.entries()) { + is(key.constructor, ArrayBuffer, "keyId should be ArrayBuffer"); + ok(map.has(key), "MediaKeyStatusMap.has() should work."); + is(map.get(key), val, "MediaKeyStatusMap.get() should work."); + keyList.push(key); + valueList.push(val); + is(val, "usable", token + ": key status should be usable"); + var kid = Base64ToHex(window.btoa(ArrayBufferToString(key))); + ok(kid in s.keyIdsReceived, TimeStamp(token) + " session[" + s.sessionId + "].keyIdsReceived contained " + kid + " as expected."); + s.keyIdsReceived[kid] = true; + } + + var index = 0; + for (var keyId of map.keys()) { + ok(ArrayBuffersEqual(keyId, keyList[index]), "MediaKeyStatusMap.keys() should correspond to entries"); + index++; + } + index = 0; + for (let val of map.values()) { + is(val, valueList[index], "MediaKeyStatusMap.values() should correspond to entries"); + index++; + } + } +} + +function startTest(test, token) +{ + manager.started(token); + + var sessions = []; + + function onSessionCreated(session) { + sessions.push(session); + session.addEventListener("keystatuseschange", KeysChangeFunc(session, test.keys, token)); + + session.numKeystatuseschangeEvents = 0; + session.numOnkeystatuseschangeEvents = 0; + + session.addEventListener("keystatuseschange", function() { + session.numKeystatuseschangeEvents += 1; + }); + session.onkeystatuseschange = function() { + session.numOnkeystatuseschangeEvents += 1; + }; + + session.numMessageEvents = 0; + session.numOnMessageEvents = 0; + session.addEventListener("message", function() { + session.numMessageEvents += 1; + }); + session.onmessage = function() { + session.numOnMessageEvents += 1; + }; + } + + let v = document.createElement("video"); + document.body.appendChild(v); + + var gotEncrypted = 0; + let finish = new EMEPromise; + + v.addEventListener("encrypted", function(ev) { + gotEncrypted += 1; + }); + + v.addEventListener("loadedmetadata", function() { + ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v), + TimeStamp(token) + " isEncrypted should be true"); + is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content"); + }); + + v.addEventListener("ended", function(ev) { + ok(true, TimeStamp(token) + " got ended event"); + + is(gotEncrypted, test.sessionCount, + TimeStamp(token) + " encrypted events expected: " + test.sessionCount + + ", actual: " + gotEncrypted); + + ok(Math.abs(test.duration - v.duration) < 0.1, + TimeStamp(token) + " Duration of video should be corrrect"); + ok(Math.abs(test.duration - v.currentTime) < 0.1, + TimeStamp(token) + " Current time should be same as duration"); + + // Verify all sessions had all keys went sent to the CDM usable, and thus + // that we received keystatuseschange event(s). + is(sessions.length, test.sessionCount, TimeStamp(token) + " should have " + + test.sessionCount + + " session" + (test.sessionCount === 1 ? "" : "s")); + var keyIdsReceived = []; + for (var keyid in test.keys) { keyIdsReceived[keyid] = false; } + for (var i = 0; i < sessions.length; i++) { + var session = sessions[i]; + ok(session.gotKeysChanged, + TimeStamp(token) + " session[" + session.sessionId + + "] should have received at least one keychange event"); + for (let kid in session.keyIdsReceived) { + Log(token, "session[" + session.sessionId + "] key " + kid + " = " + (session.keyIdsReceived[kid] ? "true" : "false")); + if (session.keyIdsReceived[kid]) { keyIdsReceived[kid] = true; } + } + ok(session.numKeystatuseschangeEvents > 0, TimeStamp(token) + " should get key status changes"); + is(session.numKeystatuseschangeEvents, session.numOnkeystatuseschangeEvents, + TimeStamp(token) + " should have as many keystatuseschange event listener calls as event handler calls."); + + ok(session.numMessageEvents > 0, TimeStamp(token) + " should get message events"); + is(session.numMessageEvents, session.numOnMessageEvents, + TimeStamp(token) + " should have as many message event listener calls as event handler calls."); + } + for (let kid in keyIdsReceived) { + ok(keyIdsReceived[kid], TimeStamp(token) + " key with id " + kid + " was usable as expected"); + } + + CloseSessions(v, sessions).then(finish.resolve, finish.reject); + }); + + Promise.all([ + LoadInitData(v, test, token), + CreateAndSetMediaKeys(v, test, token), + LoadTest(test, v, token)]) + .then(values => { + v.play(); + let initData = values[0]; + initData.map(ev => { + let session = v.mediaKeys.createSession(); + onSessionCreated(session); + MakeRequest(test, token, ev, session); + }); + return finish.promise; + }) + .catch(reason => ok(false, reason)) + .then(() => manager.finished(token)); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_pssh_in_moof.html b/dom/media/test/test_eme_pssh_in_moof.html new file mode 100644 index 0000000000..d1965be844 --- /dev/null +++ b/dom/media/test/test_eme_pssh_in_moof.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML>
+<html>
+
+ <head>
+ <title>Test Encrypted Media Extensions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="eme.js"></script>
+ </head>
+
+ <body>
+ <script class="testbody" type="text/javascript">
+ let manager = new MediaTestManager;
+
+ let psshTests = [
+ { name: "bear-640x360-cenc-key-rotation",
+ tracks : [
+ {
+ name: "video",
+ type: "video/mp4; codecs=\"avc1.64000d\"",
+ fragments: ["bear-640x360-v_frag-cenc-key_rotation.mp4"],
+ initDatas: 6
+ },
+ {
+ name: "audio",
+ type: "audio/mp4; codecs=\"mp4a.40.2\"",
+ fragments: ["bear-640x360-a_frag-cenc-key_rotation.mp4"],
+ initDatas: 7
+ }
+ ],
+ keys : {
+ "30313233343536373839303132333435" :
+ "ebdd62f16814d27b68ef122afce4ae3c"
+ }
+ },
+ { name: "bipbop-clearkey-keyrotation-clear-lead",
+ tracks : [
+ {
+ name: "video",
+ type: "video/mp4; codecs=\"avc1.4d4015\"",
+ fragments: ["bipbop-clearkey-keyrotation-clear-lead-video.mp4"],
+ initDatas: 3
+ },
+ {
+ name: "audio",
+ type: "audio/mp4; codecs=\"mp4a.40.2\"",
+ fragments: ["bipbop-clearkey-keyrotation-clear-lead-audio.mp4"],
+ initDatas: 3
+ }
+ ],
+ keys : {
+ "00112233445566778899aabbccddeeff" :
+ "00112233445566778899aabbccddeeff",
+ "112233445566778899aabbccddeeff00" :
+ "112233445566778899aabbccddeeff00"
+ }
+ }
+ ];
+
+ // Specialized create media keys function, since the one in eme.js relies
+ // on listening for encrypted events, and we want to manage those
+ // ourselves within this test.
+ async function createAndSetMediaKeys(video, test, token) {
+ function streamType(type) {
+ var x = test.tracks.find(o => o.name == type);
+ return x ? x.type : undefined;
+ }
+
+ var configuration = { initDataTypes: ["cenc"] };
+ if (streamType("video")) {
+ configuration.videoCapabilities = [{contentType: streamType("video")}];
+ }
+ if (streamType("audio")) {
+ configuration.audioCapabilities = [{contentType: streamType("audio")}];
+ }
+ let keySystemAccess = await navigator.requestMediaKeySystemAccess(
+ "org.w3.clearkey", [configuration]);
+ let mediaKeys = await keySystemAccess.createMediaKeys();
+ return video.setMediaKeys(mediaKeys);
+ }
+
+ function startTest(test, token) {
+ manager.started(token);
+ let video = document.createElement("video");
+ document.body.appendChild(video);
+
+ let encryptedEventCount = 0;
+
+ let initDatas = new Map();
+
+ function handleEncrypted(event) {
+ // We get one 'encrypted' event for every run of contiguous PSSH boxes
+ // in each stream. Note that some of the PSSH boxes contained in the
+ // "bear" streams aren't in the Common Open PSSH box format, so our
+ // ClearKey CDM will reject those license requests. Some of the init
+ // data is also repeated.
+ encryptedEventCount++;
+
+ let hexStr = StringToHex(new TextDecoder().decode(event.initData));
+ if (initDatas.has(hexStr)) {
+ // Already have a session for this.
+ return;
+ }
+
+ let initData = new Uint8Array(event.initData);
+ is(event.initDataType, "cenc", "'encrypted' event should have 'cenc' " +
+ "initDataType");
+ Log(token, "encrypted event => len=" + initData.length + " " + hexStr);
+ let session = event.target.mediaKeys.createSession();
+ initDatas.set(hexStr, session);
+ session.addEventListener("message", e => {
+ e.target.update(
+ GenerateClearKeyLicense(e.message, test.keys));
+ });
+
+ session.generateRequest(event.initDataType, event.initData);
+ }
+
+ video.addEventListener("encrypted", handleEncrypted);
+
+ video.addEventListener("ended", () => {
+ let expectedEncryptedEvents =
+ test.tracks.reduce((sum, track) => sum += track.initDatas, 0);
+ is(encryptedEventCount, expectedEncryptedEvents,
+ "Should get one 'encrypted' event per run of contiguous PSSH " +
+ "boxes in media.");
+ manager.finished(token);
+ });
+
+ video.autoplay = true;
+
+ createAndSetMediaKeys(video, test, token)
+ .then(() => LoadTest(test, video, token))
+ }
+
+ manager.runTests(psshTests, startTest);
+
+ </script>
+ </body>
+</html>
diff --git a/dom/media/test/test_eme_requestKeySystemAccess.html b/dom/media/test/test_eme_requestKeySystemAccess.html new file mode 100644 index 0000000000..59c7ef566d --- /dev/null +++ b/dom/media/test/test_eme_requestKeySystemAccess.html @@ -0,0 +1,480 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const SUPPORTED_LABEL = "pass label"; + +function ValidateConfig(name, expected, observed) { + info("ValidateConfig " + name); + info("expected cfg=" + JSON.stringify(expected)); + info("observed cfg=" + JSON.stringify(observed)); + + is(observed.label, expected.label, name + " label should match"); + if (expected.initDataTypes) { + ok(expected.initDataTypes.every((element, index, array) => observed.initDataTypes.includes(element)), name + " initDataTypes should match."); + } + + if (expected.audioCapabilities) { + ok(expected.audioCapabilities.length == 1, "Test function can only handle one capability."); + ok(observed.audioCapabilities.length == 1, "Test function can only handle one capability."); + is(observed.audioCapabilities[0].contentType, expected.audioCapabilities[0].contentType, name + " audioCapabilities should match."); + } + if (typeof expected.videoCapabilities !== 'undefined') { + info("expected.videoCapabilities=" + expected.videoCapabilities); + dump("expected.videoCapabilities=" + expected.videoCapabilities + "\n"); + ok(expected.videoCapabilities.length == 1, "Test function can only handle one capability."); + ok(observed.videoCapabilities.length == 1, "Test function can only handle one capability."); + is(observed.videoCapabilities[0].contentType, expected.videoCapabilities[0].contentType, name + " videoCapabilities should match."); + } + if (expected.sessionTypes) { + is(expected.sessionTypes.length, observed.sessionTypes.length, "Should have expected number of sessionTypes"); + for (var i = 0; i < expected.sessionTypes.length; i++) { + is(expected[i], observed[i], "Session type " + i + " should match"); + } + } +} + +function Test(test) { + var name = "'" + test.name + "'"; + return new Promise(function(resolve, reject) { + var p; + if (test.options) { + var keySystem = (test.keySystem !== undefined) ? test.keySystem : CLEARKEY_KEYSYSTEM; + p = navigator.requestMediaKeySystemAccess(keySystem, test.options); + } else { + p = navigator.requestMediaKeySystemAccess(keySystem); + } + p.then( + function(keySystemAccess) { + ok(test.shouldPass, name + " passed and was expected to " + (test.shouldPass ? "pass" : "fail")); + is(keySystemAccess.keySystem, CLEARKEY_KEYSYSTEM, "CDM keySystem should be in MediaKeySystemAccess.keySystem"); + ValidateConfig(name, test.expectedConfig, keySystemAccess.getConfiguration()); + resolve(); + }, + function(ex) { + if (test.shouldPass) { + info(name + " failed: " + ex); + } + ok(!test.shouldPass, name + " failed and was expected to " + (test.shouldPass ? "pass" : "fail")); + resolve(); + }); + }); +} + +var tests = [ + { + name: 'Empty keySystem string', + keySystem: '', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + } + ], + shouldPass: false, + }, + { + name: 'Empty options specified', + options: [ ], + shouldPass: false, + }, + { + name: 'Undefined options', + shouldPass: false, + }, + { + name: 'Basic MP4 cenc', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + videoCapabilities: [{contentType: 'video/mp4'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + videoCapabilities: [{contentType: 'video/mp4'}], + }, + shouldPass: true, + }, + { + name: 'Invalid keysystem failure', + keySystem: 'bogusKeySystem', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + } + ], + shouldPass: false, + }, + { + name: 'Invalid initDataType', + options: [ + { + initDataTypes: ['bogus'], + audioCapabilities: [{contentType: 'audio/mp4'}], + } + ], + shouldPass: false, + }, + { + name: 'Valid initDataType after invalid', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['bogus', 'invalid', 'cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + }, + shouldPass: true, + }, + { + name: 'Invalid videoType', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/bogus'}], + } + ], + shouldPass: false, + }, + { + name: 'Invalid distinctiveIdentifier fails', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + distinctiveIdentifier: 'bogus', + persistentState: 'bogus', + } + ], + shouldPass: false, + }, + { + name: 'distinctiveIdentifier is prohibited for ClearKey', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + distinctiveIdentifier: 'required', + } + ], + shouldPass: false, + }, + { + name: 'Invalid persistentState fails', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + persistentState: 'bogus', + } + ], + shouldPass: false, + }, + { + name: 'Invalid robustness unsupported', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4', robustness: 'very much so'}], + } + ], + shouldPass: false, + }, + { + name: 'Unexpected config entry should be ignored', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + unexpectedEntry: 'this should be ignored', + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + }, + shouldPass: true, + }, + { + name: 'Invalid option followed by valid', + options: [ + { + label: "this config should not be supported", + initDataTypes: ['bogus'], + }, + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + }, + shouldPass: true, + }, + { + name: 'Persistent-license should not be supported by ClearKey', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + sessionTypes: ['persistent-license'], + persistentState: 'optional', + } + ], + shouldPass: false, + }, + { + name: 'Persistent-usage-record should not be supported by ClearKey', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4'}], + sessionTypes: ['persistent-usage-record'], + persistentState: 'optional', + } + ], + shouldPass: false, + }, + { + name: 'MP4 audio container', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4'}], + }, + shouldPass: true, + }, + { + name: 'MP4 audio container with AAC-LC', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}], + }, + shouldPass: true, + }, + { + name: 'MP4 audio container with invalid codecs', + options: [ + { + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4; codecs="bogus"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 audio container with mp3 is unsupported', + options: [ + { + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4; codecs="mp3"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 video container type with an mp3 codec is unsupported', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="mp3"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 audio container type with a video codec is unsupported', + options: [ + { + initDataTypes: ['cenc'], + audioCapabilities: [{contentType: 'audio/mp4; codecs="avc1.42E01E"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 video container with constrained baseline h.264', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}], + }, + shouldPass: true, + }, + { + name: 'MP4 video container with invalid codecs', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="bogus"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 video container with both audio and video codec type in videoType', + options: [ + { + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E,mp4a.40.2"'}], + } + ], + shouldPass: false, + }, + { + name: 'MP4 audio and video type both specified', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}], + audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['cenc'], + videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}], + audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}], + }, + shouldPass: true, + }, + { + name: 'Basic WebM video', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm'}], + }, + shouldPass: true, + }, + { + name: 'Basic WebM audio', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + audioCapabilities: [{contentType: 'audio/webm'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + audioCapabilities: [{contentType: 'audio/webm'}], + }, + shouldPass: true, + }, + { + name: 'Webm with Vorbis audio and VP8 video.', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}], + audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}], + audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}], + }, + shouldPass: true, + }, + { + name: 'Webm with Vorbis audio and VP9 video.', + options: [ + { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}], + audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}], + } + ], + expectedConfig: { + label: SUPPORTED_LABEL, + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}], + audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}], + }, + shouldPass: true, + }, + { + name: 'Webm with bogus video.', + options: [ + { + initDataTypes: ['webm'], + videoCapabilities: [{contentType: 'video/webm;codecs="bogus"'}], + } + ], + shouldPass: false, + }, +]; + +function beginTest() { + Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); }); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html b/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html new file mode 100644 index 0000000000..6bafb768c1 --- /dev/null +++ b/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html @@ -0,0 +1,209 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions access can be gated by application</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// These test cases should be used to make a request to +// requestMediaKeySystemAccess and have the following members: +// name: a name describing the test. +// askAppApproval: used to set prefs such so that Gecko will ask for app +// approval for EME if true, or not if false. +// appApproves: used to set prefs to simulate app approval of permission +// request, true if the app approves the request, false if not. +// expectedKeySystemAccess: true if we expect to be granted key system access, +// false if not. +const testCases = [ + { + name: "Don't check for app approval", + askAppApproval: false, + expectedKeySystemAccess: true, + }, + { + name: "Check for app approval and app denies request", + askAppApproval: true, + appApproves: false, + expectedKeySystemAccess: false, + }, + { + name: "Check for app approval and app allows request", + askAppApproval: true, + appApproves: true, + expectedKeySystemAccess: true, + }, +]; + +// Options for requestMediaKeySystemAccess that are expected to work. +const options = [{ + initDataTypes: ['webm'], + audioCapabilities: [ + { contentType: 'audio/webm; codecs="opus"' }, + ], + videoCapabilities: [ + { contentType: 'video/webm; codecs="vp8"' } + ] +}]; + +async function setTestPrefs({askAppApproval, appApproves}) { + if (!askAppApproval) { + // Test doesn't want app approval, set pref so we don't ask and unset prefs + // used to determine permission response as we don't need them. + await SpecialPowers.pushPrefEnv({ + set: [["media.eme.require-app-approval", false]], + clear: [ + ["media.eme.require-app-approval.prompt.testing"], + ["media.eme.require-app-approval.prompt.testing.allow"], + ] + }); + return; + } + + // Test wants app approval, and will approve deny requests per appApproces + // value, set prefs accordingly. + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.eme.require-app-approval", true], + ["media.eme.require-app-approval.prompt.testing", true], + ["media.eme.require-app-approval.prompt.testing.allow", appApproves], + ], + }); +} + +// Run a test case that makes a single requestMediaKeySystemAccess call. The +// outcome of such a test run should depend on the test case's setting of +// preferences controlling the eme app approval. +async function testSingleRequest(testCase) { + await setTestPrefs(testCase); + + try { + await navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options); + ok(testCase.expectedKeySystemAccess, + `testSingleRequest ${testCase.name}: allowed media key system access.`); + } catch(e) { + is(e.name, + "NotSupportedError", + "Should get NotSupportedError when request is blocked."); + is(e.message, + "The application embedding this user agent has blocked MediaKeySystemAccess", + "Should get blocked error message."); + ok(!testCase.expectedKeySystemAccess, + `testSingleRequest ${testCase.name}: denied media key system access.`); + } +} + +// Run a test case that, but using invalid arguments for +// requestMediaKeySystemAccess. Because we expect the args to be checked +// before requesting app approval, this test ensures that we always fail when +// using bad args, regardless of the app approval prefs set. +async function testRequestWithInvalidArgs(testCase) { + const badOptions = [{ + initDataTypes: ['badType'], + audioCapabilities: [ + { contentType: 'audio/webm; codecs="notACodec"' }, + ], + videoCapabilities: [ + { contentType: 'video/webm; codecs="notACodec"' } + ] + }]; + + await setTestPrefs(testCase); + + // Check that calls with a bad key system fail. + try { + await navigator.requestMediaKeySystemAccess("BadKeySystem", options); + ok(false, + `testRequestWithInvalidArgs ${testCase.name}: should not get access when using bad key system.`); + } catch(e) { + is(e.name, + "NotSupportedError", + "Should get NotSupportedError using bad key system."); + is(e.message, + "Key system is unsupported", + "Should get not supported key system error message."); + } + + // Check that calls with the bad options fail. + try { + await navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, badOptions); + ok(false, + `testRequestWithInvalidArgs ${testCase.name}: should not get access when using bad options.`); + } catch(e) { + is(e.name, + "NotSupportedError", + "Should get NotSupportedError using bad options."); + is(e.message, + "Key system configuration is not supported", + "Should get not supported config error message."); + } +} + +// Run a test case and make multiple requests with the same case. Check that +// all requests are resolved with the expected outcome. +async function testMultipleRequests(testCase) { + // Number of requests to concurrently make. + const NUM_REQUESTS = 5; + + await setTestPrefs(testCase); + + let resolveHandler = () => { + ok(testCase.expectedKeySystemAccess, + `testMultipleRequests ${testCase.name}: allowed media key system access.`); + } + + let rejectHandler = e => { + is(e.name, + "NotSupportedError", + "Should get NotSupportedError when request is blocked."); + is(e.message, + "The application embedding this user agent has blocked MediaKeySystemAccess", + "Should get blocked error message."); + ok(!testCase.expectedKeySystemAccess, + `testMultipleRequests ${testCase.name}: denied media key system access.`); + } + + let accessPromises = []; + for(let i = 0; i < NUM_REQUESTS; i++) { + // Request access then chain to our resolve and reject handlers. The + // handlers assert test state then resolve the promise chain. Ensuring the + // chain is always resolved allows us to correctly await all outstanding + // requests -- otherwise rejects short circuit the Promise.all call below. + let accessPromise = navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options) + .then(resolveHandler, rejectHandler); + accessPromises.push(accessPromise); + } + // Wait for all promises to be resolved. If not, we'll time out. Because + // our reject handler chains back into a resolved promise, this should wait + // for all requests to be serviced, even when requestMediaKeySystemAccess's + // promise is rejected. + await Promise.all(accessPromises); +} + +async function beginTest() { + // The tests rely on prefs being set, so run them in sequence. If we run in + // parallel the tests break each other by overriding prefs. + for (const testCase of testCases) { + await testSingleRequest(testCase); + } + for (const testCase of testCases) { + await testRequestWithInvalidArgs(testCase); + } + for (const testCase of testCases) { + await testMultipleRequests(testCase); + } + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_request_notifications.html b/dom/media/test/test_eme_request_notifications.html new file mode 100644 index 0000000000..b3c62c9cf6 --- /dev/null +++ b/dom/media/test/test_eme_request_notifications.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function SetPrefs(prefs) { + return SpecialPowers.pushPrefEnv({"set": prefs}); +} + +function observe() { + return new Promise(function(resolve, reject) { + var observer = function(subject, topic, data) { + SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request"); + resolve(JSON.parse(data).status); + }; + SpecialPowers.Services.obs.addObserver(observer, "mediakeys-request"); + }); +} + +function Test(test) { + var p = test.prefs ? SetPrefs(test.prefs) : Promise.resolve(); + var name = "'" + test.keySystem + "'"; + + var res = observe().then((status) => { + is(status, test.expectedStatus, name + " expected status"); + }); + + p.then(() => navigator.requestMediaKeySystemAccess(test.keySystem, gCencMediaKeySystemConfig)) + .then((keySystemAccess) => keySystemAccess.createMediaKeys()); + + return res; +} + +var tests = [ + { + keySystem: CLEARKEY_KEYSYSTEM, + expectedStatus: 'cdm-created', + prefs: [["media.eme.enabled", false]] + }, + { + keySystem: "com.widevine.alpha", + expectedStatus: 'api-disabled', + prefs: [["media.eme.enabled", false]] + }, + { + keySystem: "com.widevine.alpha", + expectedStatus: 'cdm-disabled', + prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", false]] + }, + { + keySystem: "com.widevine.alpha", + expectedStatus: 'cdm-not-installed', + prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", true]] + }, + { + keySystem: CLEARKEY_KEYSYSTEM, + expectedStatus: 'cdm-created', + prefs: [["media.eme.enabled", true]] + } +]; + +SetupEMEPref(function() { + tests.reduce(function(p,c,i,array) { + return p.then(function() { return Test(c); }); + }, Promise.resolve()).then(SimpleTest.finish); +}); + + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_sample_groups_playback.html b/dom/media/test/test_eme_sample_groups_playback.html new file mode 100644 index 0000000000..be56bff21d --- /dev/null +++ b/dom/media/test/test_eme_sample_groups_playback.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> + +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> + +<body> + <video controls id="video"></video> + <pre id="test"> + <script class="testbody" type="text/javascript"> + + // Tests that files with a default key and a seperate sample keyids in the + // sgpd box play correctly (if the keyid from the sgpd box is not parsed + // or assigned to the sample we will wait indefinitely for the default + // key). + + SimpleTest.waitForExplicitFinish(); + + // Test files for samples encrypted with different media keys. + var gEMESampleGoupTests = [ + { + name:"video with 4 keys in sgpd (sbgp in traf sgpd in stbl)", + track: { + name:"video", + type:"video/mp4; codecs=\"avc1.64000d\"", + fragments:[ "sample-encrypted-sgpdstbl-sbgptraf.mp4" + ] + }, + keys: { + // "keyid" : "key" + "279926496a7f5d25da69f2b3b2799a7f": "5544694d47473326622665665a396b36", + "597669572e55547e656b56586e2f6f68": "7959493a764556786527517849756635", + "205b2b293a342f3d3268293e6f6f4e29": "3a4f3674376d6c48675a273464447b40", + "32783e367c2e4d4d6b46467b3e6b5478": "3e213f6d45584f51713d534f4b417855", + }, + sessionType:"temporary", + sessionCount:1, + duration:2, + }, + ], + test = gEMESampleGoupTests[0]; + + var video = document.getElementById("video"); + video.addEventListener("encrypted", () => { + Log(test.name, "Recieved encrypted event"); + }); + + video.addEventListener("waitingforkey", () => { + Log(test.name, "waitingforkey"); + ok(false, test.name + " Video is waitingforkey, indicating that the samples are not being assigned the correct id from the sgpd box!"); + SimpleTest.finish(); + }); + + function LoadEME() { + var options = [{ + initDataType: "cenc", + videoType: test.track.type, + }]; + + return navigator.requestMediaKeySystemAccess("org.w3.clearkey", options) + .then((keySystemAccess) => { + return keySystemAccess.createMediaKeys(); + }, bail("Failed to request key system access.")) + + .then((mediaKeys) => { + video.setMediaKeys(mediaKeys); + + var session = mediaKeys.createSession(); + once(session, "message", (ev) => { + is(ev.messageType, "license-request", "Expected a license-request"); + session.update(GenerateClearKeyLicense(ev.message, test.keys)); + }); + + var json = JSON.stringify({ + "kids":Object.keys(test.keys).map(HexToBase64) + }); + var request = new TextEncoder().encode(json); + session.generateRequest("keyids", request) + .then(e => { + Log(test.name, "Request license success"); + }, reason => { + Log("Request license failed! " + reason); + }); + }); + } + + function DownloadMedia(url, type, mediaSource) { + return new Promise((resolve, reject) => { + var sourceBuffer = mediaSource.addSourceBuffer(type); + fetchWithXHR(url, (response) => { + once(sourceBuffer, "updateend", resolve); + sourceBuffer.appendBuffer(new Uint8Array(response)); + }); + }); + } + + function LoadMSE() { + // Only set the source of the video and download the tracks after we + // have set the license keys, so we don't hit the waitingforkey event + // unless samples are being incorrectly assigned the default key + // (and we can safely fail). + LoadEME() + .then(() => { + var ms = new MediaSource(); + video.src = URL.createObjectURL(ms); + + once(ms, "sourceopen", () => { + Promise.all(test.track.fragments.map(fragment => DownloadMedia(fragment, test.track.type, ms))) + .then(() => { + ms.endOfStream(); + video.play(); + }); + }); + + once(video, "ended", SimpleTest.finish); + }); + } + + SetupEMEPref(LoadMSE); + + </script> + </pre> +</body> + +</html>
\ No newline at end of file diff --git a/dom/media/test/test_eme_session_callable_value.html b/dom/media/test/test_eme_session_callable_value.html new file mode 100644 index 0000000000..24413a025d --- /dev/null +++ b/dom/media/test/test_eme_session_callable_value.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function Test() { + navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig) + .then(access => access.createMediaKeys()) + .then(mediaKeys => { + var initData = (new TextEncoder()).encode( 'this is an invalid license, and that is ok'); + var s = mediaKeys.createSession("temporary"); + s.generateRequest("cenc", initData); // ignore result. + // "update()" call should fail, because MediaKeySession is "not callable" + // yet, since CDM won't have had a chance to set the sessionId on MediaKeySession. + return s.update(initData); + }) + .then(()=>{ok(false, "An exception should be thrown; MediaKeySession should be not callable."); SimpleTest.finish();}, + ()=>{ok(true, "We expect this to fail; MediaKeySession should be not callable."); SimpleTest.finish();}); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(Test); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html b/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html new file mode 100644 index 0000000000..bb36b23a2e --- /dev/null +++ b/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function beginTest() { + var video = document.createElement("video"); + + navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig) + .then(function(keySystemAccess) { + return keySystemAccess.createMediaKeys(); + }) + .then(mediaKeys => { + return video.setMediaKeys(mediaKeys); + }) + .then(() => { + var ms = new MediaSource(); + ms.addEventListener("sourceopen", ()=>{ok(true, "MediaSource should open"); SimpleTest.finish();}); + video.addEventListener("error", ()=>{ok(false, "Shouldn't error."); SimpleTest.finish();}); + video.src = URL.createObjectURL(ms); + }); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_stream_capture_blocked_case1.html b/dom/media/test/test_eme_stream_capture_blocked_case1.html new file mode 100644 index 0000000000..228baee13a --- /dev/null +++ b/dom/media/test/test_eme_stream_capture_blocked_case1.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) +{ + // Case 1. setting MediaKeys on an element captured by MediaElementSource should fail. + var case1token = token + "_case1"; + let v1 = document.createElement("video"); + + function setMediaKeys() { + let p = new EMEPromise; + CreateMediaKeys(v1, test, case1token) + .then(mediaKeys => { + v1.setMediaKeys(mediaKeys) + .then(() => { + p.reject(`${case1token} setMediaKeys shouldn't succeed.`); + }, () => { + ok(true, TimeStamp(case1token) + " setMediaKeys failed as expected."); + p.resolve(); + }) + }, p.reject); + return p.promise; + } + + var context = new AudioContext(); + context.createMediaElementSource(v1); + v1.addEventListener("loadeddata", function(ev) { + ok(false, TimeStamp(case1token) + " should never reach loadeddata, as setMediaKeys should fail"); + }); + + manager.started(case1token); + + Promise.all([ + LoadTest(test, v1, case1token), + setMediaKeys()]) + .catch(reason => ok(false, reason)) + .then(() => { + CleanUpMedia(v1); + manager.finished(case1token); + }); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_stream_capture_blocked_case2.html b/dom/media/test/test_eme_stream_capture_blocked_case2.html new file mode 100644 index 0000000000..8fb8f5431b --- /dev/null +++ b/dom/media/test/test_eme_stream_capture_blocked_case2.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) +{ + // Case 2. creating a MediaElementSource on a media element should always succeed + // (no matter whether it's restricted content or not), and + var p1 = new EMEPromise; + var case2token = token + "_case2"; + let v2 = document.createElement("video"); + + v2.addEventListener("loadeddata", function(ev) { + ok(true, case2token + " should reach loadeddata"); + var threw = false; + try { + var context = new AudioContext(); + context.createMediaElementSource(v2); + } catch (e) { + threw = true; + } + ok(!threw, "Should always work when creating a MediaElementSource."); + p1.resolve(); + }); + + manager.started(case2token); + let p2 = SetupEME(v2, test, case2token); + + Promise.all([p1.promise, p2]) + .catch(reason => ok(false, reason)) + .then(() => { + CleanUpMedia(v2); + manager.finished(case2token); + }); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_stream_capture_blocked_case3.html b/dom/media/test/test_eme_stream_capture_blocked_case3.html new file mode 100644 index 0000000000..5703328d40 --- /dev/null +++ b/dom/media/test/test_eme_stream_capture_blocked_case3.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) +{ + // Case 3. capturing a media element with mozCaptureStream that has a MediaKeys should fail. + var p1 = new EMEPromise; + var case3token = token + "_case3"; + let v3 = document.createElement("video"); + + v3.addEventListener("loadeddata", function(ev) { + ok(true, TimeStamp(case3token) + " should reach loadeddata"); + var threw = false; + try { + v3.mozCaptureStreamUntilEnded(); + } catch (e) { + threw = true; + } + ok(threw, TimeStamp(case3token) + " Should throw an error calling mozCaptureStreamUntilEnded an EME video."); + p1.resolve(); + }); + + manager.started(case3token); + let p2 = SetupEME(v3, test, case3token); + + Promise.all([p1.promise, p2]) + .catch(reason => ok(false, reason)) + .then(() => { + CleanUpMedia(v3); + manager.finished(case3token); + }); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_unsetMediaKeys_then_capture.html b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html new file mode 100644 index 0000000000..c3691fe71c --- /dev/null +++ b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html @@ -0,0 +1,113 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="https://example.com:443/tests/dom/media/test/eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +// Test that if we can capture a video frame while playing clear content after +// removing the MediaKeys object which was used for a previous encrypted content +// playback on the same video element +function startTest(test, token) +{ + manager.started(token); + var sessions = []; + function onSessionCreated(session) { + sessions.push(session); + } + + function closeSessions() { + let p = new EMEPromise; + Promise.all(sessions.map(s => s.close())) + .then(p.resolve, p.reject); + return p.promise; + } + + let v = document.createElement("video"); + document.body.appendChild(v); + + let finish = new EMEPromise; + + function onVideoEnded(ev) { + ok(true, TimeStamp(token) + " (ENCRYPTED) content playback ended."); + + function playClearVideo() { + var p1 = once(v, 'loadeddata', (e) => { + ok(true, TimeStamp(token) + " Receiving event 'loadeddata' for (CLEAR) content."); + let canvasElem = document.createElement('canvas'); + document.body.appendChild(canvasElem); + let ctx2d = canvasElem.getContext('2d'); + + var gotTypeError = false; + try { + ctx2d.drawImage(v, 0, 0); + } catch (ex) { + if (ex instanceof TypeError) { + gotTypeError = true; + } + } + ok(!gotTypeError, TimeStamp(token) + " Canvas2D context drawImage succeed.") + }); + v.src = 'bipbop_225w_175kbps.mp4'; + v.play(); + Promise.all([p1]).then(() => { + manager.finished(token); + }, () => { + ok(false, TimeStamp(token) + " Something wrong."); + manager.finished(token); + }); + } + + closeSessions() + .then(() => { + v.setMediaKeys(null) + .then(() => { + ok(true, TimeStamp(token) + " Setting MediaKeys to null."); + playClearVideo(); + }, () => { + ok(false, TimeStamp(token) + " Setting MediaKeys to null."); + });; + }); + } + + once(v, "ended", onVideoEnded); + + // Create a MediaKeys object and set to HTMLMediaElement then start the playback. + Promise.all([ + LoadInitData(v, test, token), + CreateAndSetMediaKeys(v, test, token), + LoadTest(test, v, token)]) + .then(values => { + let initData = values[0]; + v.play(); + initData.map(ev => { + let session = v.mediaKeys.createSession(); + onSessionCreated(session); + MakeRequest(test, token, ev, session); + }); + }) + .then(() => { + return finish.promise; + }) + .catch(reason => ok(false, reason)) +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_eme_waitingforkey.html b/dom/media/test/test_eme_waitingforkey.html new file mode 100644 index 0000000000..2506c56fd1 --- /dev/null +++ b/dom/media/test/test_eme_waitingforkey.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Encrypted Media Extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="https://example.com:443/tests/dom/media/test/eme.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/* import-globals-from eme.js */ +var manager = new MediaTestManager; + +function startTest(test, token) +{ + manager.started(token); + + let v = document.createElement("video"); + document.body.appendChild(v); + + var gotWaitingForKey = 0; + var gotOnwaitingforkey = 0; + + let waitForKey = new EMEPromise; + v.addEventListener("waitingforkey", function() { + gotWaitingForKey += 1; + waitForKey.resolve(); + }); + + v.onwaitingforkey = function() { + gotOnwaitingforkey += 1; + }; + + v.addEventListener("loadedmetadata", function() { + ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v), + TimeStamp(token) + " isEncrypted should be true"); + is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content"); + }); + + let finish = new EMEPromise; + v.addEventListener("ended", function() { + ok(true, TimeStamp(token) + " got ended event"); + // We expect only one waitingForKey as we delay until all sessions are ready. + // I.e. one waitingForKey should be fired, after which, we process all sessions, + // so it should not be possible to be blocked by a key after that point. + ok(gotWaitingForKey == 1, "Expected number 1 wait, got: " + gotWaitingForKey); + ok(gotOnwaitingforkey == gotWaitingForKey, "Should have as many event listener calls as event handler calls, got: " + gotOnwaitingforkey); + + finish.resolve(); + }); + + Promise.all([ + LoadInitData(v, test, token), + CreateAndSetMediaKeys(v, test, token), + LoadTest(test, v, token), + waitForKey.promise]) + .then(values => { + let initData = values[0]; + return ProcessInitData(v, test, token, initData); + }) + .then(sessions => { + Log(token, "Updated all sessions, loading complete -> Play"); + v.play(); + finish.promise.then(() => CloseSessions(v, sessions)); + return finish.promise; + }) + .catch(reason => ok(false, reason)) + .then(() => manager.finished(token)); +} + +function beginTest() { + manager.runTests(gEMETests, startTest); +} + +SimpleTest.waitForExplicitFinish(); +SetupEMEPref(beginTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_empty_resource.html b/dom/media/test/test_empty_resource.html new file mode 100644 index 0000000000..8f65bb503d --- /dev/null +++ b/dom/media/test/test_empty_resource.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1094549 +--> +<head> + <title>Test for Bug 1094549</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094549">Mozilla Bug 1094549</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Shorter timeout for this test should finish soon. +SimpleTest.requestLongerTimeout(0.3); + +function finish(v) { + isnot(v.error, null, "should've got an error event"); + SimpleTest.finish(); +} + +function onload() { + info("iframe loaded"); + var v = SpecialPowers.wrap(document.body.getElementsByTagName("iframe")[0]) + .contentDocument.body.getElementsByTagName("video")[0]; + + // Got 'error' as expected, finish the test. + if (v.error) { + finish(v); + return; + } + + // Otherwise, wait for it. + v.onerror = function() { + finish(v); + } +} + +SimpleTest.waitForExplicitFinish(); +var f = document.createElement("iframe"); +// Assign a resource file with zero length and expect the error event from +// the video element since decoding metadata will fail. +f.src = "data:video/webm,"; +f.addEventListener("load", onload); +document.body.appendChild(f); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_error_in_video_document.html b/dom/media/test/test_error_in_video_document.html new file mode 100644 index 0000000000..e376ea95e3 --- /dev/null +++ b/dom/media/test/test_error_in_video_document.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=604067 +--> +<head> + <title>Test for Bug 604067</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=604067">Mozilla Bug 604067</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 604067 **/ + +function documentVideo() { + return document.body.getElementsByTagName("iframe")[0] + .contentDocument.body.getElementsByTagName("video")[0]; +} + +function check() { + var v = documentVideo(); + + // Debug info for Bug 608634 + ok(true, "iframe src=" + document.body.getElementsByTagName("iframe")[0].src); + is(v.readyState, v.HAVE_NOTHING, "Ready state"); + + isnot(v.error, null, "Error object"); + is(v.networkState, v.NETWORK_NO_SOURCE, "Network state"); + is(v.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected media not supported error"); + SimpleTest.finish(); +} + +// Find an error test that we'd think we should be able to play (if it +// wasn't already known to fail). +var t = getPlayableVideo(gErrorTests); +if (!t) { + todo(false, "No types supported"); +} else { + SimpleTest.waitForExplicitFinish(); + + var f = document.createElement("iframe"); + f.src = t.name; + f.addEventListener("load", check); + document.body.appendChild(f); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_error_on_404.html b/dom/media/test/test_error_on_404.html new file mode 100644 index 0000000000..94f7cc2ea7 --- /dev/null +++ b/dom/media/test/test_error_on_404.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=476731 +--> +<head> + <title>Test for Bug 476731</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=476731">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> +/** Test for Bug 476731 **/ + +var videos = []; + +function FinishedLoads() { + if (videos.length == 0) + return false; + for (var i=0; i<videos.length; ++i) { + if (videos[i]._loadedData) { + // A video loadeddata, it should have 404'd. Signal the end of the test + // so the error will be reported. + return true; + } + if (!videos[i]._loadError) { + return false; + } + } + return true; +} + +function loadError(evt) { + var v = evt.target; + is(v._loadError, false, "Shouldn't receive multiple error events for " + v.src); + v._loadError = true; + is(v._loadStart, true, "Receive loadstart for " + v.src); + is(v._loadedData, false, "Shouldn't receive loadeddata for " + v.src); + if (FinishedLoads(videos)) + SimpleTest.finish(); +} + +function loadedData(evt) { + evt.target._loadedData = true; +} + +function loadStart(evt) { + evt.target._loadStart = true; +} + +// Create all video objects. +for (var i=0; i<g404Tests.length; ++i) { + var v = document.createElement("video"); + v.preload = "metadata"; + var test = g404Tests[i]; + if (!v.canPlayType(test.type)) { + continue; + } + v.src = test.name; + v._loadedData = false; + v._loadStart = false; + v._loadError = false; + v.addEventListener("error", loadError); + v.addEventListener("loadstart", loadStart); + v.addEventListener("loadeddata", loadedData); + document.body.appendChild(v); // Will start load. + videos.push(v); +} + +if (videos.length == 0) { + todo(false, "No types supported"); +} else { + SimpleTest.waitForExplicitFinish(); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_fastSeek-forwards.html b/dom/media/test/test_fastSeek-forwards.html new file mode 100644 index 0000000000..1e50021b13 --- /dev/null +++ b/dom/media/test/test_fastSeek-forwards.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1022913 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1022913</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1022913">Mozilla Bug 1022913</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + <script type="application/javascript"> + + // Test that if we're doing a fastSeek() forwards that we don't end up + // seeking backwards. This can happen if the keyframe before the seek + // target is before the current playback position. We'd prefer to seek to + // the keyframe after the seek target in this case, but we don't implement + // this yet (bug 1026330). + var manager = new MediaTestManager; + + var onSecondSeekComplete = function(event) { + var v = event.target; + v.removeEventListener("seeked", onSecondSeekComplete); + ok(v.currentTime >= v.firstSeekTarget, v.name + " seek never go backwards. time=" + v.currentTime + " firstSeekTarget=" + v.firstSeekTarget + " secondSeekTarget=" + v.secondSeekTarget); + manager.finished(v.token); + removeNodeAndSource(v); + }; + + var onFirstSeekComplete = function(event) { + var v = event.target; + v.removeEventListener("seeked", onFirstSeekComplete); + // Seek to 75% of the way between the start and the first keyframe + // using fastSeek. We then test that the currentTime doesn't drop back + // to the previous keyframe, currentTime should go forwards. + v.addEventListener("seeked", onSecondSeekComplete); + v.secondSeekTarget = v.keyframes[1] * 0.75; + v.fastSeek(v.secondSeekTarget); + } + + var onLoadedMetadata = function(event) { + // Seek to the mid-point between the start and the first keyframe. + var v = event.target; + v.removeEventListener("loadedmetadata", onLoadedMetadata); + v.addEventListener("seeked", onFirstSeekComplete); + v.firstSeekTarget = v.keyframes[1] * 0.5; + v.currentTime = v.firstSeekTarget; + } + + function startTest(test, token) { + manager.started(token); + let v = document.createElement("video"); + v.src = test.name; + v.name = test.name; + v.preload = "metadata"; + v.token = token; + v.target = 0; + v.keyframes = test.keyframes; + v.keyframeIndex = 0; + ok(v.keyframes.length >= 2, v.name + " - video should have at least two sync points"); + v.addEventListener("loadedmetadata", onLoadedMetadata); + document.body.appendChild(v); + } + + manager.runTests(gFastSeekTests, startTest); + + </script> +</body> +</html> diff --git a/dom/media/test/test_fastSeek.html b/dom/media/test/test_fastSeek.html new file mode 100644 index 0000000000..e3078c6c4e --- /dev/null +++ b/dom/media/test/test_fastSeek.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=778077 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 778077</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=778077">Mozilla Bug 778077</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + <script type="application/javascript"> + + /** Test for Bug 778077 - HTMLMediaElement.fastSeek() **/ + // Iterate through a list of keyframe timestamps, and seek to + // halfway between the keyframe and the keyframe after it. + var manager = new MediaTestManager; + + function doSeek(v) { + // fastSeek to half way between this keyframe and the next, or if this is the last + // keyframe seek to halfway between this keyframe and the end of media. + var nextKeyFrame = (v.keyframeIndex + 1) < v.keyframes.length ? v.keyframes[v.keyframeIndex + 1] : v.duration; + v.target = (v.keyframes[v.keyframeIndex] + nextKeyFrame) / 2; + v.fastSeek(v.target); + ok(Math.abs(v.currentTime - v.target) < 0.01, + v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime + ") should be close to seek target initially"); + } + + function onloadedmetadata(event) { + var v = event.target; + doSeek(v); + } + + function onseeked(event) { + var v = event.target; + var keyframe = v.keyframes[v.keyframeIndex]; + + // Check that the current time ended up roughly after the keyframe. + // We must be a bit fuzzy here, as the decoder backend may actually + // seek to the audio sample prior to the keyframe. + ok(v.currentTime >= keyframe - 0.05, + v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime + + ") should be end up roughly after keyframe (" + keyframe + ") after fastSeek"); + + ok(v.currentTime <= v.target, + v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime + + ") should be end up less than target after fastSeek"); + + v.keyframeIndex++ + if (v.keyframeIndex == v.keyframes.length) { + v.src = ""; + v.remove(); + manager.finished(v.token); + } else { + doSeek(v); + } + } + + function startTest(test, token) { + manager.started(token); + let v = document.createElement("video"); + v.src = test.name; + v.name = test.name; + v.preload = "metadata"; + v.token = token; + v.target = 0; + v.keyframes = test.keyframes; + v.keyframeIndex = 0; + ok(v.keyframes.length > 0, v.name + " - video should have at least one sync point"); + v.addEventListener("loadedmetadata", onloadedmetadata); + v.addEventListener("seeked", onseeked); + document.body.appendChild(v); + } + + manager.runTests(gFastSeekTests, startTest); + + </script> +</body> +</html> diff --git a/dom/media/test/test_fragment_noplay.html b/dom/media/test/test_fragment_noplay.html new file mode 100644 index 0000000000..6a1119f342 --- /dev/null +++ b/dom/media/test/test_fragment_noplay.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: fragment tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="fragment_noplay.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +// Fragment parameters to try +var gFragmentParams = [ + // W3C Media fragment tests + // http://www.w3.org/2008/WebVideo/Fragments/TC/ua-test-cases + { fragment: "#t=banana", start: null, end: null }, // TC0027-UA + { fragment: "#t=3,banana", start: null, end: null }, // TC0028-UA + { fragment: "#t=banana,7", start: null, end: null }, // TC0029-UA + { fragment: "#t='3'", start: null, end: null }, // TC0030-UA + { fragment: "#t=3-7", start: null, end: null }, // TC0031-UA + { fragment: "#t=3:7", start: null, end: null }, // TC0032-UA + { fragment: "#t=3,7,9", start: null, end: null }, // TC0033-UA + { fragment: "#t%3D3", start: null, end: null }, // TC0034-UA + { fragment: "#%74=3", start: 3, end: null }, // TC0035-UA + { fragment: "#t=%33", start: 3, end: null }, // TC0036-UA + { fragment: "#t=3%2C7", start: 3, end: 7 }, // TC0037-UA + { fragment: "#t=%6Ept:3", start: 3, end: null }, // TC0038-UA + { fragment: "#t=npt%3A3", start: 3, end: null }, // TC0039-UA + { fragment: "#t=-1,3", start: null, end: null }, // TC0044-UA + { fragment: "#t=3&", start: 3, end: null }, // TC0051-UA + { fragment: "#u=12&t=3", start: 3, end: null }, // TC0052-UA + { fragment: "#t=foo:7&t=npt:3", start: 3, end: null }, // TC0053-UA + { fragment: "#&&=&=tom&jerry=&t=3&t=meow:0#", start: 3, end: null }, // TC0054-UA + { fragment: "#t=7&t=3", start: 3, end: null }, // TC0055-UA + { fragment: "#T=3,7", start: null, end: null }, // TC0058-UA + { fragment: "#t=", start: null, end: null }, // TC0061-UA + { fragment: "#t=.", start: null, end: null }, // TC0062-UA + { fragment: "#t=.0", start: null, end: null }, // TC0063-UA + { fragment: "#t=0s", start: null, end: null }, // TC0064-UA + { fragment: "#t=,0s", start: null, end: null }, // TC0065-UA + { fragment: "#t=0s,0s", start: null, end: null }, // TC0066-UA + { fragment: "#t=00:00:00s", start: null, end: null }, // TC0067-UA + { fragment: "#t=s", start: null, end: null }, // TC0068-UA + { fragment: "#t=npt:", start: null, end: null }, // TC0069-UA + { fragment: "#t=1e-1:", start: null, end: null }, // TC0070-UA + { fragment: "#t=00:00:01.1e-1", start: null, end: null }, // TC0071-UA + { fragment: "#t=3.", start: 3, end: null }, // TC0072-UA + { fragment: "#t=0:0:0", start: null, end: null }, // TC0073-UA + { fragment: "#t=0:00:60", start: null, end: null }, // TC0074-UA + { fragment: "#t=0:01:60", start: null, end: null }, // TC0075-UA + { fragment: "#t=0:60:00", start: null, end: null }, // TC0076-UA + { fragment: "#t=0:000:000", start: null, end: null }, // TC0077-UA + { fragment: "#t=00:00:03,00:00:07", start: 3, end: 7 }, // TC0078-UA + { fragment: "#t=3,00:00:07", start: 3, end: 7 }, // TC0079-UA + { fragment: "#t=00:00.", start: null, end: null }, // TC0080-UA + { fragment: "#t=0:00:00.", start: null, end: null }, // TC0081-UA + { fragment: "#t=0:00:10e-1", start: null, end: null }, // TC0082-UA + { fragment: "#t=0:00:60.000", start: null, end: null }, // TC0083-UA + { fragment: "#t=0:60:00.000", start: null, end: null }, // TC0084-UA + { fragment: "#t=3,7&t=foo", start: 3, end: 7 }, // TC0085-UA + { fragment: "#foo&t=3,7", start: 3, end: 7 }, // TC0086-UA + { fragment: "#t=3,7&foo", start: 3, end: 7 }, // TC0087-UA + { fragment: "#t=3,7&&", start: 3, end: 7 }, // TC0088-UA + { fragment: "#&t=3,7", start: 3, end: 7 }, // TC0089-UA + { fragment: "#&&t=3,7", start: 3, end: 7 }, // TC0090-UA + { fragment: "#&t=3,7&", start: 3, end: 7 }, // TC0091-UA + { fragment: "#t%3d10", start: null, end: null }, // TC0092-UA + { fragment: "#t=10%26", start: null, end: null }, // TC0093-UA + { fragment: "#&t=3,7,", start: null, end: null } // TC0094-UA +]; + +function createTestArray() { + var tests = []; + var tmpVid = document.createElement("video"); + + for (var testNum=0; testNum<gFragmentTests.length; testNum++) { + var test = gFragmentTests[testNum]; + if (!tmpVid.canPlayType(test.type)) { + continue; + } + + for (var fragNum=0; fragNum<gFragmentParams.length; fragNum++) { + var p = gFragmentParams[fragNum]; + var t = {}; + t.name = test.name + p.fragment; + t.type = test.type; + t.duration = test.duration; + t.start = p.start; + t.end = p.end; + tests.push(t); + } + } + return tests; +} + +function startTest(test, token) { + var video = document.createElement('video'); + manager.started(token); + video.preload = "metadata"; + video.src = test.name; + video.token = token; + video.controls = true; + document.body.appendChild(video); + var name = test.name + " fragment test"; + var localIs = function(n) { return function(a, b, msg) { + is(a, b, n + ": " + msg); + }}(name); + var localOk = function(n) { return function(a, msg) { + ok(a, n + ": " + msg); + }}(name); + var localFinish = function(v, m) { return function() { + removeNodeAndSource(v); + m.finished(v.token); + }}(video, manager); + window.test_fragment_noplay(video, test.start, test.end, localIs, localOk, localFinish); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_fragment_play.html b/dom/media/test/test_fragment_play.html new file mode 100644 index 0000000000..eab422bab1 --- /dev/null +++ b/dom/media/test/test_fragment_play.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="fragment_play.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +PARALLEL_TESTS = 1; +var manager = new MediaTestManager; + +// Fragment parameters to try. These tests +// try playing the video. Tests for other fragment +// formats are in test_fragment_noplay.html. +var gFragmentParams = [ + { fragment: "", start: null, end: null }, + { fragment: "#t=,", start: null, end: null }, + { fragment: "#t=3,3", start: null, end: null }, + { fragment: "#t=7,3", start: null, end: null }, + { fragment: "#t=7,15", start: 7, end: null }, + { fragment: "#t=15,20", start: 9.287982, end: null }, + { fragment: "#t=5", start: 5, end: null }, + { fragment: "#t=5.5", start: 5.5, end: null }, + { fragment: "#t=5,", start: null, end: null }, + { fragment: "#t=,5", start: 0, end: 5, todo: "See bugs 682141 and 720248" }, + { fragment: "#t=2.5,5.5", start: 2.5, end: 5.5, todo: "See bugs 682141 and 720248" }, + { fragment: "#t=1,2.5", start: 1, end: 2.5, todo: "See bugs 682141 and 720248" }, + { fragment: "#t=,15", start: 0, end: null } +]; + +function createTestArray() { + var tests = []; + var tmpVid = document.createElement("video"); + + for (var testNum=0; testNum<gFragmentTests.length; testNum++) { + var test = gFragmentTests[testNum]; + if (!tmpVid.canPlayType(test.type)) { + continue; + } + + for (var fragNum=0; fragNum<gFragmentParams.length; fragNum++) { + var p = gFragmentParams[fragNum]; + var t = {}; + t.name = test.name + p.fragment; + t.type = test.type; + t.duration = test.duration; + t.start = p.start; + t.end = p.end; + t.todo = p.todo; + tests.push(t); + } + } + return tests; +} + +function startTest(test, token) { + if (test.todo) { + todo(false, test.todo); + return; + } + var video = document.createElement('video'); + manager.started(token); + video.preload = "metadata"; + video.src = test.name; + video.token = token; + video.controls = true; + document.body.appendChild(video); + var name = test.name + " fragment test"; + var localIs = function(n) { return function(a, b, msg) { + is(a, b, n + ": " + msg); + }}(name); + var localOk = function(n) { return function(a, msg) { + ok(a, n + ": " + msg); + }}(name); + var localFinish = function(v, m) { return function() { + removeNodeAndSource(v); + m.finished(v.token); + }}(video, manager); + window.test_fragment_play(video, test.start, test.end, localIs, localOk, localFinish); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_hls_player_independency.html b/dom/media/test/test_hls_player_independency.html new file mode 100644 index 0000000000..cea5c140ed --- /dev/null +++ b/dom/media/test/test_hls_player_independency.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of 2 HLS video at the same page </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + <div id='player1'> + <video id='player4x3' controls autoplay> + </video> + </div> + <p> 4x3 basic stream<span> + <span> + <div height = 10> + <span> + <div id='player2'> + <video id='player16x9' controls autoplay> + </video> + </div> + <p> 16x9 basic stream<span> + +<script class="testbody" type="text/javascript"> + +function startTest() { + var v4x3 = document.getElementById('player4x3'); + var v16x9 = document.getElementById('player16x9'); + + var p1 = once(v4x3, 'ended', function onended(e) { + is(v4x3.videoWidth, 400, "4x3 content, the width should be 400."); + is(v4x3.videoHeight, 300, "4x3 content, the height should be 300."); + }); + + var p2 = once(v16x9, 'ended', function onended(e) { + is(v16x9.videoWidth, 416, "16x9 content, the width should be 416."); + is(v16x9.videoHeight, 234, "16x9 content, the height should be 234."); + }); + + v4x3.src = serverUrl + "/bipbop_4x3_single.m3u8"; + v16x9.src = serverUrl + "/bipbop_16x9_single.m3u8"; + Promise.all([p1, p2]).then(() => { + SimpleTest.finish(); + }); +} + +startTest(); +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_imagecapture.html b/dom/media/test/test_imagecapture.html new file mode 100644 index 0000000000..d61dd358bb --- /dev/null +++ b/dom/media/test/test_imagecapture.html @@ -0,0 +1,128 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1041393 +--> +<head> + <meta charset="utf-8"> + <title>ImageCapture tests</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1041393">ImageCapture tests</a> +<script type="application/javascript"> + +// Check if the callback returns even no JS reference on it. +async function gcTest(track) { + const repeat = 100; + const promises = []; + for (let i = 0; i < repeat; ++i) { + const imageCapture = new ImageCapture(track); + promises.push(new Promise((resolve, reject) => { + imageCapture.onphoto = resolve; + imageCapture.onerror = () => + reject(new Error(`takePhoto gcTest failure for capture ${i}`)); + })); + imageCapture.takePhoto(); + } + info("Call gc "); + SpecialPowers.gc(); + await Promise.all(promises); +} + +// Continue calling takePhoto() in rapid succession. +async function rapidTest(track) { + const repeat = 100; + const imageCapture = new ImageCapture(track); + // "error" can fire synchronously in `takePhoto` + const errorPromise = new Promise(r => imageCapture.onerror = r); + for (let i = 0; i < repeat; ++i) { + imageCapture.takePhoto(); + } + for (let i = 0; i < repeat; ++i) { + await Promise.race([ + new Promise(r => imageCapture.onphoto = r), + errorPromise.then(() => Promise.reject(new Error("Capture failed"))), + ]); + } +} + +// Check if the blob is decodable. +async function blobTest(track) { + const imageCapture = new ImageCapture(track); + // "error" can fire synchronously in `takePhoto` + const errorPromise = new Promise(r => imageCapture.onerror = r); + imageCapture.takePhoto(); + const blob = await Promise.race([ + new Promise(r => imageCapture.onphoto = r), + errorPromise.then(() => Promise.reject(new Error("Capture failed"))), + ]); + + const img = new Image(); + img.src = URL.createObjectURL(blob.data); + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = () => reject(new Error("Decode failed")); + }); +} + +// It should return an error event after disabling video track. +async function trackTest(track) { + const imageCapture = new ImageCapture(track); + track.enabled = false; + try { + const errorPromise = new Promise(r => imageCapture.onerror = r); + imageCapture.takePhoto(); + const error = await Promise.race([ + errorPromise, + new Promise(r => imageCapture.onphoto = r).then( + () => Promise.reject(new Error("Disabled video track should fail"))), + ]); + is(error.imageCaptureError.code, error.imageCaptureError.PHOTO_ERROR, + "Expected error code") + } finally { + track.enabled = true; + } +} + +async function init() { + // Use loopback camera if available, otherwise fake. + // MediaTrackGraph will be the backend of ImageCapture. + await setupGetUserMediaTestPrefs(); + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + return stream.getVideoTracks()[0]; +} + +async function start() { + try { + const track = await init(); + info("ImageCapture track disable test."); + await trackTest(track); + info("ImageCapture blob test."); + await blobTest(track); + info("ImageCapture rapid takePhoto() test."); + await rapidTest(track); + info("ImageCapture multiple instances test."); + await gcTest(track); + } catch (e) { + ok(false, "Unexpected error during test: " + e); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.requestCompleteLog(); +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.imagecapture.enabled", true], + ["media.navigator.permission.disabled", true], + ], +}, start); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_info_leak.html b/dom/media/test/test_info_leak.html new file mode 100644 index 0000000000..41b01c74c1 --- /dev/null +++ b/dom/media/test/test_info_leak.html @@ -0,0 +1,175 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=478957 +--> +<head> + <title>Test for Bug 478957</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478957">Mozilla Bug 478957</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<div id="log" style="font-size: small;"></div> + +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 478957 **/ + +// Tests whether we leak events and state change info when loading stuff from local files from a webserver. + +var manager = new MediaTestManager; + +var gEventTypes = [ 'loadstart', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'play', 'pause', 'loadedmetadata', 'loadeddata', 'waiting', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange' ]; + +var gExpectedEvents = ['loadstart', 'error']; + +function createTestArray() { + var tests = []; + var tmpVid = document.createElement("video"); + + return makeInfoLeakTests().then(infoLeakTests => { + for (var testNum=0; testNum < infoLeakTests.length; testNum++) { + var test = infoLeakTests[testNum]; + if (!tmpVid.canPlayType(test.type)) { + continue; + } + + var t = {}; + t.name = test.src; + t.type = test.type; + + tests.push(t); + } + return tests; + }) +} + +function log(msg) { + info(msg); + var l = document.getElementById('log'); + // eslint-disable-next-line no-unsanitized/property + l.innerHTML += msg + "<br>"; +} + +function finish(v) { + log("finish: " + v.name); + clearInterval(v.checkStateInterval); + + for (var i=0; i<gEventTypes.length; i++) { + v.removeEventListener(gEventTypes[i], listener); + } + removeNodeAndSource(v); + + manager.finished(v.token); + v = null; +} + +function listener(evt) { + var v = evt.target; + log(filename(v.name) + ': got ' + evt.type); + + // On slow machines like B2G emulator, progress timer could time out before + // receiving any HTTP notification. We will ignore the 'stalled' event to + // pass the tests. + if (evt.type == 'stalled') { + return; + } + + ok(v.eventNum < gExpectedEvents.length, filename(v.name) + " Too many events received"); + var expected = (v.eventNum < gExpectedEvents.length) ? gExpectedEvents[v.eventNum] : "NoEvent"; + is(evt.type, expected, filename(v.name) + " Events received in wrong order"); + v.eventNum++; + if (v.eventNum == gExpectedEvents.length) { + // In one second, move onto the next test. This give a chance for any + // other events to come in. Note: we don't expect any events to come + // in, unless we've leaked some info, and 1 second should be enough time + // for the leak to show up. + setTimeout(function() {finish(v);}, 1000); + } +} + +function createMedia(type, src, token) { + var tag = getMajorMimeType(type); + var v = document.createElement(tag); + for (var i=0; i<gEventTypes.length; i++) { + v.addEventListener(gEventTypes[i], listener); + } + v.preload = "metadata"; + v.src = src; + v.name = src; + document.body.appendChild(v); + v.eventNum = 0; + v.token = token; + setTimeout( + function() { + v.checkStateInterval = setInterval(function(){checkState(v);},1); + }, 0); +} + +// Define our own ok() and is() functions. The mochitest ones take ages constructing the log +// of all the passes, so only report failures. +function test_ok(b, msg) { + if (!b) { + log("FAILED test_ok: " + msg); + ok(b, msg); + } +} + +function test_is(a, b, msg) { + if (a != b) { + log("FAILED test_is: " + msg); + is(a,b,msg); + } +} + +function filename(uri) { + return uri.substr(uri.lastIndexOf("/")+1); +} + +function checkState(v) { + test_ok(v.networkState <= HTMLMediaElement.NETWORK_LOADING || + v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE, + "NetworkState of " + v.networkState + " was leaked."); + test_ok(v.readyState == HTMLMediaElement.HAVE_NOTHING, + "Ready state of " + v.readyState + " was leaked"); + test_is(v.seeking, false, "Seeking leaked"); + test_is(v.currentTime, 0, "Leaked currentTime"); + test_ok(isNaN(v.duration), "Leaked duration"); + test_is(v.paused, true, "Paused leaked"); + test_is(v.ended, false, "Ended leaked"); + test_is(v.autoplay, false, "Autoplay leaked"); + test_is(v.controls, false, "Controls leaked"); + test_is(v.muted, false, "muted leaked"); + test_ok(v.error==null || v.error.code==MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, + "Error code should not exist or be SRC_NOT_SUPPORTED. v.error=" + + (v.error ? v.error.code : "null")); + test_ok(filename(v.currentSrc) == filename(v.name) || + v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE, + "currentSrc should match candidate uri, if we've got a valid source"); +} + + +function startTest(test, token) { + manager.started(token); + log("Testing: " + test.type + " @ " + test.name); + createMedia(test.type, test.name, token); +} + +SimpleTest.waitForExplicitFinish(); +createTestArray().then(testArray => { + manager.runTests(testArray, startTest); +}); + +</script> +</pre> + +</body> +</html> diff --git a/dom/media/test/test_invalid_reject.html b/dom/media/test/test_invalid_reject.html new file mode 100644 index 0000000000..583847fe12 --- /dev/null +++ b/dom/media/test/test_invalid_reject.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8" /> + <title>Test rejection of invalid files</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var v = document.createElement('video'); + manager.started(token); + + // Set up event handlers. Seeing any of these is a failure. + function badEvent(type) { return function(e) { + ok(false, test.name + " should not fire '" + type + "' event"); + }}; + var events = [ + 'loadeddata', 'load', + 'canplay', 'canplaythrough', + 'playing' + ]; + events.forEach( function(e) { + v.addEventListener(e, badEvent(e)); + }); + + // Seeing a decoder error is a success. + v.addEventListener("error", function onerror(e) { + if (v.readyState == v.HAVE_NOTHING) { + is(v.error.code, v.error.MEDIA_ERR_SRC_NOT_SUPPORTED, + "decoder should reject " + test.name); + } else { + is(v.error.code, v.error.MEDIA_ERR_DECODE, + "decoder should reject " + test.name); + } + v.removeEventListener('error', onerror); + manager.finished(token); + }); + + // Now try to load and play the file, which should result in the + // error event handler above being called, terminating the test. + document.body.appendChild(v); + v.src = test.name; + v.play(); +} + +manager.runTests(gInvalidTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_invalid_reject_play.html b/dom/media/test/test_invalid_reject_play.html new file mode 100644 index 0000000000..3e658f94b8 --- /dev/null +++ b/dom/media/test/test_invalid_reject_play.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8" /> + <title>Test rejection of invalid files during playback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var v = document.createElement('video'); + manager.started(token); + + // Seeing a decoder error is a success. + v.addEventListener("error", function onerror(e) { + is(v.error.code, v.error.MEDIA_ERR_DECODE, + "decoder should reject " + test.name); + v.removeEventListener("error", onerror); + manager.finished(token); + }); + + v.addEventListener("ended", function onended(e) { + ok(false, "decoder should have rejected file before playback ended"); + v.removeEventListener("ended", onended); + manager.finished(token); + }); + + document.body.appendChild(v); + v.src = test.name; + v.play(); +} + +manager.runTests(gInvalidPlayTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_invalid_seek.html b/dom/media/test/test_invalid_seek.html new file mode 100644 index 0000000000..8aa514b977 --- /dev/null +++ b/dom/media/test/test_invalid_seek.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: invalide seek test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<video id='v'></video> +<pre id="test"> +<script class="testbody" type="text/javascript"> +// http://www.whatwg.org/specs/web-apps/current-work/#dom-media-seek +// If the media element's readyState is HAVE_NOTHING, then the user agent +// must raise an InvalidStateError exception. +var v = document.getElementById('v'); +var passed = false; + +ok(v.readyState == HTMLMediaElement.HAVE_NOTHING, + "Invalid ready state in media element"); + +try { + v.seek(1); +} +catch(e) { + passed = true; +} + +ok(passed, "Video did not raise error during invalid seek"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_load.html b/dom/media/test/test_load.html new file mode 100644 index 0000000000..de8fd63948 --- /dev/null +++ b/dom/media/test/test_load.html @@ -0,0 +1,217 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=479859 +--> +<head> + <title>Test for Bug 479859</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479859">Mozilla Bug 479859</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + +function log(msg) { + //document.getElementById('log').innerHTML += "<p>" + msg + "</p>"; +} + +// We don't track: progress, canplay, canplaythrough and stalled events, +// as these can be delivered out of order, and/or multiple times. +var gEventTypes = [ 'loadstart', 'abort', 'error', 'emptied', 'play', + 'pause', 'loadedmetadata', 'loadeddata', 'waiting', 'playing', 'seeking', + 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange' ]; + +var gEventNum = 0; +var gTestNum = 0; +var gTestFileNum = 0; +var gExpectedEvents = null; +var gTest = null; +var gTestName = "?"; + +function listener(evt) { + log('event ' + evt.type); + ok(gEventNum < gExpectedEvents.length, gTestName+" - corrent number of events received"); + var expected = (gEventNum < gExpectedEvents.length) ? gExpectedEvents[gEventNum] : "NoEvent"; + is(evt.type, expected, gTestName+" - events received in order"); + gEventNum++; + if (gEventNum == gExpectedEvents.length) { + setTimeout(nextTest, 0); + } +} + +function source_error(evt) { + log('event source_error'); + ok(evt.type == "error", "Should only get error events here"); + ok(gEventNum < gExpectedEvents.length, gTestName+" - corrent number of events received"); + var expected = (gEventNum < gExpectedEvents.length) ? gExpectedEvents[gEventNum] : "NoEvent"; + is("source_error", expected, gTestName+" - events received in order"); + gEventNum++; + if (gEventNum == gExpectedEvents.length) { + setTimeout(nextTest, 0); + } +} + +var gMedia = null; + +function createMedia(tag) { + gMedia = document.createElement(tag); + gMedia.preload = "metadata"; + for (var i=0; i<gEventTypes.length; i++) { + gMedia.addEventListener(gEventTypes[i], listener); + } +} + +function addSource(src, type) { + var s = document.createElement("source"); + s.addEventListener("error", source_error); + s.src = src; + s.type = type; + gMedia.appendChild(s); + return s; +} + +function prependSource(src, type) { + var s = document.createElement("source"); + s.addEventListener("error", source_error); + s.src = src; + s.type = type; + gMedia.insertBefore(s, gMedia.firstChild); + return s; +} + +var gTests = [ + { + // Test 0: adding video to doc, then setting src should load implicitly. + create(src, type) { + document.body.appendChild(gMedia); + gMedia.src = src; + }, + expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata'] + }, { + // Test 1: adding video to doc, then adding source. + create(src, type) { + document.body.appendChild(gMedia); + addSource(src, type); + }, + expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata'] + },{ + // Test 2: video with multiple source, the first of which are bad, we should load the last, + // and receive error events for failed loads on the source children. + create(src, type) { + document.body.appendChild(gMedia); + addSource("404a", type); + addSource("404b", type); + addSource(src, type); + }, + expectedEvents: ['loadstart', 'source_error', 'source_error', 'durationchange', 'loadedmetadata', 'loadeddata'] + }, { + // Test 3: video with bad src, good <source>, ensure that <source> aren't used. + create(src, type) { + gMedia.src = "404a"; + addSource(src, type); + document.body.appendChild(gMedia); + }, + expectedEvents: ['loadstart', 'error'] + }, { + // Test 4: video with only bad source, loading, then adding a good source + // - should resume load. + create(src, type) { + addSource("404a", type); + var s2 = addSource("404b", type); + s2.addEventListener("error", + function(e) { + // Should awaken waiting load, causing successful load. + addSource(src, type); + }); + document.body.appendChild(gMedia); + }, + expectedEvents: ['loadstart', 'source_error', 'source_error', 'durationchange', 'loadedmetadata', 'loadeddata'] + }, { + // Test 5: video with only 1 bad source, let it fail to load, then prepend + // a good <source> to the video, it shouldn't be selected, because the + // "pointer" should be after the last child - the bad source. + prepended: false, + create(src, type) { + var prepended = false; + addSource("404a", type); + var s2 = addSource("404b", type); + s2.addEventListener("error", + function(e) { + // Should awaken waiting load, causing successful load. + if (!prepended) { + prependSource(src, type); + prepended = true; + } + }); + document.body.appendChild(gMedia); + }, + expectedEvents: ['loadstart', 'source_error', 'source_error'] + }, { + // Test 6: (Bug 1165203) preload="none" then followed by an explicit + // call to load() should load metadata + create(src, type) { + gMedia.preload = "none"; + gMedia.src = src; + document.body.appendChild(gMedia); + addSource(src, type); + gMedia.load(); + }, + expectedEvents: ['emptied', 'loadstart', 'durationchange', 'loadedmetadata', 'loadeddata'] + } +]; + +function nextTest() { + if (gMedia) { + for (var i=0; i<gEventTypes.length; i++) { + gMedia.removeEventListener(gEventTypes[i], listener); + } + removeNodeAndSource(gMedia); + gMedia = null; + } + gEventNum = 0; + + if (gTestNum == gTests.length) { + gTestNum = 0; + ++gTestFileNum; + if (gTestFileNum == gSmallTests.length) { + SimpleTest.finish(); + return; + } + } + + var src = gSmallTests[gTestFileNum].name; + var type = gSmallTests[gTestFileNum].type; + + var t = gTests[gTestNum]; + gTestNum++; + + createMedia(type.match(/^audio\//) ? "audio" : "video"); + if (!gMedia.canPlayType(type)) { + // Unsupported type, skip to next test + nextTest(); + return; + } + + gTestName = "Test " + src + " " + (gTestNum - 1); + log("Starting " + gTestName); + gExpectedEvents = t.expectedEvents; + + t.create(src, type); +} + +addLoadEvent(nextTest); +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +<div id="log" style="font-size: small"></div> +</body> +</html> diff --git a/dom/media/test/test_load_candidates.html b/dom/media/test/test_load_candidates.html new file mode 100644 index 0000000000..2bc1eb531b --- /dev/null +++ b/dom/media/test/test_load_candidates.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=465458 +--> +<head> + <title>Test for Bug 465458</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465458">Mozilla Bug 465458</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 465458 **/ + +var manager = new MediaTestManager; + +function finish(evt) { + var v = evt.target; + is(v._error, 2, "Should have received 2 error events before loaded"); + v._finished = true; + // remove error event handler, because this would otherwise + // cause a failure on Windows 7, see bug 1024535 + v.onerror = null; + v.remove(); + manager.finished(v.token); +} + +function errorHandler(evt) { + evt.target.parentNode._error++; +} + +var extension = { + "audio/wav" : "wav", + "audio/x-wav": "wav", + "video/ogg" : "ogv", + "audio/ogg" : "oga", + "video/webm" : "webm" +}; + +function startTest(test, token) { + var v = document.createElement('video'); + v.preload = "auto"; + v.onerror = function(){ok(false,"Error events on source children should not bubble");} + v.token = token; + manager.started(token); + v._error = 0; + v._finished = false; + v._name = test.name; + + var s1 = document.createElement("source"); + s1.type = test.type; + s1.src = "404." + extension[test.type]; + s1.addEventListener("error", errorHandler); + v.appendChild(s1); + + var s2 = document.createElement("source"); + s2.type = test.type; + s2.src = "test_load_candidates.html"; // definitely an invalid media file, regardless of its actual mime type... + s2.addEventListener("error", errorHandler); + v.appendChild(s2); + + var s3 = document.createElement("source"); + s3.type = test.type; + s3.src = test.name; + v.appendChild(s3); + + v.addEventListener("loadeddata", finish); + document.body.appendChild(v); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_load_same_resource.html b/dom/media/test/test_load_same_resource.html new file mode 100644 index 0000000000..d3e16c88a5 --- /dev/null +++ b/dom/media/test/test_load_same_resource.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test loading of the same resource in multiple elements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.requestCompleteLog(); +var manager = new MediaTestManager; + +function checkDuration(actual, expected, name) { + ok(Math.abs(actual - expected) < 0.1, + `${name} duration: ${actual} expected: ${expected}`); +} + +function cloneLoaded(event) { + var e = event.target; + ok(true, `${e.token} loaded OK`); + checkDuration(e.duration, e._expectedDuration, e.token); + removeNodeAndSource(e); + manager.finished(e.token); +} + +function tryClone(e) { + var clone = e.cloneNode(false); + clone.token = `${e.token}(cloned)`; + manager.started(clone.token); + manager.finished(e.token); + + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(evt) { + var event = evt.target; + info(`${event.token} got ${evt.type}`); + } + events.forEach(function(event) { + clone.addEventListener(event, logEvent); + }); + + + checkDuration(e.duration, e._expectedDuration, e.token); + clone._expectedDuration = e._expectedDuration; + + clone.addEventListener("loadeddata", cloneLoaded, {once: true}); + clone.addEventListener("loadstart", function(evt) { + info(`${evt.target.token} starts loading.`); + // Since there is only one H264 decoder instance, we have to delete the + // decoder of the original element for the cloned element to load. However, + // we can't delete the decoder too early otherwise cloning decoder will + // fail to kick in. We wait for 'loadstart' event of the cloned element to + // know when the decoder is already cloned and we can delete the decoder of + // the original element. + removeNodeAndSource(e); + }, {once: true}); +} + +// This test checks that loading the same URI twice in different elements at the same time +// uses the same resource without doing another network fetch. One of the gCloneTests +// uses dynamic_resource.sjs to return one resource on the first fetch and a different resource +// on the second fetch. These resources have different lengths, so if the cloned element +// does a network fetch it will get a resource with the wrong length and we get a test +// failure. + +async function initTest(test, token) { + var e = document.createElement("video"); + e.preload = "auto"; + e.src = test.name; + e._expectedDuration = test.duration; + ok(true, `Trying to load ${test.name}, duration=${test.duration}`); + e.token = token; + manager.started(token); + + // Since 320x240.ogv is less than 32KB, we need to wait for the + // 'suspend' event to ensure the partial block is flushed to the cache + // otherwise the cloned resource will create a new channel when it + // has no data to read at position 0. The new channel will download + // a different file than the original resource and fail the duration + // test. + let p1 = once(e, "loadeddata"); + let p2 = once(e, "suspend"); + await p1; + await p2; + tryClone(e); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + {"set": [ + ["logging.MediaDecoder", "Debug"], + ["logging.nsMediaElement", "Debug"], + ["logging.MediaCache", "Debug"], + ]}, + manager.runTests.bind(manager, gCloneTests, initTest)); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_load_source.html b/dom/media/test/test_load_source.html new file mode 100644 index 0000000000..95a925b61f --- /dev/null +++ b/dom/media/test/test_load_source.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=534571 +--> +<head> + <title>Test for Bug 534571</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534571">Mozilla Bug 534571</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 534571 **/ + +// Test that when we load a video from a source child and then change the +// source's src attribute and load again, that the subsequent loads work. + +var v = null; +var s = null; + +function finish(event) { + ok(true, "Should have played both videos"); + SimpleTest.finish(); +} + +var first = null; +var second = null; + +function ended(event) { + s.type = second.type; + s.src = second.name; + v.removeEventListener("ended", ended); + v.addEventListener("ended", finish); + v.load(); +} + +// Find 2 videos we can play. +v = document.createElement('video'); +for (var i=0; i<gPlayTests.length; i++) { + if (!v.canPlayType(gPlayTests[i].type)) + continue; + if (!first) { + first = gPlayTests[i]; + } else if (!second) { + second = gPlayTests[i]; + break; + } +} + +if (first && second) { + s = document.createElement('source'); + s.type = first.type; + s.src = first.name; + v.appendChild(s); + v.autoplay = true; + v.addEventListener("ended", ended); + document.body.appendChild(v); + SimpleTest.waitForExplicitFinish(); +} else { + todo(false, "Need at least two media of supported types for this test!"); +} + + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_load_source_empty_type.html b/dom/media/test/test_load_source_empty_type.html new file mode 100644 index 0000000000..9ceee9af46 --- /dev/null +++ b/dom/media/test/test_load_source_empty_type.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Load resource from source element with empty type</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<video id="type"><source src="gizmo.mp4" type></video> +<video id="type-and-equal"><source src="gizmo.mp4" type=></video> +<video id="type-and-equal-quotation-marks"><source src="gizmo.mp4" type=""></video> +<script class="testbody" type="text/javascript"> +/** + * This test is used to ensure that media element can start loading when its + * child source element with empty type property. We would test following forms, + * `type`, `type=` and `type=""`. + */ +SimpleTest.waitForExplicitFinish(); + +const videos = document.getElementsByTagName("video"); +const videoNum = videos.length; +let loadedElementsNum = 0; + +for (let video of videos) { + video.addEventListener("loadeddata", + () => { + ok(true, `loaded element '${video.id}'`); + if (++loadedElementsNum == videoNum) { + SimpleTest.finish(); + } + }, { once: true}); +} +</script> +</body> +</html> diff --git a/dom/media/test/test_loop.html b/dom/media/test/test_loop.html new file mode 100644 index 0000000000..943059edad --- /dev/null +++ b/dom/media/test/test_loop.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test looping support</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + manager.started(token); + var v = document.createElement('video'); + v.token = token; + v.src = test.name; + v.name = test.name; + v.playCount = 0; + v.seekingCount = 0; + v.seekedCount = 0; + v.loop = true; + + v.addEventListener("play", function (e) { + e.target.playCount += 1; + ok(e.target.playCount == 1, "Should get exactly one play event."); + }); + + v.addEventListener("seeking", function (e) { + e.target.seekingCount += 1; + }); + + v.addEventListener("seeked", function (e) { + e.target.seekedCount += 1; + if (e.target.seekedCount == 2) { + ok(e.target.seekingCount == 2, "Expect matched pairs of seeking/seeked events."); + e.target.loop = false; + } + }); + + v.addEventListener("ended", function (e) { + ok(!e.target.loop, "Shouldn't get ended event while looping."); + removeNodeAndSource(e.target); + manager.finished(e.target.token); + }); + + document.body.appendChild(v); + v.play(); +} + +manager.runTests(gSmallTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_looping_eventsOrder.html b/dom/media/test/test_looping_eventsOrder.html new file mode 100644 index 0000000000..7d070de72f --- /dev/null +++ b/dom/media/test/test_looping_eventsOrder.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Looping events order</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<script type="text/javascript"> +/** + * This test is used to ensure the events order when media is looping back to + * the start position. We should see events in following order. + * 'seeking' -> 'timeupdate' -> 'seeked'. + */ +SimpleTest.waitForExplicitFinish(); + +var tests = [ + { name:"small-shot.ogg", type:"audio/ogg" }, + { name:"seek-short.webm", type:"video/webm" } +]; + +async function testTimeupdateChanged({name, type}) { + info(`- start testPlay for name=${name} -`); + const element = document.createElement(getMajorMimeType(type)); + element.src = name; + element.loop = true; + + await once(element, "canplay"); + ok(await element.play().then(() => true, () => false), `start playing ${name}`); + + let gotTimeUpdated = false; + await once(element, "seeking"); + element.addEventListener("timeupdate", function() { + gotTimeUpdated = true; + }, {once: true}); + await once(element, "seeked"); + ok(gotTimeUpdated, "Got timeupdate between seeking and seeked."); + + removeNodeAndSource(element); +} + +(async function startTest() { + for (let test of tests) { + await testTimeupdateChanged(test); + } + SimpleTest.finish(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_media_selection.html b/dom/media/test/test_media_selection.html new file mode 100644 index 0000000000..42f5a9bd43 --- /dev/null +++ b/dom/media/test/test_media_selection.html @@ -0,0 +1,142 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: media selection</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function maketest(attach_media, name, type, check_metadata) { + return function (token) { + var e = document.createElement('video'); + e.preload = "metadata"; + token = name + "-" + token; + manager.started(token); + var errorRun = false; + if (check_metadata) { + e.addEventListener('loadedmetadata', function () { + ok(e.readyState >= HTMLMediaElement.HAVE_METADATA, + 'test ' + token + ' readyState ' + e.readyState + ' expected >= ' + HTMLMediaElement.HAVE_METADATA); + is(e.currentSrc.substring(e.currentSrc.length - name.length), name, 'test ' + token); + // The load can go idle due to cache size limits + ok(e.networkState >= HTMLMediaElement.NETWORK_IDLE, + 'test ' + token + ' networkState = ' + e.networkState + ' expected >= ' + HTMLMediaElement.NETWORK_IDLE); + check_metadata(e); + removeNodeAndSource(e); + manager.finished(token); + }); + } else { + e.addEventListener('error', function onerror(event) { + is(errorRun, false, "error handler should run once only!"); + errorRun = true; + is(e.readyState, HTMLMediaElement.HAVE_NOTHING, + 'test ' + token + ' readyState should be HAVE_NOTHING when load fails.'); + e.removeEventListener('error', onerror, true); + removeNodeAndSource(e); + manager.finished(token); + }, true); + } + attach_media(e, name, type); + } +} + +function set_src(element, name, type) { + element.src = name; + document.body.appendChild(element); +} + +function add_source(element, name, type) { + do_add_source(element, name, type); + document.body.appendChild(element); +} + +function do_add_source(element, name, type) { + var source = document.createElement('source'); + if (type) { + source.type = type; + } + source.src = name; + element.appendChild(source); +} + +function add_sources_last(element, name, type) { + do_add_source(element, name, 'unsupported/type'); + do_add_source(element, name, type); + document.body.appendChild(element); +} + +function add_sources_first(element, name, type) { + do_add_source(element, name, type); + do_add_source(element, name, 'unsupported/type'); + document.body.appendChild(element); +} + +function late_add_sources_last(element, name, type) { + document.body.appendChild(element); + do_add_source(element, name, 'unsupported/type'); + do_add_source(element, name, type); +} + +function late_add_sources_first(element, name, type) { + document.body.appendChild(element); + do_add_source(element, name, type); + do_add_source(element, name, 'unsupported/type'); +} + +var nextTest = 0; +var subtests = [ + maketest(add_source, 'unknown.raw', 'bogus/type', null) +]; + +var tmpVid = document.createElement('video'); + +for (var i = 0; i < gSmallTests.length; ++i) { + var test = gSmallTests[i]; + var src = test.name; + var type = test.type; + + if (!tmpVid.canPlayType(type)) + continue; + + // The following nested function hack is to ensure that 'test' is correctly + // captured in the closure and we don't end up getting the value 'test' + // had in the last iteration of the loop. I blame Brendan. + var check = function(t) { return function (e) { + checkMetadata(t.name, e, t); + }}(test); + + var otherType = type.match(/^video\//) ? "audio/x-wav" : "video/ogg"; + subtests.push(maketest(set_src, src, null, check), + maketest(add_source, src, null, check), + maketest(add_source, src, type, check), + maketest(add_sources_last, src, null, check), + maketest(add_sources_first, src, type, check), + + // type hint matches a decoder, actual type matches different decoder + maketest(add_source, src, otherType, check), + maketest(add_source, 'unknown.raw', type, null), + + // should not start loading, type excludes it from media candiate list + maketest(add_source, src, 'bogus/type', null), + + // element doesn't notice source children attached later, needs bug 462455 fixed + maketest(late_add_sources_last, src, type, check), + maketest(late_add_sources_first, src, type, check)); +} + +function startTest(t, token) { + t(token); +} + +manager.runTests(subtests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_media_sniffer.html b/dom/media/test/test_media_sniffer.html new file mode 100644 index 0000000000..68e2bba8af --- /dev/null +++ b/dom/media/test/test_media_sniffer.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: sniffing</title> + <meta charset='utf-8'> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function finish_test(element) { + element.removeEventListener("error", onerror); + removeNodeAndSource(element); + manager.finished(element.token); +} + +function onmetadataloaded(e) { + var t = e.target; + ++t.srcIndex; + ok(true, "The media loads when loaded via " + t.src); + if (t.srcIndex < t.srcList.length) { + t.src = t.srcList[t.srcIndex]; + } else { + finish_test(t); + } +} + +function onerror(e) { + var t = e.target; + t.removeEventListener('error', onerror); + ok(false, "The media could not be loaded." + t.src + "\n"); + finish_test(t); +} + +function startTest(test, token) { + var elemType = /^audio/.test(test.type) ? "audio" : "video"; + var element = document.createElement(elemType); + // This .sjs file serve the media file without Content-Type header, or with a + // specific Content-Type header. + var baseSrc = 'contentType.sjs?file=' + test.name; + element.srcList = [ + baseSrc + "&nomime", + baseSrc + "&type=application/octet-stream", + baseSrc + "&type=audio/wav", + baseSrc + "&type=text/html", + baseSrc + "&type=absolute_nonsense" + ]; + element.srcIndex = 0; + element.src = element.srcList[element.srcIndex]; + element.token = token; + element.controls = true; + element.preload = "metadata"; + document.body.appendChild(element); + manager.started(token); + element.addEventListener("loadedmetadata", onmetadataloaded); + element.addEventListener("error", onerror); +} + +manager.runTests(gSnifferTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediacapabilities_resistfingerprinting.html b/dom/media/test/test_mediacapabilities_resistfingerprinting.html new file mode 100644 index 0000000000..1f07d1c707 --- /dev/null +++ b/dom/media/test/test_mediacapabilities_resistfingerprinting.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1369309</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1461454">Mozilla Bug 1461454</a> +<a target="_blank" href="https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/13543">Tor Issue 13543</a> + +<script type="application/javascript"> +async function testWhetherSpoofed(resistFingerprinting) { + + var unsupportedVideoConfiguration = { + contentType: 'video/bogus', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }; + var supportedVideoConfiguration = { + contentType: 'video/webm; codecs="vp09.00.10.08"', + width: 800, + height: 600, + bitrate: 3000, + framerate: 24, + }; + + await SpecialPowers.pushPrefEnv({ + "set": [ + ["privacy.resistFingerprinting", resistFingerprinting] + ], + }); + + var result; + + result = await navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: unsupportedVideoConfiguration + }); + is(result.supported, false, "video/bogus should be unsupported."); + is(result.smooth, false, "smooth is false when unsupported."); + is(result.powerEfficient, false, "powerEfficient is false when unsupported."); + + result = await navigator.mediaCapabilities.decodingInfo({ + type: 'file', + video: supportedVideoConfiguration + }); + is(result.supported, true, "'video/webm; codecs=\"vp09.00.10.08\"' should be supported."); + if (resistFingerprinting) { + is(result.smooth, true, "smooth should be spoofed to true in RFP mode."); + is(result.powerEfficient, false, "powerEfficient should be spoofed to false in RFP mode."); + } +} + +async function start() { + await testWhetherSpoofed(true); + await testWhetherSpoofed(false); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +start(); +</script> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_avoid_recursion.html b/dom/media/test/test_mediarecorder_avoid_recursion.html new file mode 100644 index 0000000000..0c733b9fc2 --- /dev/null +++ b/dom/media/test/test_mediarecorder_avoid_recursion.html @@ -0,0 +1,61 @@ +<html> +<head> + <title>MediaRecorder infinite recursion with requestData() calls in "dataavailable" event</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897776">Mozill +a Bug 897776</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function startTest() { + try { + await setupGetUserMediaTestPrefs(); + let stream = await navigator.mediaDevices.getUserMedia({audio: true}); + let mediaRecorder = new MediaRecorder(stream); + let count = 0; + mediaRecorder.onerror = function () { + ok(false, 'Unexpected onerror callback fired'); + SimpleTest.finish(); + }; + mediaRecorder.onwarning = function () { + ok(false, 'Unexpected onwarning callback fired'); + }; + mediaRecorder.ondataavailable = function (e) { + ++count; + info("got ondataavailable data size = " + e.data.size); + // no more requestData() to prevent busy main thread from starving + // the encoding thread + if (count == 30) { + info("track.stop"); + stream.getTracks()[0].stop(); + } else if (count < 30 && mediaRecorder.state == 'recording') { + info("requestData again"); + mediaRecorder.requestData(); + } + }; + mediaRecorder.onstop = function () { + ok(true, "requestData within ondataavailable successfully avoided infinite recursion"); + SimpleTest.finish(); + }; + mediaRecorder.start(); + info("mediaRecorder start"); + mediaRecorder.requestData(); + info("mediaRecorder requestData"); + } catch (e) { + ok(false, 'Unexpected error fired with: ' + e); + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +startTest(); + +</script> +</pre> +</body> +</html> + diff --git a/dom/media/test/test_mediarecorder_bitrate.html b/dom/media/test/test_mediarecorder_bitrate.html new file mode 100644 index 0000000000..693b27e3bf --- /dev/null +++ b/dom/media/test/test_mediarecorder_bitrate.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Bitrate</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; +var results = []; + +/** + * Starts a test on every media recorder file included to check that + * the bitrate control works + */ +function startTest(test, token) { + manager.started(token); + runTest(test, token, 1000000); + runTest(test, token, 100000); +} + +function runTest(test, token, bitrate) { + var element = document.createElement('video'); + + element.token = token; + + element.src = test.name; + element.preload = "metadata"; + element.onloadedmetadata = function () { + info("loadedmetadata"); + const stream = element.mozCaptureStreamUntilEnded(); + element.onloadedmetadata = null; + element.play(); + + const mediaRecorder = new MediaRecorder(stream, {videoBitsPerSecond: bitrate}); + mediaRecorder.start(); + is(mediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mediaRecorder.stream, stream, + 'Media recorder stream = element stream at the start of recording'); + + var onStopFired = false; + var onDataAvailableFired = false; + var encoded_size = 0; + + mediaRecorder.onerror = function () { + ok(false, 'Unexpected onerror callback fired'); + }; + + mediaRecorder.onwarning = function () { + ok(false, 'Unexpected onwarning callback fired'); + }; + + // This handler verifies that only a single onstop event handler is fired. + mediaRecorder.onstop = function () { + if (onStopFired) { + ok(false, 'onstop unexpectedly fired more than once'); + } else { + onStopFired = true; + + // ondataavailable should always fire before onstop + if (onDataAvailableFired) { + ok(true, 'onstop fired after ondataavailable'); + info("test " + test.name + " encoded@" + bitrate + "=" + encoded_size); + if (results[test.name]) { + var big, small, temp; + big = {}; + big.bitrate = bitrate; + big.size = encoded_size; + small = results[test.name]; + // Don't assume the order that these will finish in + if (results[test.name].bitrate > bitrate) { + temp = big; + big = small; + small = temp; + } + // Ensure there is a big enough difference in the encoded + // sizes + ok(small.size*1.25 < big.size, + test.name + ' encoded@' + big.bitrate + '=' + big.size + + ' > encoded@' + small.bitrate + '=' + small.size); + manager.finished(token); + } else { + results[test.name] = {}; + results[test.name].bitrate = bitrate; + results[test.name].size = encoded_size; + } + } else { + ok(false, 'onstop fired without an ondataavailable event first'); + } + } + }; + + // This handler verifies that only a single ondataavailable event handler + // is fired with the blob generated having greater than zero size + // and a correct mime type. + mediaRecorder.ondataavailable = function (evt) { + if (onDataAvailableFired) { + ok(false, 'ondataavailable unexpectedly fired more than once'); + } else { + onDataAvailableFired = true; + + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + ok(evt.data.size > 0, + 'Blob data received should be greater than zero'); + encoded_size = evt.data.size; + + // onstop should not have fired before ondataavailable + if (onStopFired) { + ok(false, 'ondataavailable unexpectedly fired later than onstop'); + manager.finished(token); + } + } + }; + }; +} + +manager.runTests(gMediaRecorderVideoTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_creation.html b/dom/media/test/test_mediarecorder_creation.html new file mode 100644 index 0000000000..cd23cb7a96 --- /dev/null +++ b/dom/media/test/test_mediarecorder_creation.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Creation</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +/** + * Starts a test on every media recorder file included to check that + * a media recorder object created with a stream derived from a media + * element with that file produces the correct starting attribute values. + */ +function startTest(test, token) { + var element = document.createElement('audio'); + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStreamUntilEnded(); + + var mediaRecorder = new MediaRecorder(element.stream); + + is(mediaRecorder.stream, element.stream, + 'Stream should be provided stream on creation'); + is(mediaRecorder.mimeType, '', + 'mimeType should be an empty string on creation'); + is(mediaRecorder.state, 'inactive', + 'state should be inactive on creation'); + + manager.finished(token); +} + +manager.runTests(gMediaRecorderTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_creation_fail.html b/dom/media/test/test_mediarecorder_creation_fail.html new file mode 100644 index 0000000000..f411c2d3e1 --- /dev/null +++ b/dom/media/test/test_mediarecorder_creation_fail.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record with media.ogg.enabled = false</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +function testThrows(stream, options) { + try { + new MediaRecorder(stream, options); + return false; + } catch(e) { + return e.name; + } +} +(async () => { + SimpleTest.waitForExplicitFinish(); + await SpecialPowers.pushPrefEnv({set: [ + ["media.ogg.enabled", false], + ["media.encoder.webm.enabled", false], + ]}); + const stream = new AudioContext().createMediaStreamDestination().stream; + is(testThrows(stream, {mimeType: "audio/ogg"}), "NotSupportedError", + "Creating an ogg recorder without ogg support throws"); + is(testThrows(stream, {mimeType: "audio/webm"}), "NotSupportedError", + "Creating a webm recorder without webm support throws"); + is(testThrows(stream, {mimeType: "video/webm"}), "NotSupportedError", + "Creating a webm recorder without webm support throws"); + is(testThrows(stream, {mimeType: "apa/bepa"}), "NotSupportedError", + "Creating a recorder for a bogus mime type throws"); + is(testThrows(stream, {}), false, + "Creating a default recorder never throws, even without container support"); + const recorder = new MediaRecorder(stream); + try { + recorder.start(); + ok(false, "Starting a recorder without container support should throw"); + } catch(e) { + is(e.name, "NotSupportedError", + "Starting a recorder without container support throws"); + } + SimpleTest.finish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html b/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html new file mode 100644 index 0000000000..537e1dbb47 --- /dev/null +++ b/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1395022 - MediaRecorder fires start event when erroring.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1395022">Mozilla Bug 1395022</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function startTest() {
+ let audioContext = new AudioContext();
+ let destination1 = audioContext.createMediaStreamDestination();
+ let oscilator = audioContext.createOscillator();
+ oscilator.connect(destination1);
+ oscilator.start();
+
+ let destination2 = audioContext.createMediaStreamDestination();
+
+ let rec = new MediaRecorder(destination1.stream);
+
+ let numStartEvents = 0;
+
+ rec.onstart = () => {
+ numStartEvents += 1;
+ is(numStartEvents, 1, "One start event should be fired by the recorder");
+ // Trigger an error in the recorder
+ destination1.stream.addTrack(destination2.stream.getTracks()[0]);
+ };
+
+ rec.onerror = () => {
+ is(numStartEvents, 1, "One start event should have been fired by the recorder");
+ SimpleTest.finish();
+ };
+
+ rec.start();
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</head>
+</html>
\ No newline at end of file diff --git a/dom/media/test/test_mediarecorder_onerror_pause.html b/dom/media/test/test_mediarecorder_onerror_pause.html new file mode 100644 index 0000000000..55ee5fb535 --- /dev/null +++ b/dom/media/test/test_mediarecorder_onerror_pause.html @@ -0,0 +1,107 @@ +<html> +<head> + <title>Bug 957439 - Media Recording - Assertion fail at Pause if unsupported input stream.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957439">Mozilla Bug 957439</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function unexpected(e) { + ok(false, `Got unexpected ${e.type} event`); +} + + +async function startTest() { + // also do general checks on mimetype support for audio-only + ok(MediaRecorder.isTypeSupported("audio/ogg"), + 'Should support audio/ogg'); + ok(MediaRecorder.isTypeSupported('audio/ogg; codecs=opus'), + 'Should support audio/ogg+opus'); + ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs=foobar'), + 'Should not support audio/ogg + unknown_codec'); + ok(MediaRecorder.isTypeSupported("video/webm"), + 'Should support video/webm'); + ok(!MediaRecorder.isTypeSupported("video/mp4"), + 'Should not support video/mp4'); + + try { + await setupGetUserMediaTestPrefs(); + const expectedMimeType = 'video/webm; codecs="vp8, opus"'; + const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true}); + const [audioTrack] = stream.getAudioTracks(); + + // Expected event sequence should be: + // 1. start + // 2. error (from removed track) + // 3. dataavailable + // 4. stop + const mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, 'Stream should be provided on creation'); + + mediaRecorder.onstart = unexpected; + mediaRecorder.onerror = unexpected; + mediaRecorder.ondataavailable = unexpected; + mediaRecorder.onstop = unexpected; + + mediaRecorder.start(); + is(mediaRecorder.state, 'recording', 'state should be recording'); + is(mediaRecorder.mimeType, '', 'mimetype should be unset'); + + await new Promise(r => mediaRecorder.onstart = r); + mediaRecorder.onstart = unexpected; + ok(true, 'start event fired'); + is(mediaRecorder.mimeType, expectedMimeType, 'mimetype should be set'); + + // Trigger an error + stream.removeTrack(audioTrack); + + const err = await new Promise(r => mediaRecorder.onerror = r); + mediaRecorder.onerror = unexpected; + ok(true, 'error event fired'); + is(err.error.name, 'InvalidModificationError', + 'Error name should be InvalidModificationError.'); + ok(err.error.stack.includes('test_mediarecorder_onerror_pause.html'), + 'Events fired from onerror should include an error with a stack trace indicating ' + + 'an error in this test'); + is(mediaRecorder.mimeType, '', 'mimetype should be unset'); + is(mediaRecorder.state, 'inactive', 'state is inactive'); + + try { + mediaRecorder.pause(); + ok(false, 'pause should fire an exception if called on an inactive recorder'); + } catch(e) { + ok(e instanceof DOMException, 'pause should fire an exception ' + + 'if called on an inactive recorder'); + is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError'); + } + + const evt = await new Promise(r => mediaRecorder.ondataavailable = r); + mediaRecorder.ondataavailable = unexpected; + ok(true, 'dataavailable event fired'); + isnot(evt.data.size, 0, 'data size should not be zero'); + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.data.type, expectedMimeType, 'blob mimeType is set'); + + await new Promise(r => mediaRecorder.onstop = r); + mediaRecorder.onstop = unexpected; + ok(true, 'onstop event fired'); + is(mediaRecorder.state, 'inactive', 'state should be inactive'); + } catch (err) { + ok(false, `Unexpected error fired with: ${err}`); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +window.onload = startTest; + +</script> +</head> +</html> diff --git a/dom/media/test/test_mediarecorder_pause_resume_video.html b/dom/media/test/test_mediarecorder_pause_resume_video.html new file mode 100644 index 0000000000..a9b3d6a034 --- /dev/null +++ b/dom/media/test/test_mediarecorder_pause_resume_video.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording doesn't record during pause</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> + <canvas id="video-src-canvas"></canvas> + <video id="recorded-video"></video> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + // Setup canvas and take a stream from it + let canvas = document.getElementById("video-src-canvas"); + + let canvas_size = 100; + let new_canvas_size = 50; + + canvas.width = canvas.height = canvas_size; + + let helper = new CaptureStreamTestHelper2D(100, 100); + helper.drawColor(canvas, helper.red); + + let canvasStream = canvas.captureStream(); + // Canvas set up + + // Check values for events + let numDataAvailabledRaised = 0; + // Recorded data that will be playback. + let blob; + + let mediaRecorder = new MediaRecorder(canvasStream); + is(mediaRecorder.stream, canvasStream, + "Media recorder stream = canvas stream at the start of recording"); + + mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired"); + + mediaRecorder.onerror = () => ok(false, "Recording failed"); + + mediaRecorder.ondataavailable = ev => { + info("Got 'dataavailable' event"); + ++numDataAvailabledRaised; + // Save recorded data for playback + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info("Got 'start' event"); + // We just want one frame encoded before we pause + mediaRecorder.pause(); + // We may rewrite this once we settle Bug 1363915, could listen for pause event instead + is(mediaRecorder.state, 'paused', 'Media recorder should be paused'); + + // Wait a while, then while paused draw blue at another size, then again + // green at the original size and resume. + let numberOfPaintsSincePause = 0; + window.requestAnimationFrame(function draw() { + numberOfPaintsSincePause++; + if (numberOfPaintsSincePause == 8) { + canvas.width = canvas.height = new_canvas_size; + helper.drawColor(canvas, helper.blue); + } else if (numberOfPaintsSincePause == 60) { + canvas.width = canvas.height = canvas_size; + helper.drawColor(canvas, helper.green); + } else if (numberOfPaintsSincePause == 68) { + // Waited 8 draws since changing canvas to green, should be safe to resume + mediaRecorder.resume(); + } else if (numberOfPaintsSincePause > 120) { + mediaRecorder.stop(); + return; // Early return, we don't want to request any more animation frames + } + window.requestAnimationFrame(draw); + }); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + is(mediaRecorder.state, 'inactive', 'Media recorder should be incative after stop'); + is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event"); + + ok(blob, "Should have gotten a data blob"); + let video = document.getElementById("recorded-video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + // Setup a check to make sure we don't play back any blue + let checkVideoHasNoBlue = () => { + if(helper.isPixel(helper.getPixel(video), helper.blue, 128)) { + ok(false, "Video should have no blue frames"); + // Remove handler so we don't spam the log + video.ontimeupdate = null; + } + }; + video.ontimeupdate = checkVideoHasNoBlue; + video.onerror = () => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + video.onended = () => { + ok(helper.isPixel(helper.getPixel(video), helper.green, 128), "Last frame should be green"); + SimpleTest.finish(); + }; + // The video will resize once it loads its metadata, only listen for resizes after that + video.onloadedmetadata = () => { + ok(video.videoWidth === canvas_size && video.videoHeight === canvas_size, + "video element should be same size as canvas once metadata is loaded"); + // We shouldn't have any resize events once the video is loaded + video.onresize = () => { + ok(false, "Should not have any resize events!"); + }; + }; + + video.play(); + }; + + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); +} + +SimpleTest.waitForExplicitFinish(); +startTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_playback_can_repeat.html b/dom/media/test/test_mediarecorder_playback_can_repeat.html new file mode 100644 index 0000000000..4cf2735088 --- /dev/null +++ b/dom/media/test/test_mediarecorder_playback_can_repeat.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording creates videos that can playback more than once</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+ <canvas id="video-src-canvas"></canvas>
+ <video id="recorded-video" loop></video>
+</div>
+<script class="testbody" type="text/javascript">
+
+(async function() {
+ try {
+ SimpleTest.waitForExplicitFinish();
+ await startTest();
+ } catch(e) {
+ ok(false, `Got error msg '${e}'`);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+
+async function startTest() {
+ let canvas = document.getElementById("video-src-canvas");
+
+ let canvas_size = 100;
+ canvas.width = canvas.height = canvas_size;
+ let helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
+ helper.drawColor(canvas, helper.red);
+
+ let stream = canvas.captureStream();
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the beginning of recording");
+ new Promise(r => mediaRecorder.onerror = err => r(err)).then(
+ err => Promise.reject(`MediaRecorder: error unexpectedly fired. Code ${err.name}`));
+
+ mediaRecorder.start();
+ await new Promise(r => mediaRecorder.onstart = r);
+ info("Media recorder started");
+ // Feed some more data into the recorder so the output has a non trivial duration.
+ // This avoids the case where we have only 1 frame in the output, which breaks
+ // looping (this is expected as a WebM with 1 video frame has no duration).
+ let counter = 0;
+ let draw = () => {
+ counter++;
+ helper.drawColor(canvas, helper.blue);
+ if(counter > 2) {
+ mediaRecorder.stop();
+ return;
+ }
+ requestAnimationFrame(draw);
+ };
+ requestAnimationFrame(draw);
+
+ // When recorder is stopped get recorded data.
+ const data = await new Promise(r => mediaRecorder.ondataavailable = ev => r(ev));
+ info(`Media recorder get availiable data`);
+ const blob = data.data;
+
+ await new Promise(r => mediaRecorder.onstop = r);
+ info("Media recorder stopped");
+ ok(blob, "Should have gotten a data blob");
+ const video = document.getElementById("recorded-video");
+ new Promise(r => video.onerror = err => r(err)).then(
+ err => Promise.reject(`Video: error unexpectedly fired. Code ${err.code}`));
+ video.src = URL.createObjectURL(blob);
+ video.play();
+ await new Promise(r => video.onplaying = r);
+ for (let idx = 0; idx < 2; idx++) {
+ await new Promise(r => video.onseeking = r);
+ ok(true, `waiting until video finishes seeking`);
+ await new Promise(r => video.onseeked = r);
+ ok(true, "video finished seeked");
+ }
+ video.pause();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_principals.html b/dom/media/test/test_mediarecorder_principals.html new file mode 100644 index 0000000000..00dcaa5a5b --- /dev/null +++ b/dom/media/test/test_mediarecorder_principals.html @@ -0,0 +1,132 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=489415 +--> +<head> + <title>Test for MediaRecorder Reaction to Principal Change</title> + <script type="application/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<div> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018299">Test for MediaRecorder Principal Handling</a> +</div> + +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +let throwOutside = e => setTimeout(() => { throw e; }); + +// Loading data from a resource that changes origins while streaming should +// be detected by the media cache and result in a null principal so that the +// MediaRecorder usages below fail. + +// This test relies on midflight-redirect.sjs returning the the first quarter of +// the resource as a byte range response, and then hanging up, and when Firefox +// requests the remainder midflight-redirect.sjs serves a redirect to another origin. + +async function testPrincipals(resource) { + if (!resource) { + todo(false, "No types supported"); + return; + } + await testPrincipals1(resource); + await testPrincipals2(resource); +} + +function makeVideo() { + let video = document.createElement("video"); + video.preload = "metadata"; + video.controls = true; + document.body.appendChild(video); + return video; +} + +// First test: Load file from same-origin first, then get redirected to +// another origin before attempting to record stream. +async function testPrincipals1(resource) { + let video = makeVideo(); + video.src = + "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs" + + "?resource=" + resource.name + "&type=" + resource.type; + + let errorBarrier = once(video, "error"); + // Wait for the video to load to metadata. We can then start capturing. + // Must also handle the download bursting and hitting the error before we + // reach loadedmetadata. Normally we reach loadedmetadata first, but + // rarely we hit the redirect first. + await Promise.race([once(video, "loadedmetadata"), errorBarrier]); + + let rec = new MediaRecorder(video.mozCaptureStreamUntilEnded()); + video.play(); + + // Wait until we hit a playback error. This means our download has hit the redirect. + await errorBarrier; + + // Try to record, it should be blocked with a security error. + try { + rec.start(); + ok(false, "mediaRecorder.start() must throw SecurityError, but didn't throw at all"); + } catch (ex) { + is(ex.name, "SecurityError", "mediaRecorder.start() must throw SecurityError"); + } + removeNodeAndSource(video); +} + +// Second test: Load file from same-origin first, but record ASAP, before +// getting redirected to another origin. +async function testPrincipals2(resource) { + let video = makeVideo(); + video.src = + "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs" + + "?resource=" + resource.name + "&type=" + resource.type; + + // Wait for the video to load to metadata. We can then start capturing. + // Must also handle the download bursting and hitting the error before we + // reach loadedmetadata. Normally we reach loadedmetadata first, but + // rarely we hit the redirect first. + await Promise.race([once(video, "loadedmetadata"), once(video, "error")]); + + let ended = false; + once(video, "ended", () => ended = true); + + // Start capturing. It should work. + let rec; + let errorBarrier; + try { + rec = new MediaRecorder(video.mozCaptureStreamUntilEnded()); + errorBarrier = nextEvent(rec, "error"); + rec.start(); + ok(true, "mediaRecorder.start() should not throw here, and didn't"); + } catch (ex) { + ok(false, "mediaRecorder.start() unexpectedly threw " + ex.name + " (" + ex.message + ")"); + } + + // Play the video, this should result in a SecurityError on the recorder. + let hasStopped = once(rec, "stop"); + video.play(); + let error = (await errorBarrier).error; + is(error.name, "SecurityError", "mediaRecorder.onerror must fire SecurityError"); + ok(error.stack.includes('test_mediarecorder_principals.html'), + 'Events fired from onerror should include an error with a stack trace indicating ' + + 'an error in this test'); + is(ended, false, "Playback should not have reached end"); + await hasStopped; + is(ended, false, "Playback should definitely not have reached end"); + + removeNodeAndSource(video); +} + +testPrincipals({ name:"pixel_aspect_ratio.mp4", type:"video/mp4", duration:28 }) + .catch(e => throwOutside(e)) + .then(() => SimpleTest.finish()); + +</script> +</pre> + +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html new file mode 100644 index 0000000000..99f57f181e --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record AudioContext with four channels</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + +<script class="testbody" type="text/javascript"> + +function startTest() { + var context = new AudioContext(); + var buffer = context.createBuffer(4, 80920, context.sampleRate); + for (var i = 0; i < 80920; ++i) { + for(var j = 0; j < 4; ++j) { + buffer.getChannelData(j)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate); + } + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.loop = true; + var dest = context.createMediaStreamDestination(); + var stopTriggered = false; + var onstopTriggered = false; + dest.channelCount = 4; + var expectedMimeType = 'audio/ogg; codecs=opus'; + source.channelCountMode = 'explicit'; + source.connect(dest); + var elem = document.createElement('audio'); + elem.srcObject = dest.stream; + source.start(0); + elem.play(); + let mMediaRecorder = new MediaRecorder(dest.stream); + mMediaRecorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + mMediaRecorder.onerror = function() { + ok(false, 'onerror unexpectedly fired'); + }; + + mMediaRecorder.onstop = function() { + ok(true, 'onstop fired successfully'); + is(mMediaRecorder.state, 'inactive', 'check recording status is inactive'); + onstopTriggered = true; + SimpleTest.finish(); + }; + mMediaRecorder.ondataavailable = function (e) { + ok(e.data.size > 0, 'check blob has data'); + is(e.data.type, expectedMimeType, 'blob should have expected mimetype'); + if (!stopTriggered) { + is(mMediaRecorder.mimeType, expectedMimeType, 'recorder should have expected mimetype'); + mMediaRecorder.stop(); + is(mMediaRecorder.mimeType, '', 'recorder should have reset its mimetype'); + stopTriggered = true; + } else if (onstopTriggered) { + ok(false, 'ondataavailable should come before onstop event'); + } + }; + try { + mMediaRecorder.start(1000); + is('recording', mMediaRecorder.state, "check record state recording"); + } catch (e) { + ok(false, 'Can t record audio context'); + } +} + +startTest(); +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_addtracked_stream.html b/dom/media/test/test_mediarecorder_record_addtracked_stream.html new file mode 100644 index 0000000000..046bf768ce --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_addtracked_stream.html @@ -0,0 +1,184 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder recording a constructed MediaStream</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <script src="/tests/dom/media/webrtc/tests/mochitests/head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script> +SimpleTest.waitForExplicitFinish(); +runTestWhenReady(async () => { + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 100; + document.getElementById("content").appendChild(canvas); + + const helper = new CaptureStreamTestHelper2D(100, 100); + helper.drawColor(canvas, helper.red); + + const audioCtx = new AudioContext(); + const osc = audioCtx.createOscillator(); + osc.frequency.value = 1000; + osc.start(); + const dest = audioCtx.createMediaStreamDestination(); + osc.connect(dest); + + const stream = dest.stream; + + // Timeouts are experienced intermittently in Linux due to no sound in the + // destination. As a workaround wait for the source sound to arrive. + const sourceAnalyser = new AudioStreamAnalyser(audioCtx, stream); + const sourceAudioReady = sourceAnalyser.waitForAnalysisSuccess(array => { + const freq = osc.frequency.value; + const lowerFreq = freq / 2; + const upperFreq = freq + 1000; + const lowerAmp = array[sourceAnalyser.binIndexForFrequency(lowerFreq)]; + const freqAmp = array[sourceAnalyser.binIndexForFrequency(freq)]; + const upperAmp = array[sourceAnalyser.binIndexForFrequency(upperFreq)]; + info("Analysing source audio. " + + lowerFreq + ": " + lowerAmp + ", " + + freq + ": " + freqAmp + ", " + + upperFreq + ": " + upperAmp); + return lowerAmp < 50 && freqAmp > 200 && upperAmp < 50; + }); + await sourceAudioReady; + info("Source Audio content ok"); + + canvas.captureStream(0).getVideoTracks().forEach(t => stream.addTrack(t)); + + const blobs = []; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = constructed stream at the start of recording"); + + + mediaRecorder.ondataavailable = evt => { + info("ondataavailable fired"); + + is(mediaRecorder.state, "inactive", "Blob received after stopping"); + is(blobs.length, 0, "This is the first and only blob"); + ok(evt instanceof BlobEvent, + "Events fired from ondataavailable should be BlobEvent"); + is(evt.type, "dataavailable", + "Event type should dataavailable"); + ok(evt.data.size >= 0, + "Blob data size received is greater than or equal to zero"); + + blobs.push(evt.data); + }; + + const stopped = haveEvent(mediaRecorder, "stop", wait(5000, new Error("Timeout"))); + const stoppedNoErrors = Promise.all([ + stopped, + haveNoEvent(mediaRecorder, "warning", stopped), + haveNoEvent(mediaRecorder, "error", stopped) + ]); + + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); + + await haveEvent(mediaRecorder, "start", wait(5000, new Error("Timeout"))); + info("onstart fired"); + + // The recording can be too short to cause any checks with + // waitForAnalysisSuccess(). Waiting a bit here solves this. + await wait(500); + + is(mediaRecorder.state, "recording", + "Media recorder is recording before being stopped"); + mediaRecorder.stop(); + is(mediaRecorder.state, "inactive", + "Media recorder is inactive after being stopped"); + is(mediaRecorder.stream, stream, + "Media recorder stream = constructed stream post recording"); + + await stoppedNoErrors; + info("Got 'stop' event"); + + ok(blobs.length == 1, "Should have gotten one data blob"); + + // Clean up recording sources + osc.stop(); + stream.getTracks().forEach(t => t.stop()); + + // Sanity check the recording + const video = document.createElement("video"); + document.getElementById("content").appendChild(video); + video.id = "recorded-video"; + + const blob = new Blob(blobs); + ok(blob.size > 0, "Recorded blob should contain data"); + + video.src = URL.createObjectURL(blob); + video.preload = "metadata"; + + info("Waiting for metadata to be preloaded"); + + await haveEvent(video, "loadedmetadata", wait(5000, new Error("Timeout"))); + info("Playback of recording loaded metadata"); + + const recordingStream = video.mozCaptureStream(); + is(recordingStream.getVideoTracks().length, 1, + "Recording should have one video track"); + is(recordingStream.getAudioTracks().length, 1, + "Recording should have one audio track"); + + const ended = haveEvent(video, "ended", wait(5000, new Error("Timeout"))); + const endedNoError = Promise.all([ + ended, + haveNoEvent(video, "error", ended), + ]); + + const analyser = new AudioStreamAnalyser(audioCtx, recordingStream); + const audioReady = analyser.waitForAnalysisSuccess(array => { + const freq = osc.frequency.value; + const lowerFreq = freq / 2; + const upperFreq = freq + 1000; + const lowerAmp = array[analyser.binIndexForFrequency(lowerFreq)]; + const freqAmp = array[analyser.binIndexForFrequency(freq)]; + const upperAmp = array[analyser.binIndexForFrequency(upperFreq)]; + info("Analysing audio. " + + lowerFreq + ": " + lowerAmp + ", " + + freq + ": " + freqAmp + ", " + + upperFreq + ": " + upperAmp); + return lowerAmp < 50 && freqAmp > 200 && upperAmp < 50; + }, endedNoError.then(() => new Error("Audio check failed"))); + + const videoReady = helper.pixelMustBecome( + video, helper.red, { + threshold: 128, + infoString: "Should become red", + cancelPromise: endedNoError.then(() => new Error("Video check failed")), + }); + + video.play(); + + try { + await endedNoError; + } finally { + analyser.disconnect(); + let url = video.src; + video.src = ""; + URL.revokeObjectURL(url); + } + + info("Playback of recording ended without error"); + + await audioReady; + info("Audio content ok"); + + await videoReady; + info("Video content ok"); + + SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_audiocontext.html b/dom/media/test/test_mediarecorder_record_audiocontext.html new file mode 100644 index 0000000000..686faaeb6b --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_audiocontext.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record AudioContext</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + +<script class="testbody" type="text/javascript"> + +function startTest() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 80920, context.sampleRate); + for (var i = 0; i < 80920; ++i) { + buffer.getChannelData(0)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.loop = true; + + var dest = context.createMediaStreamDestination(); + source.connect(dest); + var elem = document.createElement('audio'); + elem.srcObject = dest.stream; + source.start(0); + elem.play(); + let mMediaRecorder = new MediaRecorder(dest.stream); + mMediaRecorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + mMediaRecorder.onerror = function() { + ok(false, 'onerror unexpectedly fired'); + }; + + mMediaRecorder.onstop = function() { + ok(true, 'onstop fired successfully'); + is(mMediaRecorder.state, 'inactive', 'check recording status is inactive'); + SimpleTest.finish(); + }; + mMediaRecorder.ondataavailable = function (e) { + if (mMediaRecorder.state == 'recording') { + is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus', 'Expected MediaRecorder mimetype'); + is(e.data.type, 'audio/ogg; codecs=opus', 'Expected Blob mimetype'); + ok(e.data.size > 0, 'check blob has data'); + mMediaRecorder.stop(); + } + }; + try { + mMediaRecorder.start(1000); + is('recording', mMediaRecorder.state, "check record state recording"); + } catch (e) { + ok(false, 'Can t record audio context'); + } +} + +startTest(); +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html b/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html new file mode 100644 index 0000000000..4128702aef --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>capture for possible memory leak when record AudioContext</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=973765">Mozill +a Bug 973765</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + // This test case want to capture the memory leak if exit the browser after running those script. + var ac = new window.AudioContext(); + var destStream = ac.createMediaStreamDestination().stream; + var recorder = new MediaRecorder(destStream); + recorder.start(1000); + is(recorder.state, 'recording', 'Media recorder should be recording'); + is(recorder.stream, destStream, + 'Media recorder stream = element stream at the start of recording'); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_audionode.html b/dom/media/test/test_mediarecorder_record_audionode.html new file mode 100644 index 0000000000..8a57437b81 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_audionode.html @@ -0,0 +1,135 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record AudioContext Node</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968109">Mozilla Bug 968109</a> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function setUpSource(contextType, nodeType) { + // Use contextType to choose offline or real-time context. + const context = contextType == "offline"? + new OfflineAudioContext(2 , 80920, 44100) : new AudioContext(); + const buffer = context.createBuffer(2, 80920, context.sampleRate); + for (let i = 0; i < 80920; ++i) { + buffer.getChannelData(0)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate); + buffer.getChannelData(1)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate); + } + + const source = context.createBufferSource(); + source.buffer = buffer; + source.loop = true; + + source.start(0); + + // nodeType decides which node in graph should be the source of recording. + let node; + if (nodeType == "source") { + node = source; + } else if (nodeType == "splitter") { + const splitter = context.createChannelSplitter(); + source.connect(splitter); + node = splitter; + } else if (nodeType == "destination") { + source.connect(context.destination); + node = context.destination; + } + // Explicitly start offline context. + if (contextType == "offline") { + context.startRendering(); + } + + return node; +} + +async function testRecord(source, mimeType) { + const isOffline = source.context instanceof OfflineAudioContext; + const recorder = new MediaRecorder(source, 0, {mimeType}); + is(recorder.mimeType, mimeType, "Mime type is set"); + const extendedMimeType = `${mimeType || "audio/ogg"}; codecs=opus`; + + recorder.onwarning = () => ok(false, "should not fire onwarning"); + recorder.onerror = () => ok(false, "should not fire onerror"); + if (!isOffline) { + recorder.onstop = () => ok(false, "should not fire stop yet"); + } + + recorder.start(1000); + is("recording", recorder.state, "state should become recording after calling start()"); + is(recorder.mimeType, mimeType, "Mime type is not changed by start()"); + + await new Promise(r => recorder.onstart = r); + is(recorder.mimeType, extendedMimeType, "Mime type is fully defined"); + + const chunks = []; + let {data} = await new Promise(r => recorder.ondataavailable = r); + if (!isOffline) { + is(recorder.state, "recording", "Expected to still be recording"); + } + is(data.type, extendedMimeType, "Blob has fully defined mimetype"); + isnot(data.size, 0, "should get data and its length should be > 0"); + chunks.push(data); + + if (isOffline) { + await new Promise(r => recorder.onstop = r); + is(recorder.state, "inactive", "Offline context should end by itself"); + } else { + is(recorder.state, "recording", "Expected to still be recording"); + recorder.stop(); + ({data} = await new Promise(r => recorder.ondataavailable = r)); + is(recorder.state, "inactive", "Expected to be inactive after last blob"); + isnot(data.size, 0, "should get data and its length should be > 0"); + chunks.push(data); + + await new Promise(r => recorder.onstop = r); + is(recorder.state, "inactive", "state should remain inactive after stop event"); + } + return new Blob(chunks, {type: chunks[0].type}); +} + +addLoadEvent(async () => { + const src = setUpSource(); + let didThrow = false; + try { + new MediaRecorder(src); + } catch (e) { + didThrow = true; + } + ok(didThrow, "MediaRecorder(AudioNode) should be hidden behind a pref"); + + await SpecialPowers.pushPrefEnv({set: [ + ["media.recorder.audio_node.enabled", true], + ]}); + + // Test with various context and source node types. + for (const mimeType of [ + "audio/ogg", + "audio/webm", + "video/webm", + "", + ]) { + for (const {context, node} of [ + {context: "", node: "source"}, + {context: "", node: "splitter"}, + {context: "offline", node: "destination"}, + ]) { + info(`Testing recording ${context || "regular"} context and ${node} ` + + `node with mimeType '${mimeType}'`); + await testRecord(setUpSource(context, node), mimeType); + } + } + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_canvas_captureStream.html b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html new file mode 100644 index 0000000000..0b6cd6dbb5 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording canvas stream</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + var canvas = document.createElement("canvas"); + canvas.width = canvas.height = 100; + document.getElementById("content").appendChild(canvas); + + var helper = new CaptureStreamTestHelper2D(100, 100); + helper.drawColor(canvas, helper.red); + + var stream = canvas.captureStream(0); + + var blob; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the start of recording"); + + mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired"); + + mediaRecorder.onerror = () => ok(false, "Recording failed"); + + mediaRecorder.ondataavailable = ev => { + is(blob, undefined, "Should only get one dataavailable event"); + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info("Got 'start' event"); + // We just want one frame encoded, to see that the recorder produces something readable. + mediaRecorder.stop(); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + ok(blob, "Should have gotten a data blob"); + + var video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + video.play(); + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + document.getElementById("content").appendChild(video); + helper.pixelMustBecome(video, helper.red, { + threshold: 128, + infoString: "Should become red" + }).then(SimpleTest.finish); + }; + + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); +} + +SimpleTest.waitForExplicitFinish(); +startTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_changing_video_resolution.html b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html new file mode 100644 index 0000000000..d6354ee5a1 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html @@ -0,0 +1,174 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording canvas stream that dynamically changes resolution</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + let canvas = document.createElement("canvas"); + const resolution_change = [ + {width: 100, height: 100, color: "red"}, + {width: 150, height: 150, color: "blue"}, + {width: 100, height: 100, color: "red"}, + ]; + canvas.width = resolution_change[0].width; + canvas.height = resolution_change[0].height; + + let ctx = canvas.getContext("2d"); + ctx.fillStyle = resolution_change[0].color; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // The recorded stream coming from canvas. + let stream = canvas.captureStream(); + + // Check values for events + let numDataAvailabledRaised = 0; + let numResizeRaised = 0; + // Recorded data that will be playback. + let blob; + + // Media recorder for VP8 and canvas stream. + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the start of recording"); + + // Not expected events. + mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired"); + mediaRecorder.onerror = err => { + ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name); + SimpleTest.finish(); + }; + + // When recorder is stopped get recorded data. + mediaRecorder.ondataavailable = ev => { + info("Got 'dataavailable' event"); + ++numDataAvailabledRaised; + is(blob, undefined, "Should only get one dataavailable event"); + // Save recorded data for playback + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info('onstart fired successfully'); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + is(numDataAvailabledRaised, 1, "Should have gotten 1 dataavailable event"); + // Playback stream and verify resolution changes. + ok(blob, "Should have gotten a data blob"); + + let video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + video.preload = "metadata"; + + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + + // Check that the encoded frames have the correct sizes. + video.onresize = function() { + if (numResizeRaised < resolution_change.length) { + is(video.videoWidth, resolution_change[numResizeRaised].width, + "onresize width should be as expected"); + is(video.videoHeight, resolution_change[numResizeRaised].height, + "onresize height should be as expected"); + } else { + ok(false, "Got more resize events than expected"); + } + ++numResizeRaised; + }; + + video.onloadedmetadata = function() { + info("loadedmetadata"); + seekThroughFrames(); + }; + + video.onended = function() { + is(numResizeRaised, resolution_change.length, "Expected number of resize events"); + SimpleTest.finish(); + // This shouldn't be needed, however video.ended may not be set after + // seeking to the final frame. This can result in seekToNextFrame being + // called again by seekThroughFrames and onended being invoked again, + // resulting in multiple finish() calls. + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1386489 + video.onended = null; + }; + + document.getElementById("content").appendChild(video); + + function seekThroughFrames() { + info("Seeking to next frame"); + video.seekToNextFrame() + .then(() => { + info("Seeking to next frame finished. width=" + video.videoWidth + + ", height=" + video.videoHeight); + + if (video.ended) { + return; + } + + // After seeking finished we queue the next seek task on the event + // loop so it gets in the same queue as the "resize" events. + setTimeout(seekThroughFrames, 0); + }) + .catch(error => { + ok(false, "seekToNextFrame rejected: " + error); + }); + } + }; + + // Start here by stream recorder. + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); + requestAnimationFrame(draw); + + // Change resolution in every frame + // Stop recorder on last frame + let countFrames = 0; + let previous_time = performance.now(); + function draw(timestamp) { + if (timestamp - previous_time < 100) { + requestAnimationFrame(draw); + return; + } + previous_time = timestamp; + + if (countFrames == resolution_change.length) { + mediaRecorder.stop(); + return; + } + + canvas.width = resolution_change[countFrames].width; + canvas.height = resolution_change[countFrames].height; + ctx.fillStyle = resolution_change[countFrames].color; + // Resize and draw canvas + ctx.fillRect(0, 0, canvas.width, canvas.height); + // Register draw to be called on next rendering + requestAnimationFrame(draw); + countFrames++; + } +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.seekToNextFrame.enabled", true ], + ["media.video-queue.send-to-compositor-size", 1] + ] + }, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_downsize_resolution.html b/dom/media/test/test_mediarecorder_record_downsize_resolution.html new file mode 100644 index 0000000000..f9422a3897 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_downsize_resolution.html @@ -0,0 +1,148 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + var canvas = document.createElement("canvas"); + var canvas_size = 100; + var new_canvas_size = 50; + canvas.width = canvas.height = canvas_size; + + var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size); + helper.drawColor(canvas, helper.red); + + // The recorded stream coming from canvas. + var stream = canvas.captureStream(); + + // Check values for events + var numDataAvailabledRaised = 0; + var numResizeRaised = 0; + // Recorded data that will be playback. + var blob; + + // Media recorder for VP8 and canvas stream. + var mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the beginning of recording"); + + // Not expected events. + mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired"); + mediaRecorder.onerror = err => { + ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name); + SimpleTest.finish(); + } + + // When recorder is stopped get recorded data. + mediaRecorder.ondataavailable = ev => { + info("Got 'dataavailable' event"); + ++numDataAvailabledRaised; + is(blob, undefined, "On dataavailable event blob is undefined"); + // Save recorded data for playback + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info('onstart fired successfully'); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event"); + + // Playback stream and verify resolution changes. + ok(blob, "Should have gotten a data blob"); + var video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + + // Check here that resize is correct in the playback stream. + video.onresize = function() { + ++numResizeRaised; + if (numResizeRaised == 1) { + is(this.videoWidth, canvas_size, "1st resize event original width"); + is(this.videoHeight, canvas_size, "1st resize event original height "); + } else if (numResizeRaised == 2) { + is(this.videoWidth, new_canvas_size, "2nd resize event new width"); + is(this.videoHeight, new_canvas_size, "2nd resize event new height"); + } else { + ok(false, "Only 2 numResizeRaised events are expected"); + } + }; + + video.onended = () => { + is(numResizeRaised, 2, "Expected 2 resize event"); + }; + document.getElementById("content").appendChild(video); + video.play(); + + // Check last color + helper.pixelMustBecome(video, helper.red, { + threshold: 128, + infoString: "Should become red", + }).then(() => { + video.onresize = {}; + video.onended = {}; + SimpleTest.finish(); + }); + }; + + // Start here by stream recorder. + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder started"); + requestAnimationFrame(draw); + + // Change resolution every 100ms + var countFrames=0; + var previous_time = performance.now(); + function draw(timestamp) { + if (timestamp - previous_time < 100) { + requestAnimationFrame(draw); + return; + } + previous_time = timestamp; + + var size = 0; + var color = ""; + if (countFrames < 1) { + // Initial size + size = canvas_size; + color = helper.blue; + } else if (countFrames < 2) { + // upsize + size = new_canvas_size; + color = helper.red; + } else { + // Stop recoredr on last frame + mediaRecorder.stop(); + return; + } + // Resize and draw canvas + canvas.width = canvas.height = size; + helper.drawColor(canvas, color); + // Register next draw on every call + requestAnimationFrame(draw); + countFrames++; + } +} + +SimpleTest.waitForExplicitFinish(); +startTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_getdata_afterstart.html b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html new file mode 100644 index 0000000000..3b181ed8db --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 951008 Test MediaRecorder Record has start event</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + + var element = document.createElement('audio'); + var hasonstart = false; + var hasondataavailable = false; + var mMediaRecorder; + + element.token = token; + manager.started(token); + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStream(); + + mMediaRecorder = new MediaRecorder(element.stream); + is(mMediaRecorder.mimeType, '', 'Expected MediaRecorder mimetype'); + mMediaRecorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + mMediaRecorder.onerror = function() { + ok(false, 'onerror unexpectedly fired'); + }; + + mMediaRecorder.onstart = function() { + info('onstart fired successfully'); + hasonstart = true; + is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus', + "MediaRecorder mimetype as expected"); + mMediaRecorder.requestData(); + }; + + mMediaRecorder.onstop = function() { + info('onstop fired successfully'); + ok(hasondataavailable, "should have ondataavailable before onstop"); + is(mMediaRecorder.state, 'inactive', 'check recording status is inactive'); + SimpleTest.finish(); + }; + + mMediaRecorder.ondataavailable = function (e) { + info('ondataavailable fired successfully'); + if (mMediaRecorder.state == 'recording') { + hasondataavailable = true; + ok(hasonstart, "should have had start event first"); + is(e.data.type, mMediaRecorder.mimeType, + "blob's mimeType matches the recorder's"); + mMediaRecorder.stop(); + } + }; + + // Start recording once metadata are parsed. + element.onloadedmetadata = function() { + element.oncanplaythrough = null; + mMediaRecorder.start(0); + is(mMediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mMediaRecorder.stream, element.stream, + 'Media recorder stream = element stream at the start of recording'); + }; + + element.play(); +} + +manager.runTests(gMediaRecorderTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html new file mode 100644 index 0000000000..961a9644b2 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record gUM video with Timeslice</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<pre id="test"> +<div id="content" style="display: none"> +</div> +<script class="testbody" type="text/javascript"> + +async function startTest() { + try { + await setupGetUserMediaTestPrefs(); + let stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true}); + let dataAvailableCount = 0; + let onDataAvailableFirst = false; + const expectedMimeType = 'video/webm; codecs="vp8, opus"'; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + 'Media recorder stream = element stream at the start of recording'); + mediaRecorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + mediaRecorder.onerror = function() { + ok(false, 'onerror unexpectedly fired'); + }; + + mediaRecorder.onstop = function() { + ok(false, 'Unexpected onstop callback fired'); + }; + + mediaRecorder.onstart = function() { + is(mediaRecorder.mimeType, expectedMimeType, 'Expected mime type'); + }; + + mediaRecorder.ondataavailable = function (evt) { + info('ondataavailable fired'); + dataAvailableCount++; + + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + ok(evt.data.size >= 0, + 'Blob data size ' + evt.data.size + ' received is greater than or equal to zero'); + is(evt.data.type, expectedMimeType, 'Expected blob mime type'); + + // We'll stop recording upon the 1st blob being received + if (dataAvailableCount === 1) { + mediaRecorder.onstop = function (event) { + info('onstop fired'); + + if (!onDataAvailableFirst) { + ok(false, 'onstop unexpectedly fired before ondataavailable'); + } + + ok(true, 'onstop fired successfully'); + is(mediaRecorder.state, 'inactive', + 'check recording status is inactive'); + SimpleTest.finish(); + }; + + mediaRecorder.stop(); + is(mediaRecorder.state, 'inactive', + 'Media recorder is inactive after being stopped'); + + } else if (dataAvailableCount === 2) { + // Ensure we've received at least two ondataavailable events before + // onstop + onDataAvailableFirst = true; + } + }; + + mediaRecorder.start(250); + is(mediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mediaRecorder.mimeType, '', 'Expected mime type'); + } catch (err) { + ok(false, 'Unexpected error fired with: ' + err); + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html b/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html new file mode 100644 index 0000000000..af607280f6 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record gUM video with Timeslice, and playback of mixed memory and file blobs</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<pre id="test"> +<script type="text/javascript"> +function unexpected({type}) { + ok(false, `${type} unexpectedly fired`); +} + +(async () => { + SimpleTest.waitForExplicitFinish(); + let blobUrl = null; + let stream = null; + try { + // This is the memory limit per blob. If a blob is larger than this, + // MediaRecorder will put it in a file. For this test we need to get at + // least one blob under, and one blob over the limit. + const memoryLimit = 3000; + await SpecialPowers.pushPrefEnv({set: [ + ["media.recorder.max_memory", memoryLimit], + ]}); + // We always use fake devices since the loopback ones don't make enough + // pixels change per frame to make the encoded frames large enough. + await pushGetUserMediaTestPrefs({fakeAudio: true, fakeVideo: true}); + stream = await navigator.mediaDevices.getUserMedia( + {audio: true, video: true}); + const blobs = []; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = element stream at the start of recording"); + mediaRecorder.start(); + mediaRecorder.addEventListener("warning", unexpected); + mediaRecorder.addEventListener("error", unexpected); + mediaRecorder.addEventListener("stop", unexpected); + await new Promise(r => mediaRecorder.onstart = r); + + for (let hasMemory = false; !hasMemory;) { + mediaRecorder.requestData(); + const {data} = await new Promise(r => mediaRecorder.ondataavailable = r); + blobs.push(data); + ok(data.size < memoryLimit, "Blob should be small enough at start"); + hasMemory = data.size > 0 && data.size < memoryLimit; + info(`Blob is ${data.size} bytes.${hasMemory ? " In memory." : ""}`); + } + info("Got a memory blob"); + + SimpleTest.requestFlakyTimeout("Wait for file blob"); + for (let hasFile = false, waitTimeMs = 500; !hasFile; waitTimeMs *= 4) { + info(`Waiting ${waitTimeMs/1000} seconds for file blob`); + await new Promise(r => setTimeout(r, waitTimeMs)); + mediaRecorder.requestData(); + const {data} = await new Promise(r => mediaRecorder.ondataavailable = r); + blobs.push(data); + hasFile = data.size > memoryLimit; + info(`Blob is ${data.size} bytes. In ${hasFile ? "file" : "memory"}.`); + } + info("Got a file blob"); + + mediaRecorder.stop(); + const {data} = await new Promise(r => mediaRecorder.ondataavailable = r); + blobs.push(data); + mediaRecorder.removeEventListener("stop", unexpected); + await new Promise(r => mediaRecorder.onstop = r); + + const video = document.createElement("video"); + const blob = new Blob(blobs); + blobUrl = URL.createObjectURL(blob); + video.src = blobUrl; + info(`Starting playback. Blob-size=${blob.size}`); + video.play(); + + await Promise.race([ + new Promise(res => video.onended = res), + new Promise((res, rej) => video.onerror = () => rej(video.error.message)), + ]); + } catch (e) { + ok(false, e); + } finally { + if (stream) { + for (const t of stream.getTracks()) { + t.stop(); + } + } + if (blobUrl) { + URL.revokeObjectURL(blobUrl); + } + SimpleTest.finish(); + } +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_immediate_stop.html b/dom/media/test/test_mediarecorder_record_immediate_stop.html new file mode 100644 index 0000000000..8ed7c321a0 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_immediate_stop.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Immediate Stop</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +/** + * Stops the media recorder immediately after starting the recorder. This test + * verifies whether the media recorder can handle this scenario nicely. The + * return blob size should be greater than zero, but its duration would be zero + * length when play. + */ +function startTest(test, token) { + var element = document.createElement('audio'); + var expectedMimeType = test.type; + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStreamUntilEnded(); + + var mediaRecorder = + new MediaRecorder(element.stream, {mimeType: expectedMimeType}); + var onStopFired = false; + var onDataAvailableFired = false; + + mediaRecorder.onerror = function () { + ok(false, 'Unexpected onerror callback fired'); + }; + + mediaRecorder.onwarning = function () { + ok(false, 'Unexpected onwarning callback fired'); + }; + + // This handler verifies that only a single onstop event handler is fired. + mediaRecorder.onstop = function () { + if (onStopFired) { + ok(false, 'onstop unexpectedly fired more than once'); + } else { + onStopFired = true; + + // ondataavailable should always fire before onstop + if (onDataAvailableFired) { + manager.finished(token); + } else { + ok(false, 'onstop fired without an ondataavailable event first'); + } + } + }; + + // This handler verifies that only a single ondataavailable event handler + // is fired with the blob generated having greater than zero size + // and a correct mime type. + mediaRecorder.ondataavailable = function (evt) { + if (onDataAvailableFired) { + ok(false, 'ondataavailable unexpectedly fired more than once'); + } else { + onDataAvailableFired = true; + + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + + // The initialization of encoder can be cancelled. + // On some platforms, the stop method may run after media stream track + // available, so the blob can contain the header data. + is(evt.data.type, expectedMimeType, + 'Blob data received and should have mime type'); + is(mediaRecorder.mimeType, expectedMimeType, + 'Media Recorder mime type in ondataavailable = ' + expectedMimeType); + ok(evt.data.size >= 0, 'Blob size can not be negative'); + + // onstop should not have fired before ondataavailable + if (onStopFired) { + ok(false, 'ondataavailable unexpectedly fired later than onstop'); + manager.finished(token); + } + } + }; + + // This handler completes a start and stop of recording and verifies + // respective media recorder state. + element.onloadedmetadata = function () { + element.onloadedmetadata = null; + element.play(); + mediaRecorder.start(); + is(mediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mediaRecorder.stream, element.stream, + 'Media recorder stream = element stream at the start of recording'); + + mediaRecorder.stop(); + is(mediaRecorder.state, 'inactive', + 'Media recorder is inactive after being stopped'); + is(mediaRecorder.stream, element.stream, + 'Media recorder stream = element stream post recording'); + }; + + element.preload = "metadata"; +} + +manager.runTests(gMediaRecorderTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_no_timeslice.html b/dom/media/test/test_mediarecorder_record_no_timeslice.html new file mode 100644 index 0000000000..6ed148a108 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_no_timeslice.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record No Timeslice</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +/** + * Starts a test on every media recorder file included to check that a + * stream derived from the file can be recorded with no time slice provided. + */ +function startTest(test, token) { + var element = document.createElement('audio'); + var expectedMimeType = test.type; + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStreamUntilEnded(); + + var mediaRecorder = + new MediaRecorder(element.stream, {mimeType: expectedMimeType}); + var onStopFired = false; + var onDataAvailableFired = false; + + mediaRecorder.onerror = function () { + ok(false, 'Unexpected onerror callback fired'); + }; + + mediaRecorder.onwarning = function () { + ok(false, 'Unexpected onwarning callback fired'); + }; + + // This handler verifies that only a single onstop event handler is fired. + mediaRecorder.onstop = function () { + if (onStopFired) { + ok(false, 'onstop unexpectedly fired more than once'); + } else { + onStopFired = true; + + // ondataavailable should always fire before onstop + if (onDataAvailableFired) { + ok(true, 'onstop fired after ondataavailable'); + manager.finished(token); + } else { + ok(false, 'onstop fired without an ondataavailable event first'); + } + } + }; + + // This handler verifies that only a single ondataavailable event handler + // is fired with the blob generated having greater than zero size + // and a correct mime type. + mediaRecorder.ondataavailable = function (evt) { + if (onDataAvailableFired) { + ok(false, 'ondataavailable unexpectedly fired more than once'); + } else { + onDataAvailableFired = true; + + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + ok(evt.data.size > 0, + 'Blob data received should be greater than zero'); + is(evt.data.type, expectedMimeType, + 'Blob data received should have type = ' + expectedMimeType); + + is(mediaRecorder.mimeType, expectedMimeType, + 'Mime type in ondataavailable = ' + expectedMimeType); + + // onstop should not have fired before ondataavailable + if (onStopFired) { + ok(false, 'ondataavailable unexpectedly fired later than onstop'); + manager.finished(token); + } + } + }; + + element.preload = "metadata"; + + element.onloadedmetadata = function () { + element.onloadedmetadata = null; + mediaRecorder.start(); + is(mediaRecorder.state, 'recording', + 'Media recorder should be recording'); + is(mediaRecorder.stream, element.stream, + 'Media recorder stream = element stream at the start of recording'); + + element.play(); + } +} + +manager.runTests(gMediaRecorderTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_session.html b/dom/media/test/test_mediarecorder_record_session.html new file mode 100644 index 0000000000..88795d82d0 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_session.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=909670 +--> +<head> + <meta charset="utf-8"> + <title>Test for Media Recoder recording session</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function startTest(test, token) { + var element = document.createElement('audio'); + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStream(); + + var mStopCount = 0; + // Start and stop recording session three times continuously. + var mExpectStopCount = 3; + var mediaRecorder = new MediaRecorder(element.stream); + + // Stop callback. + // Suppose to receive mExpectStopCount + mediaRecorder.onstop = function stopCallback() { + mStopCount++; + + info("MediaRecorder.onstop callback: (" + mStopCount + ")"); + + if (mExpectStopCount === mStopCount) + { + manager.finished(token); + } + } + + // data avaliable. + mediaRecorder.ondataavailable = function(evt) {} + + mediaRecorder.onerror = function(err) { + ok(false, 'Unexpected error fired with:' + err); + } + + mediaRecorder.onwarning = function() { + ok(false, 'Unexpected warning fired'); + } + + element.preload = "metadata"; + + element.onloadedmetadata = function () { + element.onloadedmetadata = null; + element.play(); + for (var i = 0; i < mExpectStopCount; i++) { + mediaRecorder.start(1000); + mediaRecorder.stop(); + } + } + +} + +manager.runTests(gMediaRecorderTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_startstopstart.html b/dom/media/test/test_mediarecorder_record_startstopstart.html new file mode 100644 index 0000000000..b4cc62c709 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_startstopstart.html @@ -0,0 +1,75 @@ + <!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder crash on sequence start stop start method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content" style="display: none"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + var ac = new window.AudioContext(); + var dest = ac.createMediaStreamDestination(); + var recorder = new MediaRecorder(dest.stream); + var stopCount = 0; + var dataavailable = 0; + var expectedMimeType = 'audio/ogg; codecs=opus'; + recorder.onstop = function (e) { + info('onstop fired'); + is(recorder.stream, dest.stream, + 'Media recorder stream = element stream post recording'); + stopCount++; + if (stopCount == 2) { + if (dataavailable >= 2) { + SimpleTest.finish(); + } else { + ok(false, 'Should have at least two dataavailable events'); + } + } + } + recorder.ondataavailable = function (evt) { + info('ondataavailable fired'); + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + // If script runs slower, it may generate header data in blob from encoder + if (evt.data.size > 0) { + info('blob size = ' + evt.data.size); + is(evt.data.type, expectedMimeType, + 'Blob data received should have type = ' + expectedMimeType); + } + dataavailable++; + } + recorder.onerror = function (e) { + ok(false, 'it should execute normally without exception'); + } + recorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + recorder.start(2000); + is(recorder.state, 'recording', 'Media recorder should be recording'); + recorder.stop(); + is(recorder.state, 'inactive', 'check recording status is inactive'); + recorder.start(10000); // This bug would crash on this line without this fix. + is(recorder.state, 'recording', 'check recording status is recording'); + // Simulate delay stop, only delay stop no no stop can trigger crash. + setTimeout(function() { + recorder.stop(); + is(recorder.state, 'inactive','check recording status is recording'); + }, 1000); +} + +SimpleTest.requestFlakyTimeout("untriaged"); +startTest(); +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> + diff --git a/dom/media/test/test_mediarecorder_record_timeslice.html b/dom/media/test/test_mediarecorder_record_timeslice.html new file mode 100644 index 0000000000..3e547e77b4 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_timeslice.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Record Timeslice</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +/** + * Starts a test on every media recorder file included to check that a stream + * derived from the file can be recorded with a timeslice provided + */ +function startTest(test, token) { + var element = document.createElement('audio'); + var expectedMimeType = test.type; + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.preload = "auto"; + + // Set up MediaRecorder once loadedmetadata fires and tracks are available. + element.onloadedmetadata = function() { + element.onloadedmetadata = null; + + const stream = element.mozCaptureStream(); + const mediaRecorder = + new MediaRecorder(stream, {mimeType: expectedMimeType}); + + mediaRecorder.onerror = function () { + ok(false, 'Unexpected onerror callback fired'); + }; + + mediaRecorder.onwarning = function () { + ok(false, 'Unexpected onwarning callback fired'); + }; + + mediaRecorder.onstop = function () { + ok(false, 'Unexpected onstop callback fired'); + }; + + var dataAvailableCount = 0; + var onDataAvailableFirst = false; + + // This handler fires every 250ms to generate a blob. + mediaRecorder.ondataavailable = function (evt) { + info('ondataavailable fired'); + dataAvailableCount++; + + ok(evt instanceof BlobEvent, + 'Events fired from ondataavailable should be BlobEvent'); + is(evt.type, 'dataavailable', + 'Event type should dataavailable'); + ok(evt.data.size >= 0, + 'Blob data size received is greater than or equal to zero'); + + is(evt.data.type, expectedMimeType, + 'Blob data received should have type = ' + expectedMimeType); + is(mediaRecorder.mimeType, expectedMimeType, + 'Mime type in ondataavailable = ' + mediaRecorder.mimeType); + + // We'll stop recording upon the 1st blob being received + if (dataAvailableCount === 1) { + mediaRecorder.onstop = function (event) { + info('onstop fired'); + + if (!onDataAvailableFirst) { + ok(false, 'onstop unexpectedly fired before ondataavailable'); + } + element.pause(); + manager.finished(token); + }; + + mediaRecorder.stop(); + is(mediaRecorder.state, 'inactive', + 'Media recorder is inactive after being stopped'); + is(mediaRecorder.stream, stream, + 'Media recorder stream = element stream post recording'); + + } else if (dataAvailableCount === 2) { + // Ensure we've received at least two ondataavailable events before onstop + onDataAvailableFirst = true; + } + }; + + mediaRecorder.start(1); + element.play(); + is(mediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mediaRecorder.stream, stream, + 'Media recorder stream = element stream at the start of recording'); + }; +} + +manager.runTests(gMediaRecorderTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_record_upsize_resolution.html b/dom/media/test/test_mediarecorder_record_upsize_resolution.html new file mode 100644 index 0000000000..d02fd08e44 --- /dev/null +++ b/dom/media/test/test_mediarecorder_record_upsize_resolution.html @@ -0,0 +1,148 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + var canvas = document.createElement("canvas"); + var canvas_size = 100; + var new_canvas_size = 150; + canvas.width = canvas.height = canvas_size; + + var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size); + helper.drawColor(canvas, helper.red); + + // The recorded stream coming from canvas. + var stream = canvas.captureStream(); + + // Check values for events + var numDataAvailabledRaised = 0; + var numResizeRaised = 0; + // Recorded data that will be playback. + var blob; + + // Media recorder for VP8 and canvas stream. + var mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the beginning of recording"); + + // Not expected events. + mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired"); + mediaRecorder.onerror = err => { + ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name); + SimpleTest.finish(); + } + + // When recorder is stopped get recorded data. + mediaRecorder.ondataavailable = ev => { + info("Got 'dataavailable' event"); + ++numDataAvailabledRaised; + is(blob, undefined, "On dataavailable event blob is undefined"); + // Save recorded data for playback + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info('onstart fired successfully'); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event"); + + // Playback stream and verify resolution changes. + ok(blob, "Should have gotten a data blob"); + var video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + + // Check here that resize is correct in the playback stream. + video.onresize = function() { + ++numResizeRaised; + if (numResizeRaised == 1) { + is(this.videoWidth, canvas_size, "1st resize event original width"); + is(this.videoHeight, canvas_size, "1st resize event original height "); + } else if (numResizeRaised == 2) { + is(this.videoWidth, new_canvas_size, "2nd resize event new width"); + is(this.videoHeight, new_canvas_size, "2nd resize event new height"); + } else { + ok(false, "Only 2 numResizeRaised events are expected"); + } + }; + + video.onended = () => { + is(numResizeRaised, 2, "Expected 2 resize event"); + }; + document.getElementById("content").appendChild(video); + video.play(); + + // Check last color + helper.pixelMustBecome(video, helper.red, { + threshold: 128, + infoString: "Should become red", + }).then(() => { + video.onresize = {}; + video.onended = {}; + SimpleTest.finish(); + }); + }; + + // Start here by stream recorder. + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder started"); + requestAnimationFrame(draw); + + // Change resolution every 100 ms + var countFrames=0; + var previous_time = performance.now(); + function draw(timestamp) { + if (timestamp - previous_time < 100) { + requestAnimationFrame(draw); + return; + } + previous_time = timestamp; + + var size = 0; + var color = ""; + if (countFrames < 1) { + // Initial size + size = canvas_size; + color = helper.blue; + } else if (countFrames < 2) { + // upsize + size = new_canvas_size; + color = helper.red; + }else { + // Stop recoredr on last frame + mediaRecorder.stop(); + return; + } + // Resize and draw canvas + canvas.width = canvas.height = size; + helper.drawColor(canvas, color); + // Register next draw on every call + requestAnimationFrame(draw); + countFrames++; + } +} + +SimpleTest.waitForExplicitFinish(); +startTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_reload_crash.html b/dom/media/test/test_mediarecorder_reload_crash.html new file mode 100644 index 0000000000..f6d008261f --- /dev/null +++ b/dom/media/test/test_mediarecorder_reload_crash.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that reloading media recorder object</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=894348">Mozill +a Bug 894348</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + for (let i = 0; i< 5; i++) { + /* eslint-disable no-undef */ + try { o0 = document.createElement('audio') } catch(e) { } + try { o0.src = "sound.ogg" } catch(e) { } + try { (document.body || document.documentElement).appendChild(o0) } catch(e) { } + try { o1 = o0.mozCaptureStreamUntilEnded(); } catch(e) { } + try { o2 = new MediaRecorder(o1) } catch(e) { } + try { o2.start(0) } catch(e) { } + /* eslint-enable no-undef */ + SpecialPowers.gc(); + } + ok(true, "pass the crash test"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_state_event_order.html b/dom/media/test/test_mediarecorder_state_event_order.html new file mode 100644 index 0000000000..feba055f4d --- /dev/null +++ b/dom/media/test/test_mediarecorder_state_event_order.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder fires an event after changing state</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +/** + * The flow of test is start()=>pause()=>resume()=>stop(). In each steps, + * this test verifies whether each MediaRecorder methods properly change + * its state before firing an event. Checking the state is done in the + * corresponding event handlers. + */ +function startTest(test, token) { + var element = document.createElement('audio'); + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStream(); + + var mediaRecorder = new MediaRecorder(element.stream); + + mediaRecorder.onwarning = function() { + ok(false, 'onwarning unexpectedly fired'); + }; + + mediaRecorder.onerror = function() { + ok(false, 'onerror unexpectedly fired'); + }; + + mediaRecorder.onstart = function() { + info('onstart fired successfully'); + is(mediaRecorder.state, 'recording', + 'Media Recorder changes state to recording before firing a start event'); + mediaRecorder.pause(); + }; + + mediaRecorder.onpause = function() { + info('onpause fired successfully'); + is(mediaRecorder.state, 'paused', + 'Media Recorder changes state to paused before firing a pause event'); + mediaRecorder.resume(); + }; + + mediaRecorder.onresume = function() { + info('onresume fired successfully'); + is(mediaRecorder.state, 'recording', + 'Media Recorder changes state to recording before firing a resume event'); + mediaRecorder.stop(); + }; + + mediaRecorder.onstop = function() { + info('onstop fired successfully'); + is(mediaRecorder.state, 'inactive', + 'Media Recorder changes state to inactive before firing a stop event'); + SimpleTest.finish(); + }; + + // Call start() once metadata are parsed. + element.onloadedmetadata = function() { + element.play(); + mediaRecorder.start(); + is(mediaRecorder.state, 'recording', 'Media recorder should be recording'); + is(mediaRecorder.stream, element.stream, + 'Media recorder stream = element stream at the start of recording'); + }; +} + +manager.runTests(gMediaRecorderTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_state_transition.html b/dom/media/test/test_mediarecorder_state_transition.html new file mode 100644 index 0000000000..5d6483b7de --- /dev/null +++ b/dom/media/test/test_mediarecorder_state_transition.html @@ -0,0 +1,280 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder State Transition</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +// List of operation tests for media recorder objects to verify if running +// these operations should result in an exception or not +var operationTests = [ + { + operations: ['stop'], + isValid: true + }, + { + operations: ['requestData'], + isValid: false + }, + { + operations: ['pause'], + isValid: false + }, + { + operations: ['resume'], + isValid: false + }, + { + operations: ['start'], + isValid: true + }, + { + operations: ['start'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'pause'], + isValid: true + }, + { + operations: ['start', 'pause'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'start'], + isValid: false + }, + { + operations: ['start', 'resume'], + isValid: true + }, + { + operations: ['pause', 'start'], + isValid: false + }, + { + operations: ['resume', 'start'], + isValid: false + }, + { + operations: ['requestData', 'start'], + isValid: false + }, + { + operations: ['stop', 'start'], + isValid: true + }, + { + operations: ['start', 'stop'], + isValid: true + }, + { + operations: ['start', 'stop'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'requestData'], + isValid: true + }, + { + operations: ['start', 'requestData'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'pause', 'stop'], + isValid: true + }, + { + operations: ['start', 'pause', 'start'], + isValid: false + }, + { + operations: ['start', 'pause', 'pause'], + isValid: true + }, + { + operations: ['start', 'pause', 'requestData'], + isValid: true + }, + { + operations: ['start', 'pause', 'resume'], + isValid: true + }, + { + operations: ['start', 'pause', 'resume'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'resume', 'resume'], + isValid: true + }, + { + operations: ['start', 'resume', 'resume'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'resume', 'stop'], + isValid: true + }, + { + operations: ['start', 'resume', 'stop'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'start'], + isValid: true + }, + { + operations: ['start', 'stop', 'start'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'pause'], + isValid: false + }, + { + operations: ['start', 'stop', 'resume'], + isValid: false + }, + { + operations: ['start', 'stop', 'requestData'], + isValid: false + }, + { + operations: ['start', 'stop', 'stop'], + isValid: true + }, + { + operations: ['start', 'pause', 'resume', 'resume'], + isValid: true + }, + { + operations: ['start', 'pause', 'resume', 'resume'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'pause', 'pause', 'resume'], + isValid: true + }, + { + operations: ['start', 'pause', 'pause', 'resume'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'start', 'stop'], + isValid: true + }, + { + operations: ['start', 'stop', 'start', 'stop'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'start', 'pause'], + isValid: true + }, + { + operations: ['start', 'stop', 'start', 'pause'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'start', 'resume'], + isValid: true + }, + { + operations: ['start', 'stop', 'start', 'resume'], + isValid: true, + timeSlice: 200 + }, + { + operations: ['start', 'stop', 'start', 'requestData'], + isValid: true + }, + { + operations: ['start', 'stop', 'start', 'requestData'], + isValid: true, + timeSlice: 200 + }, +]; + +/** + * Runs through each available state transition test by running all + * available operations on a media recorder object. Then, we report + * back if the test was expected through an exception or not. + * + * @param {MediaStream} testStream the media stream used for media recorder + * operation tests + */ +function runStateTransitionTests(testStream) { + for (const operationTest of operationTests) { + var mediaRecorder = new MediaRecorder(testStream); + var operationsString = operationTest.operations.toString(); + + try { + for (const operation of operationTest.operations) { + if (operationTest.timeSlice && operation === 'start') { + operationsString += ' with timeslice ' + operationTest.timeSlice; + mediaRecorder[operation](operationTest.timeSlice); + } else { + mediaRecorder[operation](); + } + } + + ok(operationTest.isValid, `${operationsString} should succeed`); + } catch (err) { + if (operationTest.isValid) { + ok(false, `${operationsString} failed unexpectedly with ${err.name}`); + } else { + is(err.name, "InvalidStateError", + `${operationsString} expected to fail with InvalidStateError`); + } + } + } +} + +/** + * Starts a test on every media recorder file included to check that various + * state transition flows that can happen in the media recorder object throw + * exceptions when they are expected to and vice versa. + */ +function startTest(test, token) { + var element = document.createElement('audio'); + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.stream = element.mozCaptureStream(); + + element.oncanplaythrough = function () { + element.oncanplaythrough = null; + runStateTransitionTests(element.stream); + manager.finished(token); + }; + + element.play(); +} + +manager.runTests(gMediaRecorderTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediarecorder_webm_support.html b/dom/media/test/test_mediarecorder_webm_support.html new file mode 100644 index 0000000000..6b115ee33a --- /dev/null +++ b/dom/media/test/test_mediarecorder_webm_support.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media Recording - test WebM MIME support</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +ok(MediaRecorder.isTypeSupported('audio/webm'), + 'Should support audio/webm'); +ok(MediaRecorder.isTypeSupported('AUDIO/WEBM'), + 'Should support audio/webm, upper case'); +ok(MediaRecorder.isTypeSupported('AuDiO/wEbM'), + 'Should support audio/webm, mixed case'); + +ok(MediaRecorder.isTypeSupported('audio/webm;codecs=opus'), + 'Should support audio/webm;codecs=opus'); +ok(MediaRecorder.isTypeSupported('AUDIO/WEBM;CODECS=opus'), + 'Should support audio/webm;codecs=opus, upper case'); +ok(MediaRecorder.isTypeSupported('AuDiO/wEbM;cOdEcS=opus'), + 'Should support audio/webm;codecs=opus, mixed case'); + +ok(MediaRecorder.isTypeSupported('video/webm'), + 'Should support video/webm'); +ok(MediaRecorder.isTypeSupported('VIDEO/WEBM'), + 'Should support video/webm, upper case'); +ok(MediaRecorder.isTypeSupported('vIdEo/WeBm'), + 'Should support video/webm, mixed case'); + +ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8"'), + 'Should support video/webm; codecs="vp8"'); +ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8"'), + 'Should support video/webm; codecs="vp8", upper case'); +ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8"'), + 'Should support video/webm; codecs="vp8", mixed case'); + +ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8.0"'), + 'Should support video/webm; codecs="vp8.0"'); +ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8.0"'), + 'Should support video/webm; codecs="vp8.0", upper case'); +ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8.0"'), + 'Should support video/webm; codecs="vp8.0", mixed case'); + +ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'), + 'Should not support video/webm + vp8/vorbis'); +ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'), + 'Should not support video/webm + vp9/vorbis'); +ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, opus"'), + 'Should support video/webm + vp8/opus'); +ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, opus"'), + 'Should not support video/webm + vp9/opus'); +</script> +</head> +</html> diff --git a/dom/media/test/test_mediatrack_consuming_mediaresource.html b/dom/media/test/test_mediatrack_consuming_mediaresource.html new file mode 100644 index 0000000000..515df5c053 --- /dev/null +++ b/dom/media/test/test_mediatrack_consuming_mediaresource.html @@ -0,0 +1,198 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test track interfaces when consuming media resources</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const manager = new MediaTestManager; + +function startTest(test, token) { + const elemType = getMajorMimeType(test.type); + const element = document.createElement(elemType); + + let audioOnchange = 0; + let audioOnaddtrack = 0; + let audioOnremovetrack = 0; + let videoOnchange = 0; + let videoOnaddtrack = 0; + let videoOnremovetrack = 0; + let isPlaying = false; + + isnot(element.audioTracks, undefined, + 'HTMLMediaElement::AudioTracks() property should be available.'); + isnot(element.videoTracks, undefined, + 'HTMLMediaElement::VideoTracks() property should be available.'); + + element.audioTracks.onaddtrack = function(e) { + audioOnaddtrack++; + } + + element.audioTracks.onremovetrack = function(e) { + audioOnremovetrack++; + } + + element.audioTracks.onchange = function(e) { + audioOnchange++; + } + + element.videoTracks.onaddtrack = function(e) { + videoOnaddtrack++; + } + + element.videoTracks.onremovetrack = function(e) { + videoOnremovetrack++; + } + + element.videoTracks.onchange = function(e) { + videoOnchange++; + } + + function checkTrackNotRemoved() { + is(audioOnremovetrack, 0, 'Should have no calls of onremovetrack on audioTracks.'); + is(videoOnremovetrack, 0, 'Should have no calls of onremovetrack on videoTracks.'); + if (isPlaying) { + is(element.audioTracks.length, test.hasAudio ? 1 : 0, + 'Expected length of audioTracks.'); + is(element.videoTracks.length, test.hasVideo ? 1 : 0, + 'Expected length of videoTracks.'); + } + } + + function checkTrackRemoved() { + is(element.audioTracks.length, 0, 'The length of audioTracks should be 0.'); + is(element.videoTracks.length, 0, 'The length of videoTracks should be 0.'); + if (isPlaying) { + is(audioOnremovetrack, test.hasAudio ? 1 : 0, + 'Expected calls of onremovetrack on audioTracks.'); + is(videoOnremovetrack, test.hasVideo ? 1 : 0, + 'Expected calls of onremovetrack on videoTracks.'); + } + } + + function onended() { + ok(true, 'Event ended is expected to be fired on element.'); + checkTrackNotRemoved(); + element.onended = null; + element.onplaying = null; + element.onpause = null; + element.src = ""; + is(element.audioTracks.length, 0, 'audioTracks have been forgotten'); + is(element.videoTracks.length, 0, 'videoTracks have been forgotten'); + is(audioOnremovetrack, 0, 'No audio removetrack events yet'); + is(videoOnremovetrack, 0, 'No video removetrack events yet'); + setTimeout(() => { + checkTrackRemoved(); + manager.finished(element.token); + }, 100); + } + + function checkTrackAdded() { + isPlaying = true; + if (test.hasAudio) { + is(audioOnaddtrack, 1, 'Calls of onaddtrack on audioTracks should be 1.'); + is(element.audioTracks.length, 1, 'The length of audioTracks should be 1.'); + ok(element.audioTracks[0].enabled, 'Audio track should be enabled as default.'); + } + if (test.hasVideo) { + is(videoOnaddtrack, 1, 'Calls of onaddtrack on videoTracks should be 1.'); + is(element.videoTracks.length, 1, 'The length of videoTracks should be 1.'); + is(element.videoTracks.selectedIndex, 0, + 'The first video track is set selected as default.'); + } + } + + function setTrackEnabled(enabled) { + if (test.hasAudio) { + element.audioTracks[0].enabled = enabled; + } + if (test.hasVideo) { + element.videoTracks[0].selected = enabled; + } + } + + function checkTrackChanged(calls, enabled) { + if (test.hasAudio) { + is(audioOnchange, calls, 'Calls of onchange on audioTracks should be '+calls); + is(element.audioTracks[0].enabled, enabled, + 'Enabled value of the audio track should be ' +enabled); + } + if (test.hasVideo) { + is(videoOnchange, calls, 'Calls of onchange on videoTracks should be '+calls); + is(element.videoTracks[0].selected, enabled, + 'Selected value of the video track should be ' +enabled); + var index = enabled ? 0 : -1; + is(element.videoTracks.selectedIndex, index, + 'SelectedIndex of video tracks should be ' +index); + } + } + + function onpause() { + element.onpause = null; + if (element.ended) { + return; + } + if (steps == 1) { + setTrackEnabled(false); + element.onplaying = onplaying; + element.play(); + steps++; + } else if (steps == 2) { + setTrackEnabled(true); + element.onplaying = onplaying; + element.play(); + steps++; + } + } + + function onplaying() { + element.onplaying = null; + if (element.ended) { + return; + } + if (steps == 1) { + element.onpause = onpause; + element.pause(); + checkTrackAdded(); + } else if (steps == 2) { + element.onpause = onpause; + element.pause(); + checkTrackChanged(1, false); + } else if (steps == 3) { + checkTrackChanged(2, true); + } + } + + var steps = 0; + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.onplaying = onplaying; + element.onended = onended; + element.play(); + steps++; +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.track.enabled", true] + ] + }, + function() { + manager.runTests(gTrackTests, startTest); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediatrack_consuming_mediastream.html b/dom/media/test/test_mediatrack_consuming_mediastream.html new file mode 100644 index 0000000000..b930ca4fdc --- /dev/null +++ b/dom/media/test/test_mediatrack_consuming_mediastream.html @@ -0,0 +1,146 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test track interfaces when consuming a MediaStream from gUM</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function startTest() { + let steps = 0; + let audioOnchange = 0; + let audioOnaddtrack = 0; + let videoOnchange = 0; + let videoOnaddtrack = 0; + let isPlaying = false; + let element = document.createElement("video"); + let stream; + try { + await setupGetUserMediaTestPrefs(); + stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true}); + } catch (err) { + ok(false, 'Unexpected error fired with: ' + err); + SimpleTest.finish(); + return; + } + + element.audioTracks.onaddtrack = function(e) { + audioOnaddtrack++; + }; + + element.audioTracks.onchange = function(e) { + audioOnchange++; + }; + + element.videoTracks.onaddtrack = function(e) { + videoOnaddtrack++; + }; + + element.videoTracks.onchange = function(e) { + videoOnchange++; + }; + + function checkTrackRemoved() { + if (isPlaying) { + is(element.audioTracks.length, 0, 'The length of audioTracks should be 0.'); + is(element.videoTracks.length, 0, 'The length of videoTracks should be 0.'); + } + } + + element.onended = function() { + ok(true, 'Event ended is expected to be fired on element.'); + checkTrackRemoved(); + element.onended = null; + element.onplaying = null; + element.onpause = null; + SimpleTest.finish(); + } + + function checkTrackAdded() { + isPlaying = true; + is(audioOnaddtrack, 1, 'Calls of onaddtrack on audioTracks should be 1.'); + is(element.audioTracks.length, 1, 'The length of audioTracks should be 1.'); + ok(element.audioTracks[0].enabled, 'Audio track should be enabled as default.'); + is(videoOnaddtrack, 1, 'Calls of onaddtrack on videoTracks should be 1.'); + is(element.videoTracks.length, 1, 'The length of videoTracks should be 1.'); + is(element.videoTracks.selectedIndex, 0, + 'The first video track is set selected as default.'); + } + + function setTrackEnabled(enabled) { + element.audioTracks[0].enabled = enabled; + element.videoTracks[0].selected = enabled; + } + + function checkTrackChanged(calls, enabled) { + is(audioOnchange, calls, 'Calls of onchange on audioTracks should be '+calls); + is(element.audioTracks[0].enabled, enabled, + 'Enabled value of the audio track should be ' +enabled); + is(videoOnchange, calls, 'Calls of onchange on videoTracks should be '+calls); + is(element.videoTracks[0].selected, enabled, + 'Selected value of the video track should be ' +enabled); + var index = enabled ? 0 : -1; + is(element.videoTracks.selectedIndex, index, + 'SelectedIndex of video tracks should be ' +index); + } + + function onpause() { + element.onpause = null; + if (element.ended) { + return; + } + if (steps == 1) { + setTrackEnabled(false); + element.onplaying = onplaying; + element.play(); + steps++; + } else if (steps == 2) { + setTrackEnabled(true); + element.onplaying = onplaying; + element.play(); + steps++; + } + } + + function onplaying() { + element.onplaying = null; + if (element.ended) { + return; + } + if (steps == 1) { + element.onpause = onpause; + element.pause(); + checkTrackAdded(); + } else if (steps == 2) { + element.onpause = onpause; + element.pause(); + checkTrackChanged(1, false); + } else if (steps == 3) { + checkTrackChanged(2, true); + stream.getTracks().forEach(t => t.stop()); + } + } + + element.onplaying = onplaying; + element.srcObject = stream; + + steps++; + await element.play(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.track.enabled", true] + ] + }, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediatrack_events.html b/dom/media/test/test_mediatrack_events.html new file mode 100644 index 0000000000..5eae94f804 --- /dev/null +++ b/dom/media/test/test_mediatrack_events.html @@ -0,0 +1,135 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test events of media track interfaces</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function startTest() { + let steps = 0; + let element = document.createElement("video"); + let stream; + try { + await setupGetUserMediaTestPrefs(); + stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true}); + } catch (err) { + ok(false, 'Unexpected error fired with: ' + err); + SimpleTest.finish(); + return; + } + + function verifyEvent(e, type) { + is(e.type, type, "Event type should be " + type); + ok(e.isTrusted, "Event should be trusted."); + ok(!e.bubbles, "Event shouldn't bubble."); + ok(!e.cancelable, "Event shouldn't be cancelable."); + } + + element.audioTracks.onaddtrack = function(e) { + ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent"); + ok(true, 'onaddtrack is expected to be called from audioTracks.'); + verifyEvent(e, "addtrack"); + }; + + element.audioTracks.onremovetrack = function(e) { + ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent"); + ok(true, 'onremovetrack is expected to be called from audioTracks.'); + verifyEvent(e, "removetrack"); + }; + + element.audioTracks.onchange = function(e) { + ok(e instanceof window.Event, "Event fired from onchange should be a simple event."); + ok(true, 'onchange is expected to be called from audioTracks.'); + verifyEvent(e, "change"); + }; + + element.videoTracks.onaddtrack = function(e) { + ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent"); + ok(true, 'onaddtrack is expected to be called from videoTracks.'); + verifyEvent(e, "addtrack"); + }; + + element.videoTracks.onremovetrack = function(e) { + ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent"); + ok(true, 'onremovetrack is expected to be called from videoTracks.'); + verifyEvent(e, "removetrack"); + }; + + element.videoTracks.onchange = function(e) { + ok(e instanceof window.Event, "Event fired from onchange should be a simple event."); + ok(true, 'onchange is expected to be called from videoTracks.'); + verifyEvent(e, "change"); + }; + + element.onended = function() { + ok(true, 'Event ended is expected to be fired on element.'); + element.onended = null; + element.onplaying = null; + element.onpause = null; + //This helps to prevent these events from firing after SimpleTest.finish() + //on B2G ICS Emulator, but not sure they have been run at all, then + element.audioTracks.onremovetrack = null; + element.audioTracks.onaddtrack = null; + element.audioTracks.onchange = null; + element.videoTracks.onremovetrack = null; + element.videoTracks.onaddtrack = null; + element.videoTracks.onchange = null; + SimpleTest.finish(); + } + + function onpause() { + element.onpause = null; + if (element.ended) { + return; + } + if (steps == 1) { + element.audioTracks[0].enabled = false; + element.videoTracks[0].selected = false; + element.onplaying = onplaying; + element.play(); + steps++; + } + } + + function onplaying() { + element.onplaying = null; + if (element.ended) { + return; + } + if (steps == 1) { + element.onpause = onpause; + element.pause(); + } else if (steps == 2) { + stream.getTracks().forEach(t => t.stop()); + } + } + + element.onplaying = onplaying; + element.srcObject = stream; + + isnot(element.audioTracks, undefined, + 'HTMLMediaElement::AudioTracks() property should be available.'); + isnot(element.videoTracks, undefined, + 'HTMLMediaElement::VideoTracks() property should be available.'); + + steps++; + await element.play(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.track.enabled", true] + ] + }, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediatrack_parsing_ogg.html b/dom/media/test/test_mediatrack_parsing_ogg.html new file mode 100644 index 0000000000..aabd40e2a3 --- /dev/null +++ b/dom/media/test/test_mediatrack_parsing_ogg.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test events of media track interfaces</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function localCheckMetadata(msg, e) { + ok(msg in gOggTrackInfoResults, "File: " + msg + " is in pre-parsed gOggTrackInfoResults list"); + var r = gOggTrackInfoResults[msg]; + + var hasExpectedAudio = r && r.hasOwnProperty("audio_id"); + var hasExpectedVideo = r && r.hasOwnProperty("video_id"); + + var hasParsedAudio = e.audioTracks.length >= 1; + var hasParsedVideo = e.videoTracks.length >= 1; + + ok(!(hasExpectedAudio ^ hasParsedAudio), "Check availability of expected/parsed audio"); + ok(!(hasExpectedVideo ^ hasParsedVideo), "Check availability of expected/parsed video"); + if (hasParsedAudio) { + is(e.audioTracks.length, 1, "The length of audio track should be 1"); + is(e.audioTracks[0].id, r.audio_id, "File: " + msg + ", Audio track id"); + is(e.audioTracks[0].kind, r.audio_kind, "File: " + msg + ", Audio track kind"); + is(e.audioTracks[0].language, r.audio_language, "File: " + msg + ", Audio track language"); + is(e.audioTracks[0].label, r.audio_label, "File: " + msg + ", Audio track label"); + } + if (hasParsedVideo) { + is(e.videoTracks.length, 1, "The length of video track should be 1"); + is(e.videoTracks[0].id, r.video_id, "File: " + msg + ", Video track id"); + is(e.videoTracks[0].kind, r.video_kind, "File: " + msg + ", Video track kind"); + is(e.videoTracks[0].language, r.video_language, "File: " + msg + ", Video track language"); + is(e.videoTracks[0].label, r.video_label, "File: " + msg + ", Video track label"); + } +} + +function startTest(test, token) { + var v = document.createElement('video'); + v.preload = "metadata"; + v.token = token; + manager.started(token); + + v.src = test.name; + v.name = test.name; + + v.onloadedmetadata = function(evt) { + localCheckMetadata(evt.target.name, evt.target); + evt.target.finished = true; + evt.target.onloadedmetadata = null; + removeNodeAndSource(evt.target); + manager.finished(evt.target.token); + }; + + document.body.appendChild(v); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.track.enabled", true]]}, + function() { + manager.runTests(gMultitrackInfoOggPlayList, startTest); + } +); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mediatrack_replay_from_end.html b/dom/media/test/test_mediatrack_replay_from_end.html new file mode 100644 index 0000000000..16b0cbeb97 --- /dev/null +++ b/dom/media/test/test_mediatrack_replay_from_end.html @@ -0,0 +1,160 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test media tracks if replay after playback has ended</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const manager = new MediaTestManager; + +function startTest(test, token) { + // Scenario to test: + // 1. Audio tracks and video tracks should be added to the track list when + // metadata has loaded, and all tracks should remain even after we seek to + // the end. + // 2. No tracks should be added back to the list if we replay from the end, + // and no tracks should be removed from the list after we seek to the end. + // 3. After seek to the middle from end of playback, all tracks should remain + // in the list if we play from here, and no tracks should be removed from + // the list after we seek to the end. + // 4. Unsetting the media element's src attribute should remove all tracks. + + const elemType = getMajorMimeType(test.type); + const element = document.createElement(elemType); + + let audioOnaddtrack = 0; + let audioOnremovetrack = 0; + let videoOnaddtrack = 0; + let videoOnremovetrack = 0; + let isPlaying = false; + let steps = 0; + + element.audioTracks.onaddtrack = function(e) { + audioOnaddtrack++; + } + + element.audioTracks.onremovetrack = function(e) { + audioOnremovetrack++; + } + + element.videoTracks.onaddtrack = function(e) { + videoOnaddtrack++; + } + + element.videoTracks.onremovetrack = function(e) { + videoOnremovetrack++; + } + + function testExpectedAddtrack(expectedCalls) { + if (test.hasAudio) { + is(audioOnaddtrack, expectedCalls, + 'Calls of onaddtrack on audioTracks should be '+expectedCalls+' times.'); + } + if (test.hasVideo) { + is(videoOnaddtrack, expectedCalls, + 'Calls of onaddtrack on videoTracks should be '+expectedCalls+' times.'); + } + } + + function testExpectedRemovetrack(expectedCalls) { + if (test.hasAudio) { + is(audioOnremovetrack, expectedCalls, + 'Calls of onremovetrack on audioTracks should be '+expectedCalls+' times.'); + } + if (test.hasVideo) { + is(videoOnremovetrack, expectedCalls, + 'Calls of onremovetrack on videoTracks should be '+expectedCalls+' times.'); + } + } + + function finishTesting() { + element.onpause = null; + element.onseeked = null; + element.onplaying = null; + element.onended = null; + manager.finished(element.token); + } + + function onended() { + if (isPlaying) { + switch(steps) { + case 1: + testExpectedAddtrack(1); + testExpectedRemovetrack(0); + element.onplaying = onplaying; + element.play(); + steps++; + break; + case 2: + testExpectedAddtrack(1); + testExpectedRemovetrack(0); + element.currentTime = element.duration * 0.5; + element.onplaying = onplaying; + element.play(); + steps++; + break; + case 3: + testExpectedAddtrack(1); + testExpectedRemovetrack(0); + element.src = ""; + setTimeout(() => { + testExpectedAddtrack(1); + testExpectedRemovetrack(1); + finishTesting(); + }, 0); + break; + } + } else { + ok(true, 'Finish the test anyway if ended is fired before other events.'); + finishTesting(); + } + } + + function seekToEnd() { + element.onpause = null; + element.currentTime = element.duration * 1.1; + } + + function onseeked() { + element.onseeked = null; + element.onpause = seekToEnd; + element.pause(); + } + + function onplaying() { + isPlaying = true; + element.onplaying = null; + element.onseeked = onseeked; + } + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + element.onplaying = onplaying; + element.onended = onended; + element.play(); + steps++; +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.track.enabled", true] + ] + }, + function() { + manager.runTests(gTrackTests, startTest); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_metadata.html b/dom/media/test/test_metadata.html new file mode 100644 index 0000000000..08f28f5f47 --- /dev/null +++ b/dom/media/test/test_metadata.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test returning metadata from media files with mozGetMetadata()</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<div id="output"></div> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var a = document.createElement('audio'); + a.preload = "metadata"; + a.token = token; + manager.started(token); + + a.src = test.name; + a.name = test.name; + + // Tags should not be available immediately. + var exception_fired = false; + try { + a.mozGetMetadata(); + } catch (e) { + is(e.name, 'InvalidStateError', + "early mozGetMetadata() should throw InvalidStateError"); + exception_fired = true; + } + ok(exception_fired, + "mozGetMetadata() should throw an exception before HAVE_METADATA"); + + // Wait until metadata has loaded. + a.addEventListener('loadedmetadata', function() { + // read decoded tags + let tags = a.mozGetMetadata(); + ok(tags, "mozGetMetadata() should return a truthy value"); + // Dump them out. + var d = document.getElementById('output'); + var html = '<table>\n'; + html += '<caption><p>Called getMozMetadata()' + html += ' on '+test.name+'</p></caption>\n'; + html += '<tr><th>tag</th>'; + html += '<th>decoded value</th><th>expected value</th></tr>\n'; + for (let tag in tags) { + html += '<tr><td>'+tag+'</td>'; + html += '<td>'+tags[tag]+'</td>'; + html += '<td>'+test.tags[tag]+'</td>'; + html += '</tr>\n'; + } + if (!Object.keys(tags).length) { + html += '<tr><td colspan=3 align=center><em>no tags</em></td></tr>\n'; + } + html += '</table>\n'; + var div = document.createElement('div'); + // eslint-disable-next-line no-unsanitized/property + div.innerHTML = html; + d.appendChild(div); + // Verify decoded tag values. + for (let tag in tags) { + is(tags[tag], test.tags[tag], "Tag '"+tag+"' should match"); + } + // Verify expected tag values + for (let tag in test.tags) { + is(tags[tag], test.tags[tag], "Tag '"+tag+"' should match"); + } + removeNodeAndSource(a); + manager.finished(token); + }); +} + +manager.runTests(gMetadataTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_midflight_redirect_blocked.html b/dom/media/test/test_midflight_redirect_blocked.html new file mode 100644 index 0000000000..55b96ccd38 --- /dev/null +++ b/dom/media/test/test_midflight_redirect_blocked.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test mid-flight cross site redirects are blocked</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + </head> + <body> + <pre id='test'> + <script class="testbody" type='application/javascript'> + + function testIfLoadsToMetadata(test, useCors) { + return new Promise(function(resolve, reject) { + var elemType = getMajorMimeType(test.type); + var element = document.createElement(elemType); + + if (useCors) { + element.crossOrigin = "anonymous"; + } + + // Log events for debugging. + [ + "suspend", "play", "canplay", "canplaythrough", "loadstart", + "loadedmetadata", "loadeddata", "playing", "ended", "error", + "stalled", "emptied", "abort", "waiting", "pause" + ].forEach((eventName) => { + element.addEventListener(eventName, (event)=> { + info(test.name + " " + event.type); + }); + }); + + element.addEventListener("loadedmetadata", ()=>{ + resolve(true); + removeNodeAndSource(element); + }); + + element.addEventListener("error", ()=>{ + resolve(false); + removeNodeAndSource(element); + }); + + // Note: request redirect before the end of metadata, otherwise we won't + // error before metadata has loaded if mixed origins are allowed. + element.src = "midflight-redirect.sjs?resource=" + test.name + + (useCors ? "&cors" : "") + + "&type=" + test.type + + "&redirectAt=200"; + element.preload = "metadata"; + document.body.appendChild(element); + element.load() + }); + } + + let v = document.createElement("video"); + const testCases = gSmallTests.filter(t => v.canPlayType(t.type)); + + async function testMediaLoad(expectedToLoad, message, useCors) { + for (let test of testCases) { + let loaded = await testIfLoadsToMetadata(test, useCors); + is(loaded, expectedToLoad, test.name + " " + message); + } + } + + async function runTest() { + try { + SimpleTest.info("Allowing midflight redirects..."); + await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", false]]}); + + SimpleTest.info("Test that all media plays..."); + await testMediaLoad(true, "expected to load", false); + + SimpleTest.info("Blocking midflight redirects..."); + await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", true]]}); + + SimpleTest.info("Test that all media no longer play..."); + await testMediaLoad(false, "expected to be blocked", false); + + SimpleTest.info("Test that all media play if CORS used..."); + await testMediaLoad(true, "expected to play with CORS", true); + } catch (e) { + info("Exception " + e.message); + ok(false, "Threw exception " + e.message); + } + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(runTest); + + </script> + </pre> + </body> +</html> diff --git a/dom/media/test/test_mixed_principals.html b/dom/media/test/test_mixed_principals.html new file mode 100644 index 0000000000..c00d65b983 --- /dev/null +++ b/dom/media/test/test_mixed_principals.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=489415 +--> +<head> + <title>Test for Bug 489415</title> + <script type="application/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <style> + video { + width: 40%; + border: solid black 1px; + } + </style> +</head> + +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=489415">Mozilla Bug 489415</a> + <p id="display"></p> + <pre id="test"> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({ set: p }); + var count = 0; + + function canReadBack(video) { + var c = document.createElement("canvas"); + var ctx = c.getContext("2d"); + ctx.drawImage(video, 0, 0); + try { + c.toDataURL(); + return true; + } catch (ex) { + return false; + } + } + + function runTest(origin, shouldReadBackOnLoad) { + return new Promise(function (resolve, reject) { + // Load will redirect mid-flight, which will be detected and should error, + // and we should no longer be able to readback. + var video = document.createElement("video"); + video.preload = "metadata"; + video.controls = true; + var url = "http://" + origin + "/tests/dom/media/test/midflight-redirect.sjs" + + "?resource=pixel_aspect_ratio.mp4&type=video/mp4"; + SimpleTest.info("Loading from " + url); + video.src = url; + document.body.appendChild(video); + + once(video, "loadeddata", () => { + is(canReadBack(video), shouldReadBackOnLoad, "Should be able to readback"); + video.play(); + }); + + once(video, "error", () => { + is(canReadBack(video), false, "Should not be able to readback after error"); + removeNodeAndSource(video); + resolve(); + }); + + once(video, "ended", () => { + ok(false, "Should not be able to playback to end, we should have errored!"); + removeNodeAndSource(video); + resolve(); + }); + + }); + } + + Promise.all([ + runTest("mochi.test:8888", true), + runTest("example.org", false), + ]).then(SimpleTest.finish); + + </script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mozHasAudio.html b/dom/media/test/test_mozHasAudio.html new file mode 100644 index 0000000000..c25873c786 --- /dev/null +++ b/dom/media/test/test_mozHasAudio.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of media files that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function onloadedmetadata(e) { + var t = e.target; + is(t.mozHasAudio, t.hasAudio, "The element reports the wrong audio property." + t.token); + manager.finished(t.token); +} + +function startTest(test, token) { + var elemType = /^audio/.test(test.type) ? "audio" : "video"; + var element = document.createElement(elemType); + element.preload = "auto"; + + element.token = token; + manager.started(token); + + element.src = test.name; + element.name = test.name; + element.hasAudio = elemType == "video" ? test.hasAudio : undefined; + element.addEventListener("loadedmetadata", onloadedmetadata); + + element.load(); +} + +manager.runTests(gTrackTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_mp3_with_multiple_ID3v2.html b/dom/media/test/test_mp3_with_multiple_ID3v2.html new file mode 100644 index 0000000000..1f2f946520 --- /dev/null +++ b/dom/media/test/test_mp3_with_multiple_ID3v2.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Play mp3 file with multiple ID3v2 tags</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + +add_task(async function testPlayMP3WithMultipleID3Tags() { + info(`adjust cache size`); + await SpecialPowers.pushPrefEnv( + // The second ID3v2 header is huge (4622361 bytes) so the first audio samle + // in this file is in the position 4945370, so we have to extend the size of + // the cache. + {"set": [["media.cache_size", 5000000]]} + ); + + info(`create audio and wait its loading`); + let audio = document.createElement('audio'); + audio.src = "multi_id3v2.mp3"; + document.body.appendChild(audio); + await new Promise(r => audio.onloadeddata = r); + ok(true, `finishing loading data.`) +}); + +</script> +</head> +<body> +</body> +</html> diff --git a/dom/media/test/test_multiple_mediastreamtracks.html b/dom/media/test/test_multiple_mediastreamtracks.html new file mode 100644 index 0000000000..fb28d8652e --- /dev/null +++ b/dom/media/test/test_multiple_mediastreamtracks.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the ability of MediaStream with multiple MediaStreamTracks</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function startTest() { + try { + await setupGetUserMediaTestPrefs(); + let orgStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true}); + let a = orgStream.getAudioTracks()[0]; + let v = orgStream.getVideoTracks()[0]; + let stream = new MediaStream([a, a, a, a, v, v, v].map(track => track.clone())); + let element = document.createElement("video"); + + element.onloadedmetadata = function() { + is(stream.getAudioTracks().length, 4, 'Length of audio tracks should be 4.'); + is(stream.getVideoTracks().length, 3, 'Length of vudio tracks should be 3.'); + SimpleTest.finish(); + }; + + element.srcObject = stream; + element.play(); + } catch (err) { + ok(false, 'Unexpected error fired with: ' + err); + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.track.enabled", true] + ] + }, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_networkState.html b/dom/media/test/test_networkState.html new file mode 100644 index 0000000000..8001ca514c --- /dev/null +++ b/dom/media/test/test_networkState.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: networkState</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onunload="mediaTestCleanup();"> +<video id='v1'></video><audio id='a1'></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +"use strict"; +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var passed = "truthy"; + +try { + v1.networkState = 0; +} catch (e) { + passed = !passed; +} +try { + a1.networkState = 0; +} catch (e) { + passed = !passed; +} +ok(passed === true, + "Setting networkState throws in strict mode (readonly attribute)"); +</script> + +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var passed = false; + +var oldv1ns = v1.networkState, olda1ns = a1.networkState; +try { + v1.networkState = 0; + a1.networkState = 0; + passed = v1.networkState === oldv1ns && a1.networkState === olda1ns; +} catch (e) { } +ok(passed, "Should not be able to modify networkState (readonly attribute)"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_new_audio.html b/dom/media/test/test_new_audio.html new file mode 100644 index 0000000000..e1f8964f73 --- /dev/null +++ b/dom/media/test/test_new_audio.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=528566 +--> +<head> + <title>Test for Bug 528566</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=528566">Mozilla Bug 528566</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 528566 **/ + +var manager = new MediaTestManager; + +var player = new Audio(); + +function startTest(test, token) { + if (!player.canPlayType(test.type)) { + return; + } + manager.started(token); + var a = new Audio(test.name); + a.autoplay = true; + document.body.appendChild(a); + a.addEventListener("ended", + function(e){ + ok(true, "[" + a.src + "]We should get to the end. Oh look we did."); + a.remove(); + manager.finished(token); + }); +} + +manager.runTests(gAudioTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_no_load_event.html b/dom/media/test/test_no_load_event.html new file mode 100644 index 0000000000..1e2717d983 --- /dev/null +++ b/dom/media/test/test_no_load_event.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=715469 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 715469</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="start();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=715469">Mozilla Bug 715469</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> + +<script type="application/javascript"> + +/** Test for Bug 715469 **/ + +var gotLoadEvent = false; + +function start() { + var resource = getPlayableVideo(gSmallTests); + if (resource == null) { + todo(false, "No types supported"); + } else { + SimpleTest.waitForExplicitFinish(); + var v = document.createElement("video"); + v.src = resource.name; + + v.addEventListener("load", function() { + gotLoadEvent = true; + }); + + v.addEventListener("ended", finished); + document.body.appendChild(v); + v.play(); + } +} + +function finished() { + is(gotLoadEvent, false, "Should not receive a load on the video element"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html b/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html new file mode 100644 index 0000000000..f1b8cc8b15 --- /dev/null +++ b/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Do not reset playback rate when removing non-loaded media from a document</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script class="testbody" type="text/javascript"> +/** + * When removing media from a document, it should only trigger internal pause, + * instead of the pause method, which would trigger loading process and reset + * the media's playback rate for non-loaded media. + */ +async function startTest() { + info(`create a media and append it to a document`); + const audio = document.createElement("audio"); + document.body.appendChild(audio); + + info(`change audio's playbackRate and remove it from a document`); + const expectedRate = 0.1; + audio.playbackRate = expectedRate; + await once(audio, "ratechange"); + is(audio.playbackRate, expectedRate, + `${audio.playbackRate} is equal to ${expectedRate}`); + audio.remove(); + + info(`queue a macrotask to check if the playback rate is still unchanged`); + setTimeout(() => { + // If we unexpectedly reset the playback rate, it would happen in a + // microtask when removing media from a document [1] (Await a stable state), + // which would always be run before the macrotask. + // [1] https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document + is(audio.playbackRate, expectedRate, + `${audio.playbackRate} is equal to ${expectedRate}`); + SimpleTest.finish(); + }, 0); +} + +SimpleTest.waitForExplicitFinish(); +onload = startTest; + +</script> +</body> +</html> diff --git a/dom/media/test/test_paused.html b/dom/media/test/test_paused.html new file mode 100644 index 0000000000..17af4d3898 --- /dev/null +++ b/dom/media/test/test_paused.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: paused</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<video id='v1'></video><audio id='a1'></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +ok(v1.paused, "v1.paused must initially be true"); +ok(a1.paused, "a1.paused must initially be true"); +mediaTestCleanup(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_paused_after_ended.html b/dom/media/test/test_paused_after_ended.html new file mode 100644 index 0000000000..83f27a921f --- /dev/null +++ b/dom/media/test/test_paused_after_ended.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: paused</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function ended(evt) { + var v = evt.target; + v.removeEventListener("ended", ended); + is(v.gotPause, true, "We should have received a \"pause\" event.") + is(v.paused, true, v._name + " must be paused after end"); + manager.finished(v.token); + removeNodeAndSource(v); +} + +function pause(evt) { + var v = evt.target; + v.removeEventListener("pause", pause); + v.gotPause = true; +} + +function startTest(test, token) { + var v = document.createElement('video'); + document.body.appendChild(v); + v.token = token; + manager.started(v.token); + v.src = test.name; + v._name = test.name; + v._finished = false; + v.load(); + is(v.paused, true, v._name + " must be paused at start"); + + v.play(); + is(v.paused, false, v._name + " must not be paused after play"); + + v.addEventListener("pause", pause); + v.addEventListener("ended", ended); +} + +manager.runTests(gPausedAfterEndedTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_play_events.html b/dom/media/test/test_play_events.html new file mode 100644 index 0000000000..44b819b488 --- /dev/null +++ b/dom/media/test/test_play_events.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> + +var manager = new MediaTestManager; + +var tokens = { + 0: ["play"], + "play": ["canplay"], + "canplay": ["playing"], + "playing": ["canplay", "canplaythrough"], + "canplaythrough": ["canplay", "canplaythrough"] +}; + +function gotPlayEvent(event) { + var v = event.target; + ok(tokens[v._state].includes(event.type), + "Check expected event got " + event.type + " at " + v._state + " for " + v.src + + " tokens["+v._state+"]=" + tokens[v._state] + + " tokens["+v._state+"].indexOf(event.type)=" + tokens[v._state].indexOf(event.type)); + v._state = event.type; +} + +function ended(event) { + var v = event.target; + removeNodeAndSource(v); + manager.finished(v.token); +} + +function initTest(test, token) { + var v = document.createElement('video'); + v.token = token; + manager.started(token); + v._state = 0; + + ["play", "canplay", "playing", "canplaythrough"].forEach(function (e) { + v.addEventListener(e, gotPlayEvent); + }); + + v.addEventListener("ended", ended); + + v.src = test.name; + document.body.appendChild(v); // Causes load. + v.play(); +} + +manager.runTests(gSmallTests, initTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_play_events_2.html b/dom/media/test/test_play_events_2.html new file mode 100644 index 0000000000..022bb5373a --- /dev/null +++ b/dom/media/test/test_play_events_2.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: play() method via DOM 0 handlers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +var manager = new MediaTestManager; + +var tokens = { + 0: ["play"], + "play": ["canplay"], + "canplay": ["playing"], + "playing": ["canplay", "canplaythrough"], + "canplaythrough": ["canplay", "canplaythrough"] +}; + +function gotPlayEvent(event) { + var v = event.target; + ok(tokens[v._state].includes(event.type), + "Check expected event got " + event.type + " at " + v._state + " for " + v.src); + v._state = event.type; +} + +function ended(event) { + var v = event.target; + v._finished = true; + removeNodeAndSource(v); + manager.finished(v.token); +} + +function startTest(test, token) { + var v = document.createElement('video'); + v.token = token; + manager.started(token); + v._state = 0; + v._finished = false; + + ["play", "canplay", "playing", "canplaythrough"].forEach(function (e) { + v["on" + e] = gotPlayEvent; + }); + + v.onended = ended; + + v.src = test.name; + document.body.appendChild(v); // Causes load. + v.play(); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_play_promise_1.html b/dom/media/test/test_play_promise_1.html new file mode 100644 index 0000000000..ce4e287f34 --- /dev/null +++ b/dom/media/test/test_play_promise_1.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playBeforeCanPlay +// Case: invoke play() on an element that doesn't have enough data +// Expected result: resolve the promise + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = test.name; + ok(element.readyState == HTMLMediaElement.HAVE_NOTHING); + + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_10.html b/dom/media/test/test_play_promise_10.html new file mode 100644 index 0000000000..1c0096986c --- /dev/null +++ b/dom/media/test/test_play_promise_10.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadRejectsPendingPromises +// Case: invoke load() on an element with pending promises. +// Expected result: reject all the pending promises with AbortError DOM exception. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "AbortError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + element.load(); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_11.html b/dom/media/test/test_play_promise_11.html new file mode 100644 index 0000000000..a59baf5657 --- /dev/null +++ b/dom/media/test/test_play_promise_11.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: newSrcRejectPendingPromises +// Case: change src of an element with pending promises. +// Expected result: reject all the pending promises. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "AbortError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + element.src = test.name; +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_12.html b/dom/media/test/test_play_promise_12.html new file mode 100644 index 0000000000..50972885eb --- /dev/null +++ b/dom/media/test/test_play_promise_12.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: pausePlayAfterPlaybackStarted +// Case: invoke pause() and then play() on an element that is already playing. +// Expected result: resolve the promise with undefined. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + element.play(); + once(element, "playing").then(() => { + element.pause(); + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_13.html b/dom/media/test/test_play_promise_13.html new file mode 100644 index 0000000000..aacab88895 --- /dev/null +++ b/dom/media/test/test_play_promise_13.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadAlgorithmDoesNotCancelTasks +// Case: re-invoke the load() on an element which had dispatched a task to resolve a promise. +// Expected result: the already dispatched promise should still be resolved with undefined. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + + // We must wait for "canplay" event; otherwise, invoke play() will lead to a + // pending promise and will then be rejected by the following load(). + once(element, "canplay").then(() => { + // The play() promise will be queued to be resolved immediately, which means + // the promise is not in the pending list. + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + element.src = test.name; // Re-invoke the load algorithm again. + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_14.html b/dom/media/test/test_play_promise_14.html new file mode 100644 index 0000000000..066138ecad --- /dev/null +++ b/dom/media/test/test_play_promise_14.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadAlgorithmKeepPromisesPendingWhenNotPausing +// Case: step1: create an element with its paused member to be fause and networkState to be NETWORK_EMPTY. +// stpe2: invoke load() on the element and the load() leaves the promise pending. +// Expected result: the pending promise should finally be resolved. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + // Invoke play() -> (1) the promise will be left pending. + // (2) invoke load() -> (1) set the networkState to be NETWORK_NO_SOURCE. + // (2) queue a task to run resouce selection algorithm. + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + + once(element, "play").then(() => { + // The resouce selection algorithm has been done. + // -> set the networkState to be NETWORK_EMPTY because there is no valid resource to load. + ok(element.networkState == HTMLMediaElement.NETWORK_EMPTY); + + // Invoke load() again and since the networkState is NETWORK_EMPTY, the load() does not reject the pending promise. + // The load() will queue a task to run resouce selection algorithm which will change the readyState and finally resolve the pending promise. + element.src = test.name; + + // Since the networkState is NETWORK_EMPTY, the load() does not set paused to be true. + ok(!element.paused, `loadAlgorithmKeepPromisesPendingWhenNotPausing(${token}).paused should be false.`); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_15.html b/dom/media/test/test_play_promise_15.html new file mode 100644 index 0000000000..b8bce48651 --- /dev/null +++ b/dom/media/test/test_play_promise_15.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadAlgorithmRejectPromisesWhenPausing +// Case: step1: create an element with its paused member to be fause and networkState to be NETWORK_NO_SOURCE. +// stpe2: invoke load() on the element and the load() rejects the pending promise. +// Expected result: reject the pending promise with AbortError DOM exception. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + // Invoke play() -> (1) the promise will be left pending. + // (2) invoke load() -> (1) set the networkState to be NETWORK_NO_SOURCE. + // (2) queue a task to run resouce selection algorithm. + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "AbortError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + + ok(element.networkState == HTMLMediaElement.NETWORK_NO_SOURCE); + + // Invoke load() again and since the networkState is NETWORK_NO_SOURCE, the load() rejects the pending promise. + element.src = test.name; + + // Since the networkState is not NETWORK_EMPTY, the load() sets paused to be true. + ok(element.paused, `loadAlgorithmRejectPromisesWhenPausing(${token}).paused should be true.`); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_16.html b/dom/media/test/test_play_promise_16.html new file mode 100644 index 0000000000..a643e7fff8 --- /dev/null +++ b/dom/media/test/test_play_promise_16.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadAlgorithmResolveOrdering +// Case: invoke load() on an element should resolve pending promises in order. +// Expected result: the pending promises are resolved in order. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + once(element, "canplay").then(() => { + let firstPromiseResolved = false; + + // play + element.play().then( + () => { firstPromiseResolved = true; }, + () => { ok(false, `loadAlgorithmResolveOrdering(${token}) should not be rejected.`); } + ); + + // play again + element.play().then( + () => { ok(firstPromiseResolved, `loadAlgorithmResolveOrdering(${token}), the first play should already be resolved.`); }, + () => { ok(false, `loadAlgorithmResolveOrdering(${token}) should not be rejected.`); } + ).then( () => { manager.finished(token); } ); + + // triger load + element.src = test.name; + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_17.html b/dom/media/test/test_play_promise_17.html new file mode 100644 index 0000000000..5e5eb71fd5 --- /dev/null +++ b/dom/media/test/test_play_promise_17.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: loadAlgorithmRejectOrdering +// Case: invoke load() on an element should reject pending promises in order. +// Expected result: the pending promises are rejected in order. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + let firstPromiseRejected = false; + + // play + element.play().then( + () => { ok(false, `loadAlgorithmRejectOrdering(${token}) should not be resolved.`); }, + () => { firstPromiseRejected = true; } + ); + + // play again + element.play().then( + () => { ok(false, `loadAlgorithmRejectOrdering(${token}) should not be resolved.`); }, + () => { ok(firstPromiseRejected, `loadAlgorithmRejectOrdering(${token}), the first play should already be rejected.`); } + ).then( () => { manager.finished(token); } ); + + // triger load + element.src = test.name; +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_18.html b/dom/media/test/test_play_promise_18.html new file mode 100644 index 0000000000..e0c0837fdf --- /dev/null +++ b/dom/media/test/test_play_promise_18.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: 'playing' event should come before promise resolving +// Case: invoke play() on an element which has valis source +// Expected result: receive a 'playing' event before the promise is resolved + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = test.name; + element.receivedPlayingEvent = false; + + element.addEventListener("playing", () => { + element.receivedPlayingEvent = true; + }); + + element.play().then( + (result) => { + if (result == undefined && element.receivedPlayingEvent) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result} and receivedPlayingEvent = ${element.receivedPlayingEvent}`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_2.html b/dom/media/test/test_play_promise_2.html new file mode 100644 index 0000000000..c4befdbe91 --- /dev/null +++ b/dom/media/test/test_play_promise_2.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playWhenCanPlay +// Case: invoke play() on an element that has enough data +// Expected result: resolve the promise + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + once(element, "canplay").then(() => { + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_3.html b/dom/media/test/test_play_promise_3.html new file mode 100644 index 0000000000..8ffacdae03 --- /dev/null +++ b/dom/media/test/test_play_promise_3.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playAfterPlaybackStarted +// Case: invoke play() on an element that is already playing +// Expected result: resolve the promise + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + once(element, "playing").then(() => { + ok(element.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA, `playAfterPlaybackStarted(${token})`); + ok(!element.paused, `playAfterPlaybackStarted(${token})`); + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + }); + + element.play(); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_4.html b/dom/media/test/test_play_promise_4.html new file mode 100644 index 0000000000..dfcd96e0b9 --- /dev/null +++ b/dom/media/test/test_play_promise_4.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="play_promise.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playNotSupportedContent +// Case: invoke play() on an element with an unsupported content +// Expected result: reject the promise with NotSupportedError DOM exception + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = getNotSupportedFile(test.name); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "NotSupportedError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_5.html b/dom/media/test/test_play_promise_5.html new file mode 100644 index 0000000000..a4c3eeb936 --- /dev/null +++ b/dom/media/test/test_play_promise_5.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="play_promise.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playWithErrorAlreadySet +// Case: invoke play() on an element with MEDIA_ERR_SRC_NOT_SUPPORTED has been set +// Expected result: reject the promise with NotSupportedError DOM exception + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = getNotSupportedFile(test.name); + once(element, "error").then(() => { + ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "NotSupportedError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_6.html b/dom/media/test/test_play_promise_6.html new file mode 100644 index 0000000000..dcbea3f108 --- /dev/null +++ b/dom/media/test/test_play_promise_6.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="play_promise.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playSwitchToValidSrcAfterError +// Case: invoke play() on an element which had its source changed (to a valid source) after suffering from an error +// Expected result: resolve the promise with undefined + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = getNotSupportedFile(test.name); + once(element, "error").then(() => { + ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); + element.src = test.name; + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_7.html b/dom/media/test/test_play_promise_7.html new file mode 100644 index 0000000000..1abbe42f33 --- /dev/null +++ b/dom/media/test/test_play_promise_7.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="play_promise.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playSwitchToInvalidSrcAfterError +// Case: invoke play() on an element which had its source changed (to a invalid source) after suffering from an error +// Expected result: reject the promise with NotSupportedError DOM exception + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.src = getNotSupportedFile(test.name); + once(element, "error").then(() => { + ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); + element.src = getNotSupportedFile(test.name); + ok(element.error == null); + ok(element.readyState == HTMLMediaElement.HAVE_NOTHING); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "NotSupportedError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_8.html b/dom/media/test/test_play_promise_8.html new file mode 100644 index 0000000000..d61e12c9fe --- /dev/null +++ b/dom/media/test/test_play_promise_8.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playAndPauseWhenCanplay +// Case: invlke play() and then pause() on an element that already has enough data to play +// Expected result: resolve the promise +// Note: the pause() doesn't cancel the play() because it was alredy been queued to be resolved. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + element.preload = "auto"; + element.src = test.name; + once(element, "canplay").then(() => { + element.play().then( + (result) => { + if (result == undefined) { + ok(true, `${token} is resolved with ${result}.`); + } else { + ok(false, `${token} is resolved with ${result}.`); + } + }, + (error) => { + ok(false, `${token} is rejected with ${error.name}.`); + } + ).then( () => { manager.finished(token); } ); + ok(!element.paused, `playAndPauseWhenCanplay(${token}) element should not be paused.`); + element.pause(); + ok(element.paused, `playAndPauseWhenCanplay(${token}) element should be paused.`); + }); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_promise_9.html b/dom/media/test/test_play_promise_9.html new file mode 100644 index 0000000000..c3a3856515 --- /dev/null +++ b/dom/media/test/test_play_promise_9.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: promise-based play() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> +// Name: playAndPauseBeforeCanPlay +// Case: invlke play() and then pause() on an element that deoen't have enough data to play. +// Expected result: reject the promise with AbortError DOM exception. +// Note: the pause() cancels the play() because the promise is still pending. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + let element = document.createElement(getMajorMimeType(test.type)); + ok(element.readyState == HTMLMediaElement.HAVE_NOTHING); + element.play().then( + (result) => { + ok(false, `${token} is resolved with ${result}.`); + }, + (error) => { + if (error.name == "AbortError") { + ok(true, `${token} is rejected with ${error.name}.`); + } else { + ok(false, `${token} is rejected with ${error.name}.`); + } + } + ).then( () => { manager.finished(token); } ); + ok(!element.paused, `playAndPauseBeforeCanPlay(${token}) element should not be paused.`); + element.pause(); + ok(element.paused, `playAndPauseBeforeCanPlay(${token}) element should be paused.`); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_play_twice.html b/dom/media/test/test_play_twice.html new file mode 100644 index 0000000000..e94f28a031 --- /dev/null +++ b/dom/media/test/test_play_twice.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of media files that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.token = token; + manager.started(token); + video.src = test.name; + video.name = test.name; + video.playingCount = 0; + video._playedOnce = false; + + var check = function(t, v) { return function() { + checkMetadata(t.name, v, test); + }}(test, video); + + var noLoad = function(t, v) { return function() { + ok(false, t.name + " should not fire 'load' event"); + }}(test, video); + + function finish(v) { + removeNodeAndSource(v); + manager.finished(v.token); + } + + function mayFinish(v) { + if (v.seenEnded && v.seenSuspend) { + finish(v); + } + } + + var checkEnded = function(t, v) { return function() { + if (t.duration) { + ok(Math.abs(v.currentTime - t.duration) < 0.1, + t.name + " current time at end: " + v.currentTime); + } + + is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState"); + ok(v.ended, t.name + " checking playback has ended"); + ok(v.playingCount > 0, "Expect at least one playing event"); + v.playingCount = 0; + + if (v._playedOnce) { + v.seenEnded = true; + mayFinish(v); + } else { + v._playedOnce = true; + v.play(); + } + }}(test, video); + + var checkSuspended = function(t, v) { return function() { + if (v.seenSuspend) { + return; + } + + v.seenSuspend = true; + ok(true, t.name + " got suspend"); + mayFinish(v); + }}(test, video); + + var checkPlaying = function(t, v) { return function() { + is(t.name, v.name, "Should be testing file we think we're testing..."); + v.playingCount++; + }}(test, video); + + video.addEventListener("load", noLoad); + video.addEventListener("loadedmetadata", check); + video.addEventListener("playing", checkPlaying); + + // We should get "ended" and "suspend" events for every resource + video.addEventListener("ended", checkEnded); + video.addEventListener("suspend", checkSuspended); + + document.body.appendChild(video); + video.play(); +} + +manager.runTests(gReplayTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_playback.html b/dom/media/test/test_playback.html new file mode 100644 index 0000000000..5e28861e93 --- /dev/null +++ b/dom/media/test/test_playback.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of media files that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.preload = "metadata"; + video.token = token; + video.prevTime = 0; + video.seenEnded = false; + video.seenSuspend = false; + + var handler = { + "ontimeout": function() { + Log(token, "timed out: ended=" + video.seenEnded + ", suspend=" + video.seenSuspend); + } + }; + manager.started(token, handler); + + video.src = test.name; + video.name = test.name; + + var check = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #1"); + checkMetadata(t.name, v, t); + }}(test, video); + + var noLoad = function(t, v) { return function() { + ok(false, t.name + " should not fire 'load' event"); + }}(test, video); + + var noError = function(t, v) { return function() { + ok(false, t.name + " should not fire 'error' event " + v.error.message); + }}(test, video); + + var finish = function() { + video.finished = true; + video.removeEventListener("timeupdate", timeUpdate); + removeNodeAndSource(video); + manager.finished(video.token); + } + + // We should get "ended" and "suspend" events to finish the test. + var mayFinish = function() { + if (video.seenEnded && video.seenSuspend) { + finish(); + } + } + + var checkEnded = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #2"); + checkMetadata(t.name, v, test); + is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState"); + ok(v.ended, t.name + " checking playback has ended"); + ok(!v.finished, t.name + " shouldn't be finished"); + ok(!v.seenEnded, t.name + " shouldn't be ended"); + + v.seenEnded = true; + mayFinish(); + }}(test, video); + + var checkSuspended = function(t, v) { return function() { + if (v.seenSuspend) { + return; + } + is(t.name, v.name, t.name + ": Name should match #3"); + + v.seenSuspend = true; + mayFinish(); + }}(test, video); + + var timeUpdate = function(t, v) { return function() { + if (v.prevTime > v.currentTime) { + ok(false, t.name + " time should run forwards: p=" + + v.prevTime + " c=" + v.currentTime); + } + v.prevTime = v.currentTime; + }}(test, video); + + video.addEventListener("load", noLoad); + video.addEventListener("error", noError); + video.addEventListener("loadedmetadata", check); + video.addEventListener("timeupdate", timeUpdate); + + // We should get "ended" and "suspend" events for every resource + video.addEventListener("ended", checkEnded); + video.addEventListener("suspend", checkSuspended); + + document.body.appendChild(video); + video.play(); +} + +manager.runTests(gPlayTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_playback_errors.html b/dom/media/test/test_playback_errors.html new file mode 100644 index 0000000000..7b3f046099 --- /dev/null +++ b/dom/media/test/test_playback_errors.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of media files that should have errors</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + manager.started(token); + video._errorCount = 0; + video._ignore = false; + function endedTest(v) { + if (v._ignore) + return; + v._ignore = true; + v.remove(); + manager.finished(token); + } + var checkError = function(t, v) { return function(evt) { + v._errorCount++; + is(v._errorCount, 1, t.name + " only one error fired"); + endedTest(v); + }}(test, video); + var checkEnded = function(t, v) { return function() { + ok(false, t.name + " successfully played"); + endedTest(v); + }}(test, video); + video.addEventListener("error", checkError); + video.addEventListener("ended", checkEnded); + video.src = test.name; + document.body.appendChild(video); + video.play(); +} + +manager.runTests(gErrorTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_playback_hls.html b/dom/media/test/test_playback_hls.html new file mode 100644 index 0000000000..7d5c777fc1 --- /dev/null +++ b/dom/media/test/test_playback_hls.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback of HLS with simple m3u8 that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.preload = "metadata"; + video.token = token; + video.prevTime = 0; + video.seenEnded = false; + + var handler = { + "ontimeout": function() { + Log(token, "timed out: ended=" + video.seenEnded); + } + }; + manager.started(token, handler); + + video.src = test.name; + video.name = test.name; + + var check = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #1"); + checkMetadata(t.name, v, t); + }}(test, video); + + var noLoad = function(t, v) { return function() { + ok(false, t.name + " should not fire 'load' event"); + }}(test, video); + + var finish = function() { + video.finished = true; + video.removeEventListener("timeupdate", timeUpdate); + removeNodeAndSource(video); + manager.finished(video.token); + } + + // We should get "ended" events to finish the test. + var mayFinish = function() { + if (video.seenEnded) { + finish(); + } + } + + var checkEnded = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #2"); + checkMetadata(t.name, v, test); + is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState"); + ok(v.ended, t.name + " checking playback has ended"); + ok(!v.finished, t.name + " shouldn't be finished"); + ok(!v.seenEnded, t.name + " shouldn't be ended"); + + v.seenEnded = true; + mayFinish(); + }}(test, video); + + var timeUpdate = function(t, v) { return function() { + if (v.prevTime > v.currentTime) { + ok(false, t.name + " time should run forwards: p=" + + v.prevTime + " c=" + v.currentTime); + } + v.prevTime = v.currentTime; + }}(test, video); + + video.addEventListener("load", noLoad); + video.addEventListener("loadedmetadata", check); + video.addEventListener("timeupdate", timeUpdate); + + // We should get "ended" events for the hls resource + video.addEventListener("ended", checkEnded); + + document.body.appendChild(video); + video.play(); +} + +manager.runTests(gHLSTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_playback_rate.html b/dom/media/test/test_playback_rate.html new file mode 100644 index 0000000000..a704b9a1ba --- /dev/null +++ b/dom/media/test/test_playback_rate.html @@ -0,0 +1,175 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for the playbackRate property </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type='application/javascript'> + +let manager = new MediaTestManager; + +function rangeCheck(lhs, rhs, threshold) { + var diff = Math.abs(lhs - rhs); + if (diff < threshold) { + return true; + } + return false; +} + +function checkPlaybackRate(wallclock, media, expected, threshold) { + if (rangeCheck(media / wallclock, expected, threshold)) { + return true; + } + return false; +} + +// Those value are expected to match those at the top of HTMLMediaElement.cpp. +let VERY_SLOW_RATE = 1 / 32, + SLOW_RATE = 1 / 16, + FAST_RATE = 16, + VERY_FAST_RATE = 20, + NULL_RATE = 0.0; + +function ontimeupdate(e) { + var t = e.target; + // Skip short files for SoundTouch doesn't work well on small number of samples. + if (t.gotEnded || t.duration < 2) { + return; + } + t.testedForSlowdown = true; + if (t.currentTime > t.duration / 2) { + t.oldCurrentTime = t.currentTime; + t.timestamp = Date.now(); + var delta = t.oldCurrentTime, + delta_wallclock = (t.timestamp - t.startTimestamp - t.bufferingTime) / 1000; + + t.mozPreservesPitch = false; + is(t.mozPreservesPitch, false, t.name + ": If we disable the pitch preservation, it should appear as such."); + + t.bufferingTime = 0; + + is(t.playbackRate, SLOW_RATE, t.name + ": The playback rate shoud be "+SLOW_RATE+"."); + ok(checkPlaybackRate(delta_wallclock, delta, SLOW_RATE, 0.25), t.name + ": We are effectively slowing down playback. (" + delta_wallclock + ", " + delta + ")"); + t.removeEventListener("timeupdate", ontimeupdate); + t.addEventListener("pause", onpaused); + t.playbackRate = NULL_RATE; + t.oldCurrentTime = t.currentTime; + setTimeout(function() { + afterNullPlaybackRate(e); + }, 100); + } +} + +function onpaused(e) { + var t = e.target; + t.pausedReceived = true; +} + +function afterNullPlaybackRate(e) { + var t = e.target; + + // skip if we have received 'ended' event or 'ended' event is pending. + if (t.gotEnded || t.ended) { + return; + } + + t.testedForNull = true; + + ok(t.currentTime == t.oldCurrentTime, t.name + ": Current time should not change when playbackRate is null (" + t.currentTime + " " + t.oldCurrentTime + ")."); + ok(!t.paused, t.name + ": The element should not be in paused state."); + t.removeEventListener("paused", onpaused); + is(t.pausedReceived, undefined, t.name + ": Paused event should not have been received."); + t.timestamp = Date.now(); + t.oldCurrentTime = t.currentTime; + t.playbackRate = VERY_FAST_RATE; + is(t.playbackRate, VERY_FAST_RATE, t.name + ": Playback rate should be clamped to " + VERY_FAST_RATE + "."); +} + +function onended(e) { + var t = e.target; + t.gotEnded = true; + + t.bufferingTime = 0; + // If we got "ended" too early, skip these tests. + if (t.testedForSlowdown && t.testedForNull) { + is(t.playbackRate, FAST_RATE, t.name + ": The playback rate should still be "+FAST_RATE+"."); + ok(!t.muted, t.name + ": The audio should be muted when playing at high speed, but should not appear as such."); + is(t.currentTime, t.duration, t.name + ": Current time should be equal to the duration (not change by playback rate)."); + } + finish_test(t); +} + +function onratechange(e) { + if (!e.target.ratechangecount) { + e.target.ratechangecount = 0; + } + e.target.ratechangecount++; +} + +function finish_test(element) { + removeNodeAndSource(element); + manager.finished(element.token); +} + +// These two functions handle the case when the playback pauses for buffering. It +// adjusts the timestamps to be accurate. Despite the fact that the web servers +// is supposed to be on the same machine, buffering pauses can occur (rarely, +// but still). +function onplaying(e) { + var t = e.target; + if (t.bufferingTimestamp != undefined) { + t.bufferingTime += (Date.now() - t.bufferingTimestamp); + t.bufferingTimestamp = undefined; + } +} + +function onwaiting(e) { + var t = e.target; + t.bufferingTimestamp = Date.now(); +} + +function onvolumechange(e) { + ok(false, e.target.name + ": We should not receive a volumechange event when changing the playback rate."); +} + +function startTest(test, token) { + let elemType = /^audio/.test(test.type) ? "audio" : "video"; + let element = document.createElement(elemType); + element.src = test.name; + element.name = test.name; + element.preload = "metadata"; + element.token = token; + element.controls = true; + element.bufferingTime = 0; + document.body.appendChild(element); + element.addEventListener("ratechange", onratechange); + element.addEventListener("timeupdate", ontimeupdate); + element.addEventListener("ended", onended); + element.addEventListener("waiting", onwaiting); + element.addEventListener("playing", onplaying); + element.addEventListener("volumechange", onvolumechange); + manager.started(token); + element.startTimestamp = Date.now(); + is(element.mozPreservesPitch, true, test.name + ": Pitch preservation should be enabled by default."); + element.addEventListener("loadedmetadata", function() { + is(element.playbackRate, 1.0, test.name + ": playbackRate should be initially 1.0"); + is(element.defaultPlaybackRate, 1.0, test.name + ": defaultPlaybackRate should be initially 1.0"); + element.playbackRate = VERY_SLOW_RATE; + is(element.playbackRate, VERY_SLOW_RATE, test.name + ": PlaybackRate should be " + VERY_SLOW_RATE + "."); + element.play(); + element.playbackRate = SLOW_RATE; + }); +} + +manager.runTests(gPlayedTests, startTest); + +</script> +</pre> +<div id="elements"> +</div> +</body> +</html> diff --git a/dom/media/test/test_playback_rate_playpause.html b/dom/media/test/test_playback_rate_playpause.html new file mode 100644 index 0000000000..2f3e6d8b9a --- /dev/null +++ b/dom/media/test/test_playback_rate_playpause.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that the playbackRate property is not reset when resuming the playback</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type='application/javascript'> + +let manager = new MediaTestManager; + +function ontimeupdate(e) { + var t = e.target; + if (t.currentTime != 0.0) { + dump(t.token + " t.currentTime != 0.0.\n"); + t.removeEventListener("timeupdate", ontimeupdate); + t.pause(); + is(t.playbackRate, 0.5, "PlaybackRate should not have changed after pause."); + } else { + dump(t.token + " t.currentTime == 0.0.\n"); + } +} + +function onpaused(e) { + var t = e.target; + dump(t.token + " onpaused.\n"); + t.play(); + is(t.playbackRate, 0.5, "PlaybackRate should not have changed after resuming playback."); + finish_test(t); +} + +function finish_test(element) { + dump(element.token + " finish_test.\n"); + removeNodeAndSource(element); + manager.finished(element.token); +} + +function startTest(test, token) { + let elemType = /^audio/.test(test.type) ? "audio" : "video"; + let element = document.createElement(elemType); + element.src = test.name; + element.token = token; + element.controls = true; + element.playbackRate = 0.5; + element.preload = "metadata"; + element.addEventListener("timeupdate", ontimeupdate); + element.addEventListener("pause", onpaused); + element.addEventListener("loadedmetadata", function() { + dump(element.token + " loadedmetadata\n"); + element.play(); + }); + document.body.appendChild(element); + manager.started(token); +} + +manager.runTests(gPlayedTests, startTest); + +</script> +</pre> +<div id="elements"> +</div> +</body> +</html> diff --git a/dom/media/test/test_playback_reactivate.html b/dom/media/test/test_playback_reactivate.html new file mode 100644 index 0000000000..dec5330ce5 --- /dev/null +++ b/dom/media/test/test_playback_reactivate.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test playback with dormant of media files that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* This testcase wants to test a video element's playback is not break + by dormant. + When the metadata is loaded, we remove the video element to trigger dormant. + Then set a timer to append the video element back and play it. + Test pass if the video plays to the end. +*/ + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.preload = "metadata"; + video.token = token; + + var handler = { + "ontimeout": function() { + Log(token, "timed out: ended=" + video.seenEnded + ", suspend=" + video.seenSuspend); + } + }; + manager.started(token, handler); + + video.src = test.name; + video.name = test.name; + + var check = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #1"); + Log(v.token, "removeChild: " + v.name); + document.body.removeChild(v); + var appendAndPlayElement = function() { + Log(v.token, "appendChild: " + v.name); + document.body.appendChild(v); + Log(v.token, "Element play: " + v.name); + v.play(); + } + setTimeout(appendAndPlayElement, 2000); + }}(test, video); + + var finish = function() { + video.finished = true; + removeNodeAndSource(video); + manager.finished(video.token); + } + + var checkEnded = function(t, v) { return function() { + is(t.name, v.name, t.name + ": Name should match #2"); + checkMetadata(t.name, v, t); + is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState"); + ok(v.ended, t.name + " checking playback has ended"); + + finish(); + }}(test, video); + + + video.addEventListener("loadedmetadata", check); + video.addEventListener("ended", checkEnded); + + document.body.appendChild(video); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_played.html b/dom/media/test/test_played.html new file mode 100644 index 0000000000..e8b4906c2c --- /dev/null +++ b/dom/media/test/test_played.html @@ -0,0 +1,245 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test played member for media elements</title> +<script type="text/javascript" src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id='test'> +<script class="testbody" type='application/javascript'> + +let manager = new MediaTestManager; + +function finish_test(element) { + removeNodeAndSource(element); + manager.finished(element.token); +} + +// Check that a file has been played in its entirety. +function check_full_file_played(element) { + element.addEventListener('ended', (function(e) { + let interval_count = e.target.played.length; + is(interval_count, 1, element.token + ": played.length must be 1"); + is(element.played.start(0), 0, element.token + ": start time shall be 0"); + is(element.played.end(0), e.target.duration, element.token + ": end time shall be duration"); + finish_test(e.target); + })); +} + +var tests = [ +// Without playing, check that player.played.length == 0. +{ + setup(element) { + element.addEventListener("loadedmetadata", function() { + is(element.played.length, 0, element.token + ": initial played.length equals zero"); + finish_test(element); + }); + }, + name: "test1" +}, +// Play the file, test the range we have. +{ + setup(element) { + check_full_file_played(element); + element.play(); + }, + name: "test2" +}, + +// Play the second half of the file, pause, play +// an check we have only one range. +{ + setup (element) { + element.onended = function (e) { + var t = e.target; + t.onended = null; + check_full_file_played(t); + t.pause(); + t.currentTime = 0; + t.play(); + }; + element.addEventListener("loadedmetadata", function() { + element.currentTime = element.duration / 2; + element.play(); + }); + }, + name: "test3" +}, + +// Play the first half of the file, seek back, while +// continuing to play. We shall have only one range. +{ + setup (element) { + let onTimeUpdate = function() { + if (element.currentTime > element.duration / 2) { + info(element.token + ": currentTime=" + element.currentTime + ", duration=" + element.duration); + element.removeEventListener("timeupdate", onTimeUpdate); + element.pause(); + var oldEndRange = element.played.end(0); + element.currentTime = element.duration / 4; + is(element.played.end(0), oldEndRange, + element.token + ": When seeking back, |played| should not be changed"); + element.play(); + } + } + element.addEventListener("timeupdate", onTimeUpdate); + check_full_file_played(element); + element.play(); + }, + name: "test4" +}, + +// Play and seek to have two ranges, and check that, as well a +// boundaries. +{ + setup (element) { + let seekTarget = 0; + let onTimeUpdate = function() { + if (element.currentTime > element.duration / 2) { + info(element.token + ": currentTime=" + element.currentTime + ", duration=" + element.duration); + element.removeEventListener("timeupdate", onTimeUpdate); + element.pause(); + // Remember seek target for later comparison since duration may change + // during playback. + seekTarget = element.currentTime = element.duration / 10; + element.currentTime = seekTarget; + element.play(); + } + } + + element.addEventListener("loadedmetadata", function() { + element.addEventListener("timeupdate", onTimeUpdate); + }); + + + element.addEventListener("ended", (function() { + if(element.played.length > 1) { + is(element.played.length, 2, element.token + ": element.played.length == 2"); + is(element.played.start(1), seekTarget, element.token + ": we should have seeked forward by one tenth of the duration"); + is(element.played.end(1), element.duration, element.token + ": end of second range shall be the total duration"); + } + is(element.played.start(0), 0, element.token + ": start of first range shall be 0"); + finish_test(element); + })); + + element.play(); + }, + name: "test5" +}, + +// Play to create two ranges, in the reverse order. check that they are sorted. +{ + setup (element) { + function end() { + element.pause(); + let p = element.played; + ok(p.length >= 1, element.token + ": There should be at least one range=" + p.length); + is(p.start(0), seekTarget, element.token + ": Start of first range should be the sixth of the duration"); + ok(p.end(p.length - 1) > 5 * element.duration / 6, element.token + ": End of last range should be greater that five times the sixth of the duration"); + finish_test(element); + } + + let seekTarget = 0; + function pauseseekrestart() { + element.pause(); + // Remember seek target for later comparison since duration may change + // during playback. + seekTarget = element.duration / 6; + element.currentTime = seekTarget; + element.play(); + } + + function onTimeUpdate_pauseseekrestart() { + if (element.currentTime > 5 * element.duration / 6) { + element.removeEventListener("timeupdate", onTimeUpdate_pauseseekrestart); + pauseseekrestart(); + element.addEventListener("timeupdate", onTimeUpdate_end); + } + } + + function onTimeUpdate_end() { + if (element.currentTime > 3 * element.duration / 6) { + element.removeEventListener("timeupdate", onTimeUpdate_end); + end(); + } + } + + element.addEventListener("timeupdate", onTimeUpdate_pauseseekrestart); + + element.addEventListener('loadedmetadata', function() { + element.currentTime = 4 * element.duration / 6; + element.play(); + }); + }, + name: "test6" +}, +// Seek repeatedly without playing. No range should appear. +{ + setup(element) { + let index = 1; + + element.addEventListener('seeked', function() { + index++; + element.currentTime = index * element.duration / 5; + is(element.played.length, 0, element.token + ": played.length should be 0"); + if (index == 5) { + finish_test(element); + } + }); + + element.addEventListener('loadedmetadata', function() { + element.currentTime = element.duration / 5; + }); + }, + name: "test7" +} +]; + +function createTestArray() { + var A = []; + for (var i=0; i<tests.length; i++) { + for (var k=0; k<gPlayedTests.length; k++) { + var t = {}; + t.setup = tests[i].setup; + t.name = tests[i].name + "-" + gPlayedTests[k].name; + t.type = gPlayedTests[k].type; + t.src = gPlayedTests[k].name; + A.push(t); + } + } + return A; +} + +function startTest(test, token) { + var elemType = getMajorMimeType(test.type); + var element = document.createElement(elemType); + element.src = test.src; + element.token = token; + element.preload = "metadata"; + test.setup(element); + manager.started(token); + + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(e) { + var v = e.target; + info(v.token + ": got " + e.type); + } + events.forEach(function(e) { + element.addEventListener(e, logEvent); + }); + +} + + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_preload_actions.html b/dom/media/test/test_preload_actions.html new file mode 100644 index 0000000000..a9de54988c --- /dev/null +++ b/dom/media/test/test_preload_actions.html @@ -0,0 +1,582 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=548523 +--> +<head> + <title>Test for Bug 548523</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548523">Mozilla Bug 548523</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<!-- <button onClick="SimpleTest.finish();">Finish</button> --> +<div>Tests complete: <span id="log" style="font-size: small;"></span></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 548523 **/ + +SimpleTest.requestCompleteLog(); +var manager = new MediaTestManager; + +manager.onFinished = function() { + is(gotLoadEvent, true, "Should not have delayed the load event indefinitely"); +}; + +var test = getPlayableVideo(gSeekTests); +var baseName = test.name; +var gTest = test; +var bogusSrc = "bogus.duh"; +var bogusType = "video/bogus"; +var gotLoadEvent = false; +var finished = false; + +addLoadEvent(function() {gotLoadEvent=true;}); + +function log(m) { + var l = document.getElementById("log"); + // eslint-disable-next-line no-unsanitized/property + l.innerHTML += m; +} + +function maybeFinish(v, n) { + if (v._finished) { + return; + } + v._finished = true; + log(n + ","); + removeNodeAndSource(v); + manager.finished(v.token); +} + +function filename(uri) { + return uri.substr(uri.lastIndexOf("/")+1); +} + +// Every test must have a setup(v) function, and must call maybeFinish() when test is complete. +var tests = [ + { + // 1. Add preload:none video with src to document. Load should halt at NETWORK_IDLE and HAVE_NOTHING, + // after receiving a suspend event. Should not receive loaded events until after we call load(). + // Note the suspend event is explictly sent by our "stop the load" code, but other tests can't rely + // on it for the preload:metadata case, as there can be multiple suspend events when loading metadata. + suspend(e) { + var v = e.target; + is(v._gotLoadStart, true, "(1) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(1) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(1) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE"); + maybeFinish(v, 1); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + v.src = test.name; + document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none. + }, + + name: "test1", + }, + { + // 2. Add preload:metadata video with src to document. Should halt with NETWORK_IDLE, HAVE_CURRENT_DATA + // after suspend event and after loadedmetadata. + loadeddata(e) { + var v = e.target; + is(v._gotLoadStart, true, "(2) Must get loadstart."); + is(v._gotLoadedMetaData, true, "(2) Must get loadedmetadata."); + ok(v.readyState >= v.HAVE_CURRENT_DATA, "(2) ReadyState must be >= HAVE_CURRENT_DATA"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE"); + maybeFinish(v, 2); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "metadata"; + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadeddata", this.loadeddata); + v.src = test.name; + document.body.appendChild(v); // Causes implicit load, which will be halted after + // metadata due to preload:metadata. + }, + + name: "test2", + }, + { + // 3. Add preload:auto to document. Should receive canplaythrough eventually. + canplaythrough(e) { + var v = e.target; + is(v._gotLoadStart, true, "(3) Must get loadstart."); + is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata."); + maybeFinish(v, 3); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "auto"; + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("canplaythrough", this.canplaythrough); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + + name: "test3", + }, + { + // 4. Add preload:none video to document. Call play(), should load then play through. + suspend(e) { + var v = e.target; + if (v._gotSuspend) { + return; // We can receive multiple suspend events, like the one after download completes. + } + v._gotSuspend = true; + is(v._gotLoadStart, true, "(4) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(4) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(4) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE"); + v.play(); // Should load and play through. + }, + + ended(e) { + ok(true, "(4) Got playback ended"); + maybeFinish(e.target, 4); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v._gotSuspend = false; + v.preload = "none"; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + v.addEventListener("ended", this.ended); + v.src = test.name; + document.body.appendChild(v); + }, + + name: "test4", + }, + { + // 5. preload:none video without resource, add to document, will implicitly start a + // preload:none load. Add a src, it shouldn't load. + suspend(e) { + var v = e.target; + is(v._gotLoadStart, true, "(5) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(5) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(5) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE"); + maybeFinish(v, 5); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource. + v.src = test.name; // Load should start, and halt at preload:none. + }, + + name: "test5", + }, + { + // 6. preload:none video without resource, add to document, will implicitly start a + // preload:none load. Add a source, it shouldn't load. + suspend(e) { + var v = e.target; + is(v._gotLoadStart, true, "(6) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(6) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(6) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE"); + maybeFinish(v, 6); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource. + var s = document.createElement("source"); + s.src = test.name; + s.type = test.type; + v.appendChild(s); // Load should start, and halt at preload:none. + }, + + name: "test6", + }, + { + // 7. create a preload:none document with multiple sources, the first of which is invalid. + // Add to document, then play. It should load and play through the second source. + suspend(e) { + var v = e.target; + if (v._gotSuspend) + return; // We can receive multiple suspend events, like the one after download completes. + v._gotSuspend = true; + is(v._gotLoadStart, true, "(7) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(7) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(7) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(7) NetworkState must be NETWORK_IDLE"); + v.play(); // Should load and play through. + }, + + ended(e) { + ok(true, "(7) Got playback ended"); + var v = e.target; + is(v._gotErrorEvent, true, "(7) Should get error event from first source load failure"); + maybeFinish(v, 7); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "none"; + v._gotErrorEvent = false; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + v.addEventListener("ended", this.ended); + var s1 = document.createElement("source"); + s1.src = "not-a-real-file.404" + s1.type = test.type; + s1.addEventListener("error", function(e){v._gotErrorEvent = true;}); + v.appendChild(s1); + var s2 = document.createElement("source"); + s2.src = test.name; + s2.type = test.type; + v.appendChild(s2); + document.body.appendChild(v); // Causes implicit load, which will be halt at preload:none on the second resource. + }, + + name: "test7", + }, + { + // 8. Change preload value from none to metadata should cause metadata to be loaded. + loadeddata(e) { + var v = e.target; + is(v._gotLoadedMetaData, true, "(8) Must get loadedmetadata."); + ok(v.readyState >= v.HAVE_CURRENT_DATA, "(8) ReadyState must be >= HAVE_CURRENT_DATA on suspend."); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted"); + maybeFinish(v, 8); + }, + + setup(v) { + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadstart", function(e){v.preload = "metadata";}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadeddata", this.loadeddata); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + + name: "test8", + }, + /*{ + // 9. Change preload value from metadata to auto should cause entire media to be loaded. + // For some reason we don't always receive the canplaythrough event, particuarly on this test. + // We've disabled this test until bug 568402 is fixed. + canplaythrough: + function(e) { + var v = e.target; + is(v._gotLoadStart, true, "(9) Must get loadstart."); + is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata."); + maybeFinish(v, 9); + }, + + setup: + function(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "metadata"; + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false); + v.addEventListener("loadeddata", function(){v.preload = "auto"}, false); + v.addEventListener("canplaythrough", this.canplaythrough, false); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + },*/ + { + // 10. Change preload value from none to auto should cause entire media to be loaded. + canplaythrough(e) { + var v = e.target; + is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata."); + maybeFinish(v, 10); + }, + + setup(v) { + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadstart", function(e){v.preload = "auto";}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("canplaythrough", this.canplaythrough); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + + name: "test10", + }, + { + // 11. Change preload value from none to metadata should cause metadata to load. + loadeddata(e) { + var v = e.target; + is(v._gotLoadedMetaData, true, "(11) Must get loadedmetadata."); + ok(v.readyState >= v.HAVE_CURRENT_DATA, "(11) ReadyState must be >= HAVE_CURRENT_DATA."); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(11) NetworkState must be NETWORK_IDLE."); + maybeFinish(v, 11); + }, + + setup(v) { + v._gotLoadedMetaData = false; + v.preload = "none"; + v.addEventListener("loadstart", function(e){v.preload = "metadata";}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadeddata", this.loadeddata); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + + name: "test11", + }, + { + // 13. Change preload value from auto to none after specifying a src + // should load according to preload none, no buffering should have taken place + suspend(e) { + var v = e.target; + is(v._gotLoadStart, true, "(13) Must get loadstart."); + is(v._gotLoadedMetaData, false, "(13) Must not get loadedmetadata."); + is(v.readyState, v.HAVE_NOTHING, "(13) ReadyState must be HAVE_NOTHING"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(13) NetworkState must be NETWORK_IDLE"); + maybeFinish(v, 13); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "auto"; + v.src = test.name; + v.preload = "none"; + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("suspend", this.suspend); + document.body.appendChild(v); // Causes implicit load, should load according to preload none + document.createElement("source"); + }, + + name: "test13", + }, + { + // 14. Add preload:metadata video with src to document. Play(), should play through. + loadeddata(e) { + var v = e.target; + is(v._gotLoadStart, true, "(14) Must get loadstart."); + is(v._gotLoadedMetaData, true, "(14) Must get loadedmetadata."); + ok(v.readyState >= v.HAVE_CURRENT_DATA, "(14) ReadyState must be >= HAVE_CURRENT_DATA"); + // bug 962949 + // is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE"); + v.play(); + }, + + ended(e) { + ok(true, "(14) Got playback ended"); + var v = e.target; + maybeFinish(v, 14); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "metadata"; + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("ended", this.ended); + v.addEventListener("loadeddata", this.loadeddata); + v.src = test.name; + document.body.appendChild(v); // Causes implicit load, which will be halted after + // metadata due to preload:metadata. + }, + + name: "test14", + }, + { + // 15. Autoplay should override preload:none. + ended(e) { + ok(true, "(15) Got playback ended."); + var v = e.target; + maybeFinish(v, 15); + }, + + setup(v) { + v._gotLoadStart = false; + v._gotLoadedMetaData = false; + v.preload = "none"; + v.autoplay = true; + v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}); + v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}); + v.addEventListener("ended", this.ended); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(e) { + info(e.target.token + ": got " + e.type); + } + events.forEach(function(e) { + v.addEventListener(e, logEvent); + }); + }, + + name: "test15", + }, + { + // 16. Autoplay should override preload:metadata. + ended(e) { + ok(true, "(16) Got playback ended."); + var v = e.target; + maybeFinish(v, 16); + }, + + setup(v) { + v.preload = "metadata"; + v.autoplay = true; + v.addEventListener("ended", this.ended); + v.src = test.name; // Causes implicit load. + document.body.appendChild(v); + }, + + name: "test16", + }, + { + // 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay! + ended(e) { + ok(true, "(17) Got playback ended."); + var v = e.target; + maybeFinish(v, 17); + }, + + setup(v) { + v.addEventListener("ended", this.ended); + v.preload = "none"; + document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none. + v.autoplay = true; + v.src = test.name; + }, + + name: "test17", + }, + { + // 18. On a preload='none' video, call play() before load algorithms's sync + // has run, the play() call should override preload='none'. + ended(e) { + ok(true, "(18) Got playback ended."); + var v = e.target; + maybeFinish(v, 18); + }, + + setup(v) { + v.addEventListener("ended", this.ended); + v.preload = "none"; + v.src = test.name; // Schedules async section to continue load algorithm. + document.body.appendChild(v); + v.play(); // Should cause preload:none to be overridden. + }, + + name: "test18", + }, + { + // 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another. + // The second video should not start playing as it's preload state has been changed to 'none' from 'auto' + setup(v) { + v.preload = "auto"; + v.src = test.name; + // add a listener for when the video has loaded, so we know preload auto has worked + v.onloadedmetadata = function() { + is(v.preload, "auto", "(19) preload is initially auto"); + // set preload state to none and switch video sources + v.preload="none"; + v.src = test.name + "?asdf"; + + v.onloadedmetadata = function() { + ok(false, "(19) 'loadedmetadata' shouldn't fire when preload is none"); + } + + var ontimeout = function() { + v.removeEventListener("suspend", onsuspend); + ok(false, "(19) 'suspend' should've fired"); + maybeFinish(v, 19); + } + var cancel = setTimeout(ontimeout, 10000); + + var onsuspend = function() { + v.removeEventListener("suspend", onsuspend); + clearTimeout(cancel); + is(v.readyState, 0, "(19) no buffering has taken place"); + maybeFinish(v, 19); + } + v.addEventListener("suspend", onsuspend); + } + document.body.appendChild(v); + }, + + name: "test19", + } +]; + +var iterationCount = 0; +function startTest(t, token) { + if (t == tests[0]) { + ++iterationCount; + info("iterationCount=" + iterationCount); + } + if (iterationCount == 2) { + // Do this series of tests on logically different resources + t.name = baseName + "?" + Math.floor(Math.random()*100000); + } + var v = document.createElement("video"); + v.token = token; + t.setup(v); + manager.started(token); +} + +var twiceTests = tests.concat(tests); +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest); +function beginTest() { + manager.runTests(twiceTests, startTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_preload_attribute.html b/dom/media/test/test_preload_attribute.html new file mode 100644 index 0000000000..1e415035c5 --- /dev/null +++ b/dom/media/test/test_preload_attribute.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=479863 +--> +<head> + <title>Test for Bug 479863</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<video id='v1'></video><audio id='a1'></audio> +<video id='v2' preload="auto"></video><audio id='a2' preload="auto"></audio> + +<pre id="test"> +<script type="application/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var v2 = document.getElementById('v2'); +var a2 = document.getElementById('a2'); +is(v1.getAttribute("preload"), null, "video preload via getAttribute should be null by default"); +is(a1.getAttribute("preload"), null, "video preload via getAttribute should be null by default"); +is(v1.preload, "", "v1.preload should be empty by default"); +is(a1.preload, "", "a1.preload should be empty by default"); +is(v2.preload, "auto", "v2.preload should be auto"); +is(a2.preload, "auto", "a2.preload should be auto"); + +v1.preload = "auto"; +a1.preload = "auto"; +is(v1.preload, "auto", "video.preload should be auto"); +is(a1.preload, "auto", "audio.preload should be auto"); +is(v1.getAttribute("preload"), "auto", "video preload attribute should be auto"); +is(a1.getAttribute("preload"), "auto", "video preload attribute should be auto"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_preload_suspend.html b/dom/media/test/test_preload_suspend.html new file mode 100644 index 0000000000..7f1146360f --- /dev/null +++ b/dom/media/test/test_preload_suspend.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 479863</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +function checkSuspendCount(evt) { + var v = evt.target; + ++v.suspendCount; + is(v.networkState, v.NETWORK_IDLE, v.name + " got suspended, count=" + v.suspendCount); + if (v.suspendCount == v.expectedSuspendCount) { + removeNodeAndSource(v); + manager.finished(v.name); + } + if (v.suspendCount > v.expectedSuspendCount) { + ok(false, v.name + " got too many suspend events"); + } +} + +var tests = [ + { + name: 'v1', + preload: 'none', + expectedSuspendCount: 2, + onsuspend(evt) { + checkSuspendCount(evt); + if (evt.target.suspendCount == 1) { + evt.target.preload = 'auto'; + } + } + }, + { + name: 'v2', + preload: 'auto', + expectedSuspendCount: 1, + onsuspend: checkSuspendCount + }, + { + name: 'v3', + preload: 'none', + autoplay: true, + expectedSuspendCount: 1, + onsuspend: checkSuspendCount + }, + { + name: 'v4', + preload: 'none', + expectedSuspendCount: 2, + onsuspend(evt) { + checkSuspendCount(evt); + if (evt.target.suspendCount == 1) { + evt.target.play(); + } + } + }, + // disable v5 since media element doesn't support 'load' event anymore. + /*{ + name: 'v5', + preload: 'none', + expectedSuspendCount: 2, + onsuspend: function(evt) { + checkSuspendCount(evt); + if (evt.target.suspendCount == 1) { + evt.target.currentTime = 0.1; + } + } + },*/ + { + name: 'v6', + preload: 'none', + expectedSuspendCount: 2, + onsuspend(evt) { + checkSuspendCount(evt); + if (evt.target.suspendCount == 1) { + evt.target.autoplay = true; + } + } + } +]; + +function startTest(test, token) { + var v = document.createElement("video"); + v.name = test.name; + var key = Math.random(); + v.src = "seek.ogv?key=" + key + "&id=" + v.name; + v.preload = test.preload; + v.suspendCount = 0; + v.expectedSuspendCount = test.expectedSuspendCount; + if (test.autoplay) { + v.autoplay = true; + } + v.onsuspend = test.onsuspend; + document.body.appendChild(v); + manager.started(v.name); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, function() { + manager.runTests(tests, startTest); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_preserve_playbackrate_after_ui_play.html b/dom/media/test/test_preserve_playbackrate_after_ui_play.html new file mode 100644 index 0000000000..98bdd66675 --- /dev/null +++ b/dom/media/test/test_preserve_playbackrate_after_ui_play.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title> Bug 1013933 - preserve playbackRate after clicking play button </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"> + <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video> +</div> + +<script type="text/javascript"> +/* + * Positions of the UI elements, relative to the upper-left corner of the + * <video> box. + */ +const videoHeight = 240; +const playButtonWidth = 28; +const playButtonHeight = 28; +const playButtonCenterX = 0 + Math.round(playButtonWidth / 2); +const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2); + +var expectedPlaybackRate = 0.5 + +function runTest() { + var video = document.getElementById("video"); + video.src = "audio.wav"; + video.loop = true; + video.playbackRate = expectedPlaybackRate; + + video.oncanplaythrough = function() { + video.oncanplaythrough = null; + is(video.paused, true, "video is not playing yet."); + is(video.playbackRate, expectedPlaybackRate, + "playbackRate is correct before clicking play button."); + + // Click the play button + synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { }); + }; + + video.onplay = function() { + video.onplay = null; + is(video.paused, false, "video starts playing."); + is(video.playbackRate, expectedPlaybackRate, + "playbackRate is correct after clicking play button."); + video.pause(); + SimpleTest.finish(); + }; +} + +window.addEventListener("load", runTest); +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_progress.html b/dom/media/test/test_progress.html new file mode 100644 index 0000000000..b958b2b335 --- /dev/null +++ b/dom/media/test/test_progress.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: progress events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function do_progress(e) { + var v = e.target; + ok(!v._finished, "Check no progress events after completed for " + v._name); +} + +function do_ended(e) { + var v = e.target; + ok(!v._finished, "Only one ended event for " + v._name); + v._finished = true; + v.removeEventListener("ended", do_ended); + v.removeEventListener("progress", do_progress); + removeNodeAndSource(v); + manager.finished(v.token); +} + +function startTest(test, token) { + var type = /^video/.test(test.type) ? "video" : "audio"; + var v = document.createElement(type); + v.token = token; + manager.started(token); + v.src = test.name; + v.autoplay = true; + v._name = test.name; + v._finished = false; + v.addEventListener("ended", do_ended); + v.addEventListener("progress", do_progress); + document.body.appendChild(v); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest); +function beginTest() { + manager.runTests(gProgressTests, startTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_reactivate.html b/dom/media/test/test_reactivate.html new file mode 100644 index 0000000000..43675f7771 --- /dev/null +++ b/dom/media/test/test_reactivate.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test reactivation of a media element from a dead document</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> + +<iframe id="frame" src="reactivate_helper.html"></iframe> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var elements; + +function playElement(e) { + // All elements played out, finish the test case. + if (!e) { + SimpleTest.finish(); + return; + } + + e.play(); + info("Element play: " + e._name); + var reviveElement = function() { + document.body.appendChild(e); + e.onended = function() { + info("Element ended: " + e._name); + removeNodeAndSource(e); + // Play next element. + playElement(elements.pop()); + } + } + setTimeout(reviveElement, 2000); +} + +function loadedAll(elementList) { + elements = elementList; + + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(e) { + info(e.target._name + ": got " + e.type); + } + elementList.forEach(function(element) { + events.forEach(function(evt) { + element.addEventListener(evt, logEvent); + }); + }); + + // Blow away the subframe + document.body.removeChild(document.getElementById("frame")); + + // Play elements one by one to avoid hitting bug 847903 on MacOSX. + playElement(elements.pop()); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_readyState.html b/dom/media/test/test_readyState.html new file mode 100644 index 0000000000..17c363c503 --- /dev/null +++ b/dom/media/test/test_readyState.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: readyState</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<video id='v1'></video><audio id='a1'></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +"use strict"; +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var passed = "truthy"; + +is(v1.readyState, 0); +is(a1.readyState, 0); + +try { + v1.readyState = 0; +} catch (e) { + passed = !passed; +} +try { + a1.readyState = 0; +} catch (e) { + passed = !passed; +} +ok(passed === true, + "Setting readyState throws in strict mode (readonly attribute)"); +</script> + +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById('v1'); +var a1 = document.getElementById('a1'); +var passed = false; + +is(v1.readyState, 0); +is(a1.readyState, 0); + +try { + v1.readyState = 1; + a1.readyState = 1; + passed = v1.readyState === 0 && a1.readyState === 0; +} catch(e) { } +ok(passed, "Should not be able to set readyState (readonly attribute)"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_referer.html b/dom/media/test/test_referer.html new file mode 100644 index 0000000000..2561e72b6a --- /dev/null +++ b/dom/media/test/test_referer.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=584480 +--> +<head> + <title>Test for Bug 584480</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=584480">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +var media = []; + +function checkComplete() { + for (var i=0; i<media.length; ++i) { + if (!media[i]._complete) { + return; + } + } + + SimpleTest.finish(); +} + +function removeNode(v) { + v.removeEventListener("error", loadError); + v.removeEventListener("loadedmetadata", loadedMetadata); + v.remove(); + v.src = ""; +} + +function loadError(evt) { + // If no referer is sent then the sjs returns an error + ok(false, "check referer is sent with media request"); + evt.target._complete = true; + checkComplete(); + removeNode(evt.target); +} + +function loadedMetadata(evt) { + // If a referer is sent then the sjs returns a valid media + ok(true, "check referer is sent with media request"); + evt.target._complete = true; + checkComplete(); + removeNode(evt.target); +} + +// Create all media objects. +for (var i=0; i<gSmallTests.length; ++i) { + var test = gSmallTests[i]; + var type; + if (/^video/.test(test.type)) { + type = "video" + } else { + type = "audio"; + } + var v = document.createElement(type); + if (!v.canPlayType(test.type)) { + continue; + } + // ensure metadata is loaded for default preload is none on b2g + v.preload = "metadata"; + v.autoplay = "true"; + v._complete = false; + v.addEventListener("error", loadError); + v.addEventListener("loadedmetadata", loadedMetadata); + v.src = 'referer.sjs?name=' + test.name + '&type=' + test.type; + document.body.appendChild(v); // Will start load. + media.push(v); +} + +if (media.length == 0) { + todo(false, "No types supported"); +} else { + SimpleTest.waitForExplicitFinish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/media/test/test_replay_metadata.html b/dom/media/test/test_replay_metadata.html new file mode 100644 index 0000000000..dab022885f --- /dev/null +++ b/dom/media/test/test_replay_metadata.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=467972 +--> +<head> + <title>Test for Bug 467972</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=467972">Mozilla Bug 467972</a> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +// Test for Bug 467972. Tests that when we play to end, seek to 0, and play again, that we don't +// send/receive multiple loadeddata and loadedmetadata events. + +var manager = new MediaTestManager; + +function seekStarted(evt) { + var v = evt.target; + v._gotSeekStarted = true; +} + +function seekEnded(evt) { + var v = evt.target; + v._gotSeekEnded = true; + v.play(); +} + +function loadedData(evt) { + var v = evt.target; + v._loadedDataCount++; + ok(v._loadedDataCount <= 1, "No more than 1 onloadeddata event for " + v._name); +} + +function loadedMetaData(evt) { + var v = evt.target; + v._loadedMetaDataCount++; + ok(v._loadedMetaDataCount <= 1, "No more than 1 onloadedmetadata event for " + v._name); + checkMetadata(v._name, v, v._test); + v.play(); +} + +function playing(evt) { + evt.target._playingCount++; +} + +function removeNodeAndListener(n) { + n.removeEventListener("loadedmetadata", loadedMetaData); + n.removeEventListener("ended", playbackEnded); + n.removeEventListener("playing", playing); + n.removeEventListener("loadeddata", loadedData); + n.removeEventListener("seeking", seekStarted); + n.removeEventListener("seeked", seekEnded); + removeNodeAndSource(n); +} + +function playbackEnded(evt) { + var v = evt.target; + v._endCount++; + ok(v.currentTime >= v.duration-0.1 && v.currentTime <= v.duration + 0.1, + "CurrentTime (" + v.currentTime + ") should be around " + v.duration + + " for " + v._name); + if (!v._playedOnce) { + v.currentTime = 0; + ok(v.seeking, "seeking should be true for " + v._name); + ok(!v.ended, "ended shouldn't be true once seeking has begun for " + v._name); + v._playedOnce = true; + } else { + ok(v._gotSeekEnded, "Should have received seekended for " + v._name); + ok(v._gotSeekStarted, "Should have received seekstarted for " + v._name); + is(v._loadedDataCount, 1, "Should have 1 onloadeddata event for " + v._name); + is(v._loadedMetaDataCount, 1, "Should have 1 onloadedmetadata event for " + v._name); + is(v._endCount, 2, "Should have received two ended events for " + v._name); + ok(v._playingCount > 0, "Should have at least one playing event for " + v._name); + v._finished = true; + removeNodeAndListener(v); + manager.finished(v.token); + } +} + +function startTest(test, token) { + var v = document.createElement('video'); + v.preload = "auto"; + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name; + v._playedOnce = false; + v._gotSeekEnded = false; + v._gotSeekStarted = false; + v._loadedDataCount = 0; + v._loadedMetaDataCount = 0; + v._playingCount = 0; + v._endCount = 0; + v._test = test; + v._finished = false; + v.addEventListener("loadedmetadata", loadedMetaData); + v.addEventListener("ended", playbackEnded); + v.addEventListener("playing", playing); + v.addEventListener("loadeddata", loadedData); + v.addEventListener("seeking", seekStarted); + v.addEventListener("seeked", seekEnded); + document.body.appendChild(v); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_reset_events_async.html b/dom/media/test/test_reset_events_async.html new file mode 100644 index 0000000000..482ec55986 --- /dev/null +++ b/dom/media/test/test_reset_events_async.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=975270 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="manifest.js"></script> + <script type="application/javascript"> + + /** Test for Bug 975270 **/ + // Test that 'emptied' and 'abort' events are fired asynchronously when re-starting + // media load. + SimpleTest.waitForExplicitFinish(); + + var a = document.createElement("audio"); + a._abort = 0; + a._emptied = 0; + a.preload = "metadata"; // On B2G we default to preload:none. + + is(a.networkState, HTMLMediaElement.NETWORK_EMPTY, "Shouldn't be loading"); + + a.addEventListener("abort", function(e) { a._abort++; }); + a.addEventListener("emptied", function(e) { a._emptied++; }); + a.addEventListener("loadedmetadata", + function(e) { + is(a._abort, 0, "Should not have received 'abort' before 'loadedmetadata"); + is(a._emptied, 0, "Should not have received 'emptied' before 'loadedmetadata"); + + a.addEventListener("loadstart", + function() { + is(a._abort, 1, "Should have received 'abort' before 'loadstart"); + is(a._emptied, 1, "Should have received 'emptied' before 'loadstart"); + SimpleTest.finish(); + }); + + a.src = ""; + is(a._abort, 0, "Should not have received 'abort' during setting a.src=''"); + is(a._emptied, 0, "Should not have received 'emptied' during setting a.src=''"); + }); + + a.src = getPlayableAudio(gSmallTests).name; + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/media/test/test_reset_src.html b/dom/media/test/test_reset_src.html new file mode 100644 index 0000000000..1912369dac --- /dev/null +++ b/dom/media/test/test_reset_src.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=804875 +--> + +<head> + <title>Test for bug 804875</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" +href="https://bugzilla.mozilla.org/show_bug.cgi?id=804875">Mozilla Bug 804875</a> + +<canvas></canvas> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function finish(v) { + removeNodeAndSource(v); + manager.finished(v.token); +} + +function onLoadedData_Audio(e) { + var t = e.target; + is(t.videoHeight, 0, t.name + ": videoHeight should be zero when there is no video."); + is(t.videoWidth, 0, t.name + ": videoWidth should be zero when there is no video."); + is(t.mozPaintedFrames, 0, t.name + ": mozPaintedFrames should be zero when there is no video."); + is(t.mozFrameDelay, 0, t.name + ": mozFrameDelay should be zero when there is no video."); + var c = document.getElementsByTagName("canvas")[0].getContext("2d"); + try { + c.drawImage(t, 0, 0, t.videoHeight, t.videoWidth); + } catch (ex) { + ok(true, t.name + ": Trying to draw to a canvas should throw, since we don't have a frame anymore"); + finish(t); + return; + } + ok(false, t.name + ": We should not succeed to draw a video frame on the canvas."); + finish(t); +} + +function onTimeUpdate_Video(e) { + var t = e.target; + if (t.currentTime < t.duration / 4) { + return; + } + t.removeEventListener("timeupdate", onTimeUpdate_Video); + // There's no guarantee that a video frame composite notification reaches + // us before timeupdate fires. + ok(t.mozPaintedFrames >= 0, t.name + ": mozPaintedFrames should be positive or zero, is " + t.mozPaintedFrames + "."); + ok(t.mozFrameDelay >= 0, t.name + ": mozFrameDelay should be positive or zero, is " + t.mozFrameDelay + "."); + + if (t._firstTime) { + // eslint-disable-next-line no-self-assign + t.src = t.src; + t._firstTime = false; + } else { + var source = getPlayableAudio(gPlayTests); + if (!source) { + todo("No audio file available.") + finish(t); + } else { + t.removeEventListener("loadeddata", onLoadedData_Video); + t.addEventListener("loadeddata", onLoadedData_Audio); + t.src = source.name; + } + } +} + +function onLoadedData_Video(e) { + var t = e.target; + isnot(t.videoHeight, 0, t.name + ": We should have a videoHeight."); + isnot(t.videoWidth, 0, t.name + ": We should have a videoWidth."); + t.addEventListener("timeupdate", onTimeUpdate_Video); + t.play(); +} + +function startTest(test, token) { + var v = document.createElement('video'); + document.body.appendChild(v); + v._firstTime = true; + v.addEventListener("loadeddata", onLoadedData_Video); + v.src = test.name; + v.token = token; + v.name = test.name; + v.play(); + manager.started(token); +} + +manager.runTests(getPlayableVideos(gSmallTests.concat(gSeekTests)), startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_resolution_change.html b/dom/media/test/test_resolution_change.html new file mode 100644 index 0000000000..403f9b505d --- /dev/null +++ b/dom/media/test/test_resolution_change.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of files with resolution changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loadedData(e) {
+ var v = e.target;
+ v.addEventListener("resize", resize);
+ v.play();
+}
+
+function resize(e) {
+ var v = e.target;
+ v.seenResolutionChange = true;
+}
+
+function ended(e) {
+ var v = e.target;
+ ok(v.seenResolutionChange, v.token + ": A resolution change should have ocurred by the end of playback");
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ v.src = test.name;
+ v.seenResolutionChange = false;
+
+ v.addEventListener("loadeddata", loadedData)
+ v.addEventListener("ended", ended);
+
+ manager.started(token);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gResolutionChangeTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_resume.html b/dom/media/test/test_resume.html new file mode 100644 index 0000000000..0e22894be1 --- /dev/null +++ b/dom/media/test/test_resume.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: Test resume of server-dropped connections</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<audio preload="auto" id="a"></audio> +<iframe id="f"></iframe> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var key = Math.round(Math.random()*1000000000); +var a = document.getElementById("a"); +var f = document.getElementById("f"); + +function didEnd() { + ok(a.currentTime > 2.26, "Reached correct end time (got " + a.currentTime + ", expected > 2.26"); + SimpleTest.finish(); +} + +function didSendCancel() { + a.addEventListener("ended", didEnd); + a.play(); +} + +function didSuspend() { + a.removeEventListener("suspend", didSuspend); + + // Cache must have filled up, or something. Tell the Web server to drop + // our connection. + f.addEventListener("load", didSendCancel); + f.src = "cancellable_request.sjs?cancelkey=" + key; +} + +if (!a.canPlayType("audio/wave")) { + todo(false, "Test requires support for audio/wave"); +} else { + a.addEventListener("suspend", didSuspend); + a.src = "cancellable_request.sjs?key=" + key; + SimpleTest.waitForExplicitFinish(); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seamless_looping.html b/dom/media/test/test_seamless_looping.html new file mode 100644 index 0000000000..c9e3253a5d --- /dev/null +++ b/dom/media/test/test_seamless_looping.html @@ -0,0 +1,185 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for seamless loop of HTMLAudioElements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<canvas id="canvas" width="300" height="300"></canvas> +<script type="application/javascript"> +/** + * This test is used to ensure every time we loop audio, the audio can loop + * seamlessly which means there won't have any silenece or noise between the + * end and the start. + */ + +SimpleTest.waitForExplicitFinish(); + +// Set DEBUG to true to add a canvas with a little drawing of what is going +// on, and actually outputs the audio to the speakers. +var DEBUG = true; +var LOOPING_COUNT = 0; +var MAX_LOOPING_COUNT = 10; +var TONE_FREQUENCY = 440; + +(async function testSeamlesslooping() { + info(`- create looping audio element -`); + let audio = createAudioElement(); + + info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`); + await playAudioAndStartAnalyzingWaveData(audio); + + info(`- test seamless looping multiples times -`); + for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) { + await once(audio, "seeked"); + info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`); + } + + info(`- end of seamless looping test -`); + SimpleTest.finish(); +})(); + +/** + * Test utility functions + */ +function createSrcBuffer() { + // Generate the sine in floats, then convert, for simplicity. + let channels = 1; + let sampleRate = 44100; + let buffer = new Float32Array(sampleRate * channels); + let phase = 0; + const TAU = 2 * Math.PI; + for (let i = 0; i < buffer.length; i++) { + // Adjust the gain a little so we're sure it's not going to clip. This is + // important because we're converting to 16bit integer right after, and + // clipping will clearly introduce a discontinuity that will be + // mischaracterized as a looping click. + buffer[i] = Math.sin(phase) * 0.99; + phase += TAU * TONE_FREQUENCY / 44100; + if (phase > 2 * TAU) { + phase -= TAU; + } + } + + // Make a RIFF header, it's 23 bytes + let buf = new Int16Array(buffer.length + 23); + buf[0] = 0x4952; + buf[1] = 0x4646; + buf[2] = (2 * buffer.length + 15) & 0x0000ffff; + buf[3] = ((2 * buffer.length + 15) & 0xffff0000) >> 16; + buf[4] = 0x4157; + buf[5] = 0x4556; + buf[6] = 0x6d66; + buf[7] = 0x2074; + buf[8] = 0x0012; + buf[9] = 0x0000; + buf[10] = 0x0001; + buf[11] = 1; + buf[12] = 44100 & 0x0000ffff; + buf[13] = (44100 & 0xffff0000) >> 16; + buf[14] = (2 * channels * sampleRate) & 0x0000ffff; + buf[15] = ((2 * channels * sampleRate) & 0xffff0000) >> 16; + buf[16] = 0x0004; + buf[17] = 0x0010; + buf[18] = 0x0000; + buf[19] = 0x6164; + buf[20] = 0x6174; + buf[21] = (2 * buffer.length) & 0x0000ffff; + buf[22] = ((2 * buffer.length) & 0xffff0000) >> 16; + + // convert to int16 and copy. + for (let i = 0; i < buffer.length; i++) { + buf[i + 23] = Math.round(buffer[i] * (1 << 15)); + } + return buf; +} + +function createAudioElement() { + /* global audio */ + window.audio = document.createElement("audio"); + audio.src = URL.createObjectURL(new Blob([createSrcBuffer()], + { type: 'audio/wav' })); + audio.controls = true; + audio.loop = true; + document.body.appendChild(audio); + return audio; +} + +async function playAudioAndStartAnalyzingWaveData(audio) { + createAudioWaveAnalyser(audio); + ok(await once(audio, "canplay").then(() => true, () => false), + `audio can start playing.`) + ok(await audio.play().then(() => true, () => false), + `audio started playing successfully.`); +} + +function createAudioWaveAnalyser(source) { + /* global ac, analyser */ + window.ac = new AudioContext(); + window.analyser = ac.createAnalyser(); + analyser.frequencyBuf = new Float32Array(analyser.frequencyBinCount); + analyser.smoothingTimeConstant = 0; + analyser.fftSize = 2048; // 1024 bins + + let sourceNode = ac.createMediaElementSource(source); + sourceNode.connect(analyser); + + if (DEBUG) { + analyser.connect(ac.destination); + analyser.timeDomainBuf = new Float32Array(analyser.frequencyBinCount); + let cvs = document.querySelector("canvas"); + analyser.c = cvs.getContext("2d"); + analyser.w = cvs.width; + analyser.h = cvs.height; + } + + analyser.notifyAnalysis = () => { + if (LOOPING_COUNT >= MAX_LOOPING_COUNT) { + return; + } + let {frequencyBuf} = analyser; + analyser.getFloatFrequencyData(frequencyBuf); + // Let things stabilize at the beginning. See bug 1441509. + if (LOOPING_COUNT > 1) { + analyser.doAnalysis(frequencyBuf, ac.sampleRate); + } + + if (DEBUG) { + let {c, w, h, timeDomainBuf} = analyser; + c.clearRect(0, 0, w, h); + analyser.getFloatTimeDomainData(timeDomainBuf); + for (let i = 0; i < frequencyBuf.length; i++) { + c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels); + } + + for (let i = 0; i < timeDomainBuf.length; i++) { + c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2); + } + } + + requestAnimationFrame(analyser.notifyAnalysis); + } + + analyser.doAnalysis = (buf, ctxSampleRate) => { + // The size of an FFT is twice the number of bins in its output. + let fftSize = 2 * buf.length; + // first find a peak where we expect one. + let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate); + ok(buf[binIndexTone] > -25, + `Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz`); + + // check that the energy some octaves higher is very low. + let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate); + ok(buf[binIndexOutsidePeak] < -110, + `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db at ${TONE_FREQUENCY * 4}Hz`); + } + + analyser.notifyAnalysis(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-1.html b/dom/media/test/test_seek-1.html new file mode 100644 index 0000000000..8690a475b6 --- /dev/null +++ b/dom/media/test/test_seek-1.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 1; + +function test_seek1(v, seekTime, is, ok, finish) { + +var startPassed = false; +var endPassed = false; +var seekFlagStart = false; +var seekFlagEnd = false; +var readonly = true; +var completed = false; + +function startTest() { + ok(!completed, "Should not be completed yet"); + ok(!v.seeking, "seeking should default to false"); + try { + v.seeking = true; + readonly = v.seeking === false; + } + catch(e) { + readonly = "threw exception: " + e; + } + is(readonly, true, "seeking should be readonly"); + + v.currentTime = seekTime; + seekFlagStart = v.seeking; +} + +function seekStarted() { + ok(!completed, "should not be completed yet"); + ok(Math.abs(v.currentTime - seekTime) < 0.1, + "Video currentTime should be around " + seekTime + ": " + v.currentTime + " (seeking)"); + startPassed = true; +} + +function seekEnded() { + ok(!completed, "shuld not be completed yet"); + ok(Math.abs(v.currentTime - seekTime) < 0.1, + "Video currentTime should be around " + seekTime + ": " + v.currentTime + " (seeked)"); + endPassed = true; + seekFlagEnd = v.seeking; + v.play(); +} + +function playbackEnded() { + ok(!completed, "should not be completed yet"); + + completed = true; + ok(startPassed, "seeking event"); + ok(endPassed, "seeked event"); + ok(seekFlagStart, "seeking flag on start should be true"); + ok(!seekFlagEnd, "seeking flag on end should be false"); + finish(); +} + +once(v, "ended", playbackEnded); +once(v, "loadedmetadata", startTest); +once(v, "seeking", seekStarted); +once(v, "seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-10.html b/dom/media/test/test_seek-10.html new file mode 100644 index 0000000000..6c02384ed4 --- /dev/null +++ b/dom/media/test/test_seek-10.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 10; + +function test_seek10(v, seekTime, is, ok, finish) { + +// Test bug 523335 - ensure that if we close a stream while seeking, we +// don't hang during shutdown. This test won't "fail" per se if it's regressed, +// it will instead start to cause random hangs in the mochitest harness on +// shutdown. + +function startTest() { + // Must be duration*0.9 rather than seekTime, else we don't hit that problem. + // This is probably due to the seek bisection finishing too quickly, before + // we can close the stream. + v.currentTime = v.duration * 0.9; +} + +function done(evt) { + ok(true, "We don't acutally test anything..."); + finish(); +} + +function seeking() { + ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime); + v.onerror = done; + v.src = "not a valid video file."; + v.load(); // Cause the existing stream to close. +} + +v.addEventListener("loadeddata", startTest); +v.addEventListener("seeking", seeking); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-11.html b/dom/media/test/test_seek-11.html new file mode 100644 index 0000000000..2207f684d7 --- /dev/null +++ b/dom/media/test/test_seek-11.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +PARALLEL_TESTS = 1; +const SEEK_TEST_NUMBER = 11; + +function test_seek11(v, seekTime, is, ok, finish) { + +// Test for bug 476973, multiple seeks to the same position shouldn't cause problems. + +var seekedNonZero = false; +var completed = false; +var target = 0; + +function startTest() { + if (completed) + return; + target = v.duration / 2; + v.currentTime = target; + v.currentTime = target; + v._seekTarget = target; +} + +function startSeeking() { + ok(v.currentTime >= v._seekTarget - 0.1, + "Video currentTime should be around " + v._seekTarget + ": " + v.currentTime); + if (!seekedNonZero) { + v.currentTime = target; + v._seekTarget = target; + seekedNonZero = true; + } +} + +function seekEnded() { + if (completed) + return; + + if (v.currentTime > 0) { + ok(v.currentTime > target - 0.1 && v.currentTime < target + 0.1, + "Seek to wrong destination " + v.currentTime); + v.currentTime = 0.0; + v._seekTarget = 0.0; + } else { + ok(seekedNonZero, "Successfully seeked to nonzero"); + ok(true, "Seek back to zero was successful"); + completed = true; + finish(); + } +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", startSeeking); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-12.html b/dom/media/test/test_seek-12.html new file mode 100644 index 0000000000..28decabadc --- /dev/null +++ b/dom/media/test/test_seek-12.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 12; + +function test_seek12(v, seekTime, is, ok, finish) { +var completed = false; + +function startTest() { + if (completed) + return; + ok(!v.seeking, "seeking should default to false"); + v.currentTime = seekTime; + is(v.currentTime, seekTime, "currentTime must report seek target immediately"); + is(v.seeking, true, "seeking flag on start should be true"); +} + +function seekStarted() { + if (completed) + return; + //is(v.currentTime, seekTime, "seeking: currentTime must be seekTime"); + ok(Math.abs(v.currentTime - seekTime) < 0.01, "seeking: currentTime must be seekTime"); +} + +function seekEnded() { + if (completed) + return; + completed = true; + //is(v.currentTime, seekTime, "seeked: currentTime must be seekTime"); + ok(Math.abs(v.currentTime - seekTime) < 0.01, "seeked: currentTime must be seekTime"); + is(v.seeking, false, "seeking flag on end should be false"); + finish(); +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-13.html b/dom/media/test/test_seek-13.html new file mode 100644 index 0000000000..81eda9b658 --- /dev/null +++ b/dom/media/test/test_seek-13.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 13; + +function test_seek13(v, seekTime, is, ok, finish) { +var completed = false; + +function startTest() { + if (completed) + return; + ok(!v.seeking, "seeking should default to false"); + v.currentTime = v.duration; + is(v.currentTime, v.duration, "currentTime must report seek target immediately"); + is(v.seeking, true, "seeking flag on start should be true"); +} + +function seekStarted() { + if (completed) + return; + //is(v.currentTime, v.duration, "seeking: currentTime must be duration"); + ok(Math.abs(v.currentTime - v.duration) < 0.01, + "seeking: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")"); +} + +function seekEnded() { + if (completed) + return; + //is(v.currentTime, v.duration, "seeked: currentTime must be duration"); + ok(Math.abs(v.currentTime - v.duration) < 0.01, + "seeked: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")"); + is(v.seeking, false, "seeking flag on end should be false"); +} + +function playbackEnded() { + if (completed) + return; + completed = true; + //is(v.currentTime, v.duration, "ended: currentTime must be duration"); + ok(Math.abs(v.currentTime - v.duration) < 0.01, + "ended: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")"); + is(v.seeking, false, "seeking flag on end should be false"); + is(v.ended, true, "ended must be true"); + finish(); +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); +v.addEventListener("ended", playbackEnded); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-14.html b/dom/media/test/test_seek-14.html new file mode 100644 index 0000000000..7e19fe3bd3 --- /dev/null +++ b/dom/media/test/test_seek-14.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const SEEK_TEST_NUMBER = 14; + +function test_seek14(v, seekTime, is, ok, finish) { + var completed = false; + + function startTest() { + v.play(); + v.currentTime = v.duration; + } + + function playbackEnded() { + if (completed) { + ok(false, "'ended' should only fire once."); + return; + } + completed = true; + // Finish the test after 700ms. We should receive only one 'ended' event. + setTimeout(finish, 700); + } + + v.addEventListener("loadedmetadata", startTest); + v.addEventListener("ended", playbackEnded); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-2.html b/dom/media/test/test_seek-2.html new file mode 100644 index 0000000000..7b666df961 --- /dev/null +++ b/dom/media/test/test_seek-2.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +PARALLEL_TESTS = 1; +const SEEK_TEST_NUMBER = 2; + +function test_seek2(v, seekTime, is, ok, finish) { + +// Test seeking works if current time is set before video is +// playing. +var startPassed = false; +var endPassed = false; +var completed = false; + +function startTest() { + if (completed) + return; + + v.currentTime=seekTime; + v.play(); +} + +function seekStarted() { + if (completed) + return; + + ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime); + startPassed = true; +} + +function seekEnded() { + if (completed) + return; + + endPassed = true; +} + +function playbackEnded() { + if (completed) + return; + + completed = true; + ok(startPassed, "send seeking event"); + ok(endPassed, "send seeked event"); + ok(v.ended, "Checking playback has ended"); + ok(Math.abs(v.currentTime - v.duration) <= 0.1, "Checking currentTime at end: " + v.currentTime); + finish(); +} + +v.addEventListener("ended", playbackEnded); +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-3.html b/dom/media/test/test_seek-3.html new file mode 100644 index 0000000000..c030f03d20 --- /dev/null +++ b/dom/media/test/test_seek-3.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 3; + +function test_seek3(v, seekTime, is, ok, finish) { + +// Test seeking works if current time is set but video is not played. +var completed = false; +var gotTimeupdate = false; + +function startTest() { + if (completed) + return; + + v.currentTime=seekTime; +} + +function timeupdate() { + gotTimeupdate = true; + v.removeEventListener("timeupdate", timeupdate); +} + +function seekStarted() { + if (completed) + return; + + ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime); + v.addEventListener("timeupdate", timeupdate); +} + +function seekEnded() { + if (completed) + return; + + var t = v.currentTime; + ok(Math.abs(t - seekTime) <= 0.1, "Video currentTime should be around " + seekTime + ": " + t); + ok(gotTimeupdate, "Should have got timeupdate between seeking and seekended"); + completed = true; + finish(); +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-4.html b/dom/media/test/test_seek-4.html new file mode 100644 index 0000000000..4e5c1fee59 --- /dev/null +++ b/dom/media/test/test_seek-4.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 4; + +function test_seek4(v, seekTime, is, ok, finish) { + +// Test for a seek, followed by another seek before the first is complete. +var seekCount = 0; +var completed = false; + +function startTest() { + if (completed) + return; + + v.currentTime=seekTime; + v._seekTarget=seekTime; +} + +function seekStarted() { + if (completed) + return; + + seekCount += 1; + + ok(v.currentTime >= v._seekTarget - 0.1, + "Video currentTime should be around " + v._seekTarget + ": " + v.currentTime); + if (seekCount == 1) { + v.currentTime=seekTime/2; + v._seekTarget=seekTime/2; + } +} + +function seekEnded() { + if (completed) + return; + + if (seekCount == 2) { + ok(Math.abs(v.currentTime - seekTime/2) <= 0.1, "seek on target: " + v.currentTime); + completed = true; + finish(); + } +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-5.html b/dom/media/test/test_seek-5.html new file mode 100644 index 0000000000..a86477f77c --- /dev/null +++ b/dom/media/test/test_seek-5.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 5; + +function test_seek5(v, seekTime, is, ok, finish) { + +// Test for a seek, followed by a play before the seek completes, ensure we play at the end of the seek. +var startPassed = false; +var endPassed = false; +var completed = false; + +function startTest() { + if (completed) + return; + + v.currentTime=seekTime; +} + +function seekStarted() { + if (completed) + return; + ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime); + startPassed = true; + v.play(); +} + +function seekEnded() { + if (completed) + return; + endPassed = true; +} + +function playbackEnded() { + if (completed) + return; + ok(startPassed, "Got seeking event"); + ok(endPassed, "Got seeked event"); + completed = true; + finish(); +} + +v.addEventListener("ended", playbackEnded); +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeking", seekStarted); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-6.html b/dom/media/test/test_seek-6.html new file mode 100644 index 0000000000..32554c4c2f --- /dev/null +++ b/dom/media/test/test_seek-6.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 6; + +function test_seek6(v, seekTime, is, ok, finish) { + +// Test for bug identified by Chris Pearce in comment 40 on +// bug 449159. +var seekCount = 0; +var completed = false; +var interval; + +function poll() { + v.currentTime; +} + +function startTest() { + if (completed) + return; + interval = setInterval(poll, 10); + v.currentTime = Math.random() * v.duration; +} + +function seekEnded() { + if (completed) + return; + + seekCount++; + ok(true, "Seek " + seekCount); + if (seekCount == 3) { + clearInterval(interval); + completed = true; + finish(); + } else { + v.currentTime = Math.random() * v.duration; + } +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-7.html b/dom/media/test/test_seek-7.html new file mode 100644 index 0000000000..96139d6f83 --- /dev/null +++ b/dom/media/test/test_seek-7.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 7; + +function test_seek7(v, seekTime, is, ok, finish) { + +// If a NaN is passed to currentTime, make sure this is caught +// otherwise an infinite loop in the Ogg backend occurs. +var completed = false; +var thrown1 = false; +var thrown3 = false; + +function startTest() { + if (completed) + return; + + try { + v.currentTime = NaN; + } catch(e) { + thrown1 = true; + } + + try { + v.currentTime = Math.random; + } catch(e) { + thrown3 = true; + } + + completed = true; + ok(thrown1, "Setting currentTime to invalid value of NaN"); + ok(thrown3, "Setting currentTime to invalid value of a function"); + finish(); +} + +v.addEventListener("loadedmetadata", startTest); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-8.html b/dom/media/test/test_seek-8.html new file mode 100644 index 0000000000..2f6e390eef --- /dev/null +++ b/dom/media/test/test_seek-8.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 8; + +function test_seek8(v, seekTime, is, ok, finish) { + +function startTest() { + v.currentTime = 1000; +} + +function seekEnded() { + ok(Math.abs(v.currentTime - v.duration) < 0.2, + "currentTime " + v.currentTime + " close to " + v.duration); + finish(); +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek-9.html b/dom/media/test/test_seek-9.html new file mode 100644 index 0000000000..c03f6a75e3 --- /dev/null +++ b/dom/media/test/test_seek-9.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// The data being used in these tests is specified in manifest.js. +// The functions to build the test array and to run a specific test are in +// seek_support.js. + +const SEEK_TEST_NUMBER = 9; + +function test_seek9(v, seekTime, is, ok, finish) { + +function startTest() { + v.currentTime = -1000; +} + +function seekEnded() { + is(v.currentTime, 0, "currentTime clamped to 0"); + finish(); +} + +v.addEventListener("loadedmetadata", startTest); +v.addEventListener("seeked", seekEnded); + +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seekLies.html b/dom/media/test/test_seekLies.html new file mode 100644 index 0000000000..f3141eaabd --- /dev/null +++ b/dom/media/test/test_seekLies.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: server lies about range requests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onunload="mediaTestCleanup();"> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function on_metadataloaded() { + var v = document.getElementById('v'); + var d = Math.round(v.duration*1000); + ok(d == 4000, "Checking duration: " + d); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +<video id='v' + preload="metadata" + src='seekLies.sjs' + onloadedmetadata='on_metadataloaded();'></video> +</body> +</html> diff --git a/dom/media/test/test_seekToNextFrame.html b/dom/media/test/test_seekToNextFrame.html new file mode 100644 index 0000000000..755d06e622 --- /dev/null +++ b/dom/media/test/test_seekToNextFrame.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test seekToNextFrame of media files that should play OK</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var video = document.createElement('video'); + video.preload = "metadata"; + video.token = token; + video.seenSeeking = false; + video.seenEnded = false; + + var handler = { + "ontimeout": function() { + Log(token, "timed out: ended=" + video.seenEnded); + } + }; + manager.started(token, handler); + + video.src = test.name; + video.name = test.name; + + function callSeekToNextFrame() { + video.seekToNextFrame().then( + () => { + if (!video.seenSeeking) { + ok(false, video.token + ": Should have already received seeking event."); + } + video.seenSeeking = false; + if (!video.ended) { + callSeekToNextFrame(); + } + }, + () => { + ok(false, video.token + ": seekToNextFrame() failed."); + } + ); + } + + var onLoadedmetadata = function(t, v) { return function() { + callSeekToNextFrame(); + }}(test, video); + + var finish = function() { + video.finished = true; + video.removeEventListener("loadedmetadata", onLoadedmetadata); + video.removeEventListener("seeking", onSeeking); + removeNodeAndSource(video); + manager.finished(video.token); + } + + var onEnded = function(t, v) { return function() { + v.seenEnded = true; + finish(); + }}(test, video); + + var onSeeking = function(t, v) { return function() { + if (v.seenSeeking) { + ok(false, v.token + ": Should yet receive seeking event."); + } + v.seenSeeking = true; + }}(test, video); + + video.addEventListener("loadedmetadata", onLoadedmetadata); + video.addEventListener("seeking", onSeeking); + video.addEventListener("ended", onEnded); + + document.body.appendChild(video); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.seekToNextFrame.enabled", true ], + ["media.dormant-on-pause-timeout-ms", -1] + ] + }, + function() { + manager.runTests(gSeekToNextFrameTests, startTest); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek_duration.html b/dom/media/test/test_seek_duration.html new file mode 100644 index 0000000000..11ee4cb534 --- /dev/null +++ b/dom/media/test/test_seek_duration.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="seek_support.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + * This test is used to make sure video's duration won't be changed when it + * reachs to the end after seeking to position where the time is very close to + * video's end time. + */ + +SimpleTest.waitForExplicitFinish(); + +(async function startTest() +{ + const video = document.createElement('video'); + video.src = "bunny.webm"; + document.body.appendChild(video); + + const loadedMetadata = once(video, "loadedmetadata"); + const canplay = once(video, "canplay"); + const end = once(video, "ended"); + + info(`- wait for video loading metadata -`); + await loadedMetadata; + const originalDuration = video.duration; + + info(`- seek video to the position which is close to end time -`); + // video's duration is 2.1 and the last key frame is in 2.0, we want to seek + // to that keyframe. + video.currentTime = originalDuration - 0.1; + + info(`- play video until it ends -`); + await canplay; + await video.play(); + await end; + + ok(video.duration === originalDuration, `Duration shouldn't change`); + removeNodeAndSource(video); + + SimpleTest.finish(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek_negative.html b/dom/media/test/test_seek_negative.html new file mode 100644 index 0000000000..98f0b9c910 --- /dev/null +++ b/dom/media/test/test_seek_negative.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seeking to a negative time with readyState HAVE_NOTHING</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function startTest(test, token) { + var type = getMajorMimeType(test.type); + var v = document.createElement(type); + v.token = token; + manager.started(token); + + // Seek to negative start time. + v.currentTime = -123; + is(v.readyState, v.HAVE_NOTHING, "readyState is HAVE_NOTHING"); + ok(!v.seeking, "can't be seeking prior src defined"); + is(v.currentTime, -123, "currentTime is original seek time"); + + v.src = test.name; + + // Initialize running variables. + v._name = test.name; + v._seekStarted = false; + v._seekCompleted = false; + v._metadata = false; + + var events = [ "suspend", "play", "canplay", "canplaythrough", "loadstart", + "loadedmetadata", "loadeddata", "playing", "ended", "error", + "stalled", "emptied", "abort", "waiting", "pause" ]; + function logEvent(e) { + var video = e.target; + Log(e.target.token, "got " + e.type + " with currentTime = " + video.currentTime); + } + events.forEach(function(e) { + v.addEventListener(e, logEvent); + }); + + once(v, "seeking", function() { + v._seekStarted = true; + ok(v.currentTime >= 0, "currentTime should be positive"); + }); + once(v, "seeked", function() { + v._seekCompleted = true; + ok(v.currentTime >= 0, "currentTime should be positive"); + }); + once(v, "loadedmetadata", function() { + v._metadata = true; + ok(v.seeking, "element is seeking once readyState is HAVE_METADATA"); + ok(v.currentTime >= 0, "currentTime should be positive"); + }); + once(v, "ended", function() { + ok(v._seekStarted, "seek should have started"); + ok(v._seekCompleted, "seek should have completed"); + ok(v._metadata, "loadedmetadata fired"); + ok(v.currentTime >= 0, "currentTime should be positive"); + removeNodeAndSource(v); + manager.finished(v.token); + }); + + v.play(); +} + + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek_nosrc.html b/dom/media/test/test_seek_nosrc.html new file mode 100644 index 0000000000..56461007bd --- /dev/null +++ b/dom/media/test/test_seek_nosrc.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seek tests</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var SEEK_TIME = 3.5; +var seekStarted = false; +var seekCompleted = false; +var metadata = false; + +var v = document.createElement('video'); +document.body.appendChild(v); +SimpleTest.registerCleanupFunction(function () { + v.remove(); +}); + +try { + v.currentTime = SEEK_TIME; +} catch (e) { + ok(false, "should not fire '" + e + "' event"); +} +is(v.readyState, v.HAVE_NOTHING, "readyState is HAVE_NOTHING"); +ok(!v.seeking, "can't be seeking prior src defined"); +is(v.currentTime, SEEK_TIME, "currentTime is default playback start position"); +once(v, "seeking", function() { + seekStarted = true; +}); +once(v, "seeked", function() { + seekCompleted = true; +}); +once(v, "loadedmetadata", function() { + metadata = true; + ok(v.seeking, "element is seeking once readyState is HAVE_METADATA"); +}); +once(v, "ended", function() { + ok(seekStarted, "seek should have started"); + ok(seekCompleted, "seek should have completed"); + ok(metadata, "loadedmetadata fired"); + ok(v.currentTime >= SEEK_TIME, "currentTime should be after seek time"); + SimpleTest.finish(); +}); + +v.src = "seek.webm"; +v.play(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek_out_of_range.html b/dom/media/test/test_seek_out_of_range.html new file mode 100644 index 0000000000..a477ebdd19 --- /dev/null +++ b/dom/media/test/test_seek_out_of_range.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: seeking off the end of a file</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +// Test if the ended event works correctly. + +async function initTest(test, token) { + var type = getMajorMimeType(test.type); + var v = document.createElement(type); + v.preload = "auto"; + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name; + document.body.appendChild(v); + + await once(v, "loadedmetadata"); + info(`${v._name}: seeking to the end of the media.`); + v.currentTime = 3.0 * v.duration; + // Wait for 'seeked' and 'ended' to be fired. + await Promise.all([once(v, "seeked"), once(v, "ended")]); + // Check currentTime is near the end of the media. + ok(Math.abs(v.duration - v.currentTime) < 0.1, + "Should be at end of media for " + v._name + " t=" + v.currentTime + " d=" + v.duration); + // Call play() to start playback from the beginning. + v.play(); + await once(v, "ended"); + ok(v.ended, "Checking ended set after seeking to EOF and playing for " + v._name); + removeNodeAndSource(v); + manager.finished(v.token); +} + +manager.runTests(gSmallTests, initTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_seek_promise_bug1344357.html b/dom/media/test/test_seek_promise_bug1344357.html new file mode 100644 index 0000000000..6c441bc1cf --- /dev/null +++ b/dom/media/test/test_seek_promise_bug1344357.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: bug 1344357</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> + +<script> + +// This test always succeeds at runtime but should not cause any leaks at the end of mochitests. + +let manager = new MediaTestManager; + +function initTest(test, token) { + manager.started(token); + + var win = window.open(); + var video = win.document.createElement("video"); + video.autoplay = true; + video.src = "http://example.com/tests/dom/media/test/" + test.name; + win.document.body.appendChild(video); + video.currentTime = test.duration / 2; + video.addEventListener("seeking", () => { + win.close(); + manager.finished(token); + }, true); +} + +manager.runTests(gSmallTests, initTest); + +</script>
\ No newline at end of file diff --git a/dom/media/test/test_seekable1.html b/dom/media/test/test_seekable1.html new file mode 100644 index 0000000000..45c84b35df --- /dev/null +++ b/dom/media/test/test_seekable1.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test seekable member for media elements</title> +<script type="text/javascript" src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id='test'> +<script class="testbody" type='application/javascript'> + +let manager = new MediaTestManager; + +function finish_test(element) { + if (element.parentNode) + element.remove(); + element.src=""; + manager.finished(element.token); +} + +var tests = [ +// Test using a finite media stream, and a server supporting range requests +{ +setup(element) { + is(element.seekable.length, 0, "seekable.length should be initialy 0."); + element.addEventListener("loadedmetadata", function() { + is(element.seekable.length, 1, "seekable.length should be 1 for a server supporting range requests."); + + is(element.seekable.start(0), 0.0, "The start of the first range should be the initialTime."); + is(element.seekable.end(0), element.duration, "The end of the first range should be the duration.") + finish_test(element); + }); + } +} +]; + +function createTestArray() { + var A = []; + for (var k=0; k < gProgressTests.length; k++) { + var t = {}; + t.setup = tests[0].setup; + t.name = gProgressTests[k].name; + t.type = gProgressTests[k].type; + A.push(t); + } + return A; +} + +function startTest(test, token) { + var elemType = getMajorMimeType(test.type); + var element = document.createElement(elemType); + element.preload = "auto"; + element.src = test.name; + element.token = token; + test.setup(element); + manager.started(token); +} + +manager.runTests(createTestArray(), startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_source.html b/dom/media/test/test_source.html new file mode 100644 index 0000000000..34cc9e7f8a --- /dev/null +++ b/dom/media/test/test_source.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: append source child</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<video id="v1"></video> +<audio id="a1"></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var v1 = document.getElementById("v1"); +var a1 = document.getElementById("a1"); +v1.preload = "auto"; +a1.preload = "auto"; + +is(v1.src, "", "src should be null"); +is(a1.src, "", "src should be null"); +is(v1.currentSrc, "", "currentSrc should be null"); +is(a1.currentSrc, "", "currentSrc should be null"); +is(v1.childNodes.length, 0, "should have no children"); +is(a1.childNodes.length, 0, "should have no children"); + +function newSource(filter) { + var candidates = gSmallTests.filter(function(x){return filter.test(x.type);}); + if (candidates.length > 0) { + var e = document.createElement("source"); + e.type = candidates[0].type; + e.src = candidates[0].name; + return e; + } + return null + +} + +var audioLoaded = false; +var videoLoaded = false; + +function loaded(e) { + var media = e.target; + ok(media.networkState > 0, "networkState should be > 0"); + is(media.childNodes.length, 1, "should have 1 child"); + var sourceFile = media.currentSrc.substring(media.currentSrc.lastIndexOf('/')+1); + var resource = media.firstChild.src.substring(media.firstChild.src.lastIndexOf('/')+1); + is(sourceFile, resource, "loaded wrong resource!"); + if (media == a1) + audioLoaded = true; + else if (media == v1) + videoLoaded = true; + if (audioLoaded && videoLoaded) { + SimpleTest.finish(); + } +} + +v1.addEventListener('loadeddata', loaded); +a1.addEventListener('loadeddata', loaded); + +var videoSource = newSource(/^video/); +if (videoSource) { + v1.appendChild(videoSource); + v1.load(); +} else { + // No video backends? Don't test anything. + videoLoaded = true; +} + +var audioSource = newSource(/^audio/); +if (audioSource) { + a1.appendChild(audioSource); + a1.load(); +} else { + audioLoaded = true; +} + +if (!audioLoaded && !videoLoaded) { + SimpleTest.waitForExplicitFinish(); +} else { + if (audioLoaded) { + todo(false, "No audio types supported"); + } + if (videoLoaded) { + todo(false, "No video types supported"); + } +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_source_null.html b/dom/media/test/test_source_null.html new file mode 100644 index 0000000000..485edafd2d --- /dev/null +++ b/dom/media/test/test_source_null.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=752087 +--> +<head> + <title>Test for Bug 752087</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=752087">Mozilla Bug 752087</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<video id="v"> +<script> +var v = document.getElementById('v'); +v.src = null; // crashes on NULL access if not handled + +ok(true, "setting video.src to null didn't crash!"); +var srcPath = v.src.split('/'); +ok(srcPath.length >= 3, "src should be within dom/media/test"); +is(srcPath[srcPath.length - 1], "null", "Setting src to null is handled like 'null' string"); +</script> +</video> +</body> +</html> diff --git a/dom/media/test/test_source_write.html b/dom/media/test/test_source_write.html new file mode 100644 index 0000000000..29fffebd10 --- /dev/null +++ b/dom/media/test/test_source_write.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462455 +--> +<head> + <title>Test for Bug 462455</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462455">Mozilla Bug 462455</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<video id="v"> +<script> +var loadStarted = false; +document.write('Pause parsing!'); +var v = document.getElementById('v'); + +var resource = getPlayableVideo(gSmallTests); +if (resource != null) { + var s = document.createElement("source"); + s.src = resource.name; + s.type = resource.type; + v.appendChild(s); +} + +ok(v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE, + "We shouldn't start network load until the current task returns."); +</script> +</video> +</body> +</html> diff --git a/dom/media/test/test_standalone.html b/dom/media/test/test_standalone.html new file mode 100644 index 0000000000..094052847f --- /dev/null +++ b/dom/media/test/test_standalone.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: standalone video documents</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="doTest()"> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var iframes = []; + +for (let i=0; i<gSmallTests.length; ++i) { + var test = gSmallTests[i]; + + // We can't play WAV files in stand alone documents, so just don't + // run the test on non-video content types. + var tag = getMajorMimeType(test.type); + if (tag != "video" || !document.createElement("video").canPlayType(test.type)) + continue; + + let f = document.createElement("iframe"); + f.src = test.name; + f._test = test; + f.id = "frame" + i; + iframes.push(f); + document.body.appendChild(f); +} + + +function filename(uri) { + return uri.substr(uri.lastIndexOf("/")+1); +} + +function doTest() +{ + for (let i=0; i<iframes.length; ++i) { + let f = document.getElementById(iframes[i].id); + var v = f.contentDocument.body.firstChild; + is(v.tagName.toLowerCase(), "video", "Is video element"); + var src = filename(v.currentSrc); + is(src, iframes[i]._test.name, "Name ("+src+") should match ("+iframes[i]._test.name+")"); + is(v.controls, true, "Controls set (" + src + ")"); + is(v.autoplay, true, "Autoplay set (" + src + ")"); + } + SimpleTest.finish(); +} + +if (iframes.length == 0) { + todo(false, "No types supported"); +} else { + SimpleTest.waitForExplicitFinish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_capture_origin.html b/dom/media/test/test_streams_capture_origin.html new file mode 100644 index 0000000000..13d5589c0d --- /dev/null +++ b/dom/media/test/test_streams_capture_origin.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 1189506</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189506">Mozilla Bug 1189506</a> +<p id="display"></p> +<video id="vin"></video> +<video id="vout"></video> +<video id="vout_cors" crossorigin></video> +<canvas id="cin" width="40" height="30"></canvas> +<canvas id="cout" width="40" height="30"></canvas> +<canvas id="cout_cors" width="40" height="30"></canvas> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> +/* global vin, vout, vout_cors, cin, cout, cout_cors */ + +/** Test for Bug 1189506 **/ + +SimpleTest.waitForExplicitFinish(); + +async function start() { + const resource = getPlayableVideo(gSmallTests).name; + vin.src = "http://example.org:8000/tests/dom/media/test/" + resource; + vin.preload = "metadata"; + + await new Promise(r => vin.onloadedmetadata = r); + vout.srcObject = vin.mozCaptureStreamUntilEnded(); + vout_cors.srcObject = vin.mozCaptureStreamUntilEnded(); + vin.play(); + vout.play(); + vout_cors.play(); + + await new Promise(r => vout.onended = r); + is(vin.ended, vout.ended, "Source media element ends first"); + + const ctxin = SpecialPowers.wrap(cin.getContext("2d")); + ctxin.drawImage(vin, 0, 0); + + { + info("Testing that the last frame is rendered"); + const powerCtx = SpecialPowers.wrap(cout.getContext("2d")); + powerCtx.drawImage(vout, 0, 0); + const datain = ctxin.getImageData(0, 0, cin.width, cin.height); + const dataout = powerCtx.getImageData(0, 0, cout.width, cout.height); + for (let i = 0; i < datain.data.length; i += 4) { + const pixelin = datain.data.slice(i, i + 4).join(','); + const pixelout = dataout.data.slice(i, i + 4).join(','); + if (pixelin != pixelout) { + is(pixelout, pixelin, `Pixel #${i/4} is rendered as expected`); + break; + } + } + is(datain.data.length / 4, cin.width * cin.height, + "Checked expected number of pixels"); + } + + { + info("Testing that the principal is set"); + const ctx = cout.getContext("2d"); + ctx.drawImage(vout, 0, 0); + SimpleTest.doesThrow(() => ctx.getImageData(0, 0, cout.width, cout.height), + "SecurityError"); + } + + { + info("Testing that the crossorigin attribute is ignored for MediaStreams"); + const ctx = cout_cors.getContext("2d"); + ctx.drawImage(vout_cors, 0, 0); + SimpleTest.doesThrow( + () => ctx.getImageData(0, 0, cout_cors.width, cout_cors.height), + "SecurityError"); + } +} + +(async () => { + try { await start(); } + catch(e) { ok(false, `Rejected with ${e}`); } + finally { SimpleTest.finish(); } +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_element_capture.html b/dom/media/test/test_streams_element_capture.html new file mode 100644 index 0000000000..2aa6deb587 --- /dev/null +++ b/dom/media/test/test_streams_element_capture.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that a MediaStream captured from one element plays back in another</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +const manager = new MediaTestManager(); + +function checkDrawImage(vout, msg) { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + ctx.drawImage(vout, 0, 0); + const imgData = ctx.getImageData(0, 0, 1, 1); + is(imgData.data[3], 255, msg); +} + +function isGreaterThanOrEqualEps(a, b, msg) { + ok(a >= b, `Got ${a}, expected at least ${b}; ${msg}`); +} + +async function startTest(test, token) { + manager.started(token); + const v = document.createElement('video'); + const vout = document.createElement('video'); + + v.token = token; + v.id = "MediaDecoder"; + vout.id = "MediaStream"; + + document.body.appendChild(vout); + + // Log events for debugging. + const events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(e) { + Log(token, `${e.target.id} got ${e.type}`); + } + for (const e of events) { + v.addEventListener(e, logEvent); + vout.addEventListener(e, logEvent); + }; + + v.src = test.name; + v.preload = 'metadata'; + await new Promise(r => v.onloadedmetadata = r); + + const stream = v.mozCaptureStreamUntilEnded(); + vout.srcObject = stream; + is(vout.srcObject, stream, + `${token} set output element .srcObject correctly`); + // Wait for the resource fetch algorithm to have run, so that the media + // element is hooked up to the MediaStream and ready to go. If we don't do + // this, we're not guaranteed to render the very first video frame, which + // can make this test fail the drawImage test when a video resource only + // contains one frame. + await new Promise(r => vout.onloadstart = r); + v.play(); + vout.play(); + + await Promise.race([ + Promise.all([ + new Promise(r => vout.onended = r), + new Promise(r => v.onended = r), + ]), + new Promise((res, rej) => vout.onerror = () => rej(new Error(vout.error.message))), + new Promise((res, rej) => v.onerror = () => rej(new Error(v.error.message))), + ]); + + let duration = test.duration; + if (typeof(test.contentDuration) == "number") { + duration = test.contentDuration; + } + if (duration) { + isGreaterThanOrEqualEps(vout.currentTime, duration, + `${token} current time at end`); + } + is(vout.readyState, vout.HAVE_CURRENT_DATA, + `${token} checking readyState`); + ok(vout.ended, `${token} checking playback has ended`); + isnot(stream.getTracks().length, 0, `${token} results in some tracks`); + if (stream.getVideoTracks().length > 0) { + ok(test.type.match(/^video/), `${token} is a video resource`); + checkDrawImage(vout, `${token} checking video frame pixel has been drawn`); + } + vout.remove(); + removeNodeAndSource(v); +} + +(async () => { + SimpleTest.requestCompleteLog(); + SimpleTest.waitForExplicitFinish(); + await SpecialPowers.pushPrefEnv( + { "set": [ + ["privacy.reduceTimerPrecision", false], + // This test exhibits bug 1543980 with RDD enabled. + ["media.rdd-process.enabled", false], + ]}); + let tests = gPlayTests; + // Filter out bug1377278.webm due to bug 1541401. + tests = tests.filter(t => !t.name.includes("1377278")); + + manager.runTests(tests, async (test, token) => { + try { + await startTest(test, token); + } catch(e) { + ok(false, `Caught exception for ${token}: ${e}`); + } + manager.finished(token); + }); +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_element_capture_mediatrack.html b/dom/media/test/test_streams_element_capture_mediatrack.html new file mode 100644 index 0000000000..6b4332d8af --- /dev/null +++ b/dom/media/test/test_streams_element_capture_mediatrack.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that a media element captureStream works when disabling MediaTracks</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +const manager = new MediaTestManager(); + +async function startTest(test, token) { + manager.started(token); + const v = document.createElement('video'); + + document.body.appendChild(v); + v.token = token; + v.id = "MediaDecoder"; + + // Log events for debugging. + const events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + function logEvent(e) { + Log(token, `${token}: ${e.target.id} got ${e.type}`); + } + for (const e of events) { + v.addEventListener(e, logEvent); + }; + + v.src = test.name; + v.preload = 'metadata'; + await new Promise(r => v.onloadedmetadata = r); + + const stream = v.mozCaptureStream(); + + is(stream.getAudioTracks().length, Math.min(1, v.audioTracks.length), + `${token}: Expected number of audio tracks`); + is(stream.getVideoTracks().length, Math.min(1, v.videoTracks.length), + `${token}: Expected number of video tracks`); + + if (v.audioTracks.length) { + v.audioTracks[0].enabled = false; + const track = stream.getAudioTracks()[0]; + await new Promise(r => track.onended = r); + is(track.readyState, "ended", `${token}: Audio track has ended on removal`); + await new Promise(r => stream.onremovetrack = r); + is(stream.getAudioTracks().length, 0, + `${token}: Audio track was removed on removetrack`); + } + + if (v.videoTracks.length) { + v.videoTracks[0].selected = false; + const track = stream.getVideoTracks()[0]; + await new Promise(r => track.onended = r); + is(track.readyState, "ended", `${token}: Video track has ended on removal`); + await new Promise(r => stream.onremovetrack = r); + is(stream.getVideoTracks().length, 0, + `${token}: Video track was removed on removetrack`); + } + + stream.onaddtrack = () => ok(false, "Unexpected addtrack event"); + + v.play(); + + await Promise.race([ + new Promise(r => v.ontimeupdate = r), + new Promise((res, rej) => v.onerror = () => rej(new Error(v.error.message))), + ]); + + is(stream.getTracks().length, 0, `${token}: no tracks appeared during playback`); + removeNodeAndSource(v); +} + +(async () => { + SimpleTest.requestCompleteLog(); + SimpleTest.waitForExplicitFinish(); + await SpecialPowers.pushPrefEnv( + { "set": [ + ["media.track.enabled", true], + ]}); + let tests = gPlayTests; + // Filter out bug1377278.webm due to bug 1541401. + tests = tests.filter(t => !t.name.includes("1377278")); + + manager.runTests(tests, async (test, token) => { + try { + await startTest(test, token); + } catch(e) { + ok(false, `Caught exception for ${token}: ${e}`); + } + manager.finished(token); + }); +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_element_capture_playback.html b/dom/media/test/test_streams_element_capture_playback.html new file mode 100644 index 0000000000..ce083069ef --- /dev/null +++ b/dom/media/test/test_streams_element_capture_playback.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that capturing a stream doesn't stop the underlying element from firing events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<audio id="a"></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var a = document.getElementById('a'); +var validTimeUpdate = false; + +function startTest() { + a.src = "big.wav"; + var context = new AudioContext(); + var node = context.createMediaElementSource(a); + node.connect(context.destination); + a.addEventListener("timeupdate", function() { + if (a.currentTime > 0.0 && a.currentTime < 5.0 && !validTimeUpdate) { + validTimeUpdate = true; + ok(true, "Received reasonable currentTime in a timeupdate"); + SimpleTest.finish(); + } + }); + a.addEventListener("ended", function() { + if (!validTimeUpdate) { + ok(false, "Received reasonable currentTime in a timeupdate"); + SimpleTest.finish(); + } + }); + a.play(); +} + +if (a.canPlayType("audio/wave")) { + startTest(); +} else { + todo(false, "No playable audio"); +} +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_element_capture_reset.html b/dom/media/test/test_streams_element_capture_reset.html new file mode 100644 index 0000000000..625b0fe23f --- /dev/null +++ b/dom/media/test/test_streams_element_capture_reset.html @@ -0,0 +1,174 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that reloading, pausing and seeking in a media element that's being captured behaves as expected</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script src="manifest.js"></script> +</head> +<body> +<video id="v"></video> +<video id="vout"></video> +<video id="vout_untilended"></video> +<pre id="test"> +<script> +const v = document.getElementById('v'); +const vout = document.getElementById('vout'); +const vout_untilended = document.getElementById('vout_untilended'); + +function dumpEvent(event) { + const video = event.target; + info( + `${video.name}:${video.id} GOT EVENT ${event.type} ` + + `currentTime=${video.currentTime} paused=${video.paused} ` + + `ended=${video.ended} readyState=${video.readyState}` + ); +} + +function unexpected(event) { + ok(false, `${event.type} event received on ${event.target.id} unexpectedly`); +}; + +const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"]; +for (const e of events) { + v.addEventListener(e, dumpEvent); + vout.addEventListener(e, dumpEvent); + vout_untilended.addEventListener(e, dumpEvent); +} + +function isWithinEps(a, b, msg) { + ok(Math.abs(a - b) < 0.01, + "Got " + a + ", expected " + b + "; " + msg); +} + +function isGreaterThanOrEqualEps(a, b, msg) { + ok(a >= b - 0.01, + "Got " + a + ", expected at least " + b + "; " + msg); +} + +async function startTest(test) { + const seekTime = test.duration/2; + const contentDuration = test.contentDuration ?? test.duration; + + v.src = test.name; + v.name = test.name; + vout.name = test.name; + vout_untilended.name = test.name; + v.preload = "metadata"; + await new Promise(r => v.onloadedmetadata = r); + + vout.srcObject = v.mozCaptureStream(); + vout.play(); + + vout_untilended.srcObject = v.mozCaptureStreamUntilEnded(); + vout_untilended.play(); + + for (const track of [ + ...vout.srcObject.getTracks(), + ...vout_untilended.srcObject.getTracks(), + ]) { + ok(track.muted, `${track.kind} track ${track.id} should be muted`); + } + + v.play(); + + await Promise.all([ + ...vout.srcObject.getTracks(), + ...vout_untilended.srcObject.getTracks() + ].map(t => new Promise(r => t.onunmute = r))); + + await new Promise(r => v.onended = r); + isGreaterThanOrEqualEps(v.currentTime, test.duration, + "checking v.currentTime at first 'ended' event"); + + await Promise.all([ + new Promise(r => vout.onended = r), + new Promise(r => vout_untilended.onended = r), + ]); + + isGreaterThanOrEqualEps(vout.currentTime, contentDuration, + "checking vout.currentTime at first 'ended' event"); + ok(vout.ended, "checking vout has actually ended"); + ok(vout_untilended.ended, "checking vout_untilended has actually ended"); + + vout_untilended.srcObject.onaddtrack = unexpected; + vout_untilended.onplaying = unexpected; + vout_untilended.onended = unexpected; + + const voutPreSeekCurrentTime = vout.currentTime; + v.currentTime = seekTime; + await new Promise(r => v.onseeked = r); + + is(v.currentTime, seekTime, "Finished seeking"); + is(vout.currentTime, voutPreSeekCurrentTime, + "checking vout.currentTime has not changed after seeking"); + + v.play(); + vout.play(); + + await new Promise(r => v.onended = r); + isGreaterThanOrEqualEps(v.currentTime, test.duration, + "checking v.currentTime at second 'ended' event"); + + await new Promise(r => vout.onended = r); + isGreaterThanOrEqualEps(vout.currentTime, + (test.duration - seekTime) + contentDuration, + "checking vout.currentTime after seeking and playing through again"); + + v.src = test.name + "?1"; + vout.play(); + await v.play(); + + isnot(vout.srcObject.getTracks().length, 0, "There are some output tracks"); + + vout.onended = unexpected; + vout.srcObject.onremovetrack = unexpected; + + v.pause(); + await Promise.all( + vout.srcObject.getTracks().map(t => new Promise(r => t.onmute = r)) + ); + + for (const track of vout.srcObject.getTracks()) { + track.onunmute = unexpected; + } + + v.currentTime = 0; + await new Promise(r => v.onseeked = r); + + v.play(); + await Promise.all( + vout.srcObject.getTracks().map(t => new Promise(r => t.onunmute = r)) + ); + + vout.srcObject.onremovetrack = null; + + await new Promise(r => v.onended = r); + isGreaterThanOrEqualEps(v.currentTime, test.duration, + "checking v.currentTime at third 'ended' event"); + + await new Promise(r => vout.onended = r); + isGreaterThanOrEqualEps(vout.currentTime, + (test.duration - seekTime) + contentDuration*2, + "checking vout.currentTime after seeking, playing through and reloading"); +} + +(async () => { + SimpleTest.waitForExplicitFinish(); + try { + const testVideo = getPlayableVideo(gSmallTests); + if (testVideo) { + await startTest(testVideo); + } else { + todo(false, "No playable video"); + } + } catch(e) { + ok(false, `Error: ${e}`); + } finally { + SimpleTest.finish(); + } +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_element_capture_twice.html b/dom/media/test/test_streams_element_capture_twice.html new file mode 100644 index 0000000000..0e30be1801 --- /dev/null +++ b/dom/media/test/test_streams_element_capture_twice.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that capturing a media element, then reloading and capturing again, works</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<video id="v"></video> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +const v = document.getElementById('v'); + +function dumpEvent(event) { + const video = event.target; + info(video.name + " GOT EVENT " + event.type + + " currentTime=" + video.currentTime + + " paused=" + video.paused + + " ended=" + video.ended + + " readyState=" + video.readyState); +} + +const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"]; +for (let i = 0; i < events.length; ++i) { + v.addEventListener(events[i], dumpEvent); +} + +async function startTest(src) { + v.preload = "metadata"; + v.src = src; + await new Promise(r => v.onloadedmetadata = r); + const s1 = v.mozCaptureStream(); + const tracks = s1.getTracks(); + is(tracks.length, 2, "Expected total tracks, s1, capture 1"); + is(s1.getAudioTracks().length, 1, "Expected audio tracks, s1, capture 1"); + is(s1.getVideoTracks().length, 1, "Expected video tracks, s1, capture 1"); + is(s1.getAudioTracks()[0].readyState, "live", "Live audio, s1, capture 1"); + is(s1.getVideoTracks()[0].readyState, "live", "Live video, s1, capture 1"); + + v.src = null; + for (let i = 0; i < tracks.length; ++i) { + await Promise.race(tracks.map(t => new Promise(r => t.onended = r))); + await new Promise(r => s1.onremovetrack = r); + } + is(s1.getTracks().length, 0, "Expected total tracks, s1, metadata 2"); + + v.src = src; + await new Promise(r => v.onloadedmetadata = r); + is(s1.getTracks().length, 2, "Expected total tracks, s1, metadata 2"); + is(s1.getAudioTracks().length, 1, "Expected audio tracks, s1, metadata 2"); + is(s1.getVideoTracks().length, 1, "Expected video tracks, s1, metadata 2"); + is(s1.getAudioTracks()[0].readyState, "live", "Live audio, s1, metadata 2"); + is(s1.getVideoTracks()[0].readyState, "live", "Live video, s1, metadata 2"); + + const s2 = v.mozCaptureStream(); + is(s1.getTracks().length, 2, "Expected total tracks remains, s1, capture 2"); + is(s2.getTracks().length, 2, "Expected total tracks, s2, capture 2"); + is(s2.getAudioTracks().length, 1, "Expected audio tracks, s2, capture 2"); + is(s2.getVideoTracks().length, 1, "Expected video tracks, s2, capture 2"); + is(s2.getAudioTracks()[0].readyState, "live", "Live audio, s2, capture 2"); + is(s2.getVideoTracks()[0].readyState, "live", "Live video, s2, capture 2"); +} + +(async function() { + try { + await startTest("short-video.ogv"); + } catch(e) { + ok(false, `Caught error: ${e}${e.stack ? '\n' + e.stack : ''}`); + } finally { + SimpleTest.finish(); + } +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_firstframe.html b/dom/media/test/test_streams_firstframe.html new file mode 100644 index 0000000000..ea1dbd35c1 --- /dev/null +++ b/dom/media/test/test_streams_firstframe.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that a non-autoplaying, non-playing element with a MediaStream source triggers canplay and shows a first frame</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="manifest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +async function runTest() { + const canvas = document.createElement("canvas"); + canvas.getContext("2d"); + const helper = new CaptureStreamTestHelper2D(100, 100); + + const video = document.createElement("video"); + document.body.appendChild(video); + + video.srcObject = canvas.captureStream(); + helper.drawColor(canvas, helper.red); + + await Promise.race([ + new Promise(r => video.oncanplay = r), + new Promise(r => setTimeout(r, 30000)) + .then(() => Promise.reject(new Error("Canplay timeout"))), + ]); + + ok(true, "Got \"canplay\""); + is(video.readyState, video.HAVE_ENOUGH_DATA, "Expected readyState"); + ok(helper.isPixel(helper.getPixel(video), helper.red), + "First frame is rendered before playing"); + + helper.drawColor(canvas, helper.green); + await helper.pixelMustNotBecome(video, helper.green, { + time: 1000, + infoString: "Rendered first frame doesn't change on new frame from source" + }); + ok(helper.isPixel(helper.getPixel(video), helper.red), + "First frame is still rendered"); + + video.play(); + helper.drawColor(canvas, helper.blue); + await helper.pixelMustBecome(video, helper.blue, { + infoString: "New frame gets rendered when playing" + }); + + video.srcObject.getTracks().forEach(t => t.stop()); +} + +(async function() { + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("Explicit timeout reasons"); + try { + await runTest(); + } catch(e) { + ok(false, e); + } + SimpleTest.finish(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_gc.html b/dom/media/test/test_streams_gc.html new file mode 100644 index 0000000000..d2ba23042b --- /dev/null +++ b/dom/media/test/test_streams_gc.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test garbage collection of captured stream (bug 806754)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body onload="doTest()">
+<audio id="a" preload="metadata"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var a = document.getElementById('a');
+a.src = getPlayableAudio(gSmallTests).name;
+
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+}
+
+function doTest() {
+ a.mozCaptureStreamUntilEnded();
+
+ a.addEventListener("seeked", function() {
+ a.play();
+
+ a.addEventListener("play", function() {
+ a.addEventListener("ended", function() {
+ ok(true, "GC completed OK");
+ SimpleTest.finish();
+ });
+ });
+ });
+
+ a.currentTime = a.duration;
+ setTimeout(forceGC, 0);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_individual_pause.html b/dom/media/test/test_streams_individual_pause.html new file mode 100644 index 0000000000..c49b563d76 --- /dev/null +++ b/dom/media/test/test_streams_individual_pause.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 1073406. Pausing a video element should not pause another playing the same stream.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> + <script type="text/javascript" src="gUM_support.js"></script> +</head> +<body> +<video id="video1" autoplay></video> +<video id="video2" autoplay></video> +<script class="testbody" type="text/javascript"> +function getVideoImagePixelData(v) { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + ctx.drawImage(v, 0, 0); + const imgData = ctx.getImageData(canvas.width/2, canvas.height/2, 1, 1).data; + return "r" + imgData[0] + + "g" + imgData[1] + + "b" + imgData[2] + + "a" + imgData[3]; +} + +async function startTest() { + // This test expects fake devices so that the video color will change + // over time, explicitly request fakes. + await pushGetUserMediaTestPrefs({fakeAudio: true, fakeVideo: true}); + const stream = await navigator.mediaDevices.getUserMedia({video: true}); + const video1 = document.getElementById('video1'); + const video2 = document.getElementById('video2'); + + video1.srcObject = stream; + video2.srcObject = stream; + + await new Promise(r => video1.onplaying = r); + video1.pause(); + await new Promise(r => video1.onpause = r); + + const v1PausedImageData = getVideoImagePixelData(video1); + const v2PausedImageData = getVideoImagePixelData(video2); + + while (getVideoImagePixelData(video2) == v2PausedImageData) { + info("video2 has not progressed. Waiting."); + await new Promise(r => video2.ontimeupdate = r); + } + + // Wait for a while to be sure video1 would have gotten a frame + // if it is playing. + for (let i = 3; i != 0; i--) { + await new Promise(r => video2.ontimeupdate = r); + } + info("video2 progressed OK"); + + isnot(video1.currentTime, video2.currentTime, + "v1 and v2 should not be at the same currentTime"); + is(getVideoImagePixelData(video1), v1PausedImageData, + "video1 video frame should not have updated since video1 paused"); + + for (const t of stream.getTracks()) { + t.stop(); + } +} + +SimpleTest.waitForExplicitFinish(); +(async function() { + try { + await startTest(); + } catch(error) { + ok(false, "getUserMedia should not fail, got " + error.name); + } finally { + SimpleTest.finish(); + } +})(); +</script> +</body> +</html> diff --git a/dom/media/test/test_streams_srcObject.html b/dom/media/test/test_streams_srcObject.html new file mode 100644 index 0000000000..8ea5797d14 --- /dev/null +++ b/dom/media/test/test_streams_srcObject.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test interactions of src and srcObject</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body onload="doTests()"> +<audio id="a1"></audio> +<audio id="a2"></audio> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var doTest = srcObject => new Promise(resolve => { + var a = document.getElementById('a1'); + a.src = getPlayableAudio(gSmallTests).name; + var b = new Audio(); + + var newSrc = a.src + "?2"; + b.src = newSrc; + is(b[srcObject], null, "Initial srcObject is null"); + var stream = a.mozCaptureStream(); + b[srcObject] = stream; + is(b[srcObject], stream, "Stream set correctly"); + try { + b[srcObject] = "invalid"; + ok(false, "Setting srcObject to an invalid value should throw."); + } catch (e) { + ok(e instanceof TypeError, "Exception should be a TypeError"); + } + is(b[srcObject], stream, "Stream not set to invalid value"); + is(b.src, newSrc, "src attribute not affected by setting srcObject"); + var step = 0; + b.addEventListener("loadedmetadata", function() { + if (step == 0) { + is(b.currentSrc, "", "currentSrc set to empty string while playing srcObject"); + b[srcObject] = null; + is(b[srcObject], null, "Stream set to null"); + // The resource selection algorithm will run again and choose b.src + } else if (step == 1) { + is(b.currentSrc, b.src, "currentSrc set to src now that srcObject is null"); + resolve(); + } + ++step; + }); + a.play(); + b.play(); +}); + +var doTests = () => doTest("srcObject") + .catch(e => ok(false, "Unexpected error: " + e)) + .then(() => SimpleTest.finish()) + .catch(e => ok(false, "Coding error: " + e)); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_streams_tracks.html b/dom/media/test/test_streams_tracks.html new file mode 100644 index 0000000000..845bc36ca3 --- /dev/null +++ b/dom/media/test/test_streams_tracks.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaStreamTrack interfaces</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +const manager = new MediaTestManager; + +function testTracks(tracks, hasTrack, kind, src) { + is(tracks.length, hasTrack ? 1 : 0, `Correct ${kind} track count for ${src}`); + for (const track of tracks) { + is(track.readyState, "live", `Track ${track.id} should still be live`); + is(track.kind, kind, `Correct track kind for track ${track.id} of ${src}`); + ok(/\{........-....-....-....-............\}/.test(track.id), + `id ${track.id} for ${track.kind} track of ${src} has correct form`); + } +} + +async function startTest(test, token) { + try { + info(`Starting test of ${test.name}`); + const element = document.createElement("video"); + + element.token = token; + manager.started(token); + + element.src = test.name; + element.test = test; + const stream = element.mozCaptureStreamUntilEnded(); + + element.play(); + + await new Promise(r => element.onloadedmetadata = r); + + testTracks(stream.getAudioTracks(), test.hasAudio, "audio", test.name); + testTracks(stream.getVideoTracks(), test.hasVideo, "video", test.name); + const tracks = stream.getTracks(); + + await new Promise(r => element.onended = r); + + for (let i = 0; i < tracks.length; ++i) { + await Promise.race( + tracks.map(t => new Promise(r => t.onended = r)) + ); + await new Promise(r => stream.onremovetrack = r); + } + + testTracks(stream.getAudioTracks(), false, "audio", test.name); + testTracks(stream.getVideoTracks(), false, "video", test.name); + } catch(e) { + ok(false, `Caught error: ${e}`); + } finally { + manager.finished(token); + } +} + +manager.runTests(gTrackTests, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_suspend_media_by_inactive_docshell.html b/dom/media/test/test_suspend_media_by_inactive_docshell.html new file mode 100644 index 0000000000..7f819ca33b --- /dev/null +++ b/dom/media/test/test_suspend_media_by_inactive_docshell.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test suspending media by inactive docShell</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<video id="testVideo" src="gizmo.mp4" loop></video> +<script class="testbody" type="text/javascript"> +/** + * When calling `browser.suspendMediaWhenInactive`, it can set the docShell's + * corresponding flag that is used to suspend media when the docShell is + * inactive. This test is used to check if we can suspend/resume the media + * correctly when changing docShell's active state. + */ +async function startTest() { + const video = document.getElementById("testVideo"); + + info(`start video`); + await video.play(); + + info(`set docShell inactive which would suspend media`); + await setDocShellActive(false); + + info(`set docShell active which would resume media`); + await setDocShellActive(true); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + {"set": [["media.testing-only-events", true]]}, startTest); + +/** + * The following are test helper functions. + */ +function mediaSuspendedStateShouldEqualTo(expected) { + const video = document.getElementById("testVideo"); + const result = SpecialPowers.wrap(video).isSuspendedByInactiveDocOrDocShell; + is(result, expected, `media's suspended state is correct`); +} + +function setDocShellActive(isActive) { + const win = SpecialPowers.wrap(window); + const docShell = win.docShell; + const browsingContext = win.browsingContext; + // This flag is used to prevent media from playing when docShell is inactive. + browsingContext.top.suspendMediaWhenInactive = true; + browsingContext.isActive = isActive; + // After updating `docshell.isActive`, it would suspend/resume media and we + // wait suspending/resuming finishing by listening to `MozMediaSuspendChanged` + return new Promise(r => { + docShell.chromeEventHandler.addEventListener("MozMediaSuspendChanged", + () => { + mediaSuspendedStateShouldEqualTo(!isActive); + r(); + }, {once : true} + ); + }); +} + +</script> +</body> +</html> diff --git a/dom/media/test/test_temporary_file_blob_video_plays.html b/dom/media/test/test_temporary_file_blob_video_plays.html new file mode 100644 index 0000000000..87f6b3c4e6 --- /dev/null +++ b/dom/media/test/test_temporary_file_blob_video_plays.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaRecorder Recording canvas stream</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> + +function startTest() { + var canvas = document.createElement("canvas"); + canvas.width = canvas.height = 100; + document.getElementById("content").appendChild(canvas); + + var helper = new CaptureStreamTestHelper2D(100, 100); + helper.drawColor(canvas, helper.red); + + var stream = canvas.captureStream(0); + + var blob; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the start of recording"); + + mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired"); + + mediaRecorder.onerror = () => ok(false, "Recording failed"); + + mediaRecorder.ondataavailable = ev => { + is(blob, undefined, "Should only get one dataavailable event"); + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info("Got 'start' event"); + // We just want one frame encoded, to see that the recorder produces something readable. + mediaRecorder.stop(); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + ok(blob, "Should have gotten a data blob"); + + var video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(blob); + video.play(); + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + document.getElementById("content").appendChild(video); + helper.pixelMustBecome(video, helper.red, { + threshold: 128, + infoString: "Should become red", + }).then(SimpleTest.finish); + }; + + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({set:[["media.recorder.max_memory", 1]]}, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_timeupdate_small_files.html b/dom/media/test/test_timeupdate_small_files.html new file mode 100644 index 0000000000..fca35e5b71 --- /dev/null +++ b/dom/media/test/test_timeupdate_small_files.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=495319 +--> + +<head> + <title>Bug 495319 - playing back small audio files should fire timeupdate</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495319">Mozilla Bug 495319</a> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function ended(e) { + var v = e.target; + ++v.counter.ended; + is(v.counter.ended, 1, v._name + " should see ended only once"); + ok(v.counter.timeupdate > 0, v._name + " should see at least one timeupdate: " + v.currentTime); + + // Rest event counters for we don't allow events after ended. + eventsToLog.forEach(function(event) { + v.counter[event] = 0; + }); + + // Finish the test after 500ms. We shouldn't receive any timeupdate events + // after the ended event, so this gives time for any pending timeupdate events + // to fire so we can ensure we don't regress behaviour. + setTimeout( + function() { + // Remove the event listeners before removing the video from the document. + // We should receive a timeupdate and pause event when we remove the element + // from the document (as the element is specified to behave as if pause() was + // invoked when it's removed from a document), and we don't want those + // confusing the test results. + v.removeEventListener("ended", ended); + eventsToLog.forEach(function(event) { + v.removeEventListener(event, logEvent); + }); + removeNodeAndSource(v); + manager.finished(v.token); + }, + 500); +} + +var eventsToLog = ["play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "timeupdate", "error", "stalled", "emptied", "abort", + "waiting", "pause"]; + +function logEvent(event) { + var v = event.target; + ++v.counter[event.type]; + if (v.counter.ended > 0) { + is(v.counter[event.type], 0, v._name + " got unexpected " + event.type + " after ended"); + } +} + +function startTest(test, token) { + var type = getMajorMimeType(test.type); + var v = document.createElement(type); + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name; + + // Keep how many events received for each event type. + v.counter = {}; + eventsToLog.forEach(function(e) { + v.addEventListener(e, logEvent); + v.counter[e] = 0; + }); + v.addEventListener("ended", ended); + v.counter.ended = 0; + document.body.appendChild(v); + v.play(); +} + +manager.runTests(gSmallTests, startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_unseekable.html b/dom/media/test/test_unseekable.html new file mode 100644 index 0000000000..e6d69f09f4 --- /dev/null +++ b/dom/media/test/test_unseekable.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: unseekable</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* + +Test that unseekable media can't be seeked. We load a media that shouldn't +be seekable, and play through once. While playing through we repeatedly try +to seek and check that nothing happens when we do. We also verify that the +seekable ranges are empty. + +*/ + +var manager = new MediaTestManager; + +var onseeking = function(event) { + var v = event.target; + v.actuallySeeked = true; +}; + +var onseeked = function(event) { + var v = event.target; + v.actuallySeeked = true; +}; + +var ontimeupdate = function(event) { + var v = event.target; + + // Check that when we seek nothing happens. + var t = v.currentTime; + v.currentTime = v.currentTime /= 2; + ok(Math.abs(t - v.currentTime) < 0.01, "Current time shouldn't change when seeking in unseekable media: " + v.name); + + // Check that the seekable ranges are empty. + is(v.seekable.length, 0, "Should have no seekable ranges in unseekable media: " + v.name); +}; + +var onended = function(event) { + var v = event.target; + + // Remove the event listeners so that they can't run if there are any pending + // events. + v.removeEventListener("seeking", onseeking); + v.removeEventListener("seeked", onseeked); + v.removeEventListener("timeupdate", ontimeupdate); + v.removeEventListener("ended", onended); + + v.src = ""; + if (v.parentNode) { + v.remove(); + } + + // Verify that none of the seeks we did in timeupdate actually seeked. + ok(!v.actuallySeeked, "Should not be able to seek in unseekable media: " + v.name); + + manager.finished(v.token); +} + +function startTest(test, token) { + var v = document.createElement('video'); + manager.started(token); + v.name = test.name; + v.src = test.name; + v.token = token; + v.autoplay = "true"; + + v.actuallySeeked = false; + + v.addEventListener("seeking", onseeking); + v.addEventListener("seeked", onseeked); + v.addEventListener("timeupdate", ontimeupdate); + v.addEventListener("ended", onended); + + document.body.appendChild(v); +} + +function canPlay(candidates) { + var v = document.createElement("video"); + var resources = candidates.filter(function(x){return v.canPlayType(x.type);}); + return (resources.length > 0); +} + +if (canPlay(gUnseekableTests)) { + manager.runTests(gUnseekableTests, startTest); +} else { + todo(false, "No files of supported format to test"); +} + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_videoDocumentTitle.html b/dom/media/test/test_videoDocumentTitle.html new file mode 100644 index 0000000000..dd52dba26c --- /dev/null +++ b/dom/media/test/test_videoDocumentTitle.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=463830 +--> +<head> + <title>Test for Bug 463830</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463830">Mozilla Bug 463830</a> +<p id="display"></p> +<iframe id="i"></iframe> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 463830 **/ + +var gTests = [ + { file: "320x240.ogv", title: "320x240.ogv" }, + { file: "bug461281.ogg", title: "bug461281.ogg" }, +]; + +var gTestNum = 0; + +addLoadEvent(runTest); + +var title; +var i = document.getElementById("i"); + +function runTest() { + if (gTestNum == gTests.length) { + SimpleTest.finish(); + return; + } + if (gTestNum == 0) { + i.addEventListener("load", function() { + is(i.contentDocument.title, title, "Doc title incorrect"); + setTimeout(runTest, 0); + }); + } + + title = gTests[gTestNum].title; + i.src = gTests[gTestNum].file; + gTestNum++; +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_videoPlaybackQuality_totalFrames.html b/dom/media/test/test_videoPlaybackQuality_totalFrames.html new file mode 100644 index 0000000000..1b69a3b64f --- /dev/null +++ b/dom/media/test/test_videoPlaybackQuality_totalFrames.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Count the tatol frames of a video</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +var startTest = function(test, token) { + manager.started(token); + var v = document.createElement('video'); + v.token = token; + document.body.appendChild(v); + v.src = test.name; + + function ended(event) { + var video = event.target; + is(video.getVideoPlaybackQuality().totalVideoFrames, test.totalFrameCount,test.name+ " totalFrames should match!"); + removeNodeAndSource(video); + manager.finished(video.token); + } + v.addEventListener("ended", ended); + v.play(); +}; + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + "set": [ + ["media.decoder.skip-to-next-key-frame.enabled", false], + ["media.av1.use-dav1d", true] + ] + }, + function() { + manager.runTests(getPlayableVideos(gFrameCountTests), startTest); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_video_dimensions.html b/dom/media/test/test_video_dimensions.html new file mode 100644 index 0000000000..4d9c2a185f --- /dev/null +++ b/dom/media/test/test_video_dimensions.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that a video element has set video dimensions on loadedmetadata</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var manager = new MediaTestManager; + +var startTest = function(test, token) { + manager.started(token); + var v1 = document.createElement('video'); + var v2 = document.createElement('video'); + var vout = document.createElement('video'); + + // Avoid a race for hardware resources between v1 and v2 on platforms with + // a hardware decoder, like B2G. + v1.preload = 'none'; + v2.preload = 'none'; + + var numVideoElementsFinished = 0; + + var ondurationchange = function(ev) { + var v = ev.target; + info(v.testName + " got durationchange"); + v.durationchange = true; + }; + var onresize = function(ev) { + var v = ev.target; + info(v.testName + " got resize"); + ok(!v.resize, v.testName + " should only fire resize once for same size"); + v.resize = true; + ok(v.durationchange, v.testName + + " durationchange event should have been emitted before resize"); + is(v.videoWidth, test.width, v.testName + " width should be set on resize"); + is(v.videoHeight, test.height, v.testName + " height should be set on resize"); + }; + var onloadedmetadata = function(ev) { + var v = ev.target; + info(v.testName + " got loadedmetadata"); + ok(!v.loadedmetadata, v.testName + " should only fire loadedmetadata once"); + v.loadedmetadata = true; + ok(v.resize, v.testName + + " resize event should have been emitted before loadedmetadata"); + + numVideoElementsFinished += 1; + if (v === v1) { + removeNodeAndSource(v1); + v2.load(); + } + + if (v === v2) { + vout.srcObject = v2.mozCaptureStreamUntilEnded(); + v2.play(); + vout.play(); + } + + if (numVideoElementsFinished === 3) { + removeNodeAndSource(v2); + removeNodeAndSource(vout); + manager.finished(token); + } + }; + var setupElement = function(v, id) { + v.durationchange = false; + v.ondurationchange = ondurationchange; + v.resize = false; + v.onresize = onresize; + v.loadedmetadata = false; + v.onloadedmetadata = onloadedmetadata; + document.body.appendChild(v); + }; + + v1.testName = test.name; + v2.testName = test.name + " (Captured)"; + vout.testName = test.name + " (Stream)"; + + v1.src = test.name; + v2.src = test.name; + + setupElement(v1, "v1"); + setupElement(v2, "v2"); + setupElement(vout, "vout"); + + v1.play(); +}; + +manager.runTests(getPlayableVideos(gSmallTests), startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_video_gzip_encoding.html b/dom/media/test/test_video_gzip_encoding.html new file mode 100644 index 0000000000..355a245713 --- /dev/null +++ b/dom/media/test/test_video_gzip_encoding.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1370177 gzipped mp4 with Content-Length</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ </head>
+ <body>
+ <!--
+ Tests that an MP4 file served over a "Content-Encoding: gzip"
+ HTTP channel with a "Content-Length" header set to the length
+ of the compressed file works.
+ -->
+ <video id='v' src="http://mochi.test:8888/tests/dom/media/test/gzipped_mp4.sjs" controls autoplay></video>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var v = document.getElementById('v');
+ v.addEventListener("ended", ()=>{
+ SimpleTest.finish();
+ mediaTestCleanup();
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/media/test/test_video_in_audio_element.html b/dom/media/test/test_video_in_audio_element.html new file mode 100644 index 0000000000..a53adf9414 --- /dev/null +++ b/dom/media/test/test_video_in_audio_element.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1060896 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1060896</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="manifest.js"></script> + <script type="application/javascript"> + + /** + * Test for Bug 1060896; tests that loading a video inside an audio element works. + **/ + + var manager = new MediaTestManager; + + function error(event) { + var a = event.target; + ok(!a.mozHasAudio, "Media must've had no active tracks to play"); + a.removeEventListener("error", error); + a.removeEventListener("ended", ended); + removeNodeAndSource(a); + manager.finished(a.token); + } + + function ended(event) { + var a = event.target; + a.removeEventListener("error", error); + a.removeEventListener("ended", ended); + removeNodeAndSource(a); + manager.finished(a.token); + } + + function initTest(test, token) { + var a = document.createElement('audio'); + a.token = token; + manager.started(token); + a.autoplay = true; + + a.addEventListener("error", error); + a.addEventListener("ended", ended); + + a.src = test.name; + } + + var videos = getPlayableVideos(gSmallTests); + // Bug 1216012, skip the test on emulator-kk. + if (getAndroidVersion() == 19) { + todo(false, "Test disabled on emulator-kk."); + } else { + manager.runTests(videos, initTest); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1060896">Mozilla Bug 1060896</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/media/test/test_video_stats_resistfingerprinting.html b/dom/media/test/test_video_stats_resistfingerprinting.html new file mode 100644 index 0000000000..331e6a8101 --- /dev/null +++ b/dom/media/test/test_video_stats_resistfingerprinting.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +Mozilla Bug: +https://bugzilla.mozilla.org/show_bug.cgi?id=1369309 +Tor Ticket: +https://trac.torproject.org/projects/tor/ticket/15757 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1369309</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="manifest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=682299">Mozilla Bug 1369309</a> +<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/15757">Tor Ticket 15757</a> + +<!-- The main testing script --> +<script class="testbody" type="text/javascript"> + var manager = new MediaTestManager; + const SPOOFED_FRAMES_PER_SECOND = 30; + const SPOOFED_DROPPED_RATIO = 0.05; + // Push the setting of 'privacy.resistFingerprinting' into gTestPrefs, which + // will be set during MediaTestManager.runTests(). + gTestPrefs.push( + ["privacy.resistFingerprinting", true], + ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 100000], + ["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false], + // We use 240p as the target resoultion since 480p is greater than every video + // source in our test suite, so we need to use 240p here for allowing us to + // test dropped rate here. + ["privacy.resistFingerprinting.target_video_res", 240] + ); + var testCases = [ + { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266, drop: false }, + { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966, drop: false }, + { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56, drop: true } + ]; + + function checkStats(v, shouldDrop) { + // Rounding the current time to 100ms. + let currentTime = Math.floor(v.currentTime * 10) / 10; + let dropRate = 0; + + if (shouldDrop) { + dropRate = SPOOFED_DROPPED_RATIO; + } + + is(v.mozParsedFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10), + "mozParsedFrames should be spoofed if fingerprinting resistance is enabled"); + is(v.mozDecodedFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10), + "mozDecodedFrames should be spoofed if fingerprinting resistance is enabled"); + is(v.mozPresentedFrames, + parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * (1 - dropRate), 10), + "mozPresentedFrames should be spoofed if fingerprinting resistance is enabled"); + is(v.mozPaintedFrames, + parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * (1 - dropRate), 10), + "mozPaintedFrames should be spoofed if fingerprinting resistance is enabled"); + is(v.mozFrameDelay, 0.0, + "mozFrameDelay should be 0.0 if fingerprinting resistance is enabled"); + let playbackQuality = v.getVideoPlaybackQuality(); + is(playbackQuality.totalVideoFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10), + "VideoPlaybackQuality.totalVideoFrames should be spoofed if fingerprinting resistance is enabled"); + is(playbackQuality.droppedVideoFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * dropRate, 10), + "VideoPlaybackQuality.droppedVideoFrames should be spoofed if fingerprinting resistance is enabled"); + } + + function startTest(test, token) { + let v = document.createElement("video"); + v.token = token; + v.src = test.name; + manager.started(token); + once(v, "ended", () => { + checkStats(v, test.drop); + removeNodeAndSource(v); + manager.finished(v.token); + }); + v.play(); + } + + manager.runTests(testCases, startTest); + +</script> +</body> +</html> diff --git a/dom/media/test/test_video_to_canvas.html b/dom/media/test/test_video_to_canvas.html new file mode 100644 index 0000000000..3267dc63f5 --- /dev/null +++ b/dom/media/test/test_video_to_canvas.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=486646 +--> + +<head> + <title>Test for Bug 486646</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="manifest.js"></script> +</head> +<body> +<script class="testbody" type="text/javascript"> + +var manager = new MediaTestManager; + +function loaded(e) { + var v = e.target; + ok(v.readyState >= v.HAVE_CURRENT_DATA, + "readyState must be >= HAVE_CURRENT_DATA for " + v._name); + + var canvas = document.createElement("canvas"); + canvas.width = v.videoWidth; + canvas.height = v.videoHeight; + document.body.appendChild(canvas); + var ctx = canvas.getContext("2d"); + try { + ctx.drawImage(v, 0, 0); + ok(true, "Shouldn't throw exception while drawing to canvas from video for " + v._name); + } catch (ex) { + ok(false, "Shouldn't throw exception while drawing to canvas from video for " + v._name); + } + + v._finished = true; + v.remove(); + manager.finished(v.token); +} + +function startTest(test, token) { + var type = getMajorMimeType(test.type); + if (type != "video") + return; + + var v = document.createElement('video'); + v.token = token; + manager.started(token); + v.src = test.name; + v._name = test.name; + v._finished = false; + v.autoplay = true; + v.style.display = "none"; + v.addEventListener("ended", loaded); + document.body.appendChild(v); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest); +function beginTest() { + manager.runTests(gSmallTests, startTest); +} + +</script> +</pre> + +</body> +</html> diff --git a/dom/media/test/test_volume.html b/dom/media/test/test_volume.html new file mode 100644 index 0000000000..3d9e5bd91a --- /dev/null +++ b/dom/media/test/test_volume.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media test: volume attribute set</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<video id='v1'></video><audio id='a1'></audio> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function test(element, value, shouldThrow) { + var threw = null; + try { + element.volume = value; + } catch (ex) { + threw = ex.name; + } + is(shouldThrow, threw, "Case: " +element.id+ " setVolume=" + value); +} + + +var ids = [document.getElementById('v1'), document.getElementById('a1')]; + +for (let i=0; i<ids.length; i++) { + var element = ids[i]; + test(element, 0.0, null); + test(element, 1.0, null); + test(element, -0.1, "IndexSizeError"); + test(element, 1.1, "IndexSizeError"); + test(element, undefined, "TypeError"); + test(element, NaN, "TypeError"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_vp9_superframes.html b/dom/media/test/test_vp9_superframes.html new file mode 100644 index 0000000000..c570561b31 --- /dev/null +++ b/dom/media/test/test_vp9_superframes.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that all VP9 frames are decoded (contains superframes)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test() { + var video = document.createElement("video"); + video.src = "vp9-superframes.webm"; + video.play(); + video.addEventListener("ended", function () { + let vpq = video.getVideoPlaybackQuality(); + is(vpq.totalVideoFrames, 120, "totalVideoFrames must contains 120 frames"); + SimpleTest.finish(); + }); +} + +addLoadEvent(function() { + test(); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/test/test_wav_ended1.html b/dom/media/test/test_wav_ended1.html new file mode 100644 index 0000000000..99f6e38243 --- /dev/null +++ b/dom/media/test/test_wav_ended1.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Wave Media test: ended</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +// Test if the ended event works correctly. +var endPassed = false; +var completed = false; + +function startTest() { + if (completed) + return; + var v = document.getElementById('v'); + v.play(); +} + +function playbackEnded() { + if (completed) + return; + + var v = document.getElementById('v'); + completed = true; + ok(v.currentTime >= 0.9 && v.currentTime <= 1.1, + "Checking currentTime at end: " + v.currentTime); + ok(v.ended, "Checking playback has ended"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +<audio id='v' + onloadedmetadata='return startTest();' + onended='return playbackEnded();'> + <source type='audio/x-wav' src='r11025_s16_c1.wav'> +</audio> +</body> +</html> diff --git a/dom/media/test/test_wav_ended2.html b/dom/media/test/test_wav_ended2.html new file mode 100644 index 0000000000..0c172139f0 --- /dev/null +++ b/dom/media/test/test_wav_ended2.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Wave Media test: ended and replaying</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<!-- try with autoplay and no v.play in starttest, also with both --> +<pre id="test"> +<script class="testbody" type="text/javascript"> +// Test if audio can be replayed after ended. +var completed = false; +var playingCount = 0; +var endCount = 0; + +function startTest() { + if (completed) + return; + + var v = document.getElementById('v'); + v.play(); +} + +function playbackStarted() { + if (completed) + return; + + playingCount++; +} + +function playbackEnded() { + if (completed) + return; + + endCount++; + var v = document.getElementById('v'); + ok(v.currentTime >= 0.9 && v.currentTime <= 1.1, + "Checking currentTime at end: " + v.currentTime); + ok(v.ended, "Checking playback has ended"); + ok(playingCount > 0, "Expect at least one playing event"); + playingCount = 0; + if (endCount < 2) { + v.play(); + } else { + ok(endCount == 2, "Check playback after ended event"); + completed = true; + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +<audio id='v' + onloadedmetadata='return startTest();' + onplaying='return playbackStarted();' + onended='return playbackEnded();'> + <source type='audio/x-wav' src='r11025_s16_c1.wav'> +</audio> +</body> +</html> diff --git a/dom/media/test/variable-channel.ogg b/dom/media/test/variable-channel.ogg Binary files differnew file mode 100644 index 0000000000..77e116889c --- /dev/null +++ b/dom/media/test/variable-channel.ogg diff --git a/dom/media/test/variable-channel.ogg^headers^ b/dom/media/test/variable-channel.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/variable-channel.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/variable-channel.opus b/dom/media/test/variable-channel.opus Binary files differnew file mode 100644 index 0000000000..76b8991167 --- /dev/null +++ b/dom/media/test/variable-channel.opus diff --git a/dom/media/test/variable-channel.opus^headers^ b/dom/media/test/variable-channel.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/variable-channel.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/variable-preskip.opus b/dom/media/test/variable-preskip.opus Binary files differnew file mode 100644 index 0000000000..a82e831ce6 --- /dev/null +++ b/dom/media/test/variable-preskip.opus diff --git a/dom/media/test/variable-preskip.opus^headers^ b/dom/media/test/variable-preskip.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/variable-preskip.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/variable-samplerate.ogg b/dom/media/test/variable-samplerate.ogg Binary files differnew file mode 100644 index 0000000000..0c2726e835 --- /dev/null +++ b/dom/media/test/variable-samplerate.ogg diff --git a/dom/media/test/variable-samplerate.ogg^headers^ b/dom/media/test/variable-samplerate.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/variable-samplerate.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/variable-samplerate.opus b/dom/media/test/variable-samplerate.opus Binary files differnew file mode 100644 index 0000000000..1d15170798 --- /dev/null +++ b/dom/media/test/variable-samplerate.opus diff --git a/dom/media/test/variable-samplerate.opus^headers^ b/dom/media/test/variable-samplerate.opus^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/variable-samplerate.opus^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vbr-head.mp3 b/dom/media/test/vbr-head.mp3 Binary files differnew file mode 100644 index 0000000000..35f4105491 --- /dev/null +++ b/dom/media/test/vbr-head.mp3 diff --git a/dom/media/test/vbr-head.mp3^headers^ b/dom/media/test/vbr-head.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vbr-head.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vbr.mp3 b/dom/media/test/vbr.mp3 Binary files differnew file mode 100644 index 0000000000..38eb376a97 --- /dev/null +++ b/dom/media/test/vbr.mp3 diff --git a/dom/media/test/vbr.mp3^headers^ b/dom/media/test/vbr.mp3^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vbr.mp3^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/very-short.mp3 b/dom/media/test/very-short.mp3 Binary files differnew file mode 100644 index 0000000000..1d15dcad59 --- /dev/null +++ b/dom/media/test/very-short.mp3 diff --git a/dom/media/test/video-overhang.ogg b/dom/media/test/video-overhang.ogg Binary files differnew file mode 100644 index 0000000000..e11b28fb5b --- /dev/null +++ b/dom/media/test/video-overhang.ogg diff --git a/dom/media/test/video-overhang.ogg^headers^ b/dom/media/test/video-overhang.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/video-overhang.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vp9-short.webm b/dom/media/test/vp9-short.webm Binary files differnew file mode 100644 index 0000000000..16d32abee3 --- /dev/null +++ b/dom/media/test/vp9-short.webm diff --git a/dom/media/test/vp9-short.webm^headers^ b/dom/media/test/vp9-short.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vp9-short.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vp9-superframes.webm b/dom/media/test/vp9-superframes.webm Binary files differnew file mode 100644 index 0000000000..d695e42357 --- /dev/null +++ b/dom/media/test/vp9-superframes.webm diff --git a/dom/media/test/vp9-superframes.webm^headers^ b/dom/media/test/vp9-superframes.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vp9-superframes.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vp9.webm b/dom/media/test/vp9.webm Binary files differnew file mode 100644 index 0000000000..221877e303 --- /dev/null +++ b/dom/media/test/vp9.webm diff --git a/dom/media/test/vp9.webm^headers^ b/dom/media/test/vp9.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vp9.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vp9cake-short.webm b/dom/media/test/vp9cake-short.webm Binary files differnew file mode 100644 index 0000000000..2d353d98a7 --- /dev/null +++ b/dom/media/test/vp9cake-short.webm diff --git a/dom/media/test/vp9cake-short.webm^headers^ b/dom/media/test/vp9cake-short.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vp9cake-short.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/vp9cake.webm b/dom/media/test/vp9cake.webm Binary files differnew file mode 100644 index 0000000000..4ea70ed302 --- /dev/null +++ b/dom/media/test/vp9cake.webm diff --git a/dom/media/test/vp9cake.webm^headers^ b/dom/media/test/vp9cake.webm^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/vp9cake.webm^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata.wav b/dom/media/test/wave_metadata.wav Binary files differnew file mode 100644 index 0000000000..5e17547c30 --- /dev/null +++ b/dom/media/test/wave_metadata.wav diff --git a/dom/media/test/wave_metadata.wav^headers^ b/dom/media/test/wave_metadata.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata_bad_len.wav b/dom/media/test/wave_metadata_bad_len.wav Binary files differnew file mode 100644 index 0000000000..b89c4818be --- /dev/null +++ b/dom/media/test/wave_metadata_bad_len.wav diff --git a/dom/media/test/wave_metadata_bad_len.wav^headers^ b/dom/media/test/wave_metadata_bad_len.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata_bad_len.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata_bad_no_null.wav b/dom/media/test/wave_metadata_bad_no_null.wav Binary files differnew file mode 100644 index 0000000000..18063048c3 --- /dev/null +++ b/dom/media/test/wave_metadata_bad_no_null.wav diff --git a/dom/media/test/wave_metadata_bad_no_null.wav^headers^ b/dom/media/test/wave_metadata_bad_no_null.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata_bad_no_null.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata_bad_utf8.wav b/dom/media/test/wave_metadata_bad_utf8.wav Binary files differnew file mode 100644 index 0000000000..b6f2a675b2 --- /dev/null +++ b/dom/media/test/wave_metadata_bad_utf8.wav diff --git a/dom/media/test/wave_metadata_bad_utf8.wav^headers^ b/dom/media/test/wave_metadata_bad_utf8.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata_bad_utf8.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata_unknown_tag.wav b/dom/media/test/wave_metadata_unknown_tag.wav Binary files differnew file mode 100644 index 0000000000..b19fb5170f --- /dev/null +++ b/dom/media/test/wave_metadata_unknown_tag.wav diff --git a/dom/media/test/wave_metadata_unknown_tag.wav^headers^ b/dom/media/test/wave_metadata_unknown_tag.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata_unknown_tag.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wave_metadata_utf8.wav b/dom/media/test/wave_metadata_utf8.wav Binary files differnew file mode 100644 index 0000000000..352db285bb --- /dev/null +++ b/dom/media/test/wave_metadata_utf8.wav diff --git a/dom/media/test/wave_metadata_utf8.wav^headers^ b/dom/media/test/wave_metadata_utf8.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wave_metadata_utf8.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_alaw.wav b/dom/media/test/wavedata_alaw.wav Binary files differnew file mode 100644 index 0000000000..ef090d16e0 --- /dev/null +++ b/dom/media/test/wavedata_alaw.wav diff --git a/dom/media/test/wavedata_alaw.wav^headers^ b/dom/media/test/wavedata_alaw.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_alaw.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_float.wav b/dom/media/test/wavedata_float.wav Binary files differnew file mode 100644 index 0000000000..155f891d51 --- /dev/null +++ b/dom/media/test/wavedata_float.wav diff --git a/dom/media/test/wavedata_float.wav^headers^ b/dom/media/test/wavedata_float.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_float.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_s16.wav b/dom/media/test/wavedata_s16.wav Binary files differnew file mode 100644 index 0000000000..6a69cd78f6 --- /dev/null +++ b/dom/media/test/wavedata_s16.wav diff --git a/dom/media/test/wavedata_s16.wav^headers^ b/dom/media/test/wavedata_s16.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_s16.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_s24.wav b/dom/media/test/wavedata_s24.wav Binary files differnew file mode 100644 index 0000000000..dbdb6aac1e --- /dev/null +++ b/dom/media/test/wavedata_s24.wav diff --git a/dom/media/test/wavedata_s24.wav^headers^ b/dom/media/test/wavedata_s24.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_s24.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_u8.wav b/dom/media/test/wavedata_u8.wav Binary files differnew file mode 100644 index 0000000000..1d895c2ce0 --- /dev/null +++ b/dom/media/test/wavedata_u8.wav diff --git a/dom/media/test/wavedata_u8.wav^headers^ b/dom/media/test/wavedata_u8.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_u8.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/wavedata_ulaw.wav b/dom/media/test/wavedata_ulaw.wav Binary files differnew file mode 100644 index 0000000000..0874face21 --- /dev/null +++ b/dom/media/test/wavedata_ulaw.wav diff --git a/dom/media/test/wavedata_ulaw.wav^headers^ b/dom/media/test/wavedata_ulaw.wav^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/test/wavedata_ulaw.wav^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store |