| <!DOCTYPE html> |
| <head> |
| <title>Test AudioContext.setSinkId() state change</title> |
| </head> |
| <script src=/resources/testharness.js></script> |
| <script src=/resources/testharnessreport.js></script> |
| <script> |
| "use strict"; |
| |
| let outputDeviceList = null; |
| let firstDeviceId = null; |
| |
| // Setup: Get permission via getUserMedia() and a list of audio output devices. |
| promise_setup(async t => { |
| await navigator.mediaDevices.getUserMedia({ audio: true }); |
| const deviceList = await navigator.mediaDevices.enumerateDevices(); |
| outputDeviceList = |
| deviceList.filter(({kind}) => kind === 'audiooutput'); |
| assert_greater_than(outputDeviceList.length, 1, |
| 'the system must have more than 1 device.'); |
| firstDeviceId = outputDeviceList[1].deviceId; |
| }, 'Get permission via getUserMedia() and a list of audio output devices.'); |
| |
| // Test the sink change when from a suspended context. |
| promise_test(async t => { |
| let events = []; |
| const audioContext = new AudioContext(); |
| await audioContext.suspend(); |
| |
| // Step 6. Set wasRunning to false if the [[rendering thread state]] on the |
| // AudioContext is "suspended". |
| assert_equals(audioContext.state, 'suspended'); |
| |
| // Step 11.5. Fire an event named sinkchange at the associated AudioContext. |
| audioContext.onsinkchange = t.step_func(() => { |
| events.push('sinkchange'); |
| assert_equals(audioContext.sinkId, firstDeviceId); |
| }); |
| |
| await audioContext.setSinkId(firstDeviceId); |
| assert_equals(events[0], 'sinkchange'); |
| t.done(); |
| }, 'Calling setSinkId() on a suspended AudioContext should fire only sink ' + |
| 'change events.'); |
| |
| // Test the sink change on a running AudioContext. |
| promise_test(async t => { |
| let events = []; |
| let silentSinkOption = {type: 'none'}; |
| const audioContext = new AudioContext(); |
| |
| // Make sure the context is "running". This also will fire a state change |
| // event upon resolution. |
| await audioContext.resume(); |
| |
| return new Promise(async resolve => { |
| |
| function eventCheckpoint() { |
| // We're expecting 4 events from AudioContext. |
| if (events.length === 4) { |
| // The initial context state was "running". |
| assert_equals(events[0], 'statechange:running'); |
| assert_equals(events[1], 'statechange:suspended'); |
| assert_equals(events[2], 'sinkchange'); |
| assert_equals(events[3], 'statechange:running'); |
| resolve(); |
| } |
| } |
| |
| // This is to catch a sink change event: |
| // - Step 11.5. Fire an event named sinkchange at the associated |
| // AudioContext. |
| audioContext.onsinkchange = t.step_func(() => { |
| assert_equals(audioContext.sinkId.type, silentSinkOption.type); |
| events.push('sinkchange'); |
| eventCheckpoint(); |
| }); |
| |
| // The following event handler will catch 3 state change events: |
| // - The initial 'running' state change. |
| // - Step 9.2.1. Set the state attribute of the AudioContext to "suspended". |
| // Fire an event named statechange at the associated AudioContext. |
| // - Step 12.2. Set the state attribute of the AudioContext to "running". |
| // Fire an event named statechange at the associated AudioContext. |
| audioContext.onstatechange = async () => { |
| events.push(`statechange:${audioContext.state}`); |
| eventCheckpoint(); |
| }; |
| |
| // To trigger a series of state changes, we need a device change. The |
| // context started with the default device, and this call changes it to a |
| // silent sink. |
| audioContext.setSinkId(silentSinkOption); |
| }); |
| }, 'Calling setSinkId() on a running AudioContext should fire both state ' + |
| 'and sink change events.'); |
| </script> |
| </html> |