Skip to content

Commit

Permalink
FF116 Audio Output Device API updates (#27818)
Browse files Browse the repository at this point in the history
* FF116 Audio Output Device API updates

* selectAudioOutput - fix up errors

* setSinkId, sinkId fixes

* minor fix

* Tidy enumeratedDevices

* enumerateDevices - currently available to make it clear that this list can change

* Improve the overview

* Apply suggestions from code review

* Update index.md

* Run prettier

* Apply suggestions from code review

Co-authored-by: wbamberg <[email protected]>

* Fulfills not receives for promise

* Spec urls fixup

* Remove early note of security considerations

* Better explain persisting IDs

* Prettier

---------

Co-authored-by: wbamberg <[email protected]>
  • Loading branch information
hamishwillee and wbamberg committed Jul 20, 2023
1 parent a384997 commit d458c22
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 37 deletions.
76 changes: 63 additions & 13 deletions files/en-us/web/api/audio_output_devices_api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ browser-compat:
- api.MediaDevices.selectAudioOutput
- api.HTMLMediaElement.setSinkId
- api.HTMLMediaElement.sinkId
- http.headers.Permissions-Policy.speaker-selection
spec-urls: https://w3c.github.io/mediacapture-output/#dom-mediadevices-selectaudiooutput
---

{{DefaultAPISidebar("Audio Output Devices API")}}{{securecontext_header}}{{SeeCompatTable}}

The **Audio Output Devices API** allows web applications to prompt users about what audio output device should be used for playback.
The **Audio Output Devices API** allows web applications to prompt users about what output device should be used for audio playback.

## Concepts and usage

Operating systems commonly allow users to specify that audio should be played from speakers, a Bluetooth headset, or some other audio output device.
This API allows applications to provide this same functionality from within a web page, which may provide a better user experience.
This API allows applications to provide this same functionality from within a web page.

Setting a particular audio output device requires explicit user permission, as the user may be in a location where playing audio is not appropriate through some output devices.
For more information see [Security requirements](#security-requirements) below.
Even if allowed by a permission policy, access to a particular audio output device still requires explicit user permission, as the user may be in a location where playing audio through some output devices is not appropriate.

The API provides the [`MediaDevices.selectAudioOutput()`](/en-US/docs/Web/API/MediaDevices/selectAudioOutput) method that allows users to select their desired audio output from those that are allowed by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) directive of the [`Permissions-Policy`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy) HTTP header for the document.
The selected device then has user permission, allowing it to be enumerated with [`MediaDevices.enumerateDevices()`](/en-US/docs/Web/API/MediaDevices/enumerateDevices) and set as the audio output device using [`HTMLMediaElement.setSinkId()`](/en-US/docs/Web/API/HTMLMediaElement/setSinkId).

Audio devices may arbitrarily connect and disconnect.
Applications that wish to react to this kind of change can listen to the [`devicechange` event](/en-US/docs/Web/API/MediaDevices/devicechange_event) and use [`enumerateDevices()`](/en-US/docs/Web/API/MediaDevices/enumerateDevices) to determine if `sinkId` is present in the returned devices.
Expand All @@ -32,30 +36,76 @@ This might trigger, for example, pausing or unpausing playback.

The Audio Output Devices API extends the following APIs, adding the listed features:

#### MediaDevices

- [`MediaDevices.selectAudioOutput()`](/en-US/docs/Web/API/MediaDevices/selectAudioOutput)
- : This method prompts the user to select a specific audio output device, for example a speaker or headset.
Selecting a device grants user permission to use that device and returns information about the device, including its ID.

#### HTMLMediaElement

- [`HTMLMediaElement.setSinkId()`](/en-US/docs/Web/API/HTMLMediaElement/setSinkId)
- : This method sets the ID of the audio device to use for output, which will be used if permitted.
- [`HTMLMediaElement.sinkId`](/en-US/docs/Web/API/HTMLMediaElement/sinkId)
- : This property returns the unique ID of the audio device being used for output, or an empty string if the default user agent device is being used.

## Security requirements

Access to [`MediaDevices.selectAudioOutput()`](/en-US/docs/Web/API/MediaDevices/selectAudioOutput) is subject to the following constraints:
Access to the API is subject to the following constraints:

- It may only be used in a [secure context](/en-US/docs/Web/Security/Secure_Contexts).
- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/midi) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- [Transient user activation](/en-US/docs/Web/Security/User_activation) is required.
The user has to interact with the page or a UI element for this feature to work.
- The user must explicitly grant permission to use the audio output device through a user-agent specific mechanism, or have previously granted permission.
Note that if access is denied by a permission policy it cannot be granted by a user permission.
- All methods and properties may only be called in a [secure context](/en-US/docs/Web/Security/Secure_Contexts).

