1. 引言
人们越来越多地通过 Web 消费媒体(音频/视频), Web 已成为访问这类内容的主要渠道。 但是,Web 上的媒体通常缺乏与底层平台的无缝集成。 Audio Session API 通过在支持音频会话管理或类似音频焦点功能的平台上 增强媒体处理来弥补这一空白。 此 API 改进了基于 Web 的音频与其他应用的交互方式, 允许根据上下文进行更好的音频混音或独占播放, 从而在各种设备上提供更一致且更集成的媒体体验。
此外,一些平台会根据媒体播放以及用于播放音频的 API 自动管理站点的音频会话。 但是,这种行为可能并不总是符合用户预期。 此 API 允许开发者覆盖默认行为,并获得对音频会话的更多控制。
2. 概念
网页可以通过多种方式进行音频处理,结合使用不同的 API,例如 HTMLMediaElement
或 AudioContext。
此音频处理有开始和停止,它聚合了所使用的所有不同音频 API。
音频会话
表示这种聚合的音频处理。它允许网页表达该网页所执行的音频处理的一般性质。
音频会话可以具有特定的 类型,并且 处于特定的状态。 音频会话管理一组单独源(麦克风录制) 和接收端(音频渲染)的音频,这些源和接收端称为音频会话 元素。
如果一个音频会话元素的可听标志为 true,则它是一个
可听元素。
此外,音频会话元素具有关联步骤,用于处理各种状态 变化。默认情况下,这些步骤中的每一个都是空的步骤列表:
-
元素 更新步骤,每当音频会话状态变化时运行。
-
元素 挂起步骤,当音频会话状态从
active移动到interrupted或inactive时运行。 -
元素 恢复步骤,当音频会话状态从
interrupted移动到active时运行。
本规范在 § 6 音频源和接收端集成一节中,为一些音频会话的元素定义这些步骤、默认类型以及可听标志。 定义其他元素的规范需要定义这些步骤和属性。
顶级浏览上下文具有一个所选音频会话。在任何音频会话发生
变化的情况下,用户代理将更新哪个音频会话成为所选音频会话。
如果顶级浏览上下文的所选音频会话不为
null 且其状态为 active,
则称该顶级浏览上下文具有音频焦点。
3. AudioSession
接口
AudioSession
是此 API 的主接口。
它通过 Navigator
接口访问(见 § 4 Navigator 接口的扩展)。
[Exposed =Window ]interface :AudioSession EventTarget {attribute AudioSessionType ;type readonly attribute AudioSessionState ;state attribute EventHandler ; };onstatechange
要在 realm 中创建一个 AudioSession
对象,运行以下步骤:
-
令 audioSession 为 realm 中的一个新的
AudioSession对象,并使用以下内部槽初始化: -
返回 audioSession。
每个 AudioSession
对象都唯一地绑定到其底层音频会话。
AudioSession
state 属性反映其音频
会话的状态。
获取时,它必须返回 AudioSession
的 [[state]] 值。
AudioSession
type 属性反映其音频
会话的类型,但
auto
除外。
获取时,它必须返回 AudioSession
的 [[type]] 值。
设置时,它必须以 newValue 作为要在 audioSession 上设置的新值运行以下步骤:
-
如果 audioSession.[[type]] 等于 newValue,则中止这些步骤。
-
将 audioSession.[[type]] 设置为 newValue。
-
更新 audioSession 的类型。
3.1. 音频会话类型
按照惯例,存在若干不同的音频会话类型,用于不同
目的。
在 API 中,它们由 AudioSessionType
枚举表示:
playback- 播放音频,用于视频或音乐播放、播客等。它们不应与其他播放音频混合。(也许)它们应无限期地暂停所有其他音频。
transient- 瞬态音频,例如通知提示音。它们通常应在播放音频之上播放(并且也可能“压低”持久音频)。
transient-solo- 瞬态独奏音频,例如行车导航。它们应暂停/静音所有其他音频并独占播放。 当一个 transient-solo 音频结束时,它应恢复被暂停/静音的音频。
ambient- 环境音频,可与其他类型的音频混合。这在一些特殊情况下很有用,例如 用户想要混合来自多个页面的音频时。
play-and-record- 播放和录制音频,用于录制音频。这在使用麦克风或视频会议应用程序中很有用。
auto- Auto 允许用户代理根据网页对音频的使用选择最佳音频会话类型。
这是
AudioSession的默认类型。
enum {AudioSessionType "auto" ,"playback" ,"transient" ,"transient-solo" ,"ambient" ,"play-and-record" };
如果一个 AudioSessionType
是 playback、
play-and-record
或 transient-solo,
则它是独占类型。
3.2. 音频会话状态
音频会话可以处于以下
状态之一,这些状态在 API 中由 AudioSessionState
枚举表示:
active- 音频会话正在播放声音或录制麦克风。
interrupted- 音频会话未播放声音也未录制麦克风, 但在不再被中断时可以恢复。
inactive- 音频会话未播放声音也未录制麦克风。
enum {AudioSessionState "inactive" ,"active" ,"interrupted" };
音频会话的状态可能
变化,这将通过通知状态变化的步骤自动反映在其 AudioSession
对象上。
4. Navigator 接口的扩展
每个 Window
都有一个关联的
AudioSession,它是一个 AudioSession
对象。
它表示用户代理用于自动设置音频会话参数的默认音频会话。
当音频会话元素开始或结束播放时,
用户代理将请求或放弃音频焦点。
在创建 Window
对象时,其关联的
AudioSession 必须被设置为一个新创建的 AudioSession
对象,其 realm 为该 Window
对象的相关 realm。
关联的
AudioSession 的元素列表,会随着 Window
对象的音频源和接收端被创建或移除而动态更新。
[Exposed =Window ]partial interface Navigator { // The default audio session that the user agent will use when media elements start/stop playing.readonly attribute AudioSession ; };audioSession
5. 音频会话算法
5.1. 更新 AudioSession 的 type
要更新类型 audioSession,用户代理必须运行以下步骤:
-
如果 audioSession.[[isTypeBeingApplied]] 为
true, 则中止这些步骤。 -
将 audioSession.[[isTypeBeingApplied]] 设置为
true。 -
排队一个任务以运行以下步骤:
-
将 audioSession.[[isTypeBeingApplied]] 设置为
false。 -
如果 audioSession.[[type]] 与 audioSession.[[appliedType]] 相同,则中止这些步骤。
-
将 audioSession.[[appliedType]] 设置为 audioSession.[[type]]。
-
更新所有 AudioSession 状态,即 audioSession 的顶级浏览上下文的所有状态,并使用 audioSession。
-
对于 audioSession.[[elements]] 中的每个 element,更新 element。
-
令 newType 为计算 audioSession 的类型的结果。
-
5.2. 更新 AudioSession 的 state
当音频会话元素开始或停止时,用户代理将运行会
设置状态的步骤,即通过
停用和尝试激活算法来设置
一个音频会话的状态。
将音频会话的状态设置为 active
会产生后果,尤其是当音频会话的类型是独占类型时:
-
它可以停用 顶级浏览上下文的
AudioSession对象,如下面的算法所定义。 -
它可以暂停另一个标签页或另一个应用程序的音频。
反过来,音频会话
状态也可以在音频会话元素变化之外被
修改。
当用户代理观察到这种修改时,用户代理必须排队一个任务,以使用 audioSession、绑定到
被修改的音频会话的
AudioSession
对象,并以 newState 作为新的音频会话状态,来通知状态变化。
要使用 audioSession 和 newState 通知 状态变化,用户代理必须运行以下步骤:
-
令 isMutatingState 为
true,如果 audioSession.[[state]] 不是 newState,否则为false。 -
将 audioSession.[[state]] 设置为 newState。
-
如果 newState 为
inactive, 则将 audioSession.[[interruptedElements]] 设置为空列表。 -
对于 audioSession.[[elements]] 中的每个 element,更新 element。
-
如果 isMutatingState 为
false,则中止这些步骤。 -
更新所有 AudioSession 状态,即 audioSession 的顶级浏览上下文的所有状态,并使用 audioSession。
-
在 audioSession 上触发名为 statechange 的事件。
要停用名为
audioSession 的 AudioSession,用户代理必须运行以下步骤:
-
并行运行以下步骤:
要尝试激活名为
audioSession 的
AudioSession,用户代理必须运行以下步骤:
-
并行运行以下步骤:
5.3. 更新所选音频会话
要更新 所选音频会话,即名为 context 的顶级浏览上下文的所选音频会话, 用户代理必须运行以下步骤:
-
令 activeAudioSessions 为 context 及其子级中所有绑定到
AudioSession对象的音频会话列表, 按广度优先顺序排列,并且同时匹配以下两个约束:-
计算该
AudioSession对象类型的结果为独占类型。
-
如果 activeAudioSessions 为空,则中止这些步骤。
-
如果 activeAudioSessions 中只有一个音频 会话,则将所选音频会话 设置为此音频会话, 并中止这些步骤。
-
断言: 对于任何绑定 到 activeAudioSessions 中名为 audioSession 的音频 会话的
AudioSession对象, audioSession.[[type]] 为auto。 -
用户代理可以应用特定的启发式方法来重新排序 activeAudioSessions。
5.4. 其他算法
要更新 所有 AudioSession 状态,即名为 context 的顶级浏览上下文的所有状态,并使用 updatedAudioSession,运行以下步骤:
-
更新 context 的所选音频会话。
-
令 updatedType 为计算 updatedAudioSession 类型的结果。
-
如果 updatedType 不是独占类型,或者 updatedAudioSession.[[state]] 不是
active, 则中止这些步骤。 -
令 audioSessions 为 context 及其子级中所有
AudioSession对象的列表,按广度优先顺序排列。 -
对于 audioSessions 中除 updatedAudioSession 以外的每个 audioSession,运行以下步骤:
要计算 音频会话类型,即 audioSession 的音频会话类型,用户代理必须运行以下步骤:
-
如果 audioSession.[[type]] 不是
auto, 则返回 audioSession.[[type]]。 -
如果 audioSession.[[elements]] 中的任何 element 的默认类型为
play-and-record且其状态为active, 则返回play-and-record。 -
如果 audioSession.[[elements]] 中的任何 element 的默认类型为
playback且其状态为active, 则返回playback。 -
如果 audioSession.[[elements]] 中的任何 element 的默认类型为
transient-solo且其状态为active, 则返回transient-solo。 -
如果 audioSession.[[elements]] 中的任何 element 的默认类型为
transient且其状态为active, 则返回transient。 -
返回
ambient。
6. 音频源和接收端集成
本节描述音频
会话元素用于 AudioContext、
HTMLMediaElement
和麦克风 MediaStreamTrack
的步骤和属性。
元素状态为:
-
如果它位于其
AudioSession的 [[interruptedElements]] 中,则为interrupted。 -
否则为
inactive。
要更新元素,即名为 element 的元素,用户代理必须运行以下步骤:
-
令 audioSession 为 element 的
AudioSession。 -
运行 element 的更新步骤。
-
如果 element 是可听元素,且 audioSession.[[state]] 为
interrupted, 则运行以下步骤:-
将 element 添加到 audioSession.[[interruptedElements]]。
-
运行 element 的挂起步骤。
-
-
如果 element 位于 audioSession.[[interruptedElements]] 中,并且 audioSession.[[state]] 为
active, 则运行以下步骤:-
从 audioSession.[[interruptedElements]] 中移除 element。
-
运行 element 的恢复步骤。
-
当 audioSession 的某个元素的可听标志 发生变化时,用户代理必须运行以下步骤:
-
否则,如果 audioSession.[[elements]] 中的任何 element 的状态为
interrupted, 则中止这些步骤。 -
否则,停用 audioSession。
6.1. AudioContext
AudioContext
是具有以下属性的元素:
-
其挂起步骤为:
-
令 audioContext 为该
AudioContext对象。 -
排队一条控制消息以挂起 audioContext。
-
-
其恢复步骤为:
-
令 audioContext 为该
AudioContext对象。 -
排队一条控制消息以取消挂起 audioContext。
-
创建 AudioContext
时,用户代理必须运行以下步骤:
-
令 audioContext 为新创建的
AudioContext。 -
令 audioSession 为创建 audioContext 所在的
Window对象的AudioSession对象。 -
将 audioContext 添加到 audioSession.[[elements]]。
6.2. HTMLMediaElement
HTMLMediaElement
是具有以下属性的元素:
-
如果它正在播放、音量不是
0、未被静音且具有 音频轨道,则其可听标志为true。 -
其挂起步骤为:
-
令 mediaElement 为该
HTMLMediaElement对象。 -
排队一个任务以运行 mediaElement 的内部暂停步骤。
-
-
其恢复步骤为:
-
令 mediaElement 为该
HTMLMediaElement对象。 -
排队一个任务以运行 mediaElement 的内部播放步骤。
-
当 HTMLMediaElement
的
节点文档发生变化时,用户代理必须运行以下
步骤:
-
令 mediaElement 为其节点文档正在发生变化的
HTMLMediaElement。 -
令 previousWindow 为与 mediaElement 先前的节点文档关联的
Window对象(如果有),否则为null。 -
如果 previousWindow 不是
null,则从 previousWindow 的关联的 AudioSession.[[elements]] 中移除 mediaElement。 -
令 newWindow 为与 mediaElement 新的节点文档关联的
Window对象(如果有),否则为null。 -
如果 newWindow 不是
null,则将 mediaElement 添加到 newWindow 的关联的 AudioSession.[[elements]]。
6.3. 麦克风 MediaStreamtrack
麦克风捕获 MediaStreamTrack
是具有以下属性的元素:
-
其元素更新步骤为:
-
令 track 为该
MediaStreamTrack对象。 -
令 audioSession 为 track 的
AudioSession。 -
如果 audioSession.[[type]] 不是
play-and-record或auto, 则结束 track。
-
-
其挂起步骤为:
-
令 track 为该
MediaStreamTrack对象。
-
-
其恢复步骤为:
-
令 track 为该
MediaStreamTrack对象。
-
当创建麦克风捕获 MediaStreamTrack
时,用户代理必须运行以下步骤:
-
令 track 为新创建的
MediaStreamTrack。 -
令 audioSession 为创建 track 所在的
Window对象的AudioSession对象。 -
将 track 添加到 audioSession.[[elements]]。
FIXME:我们应当挂接到存储在 Window 的 mediaDevices 的 mediaStreamTrackSources 中的音频轨道源,而不是 MediaStreamTrack。 这应当处理已转移的麦克风轨道的情况。
7. 隐私考虑事项
8. 安全考虑事项
9. 示例
9.1. 站点主动将其音频会话类型设置为 "play-and-record"
navigator. audioSession. type= 'play-and-record' ; // 从现在开始,音量可能会基于 'play-and-record' 设置。 ... // 开始播放远程媒体 remoteVideo. srcObject= remoteMediaStream; remoteVideo. play(); // 开始捕获 navigator. mediaDevices. getUserMedia({ audio: true , video: true }) . then(( stream) => { localVideo. srcObject= stream; });
9.2. 站点对中断作出反应
navigator. audioSession. type= "play-and-record" ; // 从现在开始,音量可能会基于 'play-and-record' 设置。 ... // 开始播放远程媒体 remoteVideo. srcObject= remoteMediaStream; remoteVideo. play(); // 开始捕获 navigator. mediaDevices. getUserMedia({ audio: true , video: true }) . then(( stream) => { localVideo. srcObject= stream; }); navigator. audioSession. onstatechange= async () => { if ( navigator. audioSession. state=== "interrupted" ) { localVideo. pause(); remoteVideo. pause(); // 向用户清楚表明通话已中断。 showInterruptedBanner(); for ( const trackof localVideo. srcObject. getTracks()) { track. enabled= false ; } } else { // 让用户决定何时重启通话。 const shouldRestart= await showOptionalRestartBanner(); if ( ! shouldRestart) { return ; } for ( const trackof localVideo. srcObject. getTracks()) { track. enabled= true ; } localVideo. play(); remoteVideo. play(); } };
10. 致谢
工作组感谢以下人员为本规范作出的宝贵贡献:
-
Becca Hughes
-
Mounir Lamouri
-
Zhiqiang Zhang