// META: global=window,dedicatedworker // META: script=/webcodecs/utils.js const detachedArrayBuffer = new ArrayBuffer(4); var b = detachedArrayBuffer.transferToFixedLength(); const invalidConfigs = [ { comment: 'Missing codec', config: { sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Empty codec', config: { codec: '', sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Missing sampleRate', config: { codec: 'opus', sampleRate: 48000, }, }, { comment: 'Missing numberOfChannels', config: { codec: 'opus', sampleRate: 48000, }, }, { comment: 'Zero sampleRate', config: { codec: 'opus', sampleRate: 0, numberOfChannels: 2, }, }, { comment: 'Zero channels', config: { codec: 'opus', sampleRate: 8000, numberOfChannels: 0, }, }, { comment: 'Opus with >2 channels but no description', config: { codec: 'opus', sampleRate: 48000, numberOfChannels: 6, } }, { comment: 'Valid configuration except detached description', config: { codec: 'opus', sampleRate: 8000, numberOfChannels: 1, description: detachedArrayBuffer, }, }, ]; invalidConfigs.forEach(entry => { promise_test( t => { return promise_rejects_js( t, TypeError, AudioDecoder.isConfigSupported(entry.config)); }, 'Test that AudioDecoder.isConfigSupported() rejects invalid config: ' + entry.comment); }); invalidConfigs.forEach(entry => { async_test( t => { let codec = new AudioDecoder(getDefaultCodecInit(t)); assert_throws_js(TypeError, () => { codec.configure(entry.config); }); t.done(); }, 'Test that AudioDecoder.configure() rejects invalid config: ' + entry.comment); }); const validButUnsupportedConfigs = [ { comment: 'Unrecognized codec', config: { codec: 'bogus', sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Video codec', config: { codec: 'vp8', sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Ambiguous codec', config: { codec: 'vp9', sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Codec with MIME type', config: { codec: 'audio/webm; codecs="opus"', sampleRate: 48000, numberOfChannels: 2, }, }, { comment: 'Possible future opus codec string', config: { codec: 'opus.123', sampleRate: 48000, numberOfChannels: 2, } }, { comment: 'Possible future aac codec string', config: { codec: 'mp4a.FF.9', sampleRate: 48000, numberOfChannels: 2, } }, { comment: 'codec with spaces', config: { codec: ' mp3 ', sampleRate: 48000, numberOfChannels: 2, } }, ]; // Those configurations are supported, but attempting to configure an // AudioDecoder will fail, because `description` is invalid for this particular // codec var supportedButErrorOnConfiguration = [ { comment: 'Opus with more than two channels and without description', config: { codec: 'opus', sampleRate: '48000', numberOfChannels: 3, }, }, { comment: 'Opus with more than two channels and with a description that is too short', config: { codec: 'opus', sampleRate: '48000', numberOfChannels: 3, description: new Uint8Array(9), // at least 10 bytes are required for multichannel }, }, { comment: 'vorbis requires a description', config: { codec: 'vorbis', sampleRate: '48000', numberOfChannels: 2 }, }, { comment: 'flac requires a description', config: { codec: 'flac', sampleRate: '48000', numberOfChannels: 2 }, }, ]; validButUnsupportedConfigs.forEach(entry => { promise_test( t => { return AudioDecoder.isConfigSupported(entry.config).then(support => { assert_false(support.supported); }); }, 'Test that AudioDecoder.isConfigSupported() doesn\'t support config: ' + entry.comment); }); var shouldError = validButUnsupportedConfigs.concat(supportedButErrorOnConfiguration); shouldError.forEach(entry => { promise_test( t => { let isErrorCallbackCalled = false; let supported = AudioDecoder.isConfigSupported(entry.config); assert_implements_optional(supported, entry.config.codec + ' unsupported'); let codec = new AudioDecoder({ output: t.unreached_func('unexpected output'), error: t.step_func_done(e => { isErrorCallbackCalled = true; assert_true(e instanceof DOMException); assert_equals(e.name, 'NotSupportedError'); assert_equals(codec.state, 'closed', 'state'); }) }); codec.configure(entry.config); return codec.flush() .then(t.unreached_func('flush succeeded unexpectedly')) .catch(t.step_func(e => { assert_true(isErrorCallbackCalled, "isErrorCallbackCalled"); assert_true(e instanceof DOMException); assert_equals(e.name, 'NotSupportedError'); assert_equals(codec.state, 'closed', 'state'); })); }, 'Test that AudioDecoder.configure() doesn\'t support config: ' + entry.comment); }); function getFakeChunk() { return new EncodedAudioChunk( {type: 'key', timestamp: 0, data: Uint8Array.of(0)}); } promise_test(t => { // AudioDecoderInit lacks required fields. assert_throws_js(TypeError, () => { new AudioDecoder({}); }); // AudioDecoderInit has required fields. let decoder = new AudioDecoder(getDefaultCodecInit(t)); assert_equals(decoder.state, 'unconfigured'); decoder.close(); return endAfterEventLoopTurn(); }, 'Test AudioDecoder construction'); promise_test(t => { let decoder = new AudioDecoder(getDefaultCodecInit(t)); return testUnconfiguredCodec(t, decoder, getFakeChunk()); }, 'Verify unconfigured AudioDecoder operations');