I'm trying to change the sinkId
at an Audio-Element in a chrome app.
Code:
var audio = new Audio();
audio.setSinkId("communications");
I'll get this error:
DOMException: No permission to use requested device
So to get the permission I tried to do that:
navigator.webkitGetUserMedia({
audio: true
}, (stream) => {
console.log("stream: ", stream);
}, (error) => {
console.log("error: ", error);
});
But now I get this:
error:
NavigatorUserMediaError {name: "InvalidStateError", message: "", constraintName: ""}
I don't know, whether
There is now a mediacapture-output WG and they did define an extension to the MediaDevices
interface which adds a selectAudioOutput()
method which takes care of the internal permission requests.
This method would return a deviceInfo object from where you can extract the deviceId
you would pass to HTMLMediaElement.setSinkId()
.
const deviceInfo = await navigator.mediaDevices.selectAudioOuput();
audio_elem.setSinkId(deviceInfo.deviceId);
Note also that there is active discussions about renaming this setSinkId
method to setAudioOutput
, so it might change again when browsers will start implementing all this.
Firefox 93 starts exposing this method, under the media.setsinkid.enabled
flag.
Alternatively there should be a Permissions.request()
method, which should accept a PermissionDescriptor object which itself should support a PermissionName of value "speaker-selection"
.
So that would give us
await navigator.permissions.request( { name: "speaker-selection" } );
const all_devices = await navigator.mediaDevices.enumerateDevices();
const audio_outs = all_devices.filter( ({ kind }) => kind === "audiooutput" );
// ...
audioElem.setSinkId( audio_outs[ n ].deviceId );
But as of today August of 2021, it seems that still no browser supports this.
Chrome (which is the only one currently supporting MediaElement.setSinkId()
) does expose the Permission.request()
method under chrome://flags/#enable-experimental-web-platform-features
, but they still don't support the PermissionName "speaker-selection"
yet.
So we still need the less than optimal workaround of requesting for the "microphone"
one instead.
(async () => {
const query = await navigator.permissions.query( { name: "microphone" } );
switch( query.state ) {
case "denied": throw new Error('denied');
case "prompt":
await queryUserMedia();
await getListOfDevices();
break;
case "granted": await getListOfDevices();
}
function queryUserMedia() {
return navigator.mediaDevices.getUserMedia( { audio: true } );
}
async function getListOfDevices() {
const all_devs = await navigator.mediaDevices.enumerateDevices();
const audioouts = all_devs.filter( ({ kind }) => kind === "audiooutput" );
const options = audioouts.map( ( dev, index ) => {
const name = dev.label || ('audio out ' + index );
return new Option( name , dev.deviceId );
} );
sel.append.apply( sel, options );
sel.onchange = e => aud.setSinkId( sel.value );
}
})().catch( console.error );
As a fiddle since Stack-Snippets iframes won't allow for the use of the microphone.
Ps: Note that if/when browsers were supporting the specs defined API, then it would even be possible to run this code on non-secure contexts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With