diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/media-source/mse-for-webcodecs | |
parent | Initial commit. (diff) | |
download | firefox-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')
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 Binary files differnew file mode 100644 index 0000000000..7553e5cae9 --- /dev/null +++ b/testing/web-platform/tests/media-source/mse-for-webcodecs/tentative/vp9.mp4 |