Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AudioContext.setSinkId() #2498

Merged
merged 18 commits into from Oct 4, 2022
211 changes: 182 additions & 29 deletions index.bs
Expand Up @@ -1511,6 +1511,28 @@ enum AudioContextLatencyCategory {
</table>
</div>

<pre class="idl">
enum AudioContextSinkCategory {
"none"
};
</pre>

<div class="enum-description">
<table class="simple" dfn-type=enum-value dfn-for="AudioContextSinkCategory">
<thead>
<tr>
<th scope="col" colspan="2">
Enumeration description
hoch marked this conversation as resolved.
Show resolved Hide resolved
<tbody>
<tr>
<td>
"<dfn>none</dfn>"
<td>
The audio graph will be processoed without being played through an
hoch marked this conversation as resolved.
Show resolved Hide resolved
audio output device.
</table>
</div>

<div class="correction proposed" id="c2444-1">
<span class="marker">Proposed Addition
<a href="https://github.com/WebAudio/web-audio-api/issues/2444">Issue
Expand Down Expand Up @@ -1546,7 +1568,9 @@ enum AudioContextLatencyCategory {
constructor (optional AudioContextOptions contextOptions = {});
readonly attribute double baseLatency;
readonly attribute double outputLatency;
readonly attribute DOMString sinkId;
[SecureContext] readonly attribute AudioRenderCapacity renderCapacity;
attribute EventHandler onsinkchange;
AudioTimestamp getOutputTimestamp ();
Promise<undefined> resume ();
Promise<undefined> suspend ();
Expand All @@ -1567,13 +1591,18 @@ allows the context state to transition from "{{AudioContextState/suspended}}" to
and to allow it only when the {{AudioContext}}'s [=relevant global object=] has
[=sticky activation=].

{{AudioContext}} has an internal slot:
{{AudioContext}} has two internal slots:

<dl dfn-type=attribute dfn-for="AudioContext">
: <dfn>[[suspended by user]]</dfn>
::
A boolean flag representing whether the context is suspended by user code.
The initial value is <code>false</code>.

: <dfn>[[sink ID]]</dfn>
::
A {{DOMString}} representing the ID of the current audio output device. The
initial value is <code>""</code>.
</dl>

<h4 id="AudioContext-constructors">
Expand All @@ -1592,44 +1621,80 @@ Constructors</h4>
<span class="synchronous">When creating an {{AudioContext}},
execute these steps:</span>

1. Set a {{[[control thread state]]}} to <code>suspended</code> on the {{AudioContext}}.

2. Set a {{[[rendering thread state]]}} to <code>suspended</code> on the {{AudioContext}}.

3. Let <dfn attribute for="AudioContext">[[pending resume promises]]</dfn> be a
slot on this {{AudioContext}}, that is an initially empty ordered list of
promises.

4. If <code>contextOptions</code> is given, apply the options:
1. Let |context| be a new {{AudioContext}} object.

1. Set the internal latency of this {{AudioContext}}
according to <code>contextOptions.{{AudioContextOptions/latencyHint}}</code>, as described
in {{AudioContextOptions/latencyHint}}.
1. Let |document| be the current settings object's
[=associated Document=].

2. If <code>contextOptions.{{AudioContextOptions/sampleRate}}</code> is specified,
set the {{BaseAudioContext/sampleRate}}
of this {{AudioContext}} to this value. Otherwise, use
the sample rate of the default output device. If the
selected sample rate differs from the sample rate of the
output device, this {{AudioContext}} MUST resample the
audio output to match the sample rate of the output device.
1. Set a {{[[control thread state]]}} to <code>suspended</code> on
|context|.

Note: If resampling is required, the latency of the
AudioContext may be affected, possibly by a large
amount.
1. Set a {{[[rendering thread state]]}} to <code>suspended</code> on
|context|.

5. If the context is <a>allowed to start</a>, send a
<a>control message</a> to start processing.
1. Let
<dfn attribute for="AudioContext">[[pending resume promises]]</dfn> be
a slot on |context|, that is an initially empty ordered list of
promises.

6. Return this {{AudioContext}} object.
1. If <code>contextOptions</code> is given, perform the following
substeps:

1. If {{AudioContextOptions/sinkId}} is specified, let |sinkId| be the
value of <code>contextOptions.{{AudioContextOptions/sinkId}}</code>
and runthe following substeps:

1. If |document| is not allowed to use the feature identified by
<code>"speaker-selection"</code>, |sinkId| is the empty string,
or |sinkId| does not match any audio output device identified by
the result that would be provided by
{{MediaDevices/enumerateDevices()}}, abort these substeps.

1. Set {{AudioContext/[[sink ID]]}} to |sinkId|.

1. Set the internal latency of |context| according to
<code>contextOptions.{{AudioContextOptions/latencyHint}}</code>, as
described in {{AudioContextOptions/latencyHint}}.

1. If <code>contextOptions.{{AudioContextOptions/sampleRate}}</code> is
specified, set the {{BaseAudioContext/sampleRate}} of |context| to
this value. Otherwise, use the sample rate of the default output
device or the output device selected by |sinkId| if present.

If <code>contextOptions.{{AudioContextOptions/sampleRate}}</code>
differs from the sample rate of the output device, the user agent
MUST resample the audio output to match the sample rate of the
output device.

Note: If resampling is required, the latency of |context| may be
affected, possibly by a large amount.

1. If the context is <a>allowed to start</a>, send a
<a>control message</a> to start processing.

1. Return |context| object.
</div>

<div algorithm="sending a control message to start processing">
Sending a <a>control message</a> to start processing means
executing the following steps:

1. Attempt to <a href="#acquiring">acquire system resources</a>.
In case of failure, abort the following steps.
1. Attempt to <a href="#acquiring">acquire system resources</a> to use an
audio output device for rendering. In case of failure, abort the
hoch marked this conversation as resolved.
Show resolved Hide resolved
following steps.

1. If {{AudioContext/[[sink ID]]}} is
<code>"{{AudioContextSinkCategory/none}}"</code>, prepare to render
the audio graph without an actual audio output device. Abort these
substeps.

1. If {{AudioContext/[[sink ID]]}} is the empty string, prepare to
render the audio graph with a default audio output device. Abort
these substeps.

1. If {{AudioContext/[[sink ID]]}} is not the empty string, prepare to
render the audio graph with the underlying audio output device
identified by {{AudioContext/[[sink ID]]}}.

3. Set the {{[[rendering thread state]]}} to <code>running</code> on the {{AudioContext}}.

Expand Down Expand Up @@ -1708,6 +1773,20 @@ Attributes</h4>

</ins>
</div>

: <dfn>sinkId</dfn>
::
This attribute contains the ID of the current audio output device, or the
empty string for the user-agent default device. The attribute MUST return
the value of {{AudioContext/[[sink ID]]}} internal slot upon getting.
hoch marked this conversation as resolved.
Show resolved Hide resolved

: <dfn>onsinkchange</dfn>
::
An EventHandler for {{AudioContext/setSinkId()}}. This event will be
hoch marked this conversation as resolved.
Show resolved Hide resolved
dispatched when changing the output device is completed. NOTE: This is not
hoch marked this conversation as resolved.
Show resolved Hide resolved
dispatched for the initial device selection upon the construction of
{{AudioContext}}. The {{BaseAudioContext/onstatechange}} is available
to check the readiness of the initial output device.
</dl>

<h4 id="AudioContext-methods">
Expand Down Expand Up @@ -2073,6 +2152,74 @@ Methods</h4>
<div>
<em>Return type:</em> {{Promise}}&lt;{{undefined}}&gt;
</div>

: <dfn>setSinkId((DOMString or AudioContextSinkCategory) sinkId)</dfn>
::
Sets the ID of the output device if it is permitted to play audio. When
this method is invoked, the user agent MUST run the following steps:

<div algorithm="AudioContext.setSinkId()">
1. Let |document| be the current settings object's
[=associated Document=].

1. If |document| is not allowed to use the feature identified by
<code>"speaker-selection"</code>, return [=a promise rejected with=] a
new {{DOMException}} whose name is "{{NotAllowedError}}".

1. Let |context| be the {{AudioContext}} object on which this method was
invoked.

1. Let |sinkId| be the method's first argument.

1. If |sinkId| is equal to |context|'s {{AudioContext/[[sink ID]]}}
internal slot, return a promise resolved with {{undefined}}.

1. Let |p| be a new promise.

1. Run the following substeps:

1. If the {{BaseAudioContext/state}} attibutes of |context| is
"{{AudioContextState/closed}}", reject |p| with a new
{{DOMException}} whose name is "{{InvalidStateError}}" and abort
these substeps.

1. If |sinkId| is the empty string, reject |p| with a new
{{DOMException}} whose name is "{{NotFoundError}}" and abort these
substeps.

1. If |sinkId| does not match any audio output device identified by
hoch marked this conversation as resolved.
Show resolved Hide resolved
the result that would be provided by
{{MediaDevices/enumerateDevices()}}, reject |p| with a new
{{DOMException}} whose name is "{{NotFoundError}}" and abort these
substeps.

1. If the device specified by |sinkId| is not the user agent's
default device or is not permitted to play audio through, reject |p|
with a new {{DOMException}} whose name is {{NotAllowedError}} and
abort these substeps.

1. Stop playing audio out of the current output device.
hoch marked this conversation as resolved.
Show resolved Hide resolved

1. Run the following substeps to prepare an audio output device for
rendering:

1. Set context's {{AudioContext/[[sink ID]]}} internal slot to
|sinkId|.

1. If {{AudioContext/[[sink ID]]}} is <code>"{{AudioContextSinkCategory/none}}"</code>, acquire system
resources to render the audio graph without an actual audio
output device. Abort these substeps.

1. If {{AudioContext/[[sink ID]]}} is not the empty string, acquire system
resources to render the audio graph with the underlying audio
output device identified by {{AudioContext/[[sink ID]]}}.
hoch marked this conversation as resolved.
Show resolved Hide resolved

1. Queue a task to perform the following steps:

1. Resolve |p|.

1. Return |p|.
</div>
</dl>

<h4 dictionary id="AudioContextOptions">
Expand All @@ -2085,6 +2232,7 @@ specify user-specified options for an {{AudioContext}}.
dictionary AudioContextOptions {
(AudioContextLatencyCategory or double) latencyHint = "interactive";
float sampleRate;
(DOMString or AudioContextSinkCategory) sinkId;
};
</pre>

Expand Down Expand Up @@ -2117,6 +2265,11 @@ Dictionary {{AudioContextOptions}} Members</h5>
If {{AudioContextOptions/sampleRate}} is not
specified, the preferred sample rate of the output device for
this {{AudioContext}} is used.

: <dfn>sinkId</dfn>
::
Set the ID of the audio output device. See {{AudioContext/sinkId}} for
more details.
</dl>

<h4 dictionary id="AudioTimestamp">
Expand Down Expand Up @@ -10111,7 +10264,7 @@ Attributes</h4>
: <dfn>oversample</dfn>
::
Specifies what type of oversampling (if any) should be used
when applying the shaping curve. The default value is "{{none}}",
when applying the shaping curve. The default value is "{{OverSampleType/none}}",
meaning the curve will be applied directly to the input
samples. A value of "{{2x}}" or "{{4x}}" can improve the quality of the
processing by avoiding some aliasing, with the "{{4x}}" value
Expand Down