<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test output device selection in AudioContext.</title>
<style type="text/css">
body {
font-family: sans-serif;
padding: 1em;
}
.row {
margin-top: 1em;
}
#appView {
margin-top: 1em;
font-family: monospace;
}
#log {
line-height: 2em;
font-size: 1.0em;
padding: 0.5em;
border: 1px solid #ccc;
color: #666;
background-color: #eee;
}
#device-dropdown {
font-size: 1.0em;
padding: 0.5em;
border-radius: 0.25em;
}
#device-change {
font-size: 1.0em;
padding: 0.6em 1.0em;
border: 0;
border-radius: 0.25em;
color: #fff;
background-color: #2962ff;
}
#device-change:disabled {
background-color: #bbdefb;
}
</style>
</head>
<body>
<h1>AudioContext.setSinkId() Manual Test</h1>
<div id="appView">
<div id="log"></div>
<div class="row">
<div id="inspector"></div>
</div>
<div class="row">
<select id="device-dropdown" disabled>
<option value="">Getting device information...</option>
</select>
<button id="device-change" disabled>CHANGE</button>
</div>
</div>
<script type="text/javascript">
// Handles the button click to activate `setSinkId()` method.
const onDeviceChange = async (event, audioContext, appView) => {
const deviceId = appView.dropdown.value;
if (deviceId === 'default') {
audioContext.setSinkId('');
} else if (deviceId === 'silent') {
audioContext.setSinkId({type: 'none'});
} else {
audioContext.setSinkId(deviceId);
}
const sinkId = `AudioContext.sinkId = ` + typeof audioContext.sinkId === 'object'
? JSON.stringify(audioContext.sinkId) : audioContext.sinkId;
const maxChan = audioContext.destination.maxChannelCount;
appView.inspector.textContent = `${sinkId} (${maxChan} channels max)`;
};
// The manual test body
const startManuaTestApp = async () => {
const appView = {
log: document.getElementById('log'),
inspector: document.getElementById('inspector'),
dropdown: document.getElementById('device-dropdown'),
changeButton: document.getElementById('device-change')
};
const audioContext = new AudioContext();
const osc = new OscillatorNode(audioContext);
const amp = new GainNode(audioContext, {gain: 0.25});
osc.connect(amp).connect(audioContext.destination);
osc.start();
if (typeof audioContext.setSinkId !== 'function' ||
typeof audioContext.sinkId != 'string') {
appView.log.textContent =
'This browser does not support AudioContext.setSinkId()';
return;
}
appView.inspector.textContent =
`AudioContext.sinkId = ${audioContext.sinkId}`+
`(${audioContext.destination.maxChannelCount} channels max)`;
// Get a permission, enumerate devices, and set up the UI.
try {
const stream =
await navigator.mediaDevices.getUserMedia({ audio: true });
if (stream) {
const deviceList = await navigator.mediaDevices.enumerateDevices();
let deviceListString = '';
deviceList.forEach((device) => {
if (device.kind === 'audiooutput') {
deviceListString +=
`<option value="${device.deviceId}">${device.label}</option>`;
}
});
deviceListString +=
`<option value="silent">none (silent output)</option>`;
appView.dropdown.innerHTML = deviceListString;
appView.changeButton.addEventListener('click', (event) => {
audioContext.resume();
onDeviceChange(event, audioContext, appView);
});
appView.dropdown.disabled = false;
appView.changeButton.disabled = false;
appView.log.textContent =
'Device information successfully retrieved. The test is ready.';
} else {
appView.log.textContent =
'navigator.mediaDevices.getUserMedia() failed.';
}
} catch (error) {
// The permission dialog was dismissed, or acquiring a stream from
// getUserMedia() failed.
console.error(error);
appView.log.textContent = `The initialization failed: ${error}`;
}
};
// Entry point
window.addEventListener('load', startManuaTestApp);
</script>
</body>
</html>