From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/test/eme.js | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 dom/media/test/eme.js (limited to 'dom/media/test/eme.js') diff --git a/dom/media/test/eme.js b/dom/media/test/eme.js new file mode 100644 index 0000000000..927c99876a --- /dev/null +++ b/dom/media/test/eme.js @@ -0,0 +1,479 @@ +/* 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