w3c / mediacapture-output

API to manage the rendering of audio on any audio output device
https://w3c.github.io/mediacapture-output/
Other
26 stars 25 forks source link

Setting the audio output for a whole context or page #87

Open youennf opened 4 years ago

youennf commented 4 years ago

Currently, we are only able to control individual HTMLMediaElement audio output with setSinkId. We probably need to add something to WebAudio as well.

In most cases, it seems that pages will want to output all their audio to a single device. Adding such an API to do so would be convenient and would complement setSinkId which would be used to override this default value.

Something at navigator.mediaDevices level might make sense.

youennf commented 4 years ago

This somehow relates to https://github.com/w3c/mediacapture-output/issues/63. With this new API, an iframe could be able to update the page audio output if it were allowed to do so by feature policy.

fippo commented 4 years ago

In most cases, it seems that pages will want to output all their audio to a single device.

Actually no. You might want to have the "ring" sound to go to the speaker (so you hear it if you are not at your desk) while during the call you want to have sound going to your headset. Arguably you might want in-call notification sounds to go to your headset too then.

youennf commented 4 years ago

If the user is not interacting/interested in the web application, the application is probably not in a position to select the proper means to request user interest. For instance, you might want the device to vibrate instead of playing a ring tone if the device is in vibration mode, but you probably do not want to let the application know you are in vibration mode. This API does not seem to be a good fit for this use case, which is closer to existing notification API use cases.

On the other hand, if the web application knows the user is interacting with the page, like in the middle of a phone call, it seems natural to use the same speaker device.

I agree though that there are use cases for multi speaker setup cases (hence why HTMLMediaElement.setSinkId is good to have too). These use cases might not be as common, hence why it might be useful to have a page-wide version.

jan-ivar commented 4 years ago

We probably need to add something to WebAudio as well.

Web audio seems easy enough to work around. Instead of:

audioNode.connect(audioContext.destination);

you'd do

const dest = audioContext.createMediaStreamDestination();
element.srcObject = dest.stream;
audioNode.connect(dest);

Not sure about audio worklet though.

In most cases, it seems that pages will want to output all their audio to a single device.

The workaround is to collect all elements and set them:

async function setAllElements(sinkId) {
  const elements = [...doc.getElementsByTagName("audio"),
                    ...doc.getElementsByTagName("video")];
  await Promise.all(elements.map(element => element.setSinkId(sinkId)));
}
youennf commented 4 years ago

This is not working for third-party iframes. For WebAudio, this seems a rather convoluted solution. At least in Safari, this is most probably less efficient and would probably add some latency.

jan-ivar commented 4 years ago

This is not working for third-party iframes.

@youennf I'm confused. It should work fine for the JS inside the iframe. Or do you want https://github.com/w3c/mediacapture-output/issues/63?

If so, should we close one of the issues as a dup?

elibarzilay commented 3 years ago

I got here while trying to get this to work now in chrome, and if something like a whole page default can simplify how changing a device is done now, it would be very nice. I'm really just playing around, so take it with a sack of salt, but OTOH there's generally a lot of confusion on the interwebs.

The thing that tripped me is that if you createMediaElementSource an audio element (in my case since I want to monitor it for a visualization of a player), then it's no longer playing until you manually connect that stream to ctx.destination, but AFAICT this is currently (in chrome, at least) always playing the default device. So to get the monitor to work the only way I found was to create a second Audio, set its srcObject to a fresh createMediaStreamDestination()'s stream, and then use setSinkId on that audio. That's much more than @jan-ivar's loop suggests, and would be nice to simplify. (But maybe I'm confusion too.)