summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/media-source/mse-for-webcodecs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/media-source/mse-for-webcodecs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/media-source/mse-for-webcodecs')
-rw-r--r--testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/media-source-webcodecs-util.js136
-rw-r--r--testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-encrypted-webcodecs-appendencodedchunks-play.https.html27
-rw-r--r--testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-addsourcebuffer.html191
-rw-r--r--testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-appendencodedchunks-play.html50
-rw-r--r--testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/vp9.mp4bin0 -> 6159 bytes
5 files changed, 404 insertions, 0 deletions
diff --git a/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/media-source-webcodecs-util.js b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/media-source-webcodecs-util.js
new file mode 100644
index 0000000000..f581da77b2
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/media-source-webcodecs-util.js
@@ -0,0 +1,136 @@
+const testIV = window.crypto.getRandomValues(new Uint8Array(16));
+const testKey = window.crypto.getRandomValues(new Uint8Array(16));
+const testKeyId = window.crypto.getRandomValues(new Uint8Array(8));
+var testEncodedKey = null;
+
+const keySystemConfig = [{
+ initDataTypes: ['keyids'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="vp09.00.10.08"'}]
+}];
+
+// TODO(crbug.com/1144908): Consider extracting metadata into helper library
+// shared with webcodecs tests. This metadata is adapted from
+// webcodecs/video-decoder-any.js.
+const vp9 = {
+ async buffer() {
+ return (await fetch('vp9.mp4')).arrayBuffer();
+ },
+ // Note, file might not actually be level 1. See original metadata in
+ // webcodecs test suite.
+ codec: 'vp09.00.10.08',
+ frames: [
+ {offset: 44, size: 3315, type: 'key'},
+ {offset: 3359, size: 203, type: 'delta'},
+ {offset: 3562, size: 245, type: 'delta'},
+ {offset: 3807, size: 172, type: 'delta'},
+ {offset: 3979, size: 312, type: 'delta'},
+ {offset: 4291, size: 170, type: 'delta'},
+ {offset: 4461, size: 195, type: 'delta'},
+ {offset: 4656, size: 181, type: 'delta'},
+ {offset: 4837, size: 356, type: 'delta'},
+ {offset: 5193, size: 159, type: 'delta'}
+ ]
+};
+
+async function getOpenMediaSource(t) {
+ return new Promise(async resolve => {
+ const v = document.createElement('video');
+ document.body.appendChild(v);
+ const mediaSource = new MediaSource();
+ const url = URL.createObjectURL(mediaSource);
+ mediaSource.addEventListener(
+ 'sourceopen', t.step_func(() => {
+ URL.revokeObjectURL(url);
+ assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
+ resolve([v, mediaSource]);
+ }),
+ {once: true});
+ v.src = url;
+ });
+}
+
+async function setupEme(t, video) {
+ testEncodedKey = await crypto.subtle.importKey(
+ 'raw', testKey.buffer, 'AES-CTR', false, ['encrypt', 'decrypt']);
+
+ var handler = new MessageHandler(
+ 'org.w3.clearkey', {keys: [{kid: testKeyId, key: testKey}]});
+
+ function handleMessage(event) {
+ handler.messagehandler(event.messageType, event.message).then(response => {
+ event.target.update(response).catch(e => {
+ assert_unreached('Failed to update session: ' + e);
+ });
+ });
+ }
+
+ return navigator
+ .requestMediaKeySystemAccess('org.w3.clearkey', keySystemConfig)
+ .then(keySystemAccess => {
+ return keySystemAccess.createMediaKeys();
+ })
+ .then(createdMediaKeys => {
+ return video.setMediaKeys(createdMediaKeys);
+ })
+ .then(_ => {
+ let session = video.mediaKeys.createSession();
+ session.addEventListener('message', handleMessage, false);
+
+ let encoder = new TextEncoder();
+ let initData = encoder.encode(
+ JSON.stringify({'kids': [base64urlEncode(testKeyId)]}));
+ session.generateRequest('keyids', initData).catch(e => {
+ assert_unreached('Failed to generate a license request: ' + e);
+ });
+ })
+ .catch(e => {
+ assert_unreached('Failed to setup EME: ', e);
+ });
+}
+
+async function runEncryptedChunksTest(t) {
+ let buffer = await vp9.buffer();
+ let [videoElement, mediaSource] = await getOpenMediaSource(t);
+
+ // Makes early prototype demo playback easier to control manually.
+ videoElement.controls = true;
+
+ await setupEme(t, videoElement);
+
+ let sourceBuffer = mediaSource.addSourceBuffer(
+ {videoConfig: {codec: vp9.codec, encryptionScheme: 'cenc'}});
+ let nextTimestamp = 0;
+ let frameDuration = 100 * 1000; // 100 milliseconds
+ // forEach with async callbacks makes it too easy to have uncaught rejections
+ // that don't fail this promise_test or even emit harness error.
+ // Iterating explicitly instead.
+ for (i = 0; i < vp9.frames.length; i++, nextTimestamp += frameDuration) {
+ let frameMetadata = vp9.frames[i];
+ let frameData =
+ new Uint8Array(buffer, frameMetadata.offset, frameMetadata.size);
+ let encryptedFrameData = await window.crypto.subtle.encrypt(
+ {name: 'AES-CTR', counter: testIV, length: 128}, testEncodedKey,
+ frameData);
+
+ await sourceBuffer.appendEncodedChunks(new EncodedVideoChunk({
+ type: frameMetadata.type,
+ timestamp: nextTimestamp,
+ duration: frameDuration,
+ data: encryptedFrameData,
+ decryptConfig: {
+ encryptionScheme: 'cenc',
+ keyId: testKeyId,
+ initializationVector: testIV,
+ subsampleLayout: [{clearBytes: 0, cypherBytes: frameMetadata.size}],
+ }
+ }));
+ }
+
+ mediaSource.endOfStream();
+
+ return new Promise((resolve, reject) => {
+ videoElement.onended = resolve;
+ videoElement.onerror = reject;
+ videoElement.play();
+ });
+}
diff --git a/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-encrypted-webcodecs-appendencodedchunks-play.https.html b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-encrypted-webcodecs-appendencodedchunks-play.https.html
new file mode 100644
index 0000000000..47238af30d
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-encrypted-webcodecs-appendencodedchunks-play.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<title>Test encoded chunk buffering and playback with EME + MediaSource</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/encrypted-media/util/utf8.js"></script>
+<script src="/encrypted-media/util/utils.js"></script>
+<script src="/encrypted-media/util/clearkey-messagehandler.js"></script>
+<script src="media-source-webcodecs-util.js"></script>
+<script>
+setup(() => {
+ assert_implements(
+ SourceBuffer.prototype.hasOwnProperty('appendEncodedChunks'),
+ 'SourceBuffer prototype hasOwnProperty "appendEncodedChunks", used ' +
+ 'here to feature detect MSE-for-WebCodecs implementation.');
+});
+
+promise_test(async t => {
+ return runEncryptedChunksTest(t);
+}, "Buffer encrypted EncodedVideoChunks (VP9) one-by-one and play them with MSE");
+
+// TODO(crbug.com/1144908): More exhaustive tests (multiple sourcebuffers,
+// varying append patterns, invalid append patterns; eventually more codecs,
+// out-of-order DTS, durations, etc.)
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-addsourcebuffer.html b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-addsourcebuffer.html
new file mode 100644
index 0000000000..e29600f99c
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-addsourcebuffer.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<html>
+ <title>Test MediaSource addSourceBuffer overloads for WebCodecs Audio/VideoDecoderConfigs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+setup(() => {
+ assert_implements(
+ SourceBuffer.prototype.hasOwnProperty('appendEncodedChunks'),
+ 'SourceBuffer prototype hasOwnProperty "appendEncodedChunks", used ' +
+ 'here to feature detect MSE-for-WebCodecs implementation.');
+});
+
+testInvalidArguments();
+testValidArguments();
+
+function getValidAudioConfig() {
+ // TODO(crbug.com/1144908): Consider confirming with WebCodecs'
+ // isConfigSupported() once that API is available.
+ return {
+ codec: 'opus',
+ sampleRate: 48000,
+ numberOfChannels: 2
+ };
+}
+
+function getValidVideoConfig() {
+ // TODO(crbug.com/1144908): Consider confirming with WebCodecs'
+ // isConfigSupported() once that API is available.
+ return { codec: 'vp09.00.10.08' };
+}
+
+function testInvalidArguments() {
+ const INVALID_CASES = [
+ { arg: null,
+ name: 'null' },
+ { arg: undefined,
+ name: 'undefined' },
+ { arg: { },
+ name: '{ empty dictionary }' },
+ {
+ arg: {
+ audioConfig: getValidAudioConfig(),
+ videoConfig: getValidVideoConfig()
+ },
+ name: '{ valid audioConfig and videoConfig }',
+ },
+ { arg: { audioConfig: { sampleRate: 48000, numberOfChannels: 2 } },
+ name: 'audio config missing required member "codec"' },
+ { arg: { videoConfig: { } },
+ name: 'video config missing required member "codec"' },
+ ];
+
+ [ 'closed', 'open', 'ended' ].forEach(readyStateScenario => {
+ INVALID_CASES.forEach(invalidCase => {
+ runAddSourceBufferTest(invalidCase['arg'] /* argument */,
+ false /* isValidArgument */,
+ invalidCase['name'] /* argumentDescription */,
+ readyStateScenario);
+ });
+ });
+}
+
+function testValidArguments() {
+ const VALID_CASES = [
+ {
+ arg: {
+ audioConfig: getValidAudioConfig()
+ },
+ name: 'valid audioConfig'
+ },
+ {
+ arg: {
+ videoConfig: getValidVideoConfig()
+ },
+ name: 'valid videoConfig'
+ },
+ ];
+
+ [ 'closed', 'open', 'ended' ].forEach(readyStateScenario => {
+ VALID_CASES.forEach(validCase => {
+ runAddSourceBufferTest(
+ validCase['arg'] /* argument */,
+ true /* isValidArgument */,
+ validCase['name'] /* argumentDescription */,
+ readyStateScenario);
+ });
+ });
+}
+
+async function getClosedMediaSource(test) {
+ let mediaSource = new MediaSource();
+ assert_equals(mediaSource.readyState, 'closed');
+ return mediaSource;
+}
+
+async function getOpenMediaSource(test) {
+ return new Promise(async resolve => {
+ const v = document.createElement('video');
+ const mediaSource = new MediaSource();
+ const url = URL.createObjectURL(mediaSource);
+ mediaSource.addEventListener('sourceopen', test.step_func(() => {
+ URL.revokeObjectURL(url);
+ assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
+ resolve(mediaSource);
+ }), { once: true });
+ v.src = url;
+ });
+}
+
+async function getEndedMediaSource(test) {
+ let mediaSource = await getOpenMediaSource(test);
+ assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
+ mediaSource.endOfStream();
+ assert_equals(mediaSource.readyState, 'ended', 'MediaSource is ended');
+ return mediaSource;
+}
+
+function runAddSourceBufferTest(argument, isValidArgument, argumentDescription, readyStateScenario) {
+ const testDescription = 'addSourceBuffer call with ' +
+ (isValidArgument ? 'valid' : 'invalid') +
+ ' argument ' + argumentDescription + ' while MediaSource readyState is ' +
+ readyStateScenario;
+
+ switch(readyStateScenario) {
+ case 'closed':
+ promise_test(async t => {
+ let mediaSource = await getClosedMediaSource(t);
+ assert_equals(mediaSource.readyState, 'closed');
+ let sourceBuffer;
+ if (isValidArgument) {
+ assert_throws_dom('InvalidStateError',
+ () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
+ 'addSourceBuffer(valid config) throws InvalidStateError if MediaSource is "closed"');
+ assert_equals(sourceBuffer, undefined,
+ 'addSourceBuffer result for valid config while "closed" should be exception');
+ } else {
+ assert_throws_js(TypeError,
+ () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
+ 'addSourceBuffer(invalid config) throws TypeError if MediaSource is "closed"');
+ assert_equals(sourceBuffer, undefined,
+ 'addSourceBuffer result for invalid config while "closed" should be exception');
+ }
+ }, testDescription);
+ break;
+ case 'open':
+ promise_test(async t => {
+ let mediaSource = await getOpenMediaSource(t);
+ assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
+ let sourceBuffer;
+ if (isValidArgument) {
+ sourceBuffer = mediaSource.addSourceBuffer(argument);
+ assert_true(sourceBuffer instanceof SourceBuffer,
+ 'addSourceBuffer result for valid config while "open" should be a SourceBuffer instance');
+ } else {
+ assert_throws_js(TypeError,
+ () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
+ 'addSourceBuffer(invalid config) throws TypeError if MediaSource is "open"');
+ assert_equals(sourceBuffer, undefined,
+ 'addSourceBufferResult for invalid config while "open" should be exception');
+ }
+ }, testDescription);
+ break;
+ case 'ended':
+ promise_test(async t => {
+ let mediaSource = await getEndedMediaSource(t);
+ let sourceBuffer;
+ if (isValidArgument) {
+ assert_throws_dom('InvalidStateError',
+ () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
+ 'addSourceBuffer(valid config) throws InvalidStateError if MediaSource is "ended"');
+ assert_equals(sourceBuffer, undefined,
+ 'addSourceBuffer result for valid config while "ended" should be exception');
+ } else {
+ assert_throws_js(TypeError,
+ () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
+ 'addSourceBuffer(invalid config) throws TypeError if MediaSource is "ended"');
+ assert_equals(sourceBuffer, undefined,
+ 'addSourceBuffer result for invalid config while "ended" should be exception');
+ }
+ }, testDescription);
+ break;
+ default:
+ assert_unreached('Invalid readyStateScenario ' + readyStateScenario);
+ break;
+ }
+}
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-appendencodedchunks-play.html b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-appendencodedchunks-play.html
new file mode 100644
index 0000000000..f93f5a9883
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-appendencodedchunks-play.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+ <title>Test basic encoded chunk buffering and playback with MediaSource</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="media-source-webcodecs-util.js"></script>
+<script>
+setup(() => {
+ assert_implements(
+ SourceBuffer.prototype.hasOwnProperty('appendEncodedChunks'),
+ 'SourceBuffer prototype hasOwnProperty "appendEncodedChunks", used ' +
+ 'here to feature detect MSE-for-WebCodecs implementation.');
+});
+
+promise_test(async t => {
+ let buffer = await vp9.buffer();
+ let [ videoElement, mediaSource ] = await getOpenMediaSource(t);
+ videoElement.controls = true; // Makes early prototype demo playback easier to control manually.
+ let sourceBuffer = mediaSource.addSourceBuffer({ videoConfig: { codec: vp9.codec } });
+ let next_timestamp = 0;
+ let frame_duration = 100 * 1000; // 100 milliseconds
+ // forEach with async callbacks makes it too easy to have uncaught rejections
+ // that don't fail this promise_test or even emit harness error.
+ // Iterating explicitly instead.
+ for (i = 0; i < vp9.frames.length; i++, next_timestamp += frame_duration) {
+ let frame_metadata = vp9.frames[i];
+ await sourceBuffer.appendEncodedChunks(new EncodedVideoChunk( {
+ type: frame_metadata.type,
+ timestamp: next_timestamp,
+ duration: frame_duration,
+ data: new Uint8Array(buffer, frame_metadata.offset, frame_metadata.size)
+ }));
+ }
+
+ mediaSource.endOfStream();
+
+ return new Promise( (resolve, reject) => {
+ videoElement.onended = resolve;
+ videoElement.onerror = reject;
+ videoElement.play();
+ });
+
+}, "Buffer EncodedVideoChunks (VP9) one-by-one and play them with MSE");
+
+// TODO(crbug.com/1144908): More exhaustive tests (multiple sourcebuffers,
+// varying append patterns, invalid append patterns; eventually more codecs,
+// out-of-order DTS, durations, etc.)
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/vp9.mp4 b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/vp9.mp4
new file mode 100644
index 0000000000..7553e5cae9
--- /dev/null
+++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/vp9.mp4
Binary files differ