Skip to content

Commit bc9e57f

Browse files
Address feedback and questions on setPreferredSinkId() explainer (#1299)
Disallows child frames from calling setPreferredSinkId(). Attempts to simplify goals and examples to answer questions about behavior and permissions.
1 parent da371ad commit bc9e57f

1 file changed

Lines changed: 43 additions & 133 deletions

File tree

SetPreferredSinkId/explainer.md

Lines changed: 43 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Authors: Sunggook Chue, Ravikiran Ramachandra, Steve Becker, Andy Luhrs
44

5+
## Participate
6+
7+
- https://github.com/w3c/mediacapture-output/issues/141
8+
- https://github.com/w3c/mediacapture-output/issues/63
9+
- https://github.com/w3c/audio-session/issues/6
10+
511
## Status of this Document
612
This document is a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization. As the solutions to problems described in this document progress along the standards-track, we will retain this document as an archive and use this section to keep the community up-to-date with the most current standards venue and content location of future work and discussions.
713

@@ -11,173 +17,77 @@ This document is a starting point for engaging the community and standards bodie
1117

1218
## Introduction
1319

14-
Sites that embed third-party content they don't fully control would like the ability to influence which device audio gets routed for their entire page in response to a user stating they have a preferred device. To enable that, this proposal allows controlling the default audio output device for the entire page, including subframes.
15-
16-
In embedded web experiences, the top-level frame grants iframes access to system media devices like microphones, cameras and speakers. This is typically done through the permission policy (https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy) in the iframe tag. However, this access comes with two key challenges:
20+
The browser defines a default audio output device used by all audio renderers. Audio renderers include [HTML media elements](https://html.spec.whatwg.org/multipage/media.html#media-element) and [web audio contexts](https://webaudio.github.io/web-audio-api/#AudioContext). Each audio renderer can override the default audio output device using [`setSinkId()`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-setsinkid) to select a different audio output device. This proposal introduces `setPreferredSinkId()`, which enables a top-level frame to override the default audio output device for all audio renderers in the top-level frame and its child frames, including cross-origin child frames.
1721

18-
- Independent choice: Each iframe independently chooses its own media device. The top-level frame cannot directly influence or view this selection due to browser security restrictions (cross-origin boundary).
19-
- Unsynchronized changes: When the top-level frame changes its media device, the iframes remain unaware unless they communicate using methods like postMessage. This lack of automatic synchronization can lead to inconsistencies and a disjointed user experience.
22+
Without `setPreferredSinkId()`, a top-level frame has no mechanism to change the default audio output device in its cross-origin child frames. A top-level frame cannot use [`setSinkId()`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-setsinkid) in a cross-origin child frame due to security boundaries. Instead, the top-level frame and cross-origin child frame must collaborate using postMessage() to change the audio output device.
2023

21-
We’d like to propose a new API to set the default audio output device for the current top frame and all of its sub frames.
24+
This limitation leads to an inconsistent, disjointed user experience in composable multimedia apps that include embedded video players and embedded slideshows. Each cross-origin child frame may independently choose the audio output device, potentially causing multiple audio output devices to play at the same time. Similarly, if the top-level frame changes its audio output device, the cross-origin child frame's audio does not change, disrupting the user's expectations for audio playback.
2225

2326
## Goals
2427

25-
- Developers can modify the default audio output for the top-level frame page.
26-
- Developers can modify the default audio output for the sub frame pages.
27-
- Developers can modify the default audio output for the top-level frame and all other sub frames from any same-origin sub frame.
28-
- Any media element or audio context can continue to override this default setting using the existing setSinkId API (cross-origin iframes continue to need 'speaker_selection' permission to call setSinkId).
28+
- Top-level frames can override the default audio output device for all audio renderers in the top-level frame and its child frames, including cross-origin child frames.
2929

3030
## Non Goals
31-
- A change notification for default audio output is not in scope of this project.
32-
- Requiring a permission policy for calling the API. Since permission is gated on the speaker_selection permission, access to the API is already reasonably constrained.
33-
- A change of the sinkId property for the default device ID. It will continue to return the existing default device ID (an empty string).
3431

35-
## Use Cases
32+
- Overriding non-default audio output devices. This proposal does not change the behavior of [`setSinkId()`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-setsinkid). Each audio renderer may continue to override the default audio output device using [`setSinkId()`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-setsinkid).
33+
34+
- Dispatching an event when the default audio output device changes. Audio renders cannot detect when the browser or `setPreferredSinkId()` changes the default audio output device. The [`sinkId`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-sinkid) attribute will continue to return its default value, the empty string, before and after the default audio output device changes.
3635

37-
### Case 1: Communication app that wants to route all audio to a phone headset
38-
Communication app customers may use external headsets for calls, separate from their computer’s default audio option. For instance, they might have their default setup to play music through speakers, but would always want to have (potentially private) calls through a Bluetooth headset. Today, many communication apps have a setting for customers to pick their preferred output device.
36+
- Introduce a new permission. This proposal restricts `setPreferredSinkId()` usage to top-level frames only. The caller of `setPreferredSinkId()` must retrieve audio output device IDs using the pre-existing APIs [`mediaDevices.enumerateDevices()`](https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices) or [`MediaDevices.selectAudioOutput()`](https://www.w3.org/TR/audio-output/#dom-mediadevices-selectaudiooutput) with pre-existing permission models.
3937

40-
Communication apps may also include a variety of other applications embedded inside of it. These embedded applications may play audio during a meeting in addition to the normal audio of the call. For example, like when a video is embedded in a slide show presentation application.
38+
## Use Cases
4139

42-
### Case 2: Using a single device to play separate audio streams to two different listeners
43-
A user actively engages in online meetings using a browser communication app. During these meetings, the presenter embeds slide show presentation app within iframes. These embedded presentations often include crucial content from video share site.
44-
However, a user faces a delightful dilemma: her children are playing in the backyard, and she wants to play music for them using a Bluetooth speaker. To achieve this, she cleverly configures her audio output by using the new API what we propose here through the presentation web app and system settings:
40+
### Implementing audio output settings
4541

46-
- Bluetooth Speaker: A user sets the system default audio output to the Bluetooth speaker, ensuring her children enjoy their music outdoors.
42+
An app developer provides an option to change the audio output device through their app's settings. When the setting changes, the app uses `setPreferredSinkId()` to override the default audio output device, updating all of its existing audio renderers. `setPreferredSinkId()` will also affect any future audio renderers the app creates by continuing to override the default audio output device. Without `setPreferredSinkId()`, the app needed to call [`setSinkId()`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-setsinkid) on every existing and new audio renderer to apply the app's audio setting.
4743

48-
- Computer Speaker: Simultaneously, she selects the computer speaker as the communication app’s default audio output through communication app's speaker selection UI. This way, she can listen to both the video share site content within the presentation page and the presenter’s voice during her online meetings.
44+
### Changing audio output from speakers to a headset
4945

50-
A user’s multitasking prowess ensures a harmonious blend of work, family, and entertainment!
46+
An office worker configures their device to use speakers as the default audio output. The office worker uses a communications app to join a video call that includes a cross-origin embedded presentation app. For this scenario, two audio renderers exist: the communication app outputs voice from the video call and the cross-origin embedded presentation app outputs effects from the slideshow.
5147

52-
Developers of communication apps can provide audio output selection UI on their top level page that enable the user to select which audio output device to use in both the top level app and all sub frames.
48+
To stop disrupting colleagues, the office worker uses the communications app's settings to change the audio output device to use a headset instead of speakers. The communications app uses `setPreferredSinkId()` with the headset's device ID to update both audio renderers: the voice audio from the video call and the effect audio from the slideshow. Without `setPreferredSinkId()`, the speakers would continue to output the cross-origin slideshow effects.
5349

5450
## Proposed Solution
5551

56-
New method `setPreferredSinkId(deviceId)` on the [MediaDevices API](https://www.w3.org/TR/mediacapture-streams/#mediadevices),
57-
where `deviceId` is the [media device identifier](https://www.w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-deviceid)
52+
Extend [MediaDevices](https://w3c.github.io/mediacapture-output/#mediadevices-extensions) by adding `setPreferredSinkId(deviceId)` where `deviceId` is the [media device identifier](https://www.w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-deviceid).
5853

5954
```js
60-
[Exposed=Window, SecureContext]
61-
interface MediaDevices : EventTarget {
62-
...
55+
partial interface MediaDevices {
6356
Promise<void> setPreferredSinkId(DOMString deviceId);
6457
};
65-
66-
deviceId:
67-
This attribute contains the ID of the audio device through which output is being delivered, or the empty string if output is delivered
68-
through the user-agent default device. If nonempty, this ID should be equal to the deviceId attribute of one of the MediaDeviceInfo values
69-
returned from enumerateDevices().
70-
71-
Return:
72-
A Promise that fulfills with a value of undefined.
73-
74-
Exceptions:
75-
NotAllowedError DOMException
76-
Returned if a cross-origin sub frame tries to call the API.
77-
78-
NotFoundError DOMException
79-
Returned if the deviceId does not match any audio output device.
80-
81-
AbortError DOMException
82-
Returned if switching the audio output device to the new audio device failed.
8358
```
8459

85-
This API is accessible from the top-level frame and the same-origin sub frames and allows modification of the default audio output for both
86-
the top-level frame and all sub frames, regardless of their origins. However, it’s important to note that this change does not affect custom
87-
audio outputs specified using the setSinkId method in media element or audio context.
60+
After successfully overriding the default audio output device, the promise fulfills with `undefined`. Success does not change the [`sinkId`](https://w3c.github.io/mediacapture-output/#dom-htmlmediaelement-sinkid) attribute of any audio renderer. Only top-level frames may successfully call `setPreferredSinkId()`. After failure, the promise rejects with one of the following errors:
8861

89-
Remember to call this API within a secure context (using HTTPS).
62+
- `NotAllowedError`: Returned when a child frame calls the API. This includes both same-origin and cross-origin child frames.
63+
- `NotFoundError`: Returned if the `deviceId` does not match any audio output device.
64+
- `AbortError`: Returned if switching the audio output device to the new audio device failed.
9065

91-
### Example
66+
To revert back to using the default audio output device, call `setPreferredSinkId('')` with an empty string device ID. This is just like the pre-existing `setSinkId('')` behavior.
9267

93-
```js
94-
<!-- index.html -->
95-
<body>
96-
<button id="audioDeviceSelection">
97-
<iframe src="presentationLive.html"></iframe>
98-
...
99-
<script>
100-
const audioContext = new AudioContext();
101-
...
102-
let selectedDeviceId = "";
103-
104-
// App shows audio output devices for a user to select.
105-
audioDeviceSelection.addEventListner('click', (list) => {
106-
const devices = await navigator.mediaDevices.enumerateDevices();
107-
const audioOutputs = devices.filter((device) => device.kind === 'audiooutput');
108-
109-
// showDialogForDeviceSelection is a dialog that allow user to select
110-
// audio device, it could be 'selectAudioOutput' API
111-
// (https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/selectAudioOutput)
112-
// if the user agent supports it.
113-
selected_device = await showDialogForDeviceSelection(audioOutputs);
114-
selectedDeviceId = selected_device.deviceId;
115-
});
116-
117-
...
118-
119-
// setPreferredSinkId will change the audio output device for the entire frame that includes subframes.
120-
121-
// If selectedDeviceId is empty string, "", then setPreferredSinkId will revert to using the system default device.
122-
await navigator.mediaDevices.setPreferredSinkId(selectedDeviceId);
123-
124-
// It does not have to call audioContext.setSinkId in order to change audioContext's audio device output.
125-
</script>
126-
</body>
127-
128-
129-
<!-- presentationLive.html -->
130-
<body>
131-
...
132-
<iframe src="mediaPlay.html"></iframe>
133-
</body>
134-
135-
<!-- mediaPlay.html -->
136-
<body>
137-
...
138-
<video id="videoElem" src="https://www.mediaPlayLive.com/123abc"></video>
139-
<script>
140-
const videoElem = document.getElementById('videoElem');
141-
142-
// videoElem.sinkId === selectedDeviceId after calling setPreferredSinkId.
143-
</script>
144-
</body>
145-
```
68+
### Example Usage
14669

147-
## Privacy and Security Considerations
148-
149-
### Privacy
150-
No considerable privacy concerns are expected, but we welcome community feedback.
70+
```js
71+
// Use enumerateDevices() to retrieve all audio output devices.
72+
const mediaDeviceList = await navigator.mediaDevices.enumerateDevices();
73+
const audioOutputDeviceList = media_device_list.filter(media_device => media_device.kind === 'audiooutput');
15174

152-
### Security
153-
No considerable security concerns are expected, but we welcome community feedback.
75+
// Determine the user's preferred audio output device through a UI prompt or app settings.
76+
const preferredAudioOutputDevice = await selectPreferredAudioOutputDevice(audioOutputDeviceList);
15477

155-
Discussion: https://github.com/w3c/mediacapture-output/issues/63
78+
// Set the default audio output device on all renderers for this top-level frame and all of its child frames.
79+
await navigator.mediaDevices.setPreferredSinkId(preferredAudioOutputDevice.deviceId);
80+
```
15681

157-
## Alternative Solutions
158-
An alternative solution involves introducing a similar API to setSinkId specifically for the HTMLIFrameElement. This enhancement would allow the parent frame to modify the default audio output for its sub frames. Let’s call this new API HTMLIFrameElement::setPreferredSinkId(deviceId) or HTMLIFrameElement::setPreferredSinkId(AudioSinkOptions).
82+
## Alternative Solution
83+
Add `setPreferredSinkId()` to `HTMLIFrameElement`:
15984

16085
```js
161-
162-
[Exposed=Window]
163-
interface HTMLIFrameElement : EventTarget {
164-
...
165-
void setPreferredSinkId(DOMString deviceId);
86+
partial interface HTMLIFrameElement {
87+
void setPreferredSinkId((DOMString or AudioSinkOptions) sinkId);
16688
};
167-
168-
deviceId:
169-
This attribute contains the ID of the audio device through which output is being delivered, or the empty string if output is delivered
170-
through the user-agent default device. If nonempty, this ID should be equal to the deviceId attribute of one of the MediaDeviceInfo values
171-
returned from enumerateDevices().
172-
17389
```
17490

175-
Here are the key points of this approach:
176-
* Functionality: The setPreferredSinkId API can be invoked from any frame, enabling changes across all child frames within the sub frame hierarchy. However, it does not alter the audio output of the frame from which it is called. Therefore, developers must still manage their own frame’s audio output using setSinkId for relevant media elements and audio contexts.
177-
* Potential Benefit: By utilizing {type: 'none'} as a parameter for setPreferredSinkId, we could easily support a ‘muted’ feature for iframes. This would enhance flexibility in muting audio within the iframe context. ( {type : ‘none} scheme is already supported from AudioContext::setSinkId)
178-
* Drawback: The frame itself must explicitly call setSinkId for any specific audio outputs it requires in the current frame.
179-
180-
In summary, the pros of this alternative include muting capabilities while maintaining the responsibility for individual frame audio settings.
91+
This enables the parent frame to modify the default audio output for a child frame using a `DOMString` device ID. By supporting [`AudioSinkOptions`](https://webaudio.github.io/web-audio-api/#AudioSinkOptions), `HTMLIFrameElement::setPreferredSinkId()` also enables the parent frame to mute a child frame using the sink options `{ type: 'none' }`.
18192

182-
## Open Questions
183-
* Do we have to provide permission policy (e.g. speaker-selection) for the top-level frame to allow cross-origin sub frame to be able to call the API?
93+
However, this alternative does not fully address the use case that overrides the default audio output device for all audio renderers in a top-level frame and its child frames. For nested frames, each parent frame needs to call `setPreferredSinkId()` on its child frames. For nested cross-origin frames, changing audio output requires collaboration between frames using `postMessage()`. The top-level frame must also call `setSinkId()` on each audio renderer to override the browser's default audio output device.

0 commit comments

Comments
 (0)