blob: d2dc54aee6a7f93e846244e1f3ab88c3774cf917 [file] [log] [blame]
<!DOCTYPE html>
<head>
<title>Test AudioContext constructor with sinkId options</title>
</head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
let outputDeviceList = null;
let firstDeviceId = null;
navigator.mediaDevices.getUserMedia({audio: true}).then(() => {
navigator.mediaDevices.enumerateDevices().then((deviceList) => {
outputDeviceList =
deviceList.filter(({kind}) => kind === 'audiooutput');
assert_greater_than(outputDeviceList.length, 1,
'the system must have more than 1 device.');
firstDeviceId = outputDeviceList[1].deviceId;
// Run async tests concurrently.
async_test(t => testDefaultSinkId(t),
'Setting sinkId to the empty string at construction should ' +
'succeed.');
async_test(t => testValidSinkId(t),
'Setting sinkId with a valid device identifier at ' +
'construction should succeed.');
async_test(t => testAudioSinkOptions(t),
'Setting sinkId with an AudioSinkOptions at construction ' +
'should succeed.');
async_test(t => testExceptions(t),
'Invalid sinkId arguments should throw an appropriate ' +
'exception.')
});
});
// 1.2.1. AudioContext constructor
// https://webaudio.github.io/web-audio-api/#AudioContext-constructors
// Step 10.1.1. If sinkId is equal to [[sink ID]], abort these substeps.
const testDefaultSinkId = (t) => {
// The initial `sinkId` is the empty string. This will cause the same value
// check.
const audioContext = new AudioContext({sinkId: ''});
audioContext.addEventListener('statechange', () => {
t.step(() => {
assert_equals(audioContext.sinkId, '');
assert_equals(audioContext.state, 'running');
});
audioContext.close();
t.done();
}, {once: true});
};
// Step 10.1.2~3: See "Validating sinkId" tests below.
// Step 10.1.4. If sinkId is a type of DOMString, set [[sink ID]] to sinkId and
// abort these substeps.
const testValidSinkId = (t) => {
const audioContext = new AudioContext({sinkId: firstDeviceId});
audioContext.addEventListener('statechange', () => {
t.step(() => {
assert_true(audioContext.sinkId === firstDeviceId,
'the context sinkId should match the given sinkId.');
});
audioContext.close();
t.done();
}, {once: true});
t.step_timeout(t.unreached_func('onstatechange not fired or assert failed'),
100);
};
// Step 10.1.5. If sinkId is a type of AudioSinkOptions, set [[sink ID]] to a
// new instance of AudioSinkInfo created with the value of type of sinkId.
const testAudioSinkOptions = (t) => {
const audioContext = new AudioContext({sinkId: {type: 'none'}});
// The only signal we can use for the sinkId change after construction is
// `statechange` event.
audioContext.addEventListener('statechange', () => {
t.step(() => {
assert_equals(typeof audioContext.sinkId, 'object');
assert_equals(audioContext.sinkId.type, 'none');
});
audioContext.close();
t.done();
}, {once: true});
t.step_timeout(t.unreached_func('onstatechange not fired or assert failed'),
100);
};
// 1.2.4. Validating sinkId
// https://webaudio.github.io/web-audio-api/#validating-sink-identifier
// Step 3. If document is not allowed to use the feature identified by
// "speaker-selection", return a new DOMException whose name is
// "NotAllowedError".
// TODO(https://crbug.com/1380872): Due to the lack of "speaker-selection"
// implementation, a test for such step does not exist yet.
const testExceptions = (t) => {
t.step(() => {
// The wrong AudioSinkOption.type should cause a TypeError.
assert_throws_js(TypeError, () => {
const audioContext = new AudioContext({sinkId: {type: 'something_else'}});
audioContext.close();
}, 'An invalid AudioSinkOptions.type value should throw a TypeError ' +
'exception.');
});
t.step(() => {
// Step 4. If sinkIdArg is a type of DOMString but it is not equal to the
// empty string or it does not match any audio output device identified by
// the result that would be provided by enumerateDevices(), return a new
// DOMException whose name is "NotFoundError".
// TODO(https://crbug.com/1439947): This does not throw in Chromium because
// the logic automatically fallbacks to the default device when a given ID
// is invalid.
assert_throws_dom('NotFoundError', () => {
const audioContext = new AudioContext({sinkId: 'some_random_device_id'});
audioContext.close();
}, 'An invalid device identifier should throw a NotFoundError exception.');
});
t.done();
};
</script>
</html>