User permission to set the output device is also implicitly granted if the user has already granted permission to use a media input device in the same group, using [`MediaDevices.getUserMedia()`](/en-US/docs/Web/API/MediaDevices/getUserMedia).
- [`MediaDevices.selectAudioOutput()`](/en-US/docs/Web/API/MediaDevices/selectAudioOutput) grants user permission for a selected device to be used as the audio output sink:

Other methods/properties also require a secure context and the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/midi) permission policy.
- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- [Transient user activation](/en-US/docs/Web/Security/User_activation) is required.
The user has to interact with the page or a UI element for the method to be called.

- [`HTMLMediaElement.setSinkId()`](/en-US/docs/Web/API/HTMLMediaElement/setSinkId) sets a permitted ID as the audio output:

- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- User permission is required to set a non-default device ID.
- This can come from selection in the prompt launched by `MediaDevices.selectAudioOutput()`
- User permission to set the output device is also implicitly granted if the user has already granted permission to use a media input device in the same group with [`MediaDevices.getUserMedia()`](/en-US/docs/Web/API/MediaDevices/getUserMedia).

The permission status can be queried using the [Permissions API](/en-US/docs/Web/API/Permissions_API) method [`navigator.permissions.query()`](/en-US/docs/Web/API/Permissions/query), passing a permission descriptor with the `speaker-selection` permission.

## Examples

Here's an example of using `selectAudioOutput()`, within a function that is triggered by a button click, and then setting the selected device as the audio output.

The code first checks if `selectAudioOutput()` is supported, and if it is, uses it to select an output and return a [device ID](/en-US/docs/Web/API/MediaDeviceInfo/deviceId).
We then play some some audio using the default output, and then call `setSinkId()` in order to switch to the selected output device.

```js
document.querySelector("#myButton").addEventListener("click", async () => {
if (!navigator.mediaDevices.selectAudioOutput) {
console.log("selectAudioOutput() not supported or not in secure context.");
return;
}

// Display prompt to select device
const audioDevice = await navigator.mediaDevices.selectAudioOutput();

// Create an audio element and start playing audio on the default device
const audio = document.createElement("audio");
audio.src = "https://example.com/audio.mp3";
audio.play();

// Change the sink to the selected audio output device.
audio.setSinkId(audioDevice.deviceId);
});
```

Note that if you log the output details, they might look something like this:

```js
console.log(
`${audioDevice.kind}: ${audioDevice.label} id = ${audioDevice.deviceId}`,
);
// audiooutput: Realtek Digital Output (Realtek(R) Audio) id = 0wE6fURSZ20H0N2NbxqgowQJLWbwo+5ablCVVJwRM3k=
```

## Specifications

