Skip to content

Commit

Permalink
[MSE][webcodecs] Plumb AddSourceBufferUsingConfig to ChunkDemuxer AddId
Browse files Browse the repository at this point in the history
This change:

1) Exposes as static synchronous helpers the WebCodecs MakeMediaConfig
   methods' internals so that MediaSource can use them to obtain
   media::{Audio,Video}DecoderConfigs from WebCodecs
   {Audio,Video}DecoderConfigs.

2) Implements MediaSource::AddSourceBufferUsingConfig for encoded
   configs (not decodedMediaType), plumbing the media decoder config
   through the usual steps in MSE addSourceBuffer, and through new
   methods in WebMediaSource and WebMediaSourceImpl to let
   ChunkDemuxer::AddId know it will be expected to handle WebCodecs
   encoded chunk appends for the associated SourceBuffer.

Later changes will continue plumbing of the configs in ChunkDemuxer and
SourceBufferState, and also add implementations and ChunkDemuxer/etc
support for appendEncoded{Audio,Video}Chunks. The h264 parser and avcc
conditionally created in #1, above, could then be used by SourceBuffer
processing of appended EncodedVideoChunks, too.

BUG=1144908

Change-Id: I90c1d90c3a28d5cc1e33b1e50e32f4cfea639784
  • Loading branch information
wolenetz authored and chromium-wpt-export-bot committed Dec 3, 2020
1 parent d1aaf68 commit c13a481
Showing 1 changed file with 207 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<!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: {
codec: 'bogus',
sampleRate: 48000,
numberOfChannels: 2
}
},
name: 'bad audio config codec',
},
{ arg: { videoConfig: { codec: 'bogus' } },
name: 'bad video config codec' },
{ 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) {
// TODO(crbug.com/1144908): Update to expect success once the impl is
// more complete.
assert_throws_dom('QuotaExceededError',
() => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
'addSourceBuffer(valid config) throws QuotaExceededError');
assert_equals(sourceBuffer, undefined,
'addSourceBuffer result for valid config while "open" should be exception');
} 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>

0 comments on commit c13a481

Please sign in to comment.