{{Specifications}}
Expand Down
2 changes: 1 addition & 1 deletion files/en-us/web/api/htmlmediaelement/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ _This interface also inherits properties from its ancestors {{domxref("HTMLEleme
- {{domxref("HTMLMediaElement.seeking")}} {{ReadOnlyInline}}
- : Returns a boolean that indicates whether the media is in the process of seeking to a new position.
- {{domxref("HTMLMediaElement.sinkId")}} {{ReadOnlyInline}}
- : Returns a string that is the unique ID of the audio device delivering output, or an empty string if it is using the user agent default. This ID should be one of the `MediaDeviceInfo.deviceid` values returned from {{domxref("MediaDevices.enumerateDevices()")}}, `id-multimedia`, or `id-communications`.
- : Returns a string that is the unique ID of the audio device delivering output, or an empty string if the user agent default audio device is being used.
- {{domxref("HTMLMediaElement.src")}}
- : A string that reflects the [`src`](/en-US/docs/Web/HTML/Element/video#src) HTML attribute, which contains the URL of a media resource to use.
- {{domxref("HTMLMediaElement.srcObject")}}
Expand Down
15 changes: 11 additions & 4 deletions files/en-us/web/api/htmlmediaelement/setsinkid/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ browser-compat: api.HTMLMediaElement.setSinkId

{{APIRef("HTML DOM")}}

The **`HTMLMediaElement.setSinkId()`** method sets the ID of the audio device to use for output and returns a [`Promise`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
This only works when the application is authorized to use the specified device.
The **`HTMLMediaElement.setSinkId()`** method of the [Audio Output Devices API](/en-US/docs/Web/API/Audio_Output_Devices_API) sets the ID of the audio device to use for output and returns a [`Promise`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

This only works when the application is permitted to use the specified device.
For more information see the [security requirements](#security_requirements) below.

## Syntax

Expand Down Expand Up @@ -40,16 +42,21 @@ A {{jsxref("Promise")}} that resolves to {{jsxref("undefined")}}.
Access to the API is subject to the following constraints:

- The method must be called in a [secure context](/en-US/docs/Web/Security/Secure_Contexts).
- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/midi) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- User permission is required to access a non-default device.
The user grants permission by selecting the device associated with the ID in the prompt displayed by [`MediaDevices.selectAudioOutput()`](/en-US/docs/Web/API/MediaDevices/selectAudioOutput).

## Examples

This example shows how to select an audio output device from the array returned by [`MediaDevices.enumerateDevices()`](/en-US/docs/Web/API/MediaDevices/enumerateDevices), and set it as the sink for audio.
Note that the result of `enumerateDevices()` only includes devices for which user permission is not required or has already been been granted.

```js
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find((device) => device.kind === "audiooutput");
const audio = document.createElement("audio");
await audio.setSinkId(audioDevice.deviceId);
console.log(`Audio is being played on ${audio.sinkId}`);
console.log(`Audio is being output on ${audio.sinkId}`);
```

## Specifications
Expand Down
7 changes: 4 additions & 3 deletions files/en-us/web/api/htmlmediaelement/sinkid/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ browser-compat: api.HTMLMediaElement.sinkId

{{APIRef("HTML DOM")}}

The **`HTMLMediaElement.sinkId`** read-only property returns a string that is the unique ID of the audio device delivering output.
If it is using the user agent default, it returns an empty string.
The **`HTMLMediaElement.sinkId`** read-only property of the [Audio Output Devices API](/en-US/docs/Web/API/Audio_Output_Devices_API) returns a string that is the unique ID of the device to be used for playing audio output.

This ID should be one of the {{domxref("MediaDeviceInfo.deviceId")}} values returned from {{domxref("MediaDevices.enumerateDevices()")}}, `id-multimedia`, or `id-communications`.
If the user agent default device is being used, it returns an empty string.

## Value

A string.
A string indicating the current audio output device, or the empty string if the default user agent output device is being used.

## Security requirements

Expand Down
14 changes: 7 additions & 7 deletions files/en-us/web/api/mediadevices/enumeratedevices/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ browser-compat: api.MediaDevices.enumerateDevices

{{APIRef("WebRTC")}}

The {{domxref("MediaDevices")}} method **`enumerateDevices()`** requests a list of the available media input and output devices, such as microphones, cameras, headsets, and so forth.
The returned {{jsxref("Promise")}} is resolved with a {{domxref("MediaDeviceInfo")}} array describing the devices.
The {{domxref("MediaDevices")}} method **`enumerateDevices()`** requests a list of the currently available media input and output devices, such as microphones, cameras, headsets, and so forth.
The returned {{jsxref("Promise")}} is resolved with an array of {{domxref("MediaDeviceInfo")}} objects describing the devices.

Access to particular devices is gated by the [Permissions API](/en-US/docs/Web/API/Permissions_API).
The list of returned devices will omit any devices for which the corresponding permission has not been granted, including: [`microphone`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/microphone), [`camera`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/camera), [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) (for output devices), and so on.
The returned list will omit any devices that are blocked by the document [Permission Policy](/en-US/docs/Web/HTTP/Headers/Permissions-Policy): [`microphone`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/microphone), [`camera`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/camera), [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) (for output devices), and so on.
Access to particular non-default devices is also gated by the [Permissions API](/en-US/docs/Web/API/Permissions_API), and the list will omit devices for which the user has not granted explicit permission.

## Syntax

Expand All @@ -26,11 +26,11 @@ None.

### Return value

A {{ jsxref("Promise") }} that receives an array of {{domxref("MediaDeviceInfo")}} objects when the promise is fulfilled. Each object in the array describes one of the available media input and output devices.
A {{ jsxref("Promise") }} that is fulfilled with an array of {{domxref("MediaDeviceInfo")}} objects.
Each object in the array describes one of the available media input and output devices.
The order is significant — the default capture devices will be listed first.

Only device types for which permission has been granted are "available".
Also note that if a [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/speaker-selection) [Permissions Policy](/en-US/docs/Web/HTTP/Permissions_Policy) is used to block use of audio outputs, they won't be available in the list.
Other than default devices, only devices for which permission has been granted are "available".

If enumeration fails, the promise is rejected.

Expand Down
22 changes: 13 additions & 9 deletions files/en-us/web/api/mediadevices/selectaudiooutput/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ browser-compat: api.MediaDevices.selectAudioOutput

{{APIRef("WebRTC")}} {{SeeCompatTable}}

The {{domxref("MediaDevices")}} method **`selectAudioOutput()`** prompts the user to select a specific audio output device, for example a speaker or headset.
The {{domxref("MediaDevices.selectAudioOutput()")}} method of the [Audio Output Devices API](/en-US/docs/Web/API/Audio_Output_Devices_API) prompts the user to select an audio output device, such as a speaker or headset. If the user selects a device, the method grants user permission to use the selected device as an audio output sink.

Following selection, if the device is available it can be enumerated using [`MediaDevices.enumerateDevices()`](/en-US/docs/Web/API/MediaDevices/enumerateDevices) and set as the audio output sink using [`HTMLMediaElement.setSinkId()`](/en-US/docs/Web/API/HTMLMediaElement/setSinkId).

On success, the returned {{jsxref("Promise")}} is resolved with a {{domxref("MediaDeviceInfo")}} describing the selected device.

## Syntax
Expand All @@ -28,18 +31,19 @@ selectAudioOutput(options)

- `deviceId` {{Optional_Inline}}

- : A string representing the id of the (only) device to display in the prompt (with default value: "").
- : A string representing the ID of a single previously exposed/permitted device.
If not set, a prompt with all available audio output devices will be displayed.

The option is intended for applications that want to store a device id so that the same device can be used by default in future sessions.
Note that the method may return a new ID for the same device, and that persisted ids _must be passed_ through `selectAudioOutput()` successfully before they will work with {{domxref("HTMLMediaElement.setSinkId","setSinkId()")}}.

> **Note:** A user agent may choose to skip prompting the user if a specified non-null id was previously exposed to the user by `selectAudioOutput()` in an earlier session.
> In this case the user agent may simply resolve with this device id, or a new id for the same device if it has changed.
>
> This is intended for applications that want to use persisted device ids.
> The ids _must be passed_ through `selectAudioOutput()` successfully before they will work with {{domxref("HTMLMediaElement.setSinkId","setSinkId()")}}.
> If permission for the specified device was previously granted but has since been revoked, the user-agent might display all allowed devices, highlighting the one with the specified ID.
### Return value

A {{ jsxref("Promise") }} that receives a {{domxref("MediaDeviceInfo")}} object when the promise is fulfilled.
The object describes the user-selected audio output device.
A {{ jsxref("Promise") }} that is fulfilled with a {{domxref("MediaDeviceInfo")}} object that describes the audio output device selected by the user.

### Exceptions

Expand All @@ -58,8 +62,6 @@ Access to the API is subject to the following constraints:
- [Transient user activation](/en-US/docs/Web/Security/User_activation) is required.
The user has to interact with the page or a UI element for this feature to work.
- Access may be gated by the [`speaker-selection`](/en-US/docs/Web/HTTP/Headers/Permissions-Policy/midi) HTTP [Permission Policy](/en-US/docs/Web/HTTP/Permissions_Policy).
- The user must explicitly grant permission to use the audio output device through a user-agent specific mechanism, or have previously granted permission.
Note that if access is denied by a permission policy it cannot be granted by a user permission.

The permission status can be queried using the [Permissions API](/en-US/docs/Web/API/Permissions_API) method [`navigator.permissions.query()`](/en-US/docs/Web/API/Permissions/query), passing a permission descriptor with the `speaker-selection` permission.

Expand Down Expand Up @@ -103,4 +105,6 @@ audiooutput: Realtek Digital Output (Realtek(R) Audio) id = 0wE6fURSZ20H0N2Nbxqg

## See also

- [`HTMLMediaElement.setSinkId()`](/en-US/docs/Web/API/HTMLMediaElement/setSinkId)
- [`HTMLMediaElement.sinkId`](/en-US/docs/Web/API/HTMLMediaElement/sinkId)
- [WebRTC](/en-US/docs/Web/API/WebRTC_API) - the introductory page to the API

0 comments on commit d458c22

Please sign in to comment.