媒体采集与流

W3C 候选推荐草案

关于本文档的更多信息
此版本:
https://www.w3.org/TR/2025/CRD-mediacapture-streams-20250624/
最新发布版本:
https://www.w3.org/TR/mediacapture-streams/
最新编辑草案:
https://w3c.github.io/mediacapture-main/
历史记录:
https://www.w3.org/standards/history/mediacapture-streams/
提交历史
实现报告:
https://wpt.fyi/mediacapture-streams
编辑:
Cullen Jennings (Cisco)
Jan-Ivar Bruaroey (Mozilla)
Henrik Boström (Google)
Youenn Fablet (Apple)
前编辑:
Daniel C. Burnett (特邀专家) - 截止
Adam Bergkvist (爱立信) - 截止
Anant Narayanan (Mozilla) - 截止
Bernard Aboba (微软公司)
反馈:
GitHub w3c/mediacapture-main (拉取请求, 新建问题, 公开问题)
public-webrtc@w3.org 主题行 [mediacapture-streams] … 消息主题 … (存档)
参与方式
邮件列表
浏览器支持:
Chrome 标志53
Edge 标志12
Firefox 标志36
Safari 标志11
桌面端
Android Chrome 标志137
Android Firefox 标志139
Android UC 标志15.5
iOS Safari 标志11.0
三星 Internet 标志6.2
移动端
更多信息

摘要

本文档定义了一组 JavaScript API,可用于从平台请求本地媒体,包括音频和视频。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 出版物和本技术报告的最新修订版可在 W3C 标准与草案索引 https://www.w3.org/TR/ 上找到。

本文档尚未完成。API 基于 WHATWG 的初步工作。

在本文档进入“建议推荐”阶段之前,WebRTC 工作组计划解决广泛审查中出现的问题

本文档由 Web 实时通信工作组推荐路径 的方式发布为候选推荐草案。

作为候选推荐发布,不代表 W3C 及其成员的认可。候选推荐草案整合了工作组计划在后续快照中纳入的前一个候选推荐中的变更。

这是一个草案文件,可能随时被更新、替换或废弃为其他文件。不应将本文档作为除“进行中的工作”之外的引用。

本文档由 W3C 专利政策 约束下的工作组制作。 W3C 维护着 与本组成果相关的专利公开名单 ,该页面还包含专利披露的说明。任何实际知晓专利且认为该专利包含 必要声明 的个人,必须按照 《W3C 专利政策》第6节进行信息披露。

本文档受 2023年11月3日 W3C 流程文档 管辖。

1. 引言

本节为非规范性内容。

本文档定义了用于请求访问本地多媒体设备(如麦克风或摄像头)的 API。

本文档还定义了 MediaStream API,它提供了控制多媒体流数据消费位置的手段,并对产生媒体的设备提供部分控制。它还暴露了能够采集和呈现媒体的设备信息。

2. 一致性

除明确标注为非规范性部分外,所有创作指南、图示、示例和注释均为非规范性内容。除此之外,规范的其他内容均为规范性。

本文档中的关键字 MAYMUSTMUST NOTNOT REQUIREDSHOULD 按照 BCP 14 [RFC2119] [RFC8174] 的说明进行解释,仅当这些关键字以全部大写形式出现时,如此处所示。

本规范定义了仅适用于单一产品的符合性标准:实现本规范中接口的用户代理(User Agent)

以算法或具体步骤表达的符合性要求可以以任何方式实现,只要最终结果是等效的。(特别是,本规范定义的算法旨在便于理解,而非高性能。)

使用 ECMAScript [ECMA-262] 实现本规范中 API 的实现,必须与 Web IDL 规范中定义的 ECMAScript 绑定一致 [WEBIDL],因本规范采用该规范及术语。

3. 术语

源(source)

源是提供媒体流轨道源的“事物”。源是媒体本身的广播者。源可以是物理摄像头、麦克风、用户硬盘上的本地视频或音频文件、网络资源或静态图片。请注意,本文档仅描述麦克风和摄像头类型源的使用,其他类型源的使用在其他文档中说明。

应用如果没有事先获得关于源的授权,则只能获得可用源的数量、类型及其与其他设备的关系。应用被授权使用某个源时,关于该源的更多信息才可能可用(见9.2.1 访问控制模型)。

没有约束——轨道有约束。当源连接到轨道时,必须为该轨道产生符合其约束的媒体。多个轨道可以连接到同一个源。用户代理 处理(如降采样)可以用于确保所有轨道都拥有合适的媒体。

源具有可约束属性,这些属性的能力(capabilities)设置(settings) 通过轨道被暴露。虽然可约束属性“归属”源,但源可以同时满足不同需求。因此,能力对于使用同一个源的所有(多个)轨道是通用的,而设置可以因轨道不同而不同(例如,两个绑定到同一源的不同轨道对象查询能力和设置信息时,会得到相同的能力,但可能得到不同的设置,以满足各自的约束)。

设置(Setting)(源设置)

设置指的是源的可约束属性的即时、当前值。设置始终为只读。

源的状态可能会动态变化,例如摄像头因低光环境而切换到较低帧率。在这些情况下,与受影响源相关的轨道可能不再满足设置的约束。平台应当尽量减少此类偏离,但即使无法满足约束,也会继续传递媒体,无论是暂时还是永久条件。

尽管设置是源的属性,但只通过连接到该源的轨道向应用暴露。通过ConstrainablePattern接口暴露。

能力(Capability)

每个可约束属性都有一个能力,用于描述该属性是否被源支持,以及支持值的范围。与设置一样,能力通过ConstrainablePattern接口向应用暴露。

支持的能力值必须标准化为本规范中定义的范围和枚举类型。

在轨道上调用getCapabilities() 会返回所有连接到该源的轨道的相同底层源能力。

本 API 有意简化。能力无法描述不同值之间的交互。例如,无法准确描述一个摄像头在低帧率下能产生高分辨率视频流,在高帧率下只能产生低分辨率流。能力仅描述每个值的完整范围。约束之间的交互通过尝试应用约束来暴露。

约束(Constraint)s

约束为应用提供了通用控制面板,既可用于为轨道选择合适的源,也可在选定后影响源的操作方式。

约束限制了源在为轨道提供媒体时可用的运行模式范围。如未指定轨道约束,实现在选择源设置时可在其支持能力的完整范围内自由选择。实现也可在所应用全部约束的范围内随时调整源设置。

getUserMedia()使用约束帮助为轨道选择合适的源并进行配置。此外,轨道上的ConstrainablePattern接口包含用于动态更改轨道约束的 API。

如果轨道的初始约束无法满足,则不会通过getUserMedia()连接到源。不过,满足轨道约束的能力可能会随时间变化,约束也可更改。如果情况变化导致无法满足约束,ConstrainablePattern接口会定义合适的错误,通知应用。5. 模型:源、接收端、约束和设置详细解释了约束的交互方式。

每个可约束属性都有一个约束,其名称与相关源设置名和能力名对应。

根据约束在约束结构中的位置,约束分为三类:

  • 必要约束(required constraints)是所有非高级约束必需的部分。
  • 可选基础约束(optional basic constraints)是剩余非高级约束
  • 高级约束(advanced constraints)是所有通过 advanced 关键字指定的约束。

通常情况下,用户代理在约束较少时有更大的灵活性优化媒体流体验,因此强烈建议应用作者谨慎使用必要约束

4. MediaStream API

4.1 引言

MediaStream API 的两个主要组件是 MediaStreamTrackMediaStream 接口。 MediaStreamTrack 对象表示来自 用户代理中的一个媒体源的单一类型媒体,例如由网络摄像头产生的视频。 MediaStream用于将多个 MediaStreamTrack对象分组为一个单元,可以被录制或在媒体元素中呈现。

每个MediaStream可以包含零个或多个 MediaStreamTrack对象。一个 MediaStream中的所有轨道在呈现时应当同步。这不是硬性要求,因为来自不同时钟源的轨道可能无法同步。不同的 MediaStream对象之间不需要同步。

注意

虽然目的是同步轨道,但在某些情况下允许轨道失去同步可能更好。特别是当轨道为远程来源且为实时[WEBRTC]时,允许不同步可能比积累延迟或出现故障和其他问题更好。实现应理解关于播放同步选择的影响,以及这些对用户感知的影响。

单个 MediaStreamTrack 可表示多通道内容,如立体声或 5.1 音频或立体视频,其中各通道之间有明确定义的关系。关于通道的信息可能通过其他 API(如 [WEBAUDIO])暴露,但本规范不直接访问通道。

MediaStream 对象具有输入和输出,代表对象所有轨道的合并输入和输出。 MediaStream 的输出控制对象的呈现方式,例如录制到文件时保存的内容或用于 video 元素时显示的内容。 单个 MediaStream 对象可以同时附加到多个不同输出。

可以使用 MediaStream() 构造函数从已有的媒体流或轨道创建新的 MediaStream 对象。构造函数参数可以是现有的 MediaStream对象,此时其所有轨道会加入新的 MediaStream对象,或是一个 MediaStreamTrack对象数组。后者可实现从不同源流合成一个流。

MediaStreamMediaStreamTrack对象都可被克隆。克隆的 MediaStream包含原流中所有成员轨道的克隆。克隆的 MediaStreamTrack拥有独立于被克隆实例的约束集合,允许来自同一源的媒体在不同 消费者中应用不同约束。MediaStream对象也用于 getUserMedia之外的场景,如 [WEBRTC]。

MediaStream constructor 用于根据已有轨道组装一个新的流。它可选接收一个类型为 MediaStreamMediaStreamTrack 对象数组的参数。构造函数被调用时,用户代理必须按如下步骤执行:

  1. stream 为新构造的 MediaStream 对象。

  2. 初始化 stream.id 属性为新生成的值。

  3. 如果构造函数参数存在,执行以下步骤:

    1. 根据参数类型构造轨道集合 tracks

    2. 对于 tracks 中的每个 MediaStreamTracktrack ,执行以下步骤:

      1. 如果 track 已在 stream轨道集合中,则跳过 track

      2. 否则,将 track 添加到 stream轨道集合

  4. 返回 stream

MediaStream 的轨道存储在 轨道集合中。轨道集合必须包含与流的轨道对应的 MediaStreamTrack 对象。集合中的轨道顺序由用户代理定义,API不会对顺序有任何要求。查找集合中特定 MediaStreamTrack 的正确方式是通过其 id 查找。

MediaStream 输出读取数据的对象称为 MediaStream 消费者。当前 MediaStream 消费者包括媒体元素(如 videoaudio) [HTML],Web实时通信(WebRTC;RTCPeerConnection) [WEBRTC],媒体录制 (MediaRecorder) [mediastream-recording],图像采集 (ImageCapture) [image-capture],以及 Web 音频 (MediaStreamAudioSourceNode) [WEBAUDIO]。

注意

MediaStream 的消费者必须能够处理轨道的添加和移除。具体行为由各消费者定义。

MediaStream 对象在拥有至少一个尚未 结束MediaStreamTrack 时被称为活动。如果没有轨道或仅有已 结束 的轨道,则为非活动

MediaStream 对象在拥有至少一个 MediaStreamTrack,其 [[Kind]]"audio" 且未 结束 时被称为可听。如果没有音频轨道或仅有已 结束 的音频轨道,则为不可听

用户代理可以在响应外部事件时更新 MediaStream轨道集合。本规范未规定具体情况,但其他使用 MediaStream API 的规范可能会规定。例如 WebRTC 1.0 [WEBRTC] 规范中,从其他 Peer 接收到的 MediaStream轨道集合可因媒体会话变化而更新。

要将 轨道添加 trackMediaStream stream,用户代理必须 执行以下步骤:

  1. 如果 track 已在 stream轨道集合中,则终止这些步骤。

  2. track 添加到 stream轨道集合

  3. 触发轨道事件,事件名为 addtrack,事件内容为 track,目标为 stream

要将 轨道移除 trackMediaStream stream,用户代理必须 执行以下步骤:

  1. 如果 track 不在 stream轨道集合中,则终止这些步骤。

  2. 移除 trackstream轨道集合

  3. 触发轨道事件,事件名为 removetrack,事件内容为 track,目标为 stream

WebIDL[Exposed=Window]
interface MediaStream : EventTarget {
  constructor();
  constructor(MediaStream stream);
  constructor(sequence<MediaStreamTrack> tracks);
  readonly attribute DOMString id;
  sequence<MediaStreamTrack> getAudioTracks();
  sequence<MediaStreamTrack> getVideoTracks();
  sequence<MediaStreamTrack> getTracks();
  MediaStreamTrack? getTrackById(DOMString trackId);
  undefined addTrack(MediaStreamTrack track);
  undefined removeTrack(MediaStreamTrack track);
  MediaStream clone();
  readonly attribute boolean active;
  attribute EventHandler onaddtrack;
  attribute EventHandler onremovetrack;
};

构造函数

MediaStream

参见 MediaStream 构造函数算法

无参数。
MediaStream

参见 MediaStream 构造函数算法

MediaStream

参见 MediaStream 构造函数算法

属性

id 类型为 DOMString, 只读

id 属性必须返回对象创建时初始化的值。

创建 MediaStream 时, 用户代理必须生成一个标识符字符串,并必须将对象的 id 属性初始化为该字符串,除非对象是作为特殊算法的一部分创建,该算法指定了流 id 的初始化方式。推荐做法是使用 UUID [rfc4122],其规范形式为36位字符。为避免指纹识别,实现应当在生成UUID时使用RFC 4122的4.4或4.5节中的格式。

指定流 id 初始化方式的算法示例如,将传入网络组件与 MediaStream 对象关联的算法。[WEBRTC]

active 类型为 boolean,只读

active 属性必须在此 MediaStream活动时返回 true,否则返回 false

onaddtrack 类型为 EventHandler

该事件处理器的事件类型为 addtrack

onremovetrack 类型为 EventHandler

该事件处理器的事件类型为 removetrack

方法

getAudioTracks()

返回表示此流中音频轨道的 MediaStreamTrack 对象序列。

getAudioTracks 方法必须返回一个序列,表示此流的 轨道集合中所有 MediaStreamTrack 对象的快照,其 [[Kind]] 等于 "audio"。由 轨道集合转换为序列的过程由 用户代理定义,且多次调用时序列顺序不需保持稳定。

getVideoTracks()

返回表示此流中视频轨道的 MediaStreamTrack 对象序列。

getVideoTracks 方法必须返回一个序列,表示此流的 轨道集合中所有 MediaStreamTrack 对象的快照,其 [[Kind]] 等于 "video"。由 轨道集合转换为序列的过程由 用户代理定义,且多次调用时序列顺序不需保持稳定。

getTracks()

返回表示此流中所有轨道的 MediaStreamTrack 对象序列。

getTracks 方法必须返回此流的 轨道集合中所有 MediaStreamTrack 对象的快照,无论 [[Kind]] 类型。由 轨道集合转换为序列的过程由用户代理定义,且多次调用时序列顺序不需保持稳定。

getTrackById()

getTrackById 方法必须返回此流 轨道集合MediaStreamTrack 对象,其 [[Id]] 等于 trackId,不存在则返回 null

addTrack()

将给定的 MediaStreamTrack 添加到此 MediaStream

调用 addTrack 方法时,用户代理必须执行如下步骤:

  1. track 为方法参数,stream 为调用该方法的 MediaStream 对象。

  2. 如果 track 已在 stream轨道集合中,则终止这些步骤。

  3. 添加 trackstream轨道集合

removeTrack()

从此 MediaStream 移除给定的 MediaStreamTrack 对象。

调用 removeTrack 方法时,用户代理必须执行如下步骤:

  1. track 为方法参数,stream 为调用该方法的 MediaStream 对象。

  2. 如果 track 不在 stream轨道集合中,则终止这些步骤。

  3. 移除 trackstream轨道集合

clone()

克隆给定的 MediaStream 及其所有轨道。

调用 clone() 方法时,用户代理必须执行如下步骤:

  1. streamClone 为新构造的 MediaStream 对象。

  2. 初始化 streamClone.MediaStream.id 为新生成的值。

  3. 克隆每个轨道到此 MediaStream 对象,并将结果添加到 streamClone轨道集合中。

  4. 返回 streamClone

垃圾回收

期望触发 addtrack 或 removetrack 事件的用户代理代码应通过其他对象引用保持目标 MediaStream 对象存活。垃圾回收 MediaStream 对象时无需考虑 addtrack 或 removetrack 事件监听器的存在。

MediaStreamTrack 对象表示 用户代理中的一个媒体源。示例源是连接到 用户代理的设备。其他规范可能定义用于 MediaStreamTrack 的源以覆盖此处规定的行为。多个 MediaStreamTrack 对象可以表示同一个媒体源,例如用户在两次调用 getUserMedia() 时选择了相同的摄像头。

MediaStreamTrack 源定义如下属性:

  1. 源具有 MediaStreamTrack 源类型。 它被设置为 MediaStreamTrackMediaStreamTrack 的子类型。默认设置为 MediaStreamTrack
  2. 源具有 MediaStreamTrack 源特定构造步骤 ,在从源创建 MediaStreamTrack 时执行。步骤以新创建的 MediaStreamTrack 作为输入。默认步骤为空。
  3. 源具有 MediaStreamTrack 源特定克隆步骤 ,在克隆给定源的 MediaStreamTrack 时执行。步骤以源和目标的 MediaStreamTrack 作为输入。默认步骤为空。

MediaStreamTrack 对象中的数据不一定有规范的二进制形式;例如它可能只是“用户摄像头当前的视频”。这样允许 用户代理以最适合用户平台的方式处理媒体。

脚本可通过 stop() 方法指示 MediaStreamTrack 对象不再需要其源。当所有使用某源的轨道被 stop 或以其他方式结束,该源进入 已停止 状态。如果源是 getUserMedia() 暴露的设备,则源停止时,用户代理 必须执行如下步骤:

  1. mediaDevices 为相关的 MediaDevices 对象。

  2. deviceId 为源设备的 deviceId

  3. 设置 mediaDevices.[[devicesLiveMap]][deviceId] 为 false

  4. 如果与设备类型和 deviceId 相关权限的 权限状态mediaDevices相关设置对象 不是 "granted", 则设置 mediaDevices.[[devicesAccessibleMap]][deviceId] 为 false

创建 MediaStreamTrack,给定底层 sourcemediaDevicesToTieSourceTo,运行以下步骤:

  1. track 为一个新对象,类型为 sourceMediaStreamTrack 源类型

    初始化 track 的以下内部槽:

    • [[Source]], 初始化为 source

    • [[Id]], 初始化为新生成的唯一标识符字符串。生成方式参见 MediaStream.id 属性。

    • [[Kind]], 若 source 是音频源,则初始化为 "audio",若 source 是视频源,则初始化为 "video"

    • [[Label]], 初始化为 source 的 label(如果用户代理提供),否则为 ""用户代理 可以为音频和视频源打标签(如“内置麦克风”或“外部USB摄像头”)。

    • [[ReadyState]], 初始化为 "live"。

    • [[Enabled]], 初始化为 true

    • [[Muted]], 若 source静音则为true,否则为false

    • [[Capabilities]][[Constraints]][[Settings]], 均按 ConstrainablePattern 规定初始化。

    • [[Restrictable]],初始化为 false

  2. 如果 mediaDevicesToTieSourceTo 不为 null, 使用 将 track 源绑定到 MediaDevices ,参数为 sourcemediaDevicesToTieSourceTo

  3. track 为参数,运行 sourceMediaStreamTrack 源特定构造步骤

  4. 返回 track

初始化 track 的底层源source,请执行以下步骤:

  1. 初始化 track.[[Source]]source

  2. ConstrainablePattern 规定初始化 track[[Capabilities]][[Constraints]][[Settings]]

将 track 源绑定到 MediaDevices, 给定 sourcemediaDevices,请执行以下步骤:

  1. source 添加到 mediaDevices.[[mediaStreamTrackSources]]

停止所有源,对于 全局对象 globalObject用户代理 必须执行如下步骤:

  1. 对于 MediaStreamTrack 对象 track ,其 相关全局对象globalObject, 设置 track[[ReadyState]] 为 "ended"。

  2. 如果 globalObjectWindow, 则对于 globalObject关联的 MediaDevices.[[mediaStreamTrackSources]] 中的每个 source停止 source

用户代理 必须在以下条件下对 globalObject 执行停止所有源

  1. 如果 globalObjectWindow 对象,且为其 卸载文档清理步骤 被执行。

  2. 如果 globalObjectWorkerGlobalScope 对象且其 关闭 标志被设置为 true。

实现可以使用每个源的引用计数来跟踪源的使用,但具体细节超出本规范范围。

克隆轨道用户代理 必须执行以下步骤:

  1. track 为待克隆的 MediaStreamTrack 对象。

  2. sourcetrack[[Source]]

  3. trackClone 为用 sourcenull 创建 MediaStreamTrack 的结果。

  4. 设置 trackClone[[ReadyState]]track[[ReadyState]] 的值。

  5. 设置 trackClone[[Capabilities]]track[[Capabilities]] 的克隆。

  6. 设置 trackClone[[Constraints]]track[[Constraints]] 的克隆。

  7. 设置 trackClone[[Settings]]track[[Settings]] 的克隆。

  8. tracktrackClone 为参数,运行 sourceMediaStreamTrack 源特定克隆步骤

  9. 返回 trackClone

4.3.1 媒体流动与生命周期

4.3.1.1 媒体流动

对于"live" MediaStreamTrack ,媒体流动有两个维度:静音/未静音 和 启用/禁用。

静音(Muted)指的是输入到 MediaStreamTrack。当其源 静音(即暂时无法为轨道提供数据)时,MediaStreamTrack 处于 静音状态。 在静音时,不得MediaStreamTrack 提供实时采样。

静音状态无法被网页应用控制,但可通过读取 muted 属性并监听相关事件 muteunmute 进行观察。 MediaStreamTrack 被静音的原因由其 定义。

对于摄像头和麦克风源,静音 的原因是 由实现定义。 这允许用户代理在如下场景下实现隐私保护: 用户按下麦克风物理静音按钮,用户关闭内嵌摄像头的笔记本电脑盖,用户在操作系统中切换控制,用户在 用户代理界面点击静音按钮,用户代理(代替用户)进行静音等。

在某些操作系统上,当其他具有更高音频优先级的应用获得麦克风访问权限(如移动操作系统上来电时),麦克风访问可能会被从 用户代理“窃取”。 用户代理 应当通过 muted 及相关事件向网页应用提供此类信息。

每当 用户代理针对摄像头或麦克风源启动此类 由实现定义 的变更时,必须使用用户交互任务源,排队一个任务,设置轨道静音状态为用户期望的状态。

注意
这不适用于其他规范定义的 。其他规范如需设置轨道静音状态,需自行定义步骤。

要将轨道静音状态设置为 newState用户代理 必须执行以下步骤:

  1. track 为相关的 MediaStreamTrack

  2. 如果 track.[[Muted]] 已为 newState,则终止这些步骤。

  3. 设置 track.[[Muted]]newState

  4. 如果 newStatetrue,则 eventNamemute,否则为 unmute

  5. 触发事件,事件名为 eventName,目标为 track

启用/禁用(Enabled/disabled)则可由应用控制(并观察),通过 enabled 属性实现。

对于消费者而言,无论 MediaStreamTrack 处于静音还是禁用(或两者均有),消费者得到的信息都是零内容,即音频为静音,视频为黑帧。换句话说,仅当 MediaStreamTrack 对象既未静音又已启用时,源媒体才流动。例如,视频元素仅含静音或禁用的音视频轨道时,播放的是黑帧和无声。

对于新创建的 MediaStreamTrack 对象,默认始终启用(除非另有说明,如克隆时),静音状态反映源在轨道创建时的状态。

4.3.1.2 生命周期

MediaStreamTrack 在生命周期中有两个状态:live(活动)和 ended(结束)。新创建的 MediaStreamTrack 可以处于任一状态,取决于创建方式。例如,克隆已结束轨道会得到新的已结束轨道。当前状态由对象的 readyState 属性反映。

在活动状态下,轨道处于激活,媒体(或零内容,如果 MediaStreamTrack静音禁用)可以被消费者使用。

如果源是 navigator.mediaDevices.getUserMedia() 暴露的设备,则当某轨道变为静音或禁用,并且该设备所有已连接轨道均为静音、禁用或已停止时,UA 可以使用设备的 deviceId deviceId ,设置 navigator.mediaDevices.[[devicesLiveMap]][deviceId] 为 false ,但应在任何未停止轨道重新变为未静音或启用时,及时恢复为 true

当由设备暴露的活动、未静音且已启用的轨道变为静音或禁用,并且该设备所有轨道(在所有 UA 管理的 navigable 中)均为静音、禁用或已停止时,UA 应当在3秒内释放设备,同时给用户合理时间察觉状态变化。UA 应当在任何由该设备产生的活动轨道重新变为既未静音又启用时尝试重新获取设备,前提是该轨道的 相关全局对象关联文档 此时在视图中。如果文档当时不在视图中,UA 应当排队任务静音轨道,并在文档重新进入视图后,才排队任务取消静音。 如果重新获取设备失败,UA 必须结束轨道(UA 可以在检测到设备问题(如设备被物理移除)时更早终止)。

注意

目的是让用户通过物理摄像头(和麦克风)指示灯熄灭获得隐私保证,即物理和逻辑“隐私指示器”对齐,至少在当前文档是设备唯一使用者时。

虽然其他同时使用设备的应用和文档有时会干扰这一目标,但不会影响本规范的规则。

当轨道的源断开连接或耗尽时,MediaStreamTrack对象被认为结束

如果所有使用同一源的 MediaStreamTrack 都已 结束,则源会被 停止

应用调用 stop() 方法后,或当 永久不再为轨道生产实时采样(以先到者为准),MediaStreamTrack被认为结束(ended)

对于摄像头和麦克风源,除 stop() 外,源结束的原因是 由实现定义 (如用户撤销页面使用本地摄像头权限,或用户代理因任何原因指示轨道结束)。

MediaStreamTrack track 因除 stop() 方法外的其他原因结束时,用户代理 必须排队任务,执行以下步骤:

  1. 如果 track[[ReadyState]] 已为 "ended",则终止这些步骤。

  2. 设置 track[[ReadyState]] 为 "ended"。

  3. 通知 track[[Source]]track结束,以便源可被 停止,除非仍有其他 MediaStreamTrack 对象依赖该源。

  4. 触发事件,事件名为 ended,目标为该对象。

如果轨道因用户请求而结束,则该事件的事件源为用户交互事件源。

要执行设备权限撤销算法,参数为 permissionName,请执行以下步骤:

  1. tracks 为所有当前 "live" MediaStreamTrack,其权限与该类轨道("camera""microphone") 匹配 permissionName

  2. tracks 中的每个 track结束该轨道。

4.3.2 轨道与约束

MediaStreamTrack可约束对象,定义见 可约束模式部分。 约束是针对轨道设置的,并可能影响源。

无论 Constraints 是在轨道初始化时提供,还是需要在运行时建立, ConstrainablePattern 接口定义的 API 允许检索和操作当前在轨道上建立的约束。

轨道结束后,仍会暴露 固有可约束轨道属性列表。 该列表包含 deviceIdfacingModegroupId

4.3.3 接口定义

WebIDL[Exposed=Window]
interface MediaStreamTrack : EventTarget {
  readonly attribute DOMString kind;
  readonly attribute DOMString id;
  readonly attribute DOMString label;
  attribute boolean enabled;
  readonly attribute boolean muted;
  attribute EventHandler onmute;
  attribute EventHandler onunmute;
  readonly attribute MediaStreamTrackState readyState;
  attribute EventHandler onended;
  MediaStreamTrack clone();
  undefined stop();
  MediaTrackCapabilities getCapabilities();
  MediaTrackConstraints getConstraints();
  MediaTrackSettings getSettings();
  Promise<undefined> applyConstraints(optional MediaTrackConstraints constraints = {});
};
属性
kind 类型为 DOMString, 只读

kind 属性必须返回 this.[[Kind]]

id 类型为 DOMString, 只读

id 属性必须返回 this.[[Id]]

label 类型为 DOMString, 只读

label 属性必须返回 this.[[Label]]

enabled 类型为 boolean

enabled 属性控制对象的 启用状态。

获取时, this.[[Enabled]] 必须返回。 设置时, this.[[Enabled]] 必须设为新值。

注意

因此,当 MediaStreamTrack 结束 后,其 enabled 属性 仍可更改取值;只是新值不会产生任何作用。

muted 类型为 boolean, 只读

muted 属性 反映轨道是否已 静音必须返回 this.[[Muted]]

onmute 类型为 EventHandler

该事件处理器的事件类型为 mute

onunmute 类型为 EventHandler

该事件处理器的事件类型为 unmute

readyState 类型为 MediaStreamTrackState, 只读

获取时,readyState 属性必须返回 this.[[ReadyState]]

onended 类型为 EventHandler

该事件处理器的事件类型为 ended

方法
clone

调用 clone() 方法时,用户代理 必须返回 克隆轨道的结果,参数为 this

stop

调用 MediaStreamTrack 对象的 stop() 方法时,用户代理必须执行以下步骤:

  1. track 为当前 MediaStreamTrack 对象。

  2. 如果 track[[ReadyState]] 为 "ended", 则终止这些步骤。

  3. 通知 track 的源,track结束

    源收到轨道结束通知后,除非仍有其他 MediaStreamTrack 对象依赖该源,否则会被 停止

  4. 设置 track[[ReadyState]] 为 "ended"。

getCapabilities

返回该 MediaStreamTrack 所表示的源的能力,可约束对象

定义见 可约束模式接口

由于此方法提供可能长期存在的、跨域的底层设备信息,会增加设备指纹识别面。(这是一个指纹向量。)

getConstraints

定义见 可约束模式接口

getSettings

调用 MediaStreamTrack 对象的 MediaStreamTrack.getSettings() 方法时, 用户代理 必须执行以下步骤:

  1. track 为当前 MediaStreamTrack 对象。

  2. 如果 track[[ReadyState]] 为 "ended", 执行以下子步骤:

    1. settings 为新的 MediaTrackSettings 字典。

    2. 对于 固有可约束轨道属性列表中的每个 property, 如果 track 在结束时拥有此属性,则添加对应属性到 settings,值为轨道结束时的值。

    3. 返回 settings

  3. 返回轨道的当前设置,定义见 可约束模式接口

applyConstraints

调用 MediaStreamTrack 对象的 applyConstraints() 方法时,用户代理必须执行以下步骤:

  1. track 为当前 MediaStreamTrack 对象。

  2. 如果 track[[ReadyState]] 为 "ended", 执行以下子步骤:

    1. p 为新建的 Promise。

    2. resolve p,值为 undefined

    3. 返回 p

  3. 调用并返回 applyConstraints 模板方法的结果,参数如下:

WebIDLenum MediaStreamTrackState {
  "live",
  "ended"
};
MediaStreamTrackState 枚举说明
枚举值 说明
live

轨道处于活动状态(轨道的底层媒体源正在尽最大努力实时提供数据)。

处于 "live" 状态的轨道输出可通过 enabled 属性开关控制。

ended

轨道已结束(轨道的底层媒体源不再提供数据,并且不会再为此轨道提供更多数据)。轨道进入此状态后,永不离开此状态。

例如,当用户拔掉作为轨道媒体源的 USB 摄像头时,MediaStream中的视频轨道结束。

4.3.4 MediaTrackSupportedConstraints

MediaTrackSupportedConstraints 表示 用户代理 用于控制 能力MediaStreamTrack 对象的约束列表。 此字典仅用于作为函数返回值,而不会作为操作参数。

未来的规范可以通过定义带有 boolean 类型字典成员的 partial dictionary 来扩展 MediaTrackSupportedConstraints 字典。

注意

本规范中指定的约束仅适用于由 MediaStreamTrack 生成的实例, 这些实例来自 MediaDevices.getUserMedia(), 除非其他规范另有说明。

WebIDLdictionary MediaTrackSupportedConstraints {
  boolean width = true;
  boolean height = true;
  boolean aspectRatio = true;
  boolean frameRate = true;
  boolean facingMode = true;
  boolean resizeMode = true;
  boolean sampleRate = true;
  boolean sampleSize = true;
  boolean echoCancellation = true;
  boolean autoGainControl = true;
  boolean noiseSuppression = true;
  boolean latency = true;
  boolean channelCount = true;
  boolean deviceId = true;
  boolean groupId = true;
  boolean backgroundBlur = true;
};
width 类型为 boolean, 默认值为 true
具体见 width
height 类型为 boolean, 默认值为 true
具体见 height
aspectRatio 类型为 boolean, 默认值为 true
具体见 aspectRatio
frameRate 类型为 boolean, 默认值为 true
具体见 frameRate
facingMode 类型为 boolean, 默认值为 true
具体见 facingMode
resizeMode 类型为 boolean, 默认值为 true
具体见 resizeMode
sampleRate 类型为 boolean, 默认值为 true
具体见 sampleRate
sampleSize 类型为 boolean, 默认值为 true
具体见 sampleSize
echoCancellation 类型为 boolean, 默认值为 true
具体见 echoCancellation
autoGainControl 类型为 boolean, 默认值为 true
具体见 autoGainControl
noiseSuppression 类型为 boolean, 默认值为 true
具体见 noiseSuppression
latency 类型为 boolean, 默认值为 true
具体见 latency
channelCount 类型为 boolean, 默认值为 true
具体见 channelCount
deviceId 类型为 boolean, 默认值为 true
具体见 deviceId
groupId 类型为 boolean, 默认值为 true
具体见 groupId
backgroundBlur 类型为 boolean, 默认值为 true
具体见 backgroundBlur

4.3.5 MediaTrackCapabilities

MediaTrackCapabilities 表示 能力MediaStreamTrack 对象。

未来的规范可以通过定义带有适当类型字典成员的 partial dictionary 来扩展 MediaTrackCapabilities 字典。

WebIDLdictionary MediaTrackCapabilities {
  ULongRange width;
  ULongRange height;
  DoubleRange aspectRatio;
  DoubleRange frameRate;
  sequence<DOMString> facingMode;
  sequence<DOMString> resizeMode;
  ULongRange sampleRate;
  ULongRange sampleSize;
  sequence<(boolean or DOMString)> echoCancellation;
  sequence<boolean> autoGainControl;
  sequence<boolean> noiseSuppression;
  DoubleRange latency;
  ULongRange channelCount;
  DOMString deviceId;
  DOMString groupId;
  sequence<boolean> backgroundBlur;
};
注意

由于历史原因,deviceIdgroupIdDOMString 类型,而不是 sequence<DOMString>,这与 CapabilitiesConstrainablePattern 中的预期类型不同。

字典 MediaTrackCapabilities 成员
width 类型为 ULongRange
具体见 width
height 类型为 ULongRange
具体见 height
aspectRatio 类型为 DoubleRange
具体见 aspectRatio
frameRate 类型为 DoubleRange
具体见 frameRate
facingMode 类型为 sequence<DOMString>

一个摄像头可以报告多个朝向模式。例如,在高端远程会议系统中,有多个摄像头面向用户,用户左侧的摄像头可以同时报告 "left" 和 "user"。具体见 facingMode

resizeMode 类型为 sequence<DOMString>

用户代理 可以使用裁剪和降采样为摄像头提供比其原生分辨率更多的分辨率选择。 报告的序列必须列出 UA 可用于该摄像头的所有分辨率派生方式。值 "none" 必须存在,表示可以限制 UA 不进行裁剪和降采样。具体见 resizeMode

sampleRate 类型为 ULongRange
具体见 sampleRate
sampleSize 类型为 ULongRange
具体见 sampleSize
echoCancellation 类型为 sequence<boolean>

如果源无法进行回声消除,则列表中仅包含一个 false。如果源能进行回声消除,则列表中必须包含 true。如果脚本可控该功能,则列表必须至少同时包含 truefalse。此外,如果源允许控制取消哪些音源,则必须包含 EchoCancellationModeEnum 枚举的所有支持值。如果列表中包含 truefalse,则它们必须出现在所有 EchoCancellationModeEnum 值之前。具体见 echoCancellation

autoGainControl 类型为 sequence<boolean>

如果源无法进行自动增益控制,则只报告 false。如果自动增益控制无法关闭,则只报告 true。如果脚本可控该功能,则报告 truefalse 两种可能值。具体见 autoGainControl

noiseSuppression 类型为 sequence<boolean>

如果源无法进行降噪,则只报告 false。如果降噪功能无法关闭,则只报告 true。如果脚本可控该功能,则报告 truefalse 两种可能值。具体见 noiseSuppression

latency 类型为 DoubleRange
具体见 latency
channelCount 类型为 ULongRange
具体见 channelCount
deviceId 类型为 DOMString
具体见 deviceId
groupId 类型为 DOMString
具体见 groupId
backgroundBlur 类型为 sequence<boolean>
如果源不支持内建背景虚化,则只报告 false。如果背景虚化功能无法关闭,则只报告 true。如果脚本可控该功能,则报告 true 和 false 两种可能值。具体见 backgroundBlur

4.3.6 MediaTrackConstraints

WebIDLdictionary MediaTrackConstraints : MediaTrackConstraintSet {
  sequence<MediaTrackConstraintSet> advanced;
};
字典 MediaTrackConstraints 成员
advanced 类型为 sequence<MediaTrackConstraintSet>

定义见 约束与约束集

未来的规范可以通过定义带有适当类型字典成员的 partial dictionary 来扩展 MediaTrackConstraintSet 字典。

WebIDLdictionary MediaTrackConstraintSet {
  ConstrainULong width;
  ConstrainULong height;
  ConstrainDouble aspectRatio;
  ConstrainDouble frameRate;
  ConstrainDOMString facingMode;
  ConstrainDOMString resizeMode;
  ConstrainULong sampleRate;
  ConstrainULong sampleSize;
  ConstrainBooleanOrDOMString echoCancellation;
  ConstrainBoolean autoGainControl;
  ConstrainBoolean noiseSuppression;
  ConstrainDouble latency;
  ConstrainULong channelCount;
  ConstrainDOMString deviceId;
  ConstrainDOMString groupId;
  ConstrainBoolean backgroundBlur;
};
字典 MediaTrackConstraintSet 成员
width 类型为 ConstrainULong
具体见 width
height 类型为 ConstrainULong
具体见 height
aspectRatio 类型为 ConstrainDouble
具体见 aspectRatio
frameRate 类型为 ConstrainDouble
具体见 frameRate
facingMode 类型为 ConstrainDOMString
具体见 facingMode
resizeMode 类型为 ConstrainDOMString
具体见 resizeMode
sampleRate 类型为 ConstrainULong
具体见 sampleRate
sampleSize 类型为 ConstrainULong
具体见 sampleSize
echoCancellation 类型为 ConstrainBooleanOrDOMString
具体见 echoCancellation
autoGainControl 类型为 ConstrainBoolean
具体见 autoGainControl
noiseSuppression 类型为 ConstrainBoolean
具体见 noiseSuppression
latency 类型为 ConstrainDouble
具体见 latency
channelCount 类型为 ConstrainULong
具体见 channelCount
deviceId 类型为 ConstrainDOMString
具体见 deviceId
groupId 类型为 ConstrainDOMString
具体见 groupId
backgroundBlur 类型为 ConstrainBoolean
具体见 backgroundBlur

4.3.7 MediaTrackSettings

MediaTrackSettings 表示 设置MediaStreamTrack 对象。

未来规范可以通过定义带有适当类型字典成员的 partial dictionary 来扩展 MediaTrackSettings 字典。

WebIDLdictionary MediaTrackSettings {
  unsigned long width;
  unsigned long height;
  double aspectRatio;
  double frameRate;
  DOMString facingMode;
  DOMString resizeMode;
  unsigned long sampleRate;
  unsigned long sampleSize;
  (boolean or DOMString) echoCancellation;
  boolean autoGainControl;
  boolean noiseSuppression;
  double latency;
  unsigned long channelCount;
  DOMString deviceId;
  DOMString groupId;
  boolean backgroundBlur;
};
字典 MediaTrackSettings 成员
width 类型为 unsigned long
具体见 width
height 类型为 unsigned long
具体见 height
aspectRatio 类型为 double
具体见 aspectRatio
frameRate 类型为 double
具体见 frameRate
facingMode 类型为 DOMString
具体见 facingMode
resizeMode 类型为 DOMString
具体见 resizeMode
sampleRate 类型为 unsigned long
具体见 sampleRate
sampleSize 类型为 unsigned long
具体见 sampleSize
echoCancellation 类型为 booleanDOMString
具体见 echoCancellation
autoGainControl 类型为 boolean
具体见 autoGainControl
noiseSuppression 类型为 boolean
具体见 noiseSuppression
latency 类型为 double
具体见 latency
channelCount 类型为 unsigned long
具体见 channelCount
deviceId 类型为 DOMString
具体见 deviceId
groupId 类型为 DOMString
具体见 groupId
backgroundBlur 类型为 boolean, 默认值为 true
具体见 backgroundBlur

4.3.8 可约束属性

MediaStreamTrack 的初始可约束属性名称如下定义。

以下可约束属性适用于视频和音频 MediaStreamTrack 对象:

属性名 类型 说明
deviceId DOMString 生成 MediaStreamTrack 内容的设备标识符。符合 MediaDeviceInfo.deviceId 的定义。 注意此属性的设置值是由附加到 MediaStreamTrack 的源唯一决定的。尤其是 getCapabilities() 只会返回 deviceId 的一个值。因此此属性可用于 getUserMedia() 初始媒体选择。 但对于 applyConstraints() 后续媒体控制无效, 因为试图设置不同值会导致 ConstraintSet 无法满足。 若使用长度为0的字符串作为 deviceId 约束值配合 getUserMedia()可以视为未指定约束。
groupId DOMString 生成 MediaStreamTrack 内容的设备的 文档唯一组标识符。 符合 MediaDeviceInfo.groupId 的定义。 注意此属性的设置值由附加到 MediaStreamTrack 的源唯一决定。尤其是 getCapabilities() 只会返回 groupId 的一个值。由于该属性在会话间不稳定,用于 getUserMedia() 初始媒体选择时作用有限。 对于 applyConstraints() 后续媒体控制无效, 因为试图设置不同值会导致 ConstraintSet 无法满足。

以下可约束属性仅适用于视频 MediaStreamTrack 对象:

属性名 类型 说明
width unsigned long 宽度,单位为像素。作为能力,其有效范围应覆盖视频源预设宽度值,最小值为1,最大值为最大宽度。 用户代理 必须支持对最小宽度至原生分辨率宽度区间内任意值的降采样。
height unsigned long 高度,单位为像素。作为能力,其有效范围应覆盖视频源预设高度值,最小值为1,最大值为最大高度。 用户代理 必须支持对最小高度至原生分辨率高度区间内任意值的降采样。
frameRate double

帧率(每秒帧数)。 如果视频源预设能决定帧率,则作为能力,其有效范围应覆盖视频源预设帧率值,最小值为0,最大值为最大帧率。 用户代理 必须 支持通过对原生分辨率帧率做整数降采样获得的帧率。 如果无法确定帧率(如源不原生提供帧率,或无法从流中确定),则能力值 必须参考 用户代理 的 vsync 显示帧率。

作为设置,此值表示配置的帧率。 如降采样,则为该值而非原生帧率。例如设置为25帧/秒,摄像头原生为30帧/秒,但因光照实际只有20帧/秒,则 frameRate 报告设置值25帧/秒。

aspectRatio double 精确的宽高比(像素宽度/像素高度,双精度小数点后十位)或宽高比范围。
facingMode DOMString 该字符串为 VideoFacingModeEnum 成员之一,描述摄像头可面向的方向(用户视角)。 注意 getConstraints 不属于此枚举的字符串可能不会被原样返回,保留了未来使用WebIDL枚举的可能性。
resizeMode DOMString 此字符串为 VideoResizeModeEnum 成员之一。 描述 UA 派生分辨率的方式,即是否允许对摄像头输出进行裁剪和降采样。

UA 可以通过降采样、升采样和/或裁剪伪装并发使用摄像头,仅在摄像头被其他应用占用时对 "none" 使用。(这是一个指纹向量。)

注意 getConstraints 不属于此枚举的字符串可能不会被原样返回,保留了未来使用WebIDL枚举的可能性。
backgroundBlur boolean 某些平台或用户代理可能为视频帧(尤其是摄像头视频流)提供内建背景虚化。Web应用可能希望控制或至少感知源级虚化,以便更新UI或避免重复虚化。

在某些系统上,可能需要根据环境因素自动翻转捕获视频的X/Y轴,widthheightaspectRatio 约束与能力在所有算法中必须保持不变,并仅按 主方向考虑,除非 getSettings() 算法,需要在任何时刻根据捕获视频实际尺寸翻转这些属性。

主方向是支持自动翻转视频X/Y轴系统由用户代理定义的方向。

注意

在支持自动横竖屏切换的系统上,用户代理建议将横屏作为 主方向

WebIDLenum VideoFacingModeEnum {
  "user",
  "environment",
  "left",
  "right"
};
VideoFacingModeEnum 枚举说明
枚举值 说明
user

源面向用户(自拍摄像头)。

environment

源背向用户(面对环境)。

left

源面向用户左侧。

right

源面向用户右侧。

下图展示了视频面向模式与用户的关系。
Illustration of video facing modes in relation to user

WebIDLenum VideoResizeModeEnum {
  "none",
  "crop-and-scale"
};
VideoResizeModeEnum 枚举说明
枚举值 说明
none

该分辨率和帧率由摄像头、驱动或操作系统提供。

注意:UA 可以报告该值以伪装并发使用,仅在摄像头被其他 navigable 占用时。(这是一个指纹向量。)

crop-and-scale

该分辨率由 用户代理 从更高摄像头分辨率降采样和/或裁剪获得,或由 用户代理 对帧率降采样获得。媒体不得被升采样、拉伸或伪造非输入源数据,除下述情况外。

注意:UA 可以升采样以伪装并发使用,仅在摄像头被其他应用占用时。(这是一个指纹向量。)

以下可约束属性仅适用于音频 MediaStreamTrack 对象:

属性名 值类型 说明
sampleRate unsigned long 音频数据的每秒采样率。
sampleSize unsigned long 线性采样位数。作为约束,仅对能产生线性采样的音频设备适用。
echoCancellation booleanDOMString 当多个麦克风同时播放音频流时,通常希望尝试从麦克风输入信号中去除正在播放的声音,这被称为回声消除。 某些场景下无需此功能,关闭可避免音频伪影。应用可控制此行为。
autoGainControl boolean 通常对麦克风输入信号希望自动增益控制。有些情况下不需要,关闭可避免音频被更改。应用可控制此行为。
noiseSuppression boolean 通常对麦克风输入信号希望进行降噪。有些情况下不需要,关闭可避免音频被更改。应用可控制此行为。
latency double 延迟或延迟范围,单位秒。指从处理开始(如真实世界发生声音)到数据可用于下一步的时间。部分应用对低延迟有强需求,部分可接受高延迟以节省能耗。该数值为配置目标延迟,实际延迟可能有偏差。
channelCount unsigned long 音频数据的独立声道数,即每个采样帧的音频样本数。
WebIDLenum EchoCancellationModeEnum {
  "all",
  "remote-only"
};
EchoCancellationModeEnum 枚举说明
枚举值 说明
"all"

系统必须尝试从麦克风输入信号中去除系统播放的所有声音。

该选项用于最大化隐私,防止本地音频如通知或屏幕阅读器被传输。

"remote-only"

系统必须尝试去除来自 WebRTC RTCPeerConnectionMediaStreamTrack 音频流的声音。

此选项适用于希望传输本地播放音频的场景。例如远程音乐课,学生本地合奏需要老师听到伴奏和乐器声音,但回声需消除远端声音。

UA 决定取消哪些 RTCPeerConnection, 但捕获麦克风的浏览上下文正在播放的应是取消对象之一。建议如此。

注意
EchoCancellationModeEnum 枚举值外, echoCancellation 可约束属性也接受 truefalsefalse表示不进行回声消除, true表示由UA决定去除哪些音频。true 必须至少像 "remote-only" 一样消除回声, 建议尽可能像 "all" 一样消除回声。

4.3.9 垃圾回收

如果 MediaStreamTrack 对象尚未 结束,且注册了 muteunmuteended 事件的事件监听器, 不得被垃圾回收。 每种源类型可进一步细化垃圾回收规则,因为某些源可能永远不会触发某些事件。

注意
鼓励作者调用 MediaStreamTrack 的 stop() 方法, 尤其是采集轨道,因为底层资源代价高昂,并且会影响呈现给用户的隐私指示器。

addtrackremovetrack 事件 使用 MediaStreamTrackEvent 接口。

addtrackremovetrack 事件通知 脚本 轨道集合 已被 MediaStream用户代理 更新。

触发名为 e 的轨道事件,并关联一个 MediaStreamTrack track,表示创建并在指定目标分发一个事件,其名称为 e,该事件不会冒泡(除非另有规定),也不可取消(除非另有规定),并使用 MediaStreamTrackEvent 接口,track 属性设置为 track必须这样处理。

WebIDL[Exposed=Window]
interface MediaStreamTrackEvent : Event {
  constructor(DOMString type, MediaStreamTrackEventInit eventInitDict);
  [SameObject] readonly attribute MediaStreamTrack track;
};

构造函数

constructor()

构造一个新的 MediaStreamTrackEvent

属性

track 类型为 MediaStreamTrack,只读

track 属性表示与事件关联的 MediaStreamTrack 对象。

WebIDLdictionary MediaStreamTrackEventInit : EventInit {
  required MediaStreamTrack track;
};

字典 MediaStreamTrackEventInit 成员

track 类型为 MediaStreamTrack,必需

5. 模型:源、汇、约束与设置

本节为非规范性内容。

用户代理提供了从源到汇的媒体管道。在用户代理中, 汇指的是 <img>、 <video>、 以及 <audio> 标签。 传统源包括流式内容、文件和网络资源。这些源产生的媒体通常不会随时间改变——这些源可以视为静态的。

向用户展示这些源的汇(就是实际标签本身)有多种控制方式可以操作源内容。例如, <img> 标签会将一个1600x1200像素的大图缩放至由width="400"height="300"定义的矩形内。

源有生命周期。默认情况下,源的生命周期与创建它的上下文绑定。例如,由 MediaDevices.getUserMedia() 创建的源,视为由其 navigator.mediaDevices上下文创建。同理, RTCRtpReceiver对象的源与 RTCPeerConnection实体绑定,而该实体又与其创建上下文绑定。 除非某些源的定义中明确说明,否则当创建上下文消失时,源总是停止。需要注意的是,来自不同上下文的两个源可以同时使用同一个采集设备, 一个源可以独立于另一个源停止。

getUserMedia API 增加了麦克风和摄像头等动态源——这些源的特性可根据应用需求变化。这些源本质上是动态的。 一个 <video> 元素展示动态源的媒体时, 可以对内容进行缩放,也可以沿着媒体管道反馈信息,使源产生更适合展示的内容。

注意

注意:这种反馈回路显然只是实现了一种“优化”,但收益并不微小。这种优化可以节省电池、减少网络拥堵等……

需要注意的是, MediaStream 汇(如 <video>、 <audio>, 甚至 RTCPeerConnection)仍可以对源流进行进一步转换, 超出本规范所述的设置能力以及约束的范围。(汇的转换选项,包括 RTCPeerConnection的相关选项, 不在本规范范围之内。)

对轨道约束的变更或应用可能会影响所有共享该源的轨道的 设置, 从而影响所有使用该源的下游汇。许多汇能够适应这些变化,如 <video> 元素或RTCPeerConnection。 但如 Recorder API 之类的汇,可能会因源设置变化而失败。

RTCPeerConnection 是一个有趣的对象,因为它同时作为汇源处理网络流。作为汇,它可以对源进行转换(如降低码率、缩放分辨率、调整帧率),作为源,它也可能被轨道源改变自己的设置。

为了说明对某个源的变更如何影响不同的汇,来看以下示例。该示例只用到宽度和高度,但同理也适用于本规范所暴露的所有设置。如图所示,家庭客户端从本地摄像头获取了一个视频源。该源的宽高设置分别为800x600像素。该家庭客户端上的三个 MediaStream对象包含使用同一个 <deviceId的轨道。这三个媒体流连接到三个不同的汇:一个 <video> 元素(A)、另一个 <video> 元素(B),以及一个 peer connection(C)。该 peer connection 将源视频流传到远程客户端。远程客户端有两个媒体流,其轨道以 peer connection 为源。两个媒体流分别连接到两个 <video> 元素(Y 和 Z)。

Changing media stream source effects: before the requested change

此时,家庭客户端上的所有汇都必须对原始源尺寸进行变换。B 将视频缩小,A 将视频放大(导致画质损失),而 C 也略微放大视频以便网络传输。远程客户端上,汇 Y 大幅缩小视频,汇 Z 不做缩放。

在调用 applyConstraints() 后,其中一个轨道希望家庭客户端的视频源分辨率提升至1920x1200像素。

Changing media stream source effects: after the requested change

需要注意的是,源的改变会立刻影响家庭客户端上的所有轨道和汇,但不会影响远程客户端上的任何汇(或源)。随着家庭客户端的视频源尺寸增大,汇 A 无需再进行缩放,汇 B 需要比以前更多地缩小视频。汇 C(peer connection)则需将视频缩小,以保持向远程客户端的传输不变。

虽然未展示,远程客户端侧也可以提出同样有效的设置变更请求。该变更会像之前影响 A、B、C 一样影响 Y 和 Z,还可能导致与家庭客户端上的 peer connection 重新协商,调整其对家庭客户端视频源的转换。此类变更不要求对汇 A、B 或家庭客户端的视频源产生任何变化。

需注意,本规范未定义远程客户端视频源变化自动触发家庭客户端视频源变化的机制。只要不超出应用设定的约束,具体实现可以在源到汇间进行优化,如下例所示。

很明显,对某个源的变更会影响消费该源的汇。但在某些场景下,对某个汇的变更也可能导致实现调整源的设置。下图对此进行了说明。下图中,家庭客户端的视频源发送尺寸为1920x1200像素的视频流。该视频源未受约束,即其实际尺寸对应用而言是灵活的。两个 MediaStream对象包含同一个 deviceId,这些MediaStream 分别连接到两个不同的 <video> 汇A和B。汇A尺寸为width="1920"height="1200",直接展示源视频内容不做变换。汇B尺寸较小,因此将视频缩放至320x200像素适配其矩形区域。

Changing media stream sinks may affect sources: before the requested change

当应用将汇A尺寸缩小(宽从1920变为1024,高从1200变为768)时,用户代理的媒体管道可能意识到没有任何汇需要更高的源分辨率,源和汇A做了不必要的工作。此时,若没有其他约束强制源继续输出高分辨率视频,媒体管道可以改变源分辨率:

Changing media stream sinks may affect sources: after the requested change

如上图,家庭客户端的视频源分辨率被改为汇A和B所需尺寸中的较大值,以优化播放。虽然图中未展示,peer connection 等其他汇也可能采用同样行为。

可能会对某个轨道应用约束,而源无法满足这些约束,无论是因为源本身无法满足,还是已经满足了与之冲突的约束。当这种情况发生时, applyConstraints() 返回的 promise 会被拒绝,不会应用任何新约束。因为约束未发生变化,源本身也无需做任何变更。下面是该行为的示例。

本例中,两个媒体流各有一个共享同一源的视频轨道。第一个轨道初始未应用约束,连接到汇N。汇N分辨率为800x600像素,将源分辨率1024x768缩放适配。另一个轨道有一个必需约束,强制关闭源的补光灯,连接到汇P。汇P宽高与源一致。

Overconstrained application

此时,第一个轨道添加了一个 必需约束,要求补光灯必须开启。此时,两个必需约束无法同时满足(补光灯无法同时开和关)。由于该状态是第一个轨道试图应用冲突约束造成的,约束应用失败,源设置和两个轨道的约束都不会发生变化。

6. MediaStreams 在媒体元素中

MediaStream 可以分配给媒体元素。MediaStream 不支持预加载和寻址,表示一个简单的、可能是无限的线性 媒体时间轴。时间轴从0开始,只要媒体元素处于 可能播放状态,时间轴就会按实际时间线性递增。当MediaStream暂停播放时,时间轴不会递增。

支持本规范的用户代理 必须支持 srcObject 属性(定义在HTMLMediaElement接口中,参见 [HTML]), 其中包括对MediaStream对象的播放支持。

[HTML]文档概述了HTMLMediaElement 如何与媒体提供者对象协作。当媒体提供者对象MediaStream时,适用以下内容:

MediaStream的特性对相关HTMLMediaElement的属性行为和可执行操作有限制,具体如下:

属性名 属性类型 当提供者为MediaStream时的设置/获取行为 补充说明
preload DOMString 获取时:none。设置时:忽略。 MediaStream无法预加载。
buffered TimeRanges buffered.length 必须返回0 MediaStream无法预加载,因此缓冲区始终为空时间范围。
currentTime double 任何非负整数。初始值为0,只要元素处于可能播放状态,值线性递增。 该值为 官方播放位置,单位为秒。尝试更改该值必须被忽略。
seeking boolean false MediaStream不可寻址,因此该属性必须始终返回false
defaultPlaybackRate double 获取时:1.0。设置时:忽略。 MediaStream不可寻址,因此该属性必须始终返回1.0,尝试更改必须被忽略。注意这也意味着ratechange事件不会触发。
playbackRate double 获取时:1.0。设置时:忽略。 MediaStream不可寻址,因此该属性必须始终返回1.0,尝试更改必须被忽略。注意这也意味着ratechange事件不会触发。
played TimeRanges played.length 必须返回1
played.start(0) 必须返回0
played.end(0) 必须返回最新的currentTime
MediaStream的时间轴始终只有一个区间,从0到当前currentTime。
seekable TimeRanges seekable.length 必须返回0 MediaStream不可寻址。
loop boolean true, false 设置loop 属性无效,因为MediaStream没有定义结束,因此无法循环。

由于上述所有setter均不改变HTMLMediaElement内部状态, 一旦MediaStream不再是元素的已分配媒体提供者对象, 列出的属性会恢复到分配流之前的值。

注意

MediaStream已分配媒体提供者对象中不再被分配, 当srcObject 被赋值为null或非流对象, 发生在媒体元素加载算法之前。 因此,如果playbackRatedefaultPlaybackRate与分配流之前不同, ratechange事件可能会被触发(第7步)。

7. 错误处理

某些操作会抛出或触发 OverconstrainedError。这是 DOMException 的扩展,包含与约束失败相关的附加信息。

7.1 OverconstrainedError 接口

WebIDL[Exposed=Window]
interface OverconstrainedError : DOMException {
  constructor(DOMString constraint, optional DOMString message = "");
  readonly attribute DOMString constraint;
};

7.1.1 构造函数

OverconstrainedError

运行以下步骤:

  1. constraint 为构造函数的第一个参数。

  2. message 为构造函数的第二个参数。

  3. e 为一个新的 OverconstrainedError 对象。

  4. 调用 DOMException 的构造函数, 传入 message 参数为 messagename 参数为 "OverconstrainedError"

    注意

    该名称没有映射到旧代码,因此 ecode 属性将返回 0。

  5. 设置 e.constraintconstraint

  6. 返回 e

7.1.2 属性

constraint 类型为 DOMString, 只读

与此错误相关的约束名,如果没有特定约束名则为 ""

8. 事件摘要

本节为非规范性内容。

以下事件会在 MediaStream 对象上触发:

事件名 接口 触发时机...
addtrack MediaStreamTrackEvent 有新的 MediaStreamTrack 被添加到该流。注意,当脚本直接修改 MediaStream 的轨道时不会触发此事件。
removetrack MediaStreamTrackEvent MediaStreamTrack 被从该流移除。注意,当脚本直接修改 MediaStream 的轨道时不会触发此事件。

以下事件会在 MediaStreamTrack 对象上触发:

事件名 接口 触发时机...
mute Event MediaStreamTrack 的源暂时无法提供数据时。
unmute Event MediaStreamTrack 的源在暂时无法提供数据后重新变为活动状态时。
ended Event

MediaStreamTrack 的源将不再提供任何数据, 可能是因为用户撤销了权限、源设备被拔出,或远端节点永久停止发送数据。

以下事件会在 MediaDevices 对象上触发:

事件名 接口 触发时机...
devicechange DeviceChangeEvent 可用给 用户代理 的媒体设备集合发生改变。当前设备列表可通过 devices 属性获取。

9. 枚举本地媒体设备

本节描述了脚本可用于查询用户代理连接的媒体输入和输出设备(例如网络摄像头或耳机)的API。

MediaDevices 对象是用于检查和访问用户代理可用媒体设备的API入口。

创建 MediaDevices 对象,给定 realm,执行以下步骤:

  1. mediaDevicesMediaDevices 对象(在 realm 中),初始化以下内部槽:

    • [[devicesLiveMap]],初始化为空 映射

    • [[devicesAccessibleMap]],初始化为空 映射

    • [[kindsAccessibleMap]],初始化为空 映射

    • [[storedDeviceList]],初始化为 用户代理可用的所有媒体输入和输出设备的 列表

    • [[canExposeCameraInfo]],初始值为 false

    • [[canExposeMicrophoneInfo]],初始值为 false

    • [[mediaStreamTrackSources]],初始值为空 集合

  2. settingsmediaDevices相关设置对象

  3. 对于 MediaDevices.getUserMedia() 暴露的每种设备类型 kind,执行:

    1. mediaDevices.[[kindsAccessibleMap]][kind]true, 如果与 kind 关联的权限(如 "camera""microphone")的 permission state 为 "granted", 否则为 false

  4. 对于 MediaDevices.getUserMedia() 暴露的每个具体设备,使用设备的 deviceId deviceId,执行:

    1. mediaDevices.[[devicesLiveMap]][deviceId]false, 且设 mediaDevices.[[devicesAccessibleMap]][deviceId]true, 如果与该设备类型及 deviceId 关联的权限(针对 settings)的 permission state 为 "granted", 否则为 false

  5. 返回 mediaDevices

对于 getUserMedia() 暴露的每种设备类型 kind, 每当与 kind 相关的权限的 权限状态发生变化时, 针对 mediaDevices相关设置对象, 执行以下步骤:

  1. 如果状态从其他值变为 "granted", 则将 mediaDevices.[[kindsAccessibleMap]][kind] 设为 true

  2. 如果状态从 "granted" 变为其他值, 则将 mediaDevices.[[kindsAccessibleMap]][kind] 设为 false

对于 getUserMedia() 暴露的每个具体设备, 每当与该设备类型和 deviceId 相关的权限的 权限状态发生变化时, 针对 mediaDevices相关设置对象, 执行以下步骤:

  1. 如果状态从其他值变为 "granted", 则将 mediaDevices.[[devicesAccessibleMap]][deviceId] 设为 true(如果尚未为 true)。

  2. 如果状态从 "granted" 变为其他值, 且设备当前已 停止, 则将 mediaDevices.[[devicesAccessibleMap]][deviceId] 设为 false

当有新的媒体输入/输出设备可用,或已有设备变得不可用,或某种媒体设备类型的系统默认设备发生变化时, 用户代理 必须为每个 MediaDevices 对象 mediaDevices(满足 device enumeration can proceed 条件为 true,其他 MediaDevices 对象不处理)执行以下 设备变更通知步骤

  1. lastExposedDevices创建设备信息对象列表(参数为 mediaDevicesmediaDevices.[[storedDeviceList]])。

  2. deviceList 为用户代理可用的所有媒体输入/输出设备的列表。

  3. newExposedDevices创建设备信息对象列表(参数为 mediaDevicesdeviceList)。

  4. 如果 newExposedDevicesMediaDeviceInfo 对象与 lastExposedDevices 相同且顺序一致,则终止这些步骤。

    注意

    由于 enumerateDevices 算法,上述步骤只会对允许使用 enumerateDevices 枚举特定 MediaDeviceKind 的文档触发 devicechange 事件。

  5. mediaDevices.[[storedDeviceList]]deviceList

  6. 加入一个任务,触发事件,事件名为 devicechange, 使用 DeviceChangeEvent 构造器,devices 初始化为 newExposedDevices, 在 mediaDevices 处触发。

    用户代理 可以合并多个事件为一个事件(例如同时添加/移除多个设备,如带麦克风的摄像头)。

此外,如果曾遍历的某 MediaDevices 对象 后续满足 device enumeration can proceed 条件(如 进入可视区域), 用户代理 必须 在该 MediaDevices 对象上执行 设备变更通知步骤

注意

这些事件可能会在不同来源的文档上同时触发。用户代理 可以通过对事件时间点进行扰动,避免跨域活动相关性。(这是一个指纹向量。)

WebIDL[Exposed=Window, SecureContext]
interface MediaDevices : EventTarget {
  attribute EventHandler ondevicechange;
  Promise<sequence<MediaDeviceInfo>> enumerateDevices();
};

属性

ondevicechange 类型为 EventHandler

该事件处理器的事件类型为 devicechange

方法

enumerateDevices

收集 用户代理 可用的媒体输入和输出设备信息。

该方法返回一个 promise。promise 将在 成功时携带一个 MediaDeviceInfo 对象序列, 表示 用户代理 可用的媒体输入和输出设备(枚举成功时)。

该序列中表示输入设备的元素将为 InputDeviceInfo 类型,继承自 MediaDeviceInfo

摄像头和麦克风源 应当 可枚举。增加其他源类型的规范会给出该源类型是否可枚举的建议。

当调用 enumerateDevices() 方法时,用户代理必须执行以下步骤:

  1. p 为一个新 promise。

  2. proceed设备枚举可进行 的结果,参数为 this

  3. mediaDevicesthis

  4. 并行执行以下步骤:

    1. proceedfalse 时, 用户代理 必须等待,直到队列任务将 proceed 设置为 设备枚举可进行 的结果(参数为 mediaDevices),使 proceed 变为 true

    2. resultList创建设备信息对象列表 的结果,参数为 mediaDevicesmediaDevices.[[storedDeviceList]]

    3. resolve p,值为 resultList

  5. 返回 p

要执行 创建设备信息对象列表,给定 mediaDevicesdeviceList, 执行以下步骤:

  1. resultList 为一个空列表。

  2. microphoneListcameraListotherDeviceList 为空列表。

  3. documentmediaDevices相关全局对象关联 Document

  4. deviceList 中每个已发现的设备 device,执行如下子步骤:

    1. 如果 device 不是麦克风,或 document 不允许使用 "microphone" 功能, 跳过本设备,继续下一个设备(如有)。

    2. deviceInfo创建设备信息对象 的结果(表示 device,参数为 mediaDevices)。

    3. 如果 device 是系统默认麦克风, 则将 deviceInfo 前置到 microphoneList。 否则,将 deviceInfo 添加到 microphoneList

  5. deviceList 中每个已发现的设备 device,执行如下子步骤:

    1. 如果 device 不是摄像头,或 document 不允许使用 "camera" 功能, 跳过本设备,继续下一个设备(如有)。

    2. deviceInfo创建设备信息对象 的结果(表示 device,参数为 mediaDevices)。

    3. 如果 device 是系统默认摄像头, 则将 deviceInfo 前置到 cameraList。 否则,将 deviceInfo 添加到 cameraList

  6. 如果 麦克风信息可暴露mediaDevices 上为 false, 则将 microphoneList 截断为仅保留第一个。

  7. 如果 摄像头信息可暴露mediaDevices 上为 false, 则将 cameraList 截断为仅保留第一个。

  8. deviceList 中每个已发现的设备 device,执行如下子步骤:

    1. 如果 device 是麦克风或摄像头,跳过本设备,继续下一个设备(如有)。

    2. 执行 除摄像头和麦克风外的设备暴露决策算法, 参数为 devicemicrophoneListcameraListmediaDevices。 若结果为 false,跳过本设备,继续下一个设备(如有)。

    3. deviceInfo创建设备信息对象 的结果(表示 device,参数为 mediaDevices)。

    4. 如果 device 是系统默认音频输出, 则将 deviceInfo 前置到 otherDeviceList。 否则,将 deviceInfo 添加到 otherDeviceList

  9. 按顺序将 microphoneList 中的所有设备追加到 resultList

  10. 按顺序将 cameraList 中的所有设备追加到 resultList

  11. 按顺序将 otherDeviceList 中的所有设备追加到 resultList

  12. 返回 resultList

由于该方法会跨会话和来源返回可用媒体采集设备的持久信息, 它增加了 用户代理 暴露的指纹信息面。(这是一个指纹向量。)

只要 相关全局对象关联 Document 未进行采集,则该方法只会暴露两位信息:是否有摄像头和是否有麦克风。 用户代理 可通过假装系统有摄像头和麦克风来进行缓解, 直到 相关全局对象关联 Document 调用 getUserMedia() 并传入合理约束。(这是一个指纹向量。)

相关全局对象关联 Document 开始采集后,会通过所有媒体采集设备列表(包括分组和设备标签)向跨域共享更多持久信息,进一步增加指纹信息面。(这是一个指纹向量。)

用户代理 可通过清理设备标签来限制暴露。例如移除标签中的用户名,但保留设备制造商或型号信息。 重要的是,清理后的标签仍要让用户能识别对应设备。(这是一个指纹向量。)

9.2.1 访问控制模型

上述算法意味着,访问媒体设备信息取决于相关全局对象关联 Document是否进行了采集。

对于摄像头和麦克风设备,如果相关全局对象关联 Document未进行采集 (即getUserMedia()未被调用或未成功解析), MediaDeviceInfo对象将包含 kind的有效值,但 deviceIdlabelgroupId都为空字符串。 另外,kind每种类型最多只会在 enumerateDevices() 结果中列出一个设备。

否则, MediaDeviceInfo对象将包含有意义的 deviceIdkindlabelgroupId值。所有可用设备都会在 enumerateDevices() 结果中列出。

要执行 创建设备信息对象(用于表示已发现的设备 device,给定 mediaDevices),请执行以下步骤:

  1. deviceInfo 为一个新的 MediaDeviceInfo对象,用于表示 device

  2. 初始化 deviceInfo.kinddevice的类型。

  3. 如果 deviceInfo.kind 等于"videoinput",且 摄像头信息可暴露mediaDevices上为 false,则返回 deviceInfo

  4. 如果 deviceInfo.kind 等于"audioinput",且 麦克风信息可暴露mediaDevices上为false,则返回 deviceInfo

  5. 初始化 deviceInfo.labeldevice的标签。

  6. 如果已存在用于 devicedeviceId,则初始化 deviceInfo.deviceId 为该值; 否则令 deviceInfo.deviceId 为 按deviceId描述新生成的唯一标识符。

  7. 如果 device 属于与 document 已表示的设备相同的物理设备, 则初始化 deviceInfo.groupId 为现有 groupId值; 否则令 deviceInfo.groupId 为 按groupId描述新生成的唯一标识符。

  8. 返回 deviceInfo

9.2.2 设备信息暴露

要进行 设备枚举可进行 检查, 给定 mediaDevices, 执行以下步骤:

  1. 如果 设备信息可暴露mediaDevices 上为真, 用户代理 可以返回 true

  2. 返回 is in view 的结果,参数为 mediaDevices

要进行 设备信息可暴露 检查,给定 mediaDevices,执行以下步骤:

  1. 如果 摄像头信息可暴露mediaDevices 上为真, 返回 true

  2. 如果 麦克风信息可暴露mediaDevices 上为真, 返回 true

  3. 返回 false

要进行 摄像头信息可暴露 检查,给定 mediaDevices,执行以下步骤:

  1. 如果任何本地 "videoinput" 类型设备已绑定到 mediaDevices相关全局对象关联 Document 下的活动 MediaStreamTrack 上,返回 true

  2. 返回 mediaDevices.[[canExposeCameraInfo]]

要进行 麦克风信息可暴露 检查,给定 mediaDevices,执行以下步骤:

  1. 如果任何本地 "audioinput" 类型设备已绑定到 mediaDevices相关全局对象关联 Document 下的活动 MediaStreamTrack 上,返回 true

  2. 返回 mediaDevices.[[canExposeMicrophoneInfo]]

要进行 is in view 检查, 给定 mediaDevices,执行以下步骤:

  1. 如果 mediaDevices相关全局对象关联 Document完全激活 且其可见性状态"visible",则返回 true;否则返回 false

要进行 有系统焦点 检查,给定 mediaDevices,执行以下步骤:

  1. 如果 mediaDevices相关全局对象navigable顶级可遍历对象 具有系统焦点, 则返回 true;否则返回 false

要进行 设备暴露可扩展 检查,给定 deviceType,执行以下步骤:

  1. permission 为读取描述符名称为 deviceType权限状态的结果。

  2. 如果 permission 是 "granted", 返回 true

  3. 如果 permission 是 "prompt", 用户代理可以在知道该来源曾授予 deviceType 访问权限时返回 true

  4. 返回 false

9.2.3 设置设备信息暴露

要在 mediaDevices设置设备信息暴露,给定 requestedTypes 集合 和一个布尔值 value,执行以下步骤:

  1. 如果 "video"requestedTypes 中,则执行以下子步骤:

    1. mediaDevices.[[canExposeCameraInfo]] 设为 value

    2. 如果 valuetrue设备暴露可扩展(类型为 "microphone"), 则将 mediaDevices.[[canExposeMicrophoneInfo]] 设为 true

  2. 如果 "audio"requestedTypes 中,则执行以下子步骤:

    1. mediaDevices.[[canExposeMicrophoneInfo]] 设为 value

    2. 如果 valuetrue设备暴露可扩展(类型为 "camera"), 则将 mediaDevices.[[canExposeCameraInfo]] 设为 true

注意

用户代理 可以在任何时候将设备信息暴露重新设为 false, 例如当用户代理决定在某个 Document 上撤销设备访问时。

9.2.4 除摄像头和麦克风外设备的暴露决策算法

除摄像头和麦克风外设备的暴露决策算法devicemicrophoneListcameraListmediaDevices 作为输入,并返回一个布尔值,以决定是否向网页暴露 device 的信息。

默认返回 false

其他规范可以为特定设备类型定义该算法。

9.2.5 上下文采集状态

要对 globalObject 执行 上下文正在采集 检查, 请执行以下步骤:

  1. 如果 globalObject 不是 Window, 则返回 false。

  2. mediaDevicesglobalObject关联 MediaDevices

  3. mediaDevices.[[mediaStreamTrackSources]] 中的每个 source,执行以下子步骤:

    1. 如果 source停止静音,则中止这些步骤。

    2. deviceIdsource 设备的 deviceId。

    3. 如果 mediaDevices.[[devicesLiveMap]][deviceId] 为 true,则返回 true

  4. 返回 false

注意

该算法覆盖所有采集轨道,包括麦克风、摄像头和屏幕。

9.3 设备信息

WebIDL[Exposed=Window, SecureContext]
interface MediaDeviceInfo {
  readonly attribute DOMString deviceId;
  readonly attribute MediaDeviceKind kind;
  readonly attribute DOMString label;
  readonly attribute DOMString groupId;
  [Default] object toJSON();
};

属性

deviceId 类型为 DOMString,只读

所表示设备的标识符。设备 必须通过其标识符和 kind 唯一标识。

为确保存储的标识符可被识别,标识符 必须Document同源 顶级可遍历对象中保持一致。 在 子可导航对象中, 标识符是否跨文档一致,必须遵循 用户代理的存储分区规则 (如 localStorage), 以避免影响跨站相关性防护措施。如果标识符可以唯一识别用户,则必须在其他来源的文档中不可猜测,以防止跨来源相关性。只要标识符与用户无关且可通过其他方式(如 User-Agent 字符串)猜测,则可在不同来源间复用。

如果任何本地设备已绑定到本来源页面的活动 MediaStreamTrack, 或本来源已获得访问本地设备的 存储权限, 则该标识符必须被持久保存,除非下述特殊情况。唯一且稳定的标识符允许应用跨多次访问保存、识别设备可用性并直接请求特定源。

但只要没有本地设备被绑定到本来源页面的活动 MediaStreamTrack,且本来源没有获得本地设备的 存储权限,则用户代理 可以 在本来源最后一个浏览会话关闭后清除该标识符。如果用户代理选择不清除, 则必须允许用户可见地检查并删除该标识符,如同 cookie。

由于 deviceId 可能跨会话持久化, 且为减少其作为指纹机制的潜力,deviceId 应当如 cookie [COOKIES] 一样对待, 即用户代理 不得为被禁止使用 cookie 的站点持久化设备标识符, 且用户代理 必须在清除其他持久化存储时,旋转每个来源的设备标识符。(这是一个指纹向量。)

kind 类型为 MediaDeviceKind,只读

所表示设备的类型。

label 类型为 DOMString,只读

描述该设备的标签(例如“外部USB摄像头”)。该标签用于让终端用户区分设备。应用不能假定标签包含任何特定信息,比如设备类型或型号。 如果设备没有标签,则此属性必须返回空字符串。

groupId 类型为 DOMString,只读

所表示设备的分组标识符。如果两个设备属于同一物理设备,则具有相同的分组标识。例如,代表同一耳机的扬声器和麦克风的音频输入输出设备 groupId 相同。

分组标识符必须为每个document唯一生成。

方法

toJSON
调用时,执行 [WEBIDL] 的 默认 toJSON 步骤
WebIDLenum MediaDeviceKind {
  "audioinput",
  "audiooutput",
  "videoinput"
};
MediaDeviceKind 枚举描述
audioinput

表示音频输入设备,例如麦克风。

audiooutput

表示音频输出设备,例如耳机。

videoinput

表示视频输入设备,例如摄像头。

9.4 输入设备专用信息

InputDeviceInfo 接口用于访问其所代表输入设备的能力。

WebIDL[Exposed=Window, SecureContext]
interface InputDeviceInfo : MediaDeviceInfo {
  MediaTrackCapabilities getCapabilities();
};

方法

getCapabilities

返回一个 MediaTrackCapabilities 对象, 描述该设备主音频或视频轨道的能力(根据其 kind 值),在没有用户提供约束的情况下。 这些能力必须与调用 getCapabilities()MediaStream 返回的第一个同类 MediaStreamTrack 的结果一致, 其中 getUserMedia({deviceId: id})id为本对象的 deviceId属性值。

如果尚未获得任何本地设备的访问权限,并且本 InputDeviceInfo 已针对唯一识别信息进行过滤(详见 enumerateDevices() 结果说明),则该方法返回一个空字典。

devicechange 事件使用 DeviceChangeEvent 接口。

WebIDL[Exposed=Window]
interface DeviceChangeEvent : Event {
  constructor(DOMString type, optional DeviceChangeEventInit eventInitDict = {});
  [SameObject] readonly attribute FrozenArray<MediaDeviceInfo> devices;
  [SameObject] readonly attribute FrozenArray<MediaDeviceInfo> userInsertedDevices;
};

构造函数

constructor()

this.devices 初始化为 由 eventInitDict.devices 创建的冻结数组。

属性

devices 类型为 FrozenArray<MediaDeviceInfo>, 只读

devices 属性返回当前可用设备列表的 MediaDeviceInfo 对象数组。

userInsertedDevices 类型为 FrozenArray<MediaDeviceInfo>, 只读

userInsertedDevices 属性返回仅包含用户最近新插入或激活并随本事件首次暴露的 MediaDeviceInfo 对象的数组。 否则返回空列表。

用户代理 可以包含在调用 getUserMedia() 前用户插入或激活的设备,只要本事件首次暴露它们且用户未在 getUserMedia() 中选取设备。

这些 MediaDeviceInfo 对象(如有)必须也存在于 devices 中。

注意

用户在通话过程中(或通话前后立即)插入设备是希望立即使用该设备的强烈信号。

鼓励应用根据该属性来区分此信号与设备信息暴露变化带来的设备差异。

注意
如果该属性缺失,说明该用户代理尚未升级实现本规范版本。
WebIDLdictionary DeviceChangeEventInit : EventInit {
  sequence<MediaDeviceInfo> devices = [];
};

字典 DeviceChangeEventInit 成员

devices 类型为 sequence<MediaDeviceInfo>, 默认值为 []

devices 成员是一个 MediaDeviceInfo 对象数组,表示可用设备。

10. 获取本地多媒体内容

本节扩展了 Navigator 以及 MediaDevices,提供用于请求访问用户代理可用媒体输入设备权限的 API。

另外,可以通过某些类型的 DOM 元素(如 video 元素)捕获本地 MediaStream。 这对于自动化测试非常有用。 [mediacapture-fromelement]

10.1 MediaDevices 接口扩展

注意
本节中 getUserMedia() 的定义相较于过去在 Navigator 下的方法有两个主要变化。

首先,getUserMedia() 方法的官方定义(也是推荐开发者使用的)现在在本节 MediaDevices 下。 之所以这样决定,是因为原 API 仍然可用于 Navigator.getUserMedia (为兼容旧代码),工作组也承认早期用户曾被鼓励写成 "var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;" 以兼容不同浏览器版本。因此,为了功能等价,Navigator 下的 getUserMedia() 方法以本节定义为准。

第二,这里定义的方法基于 Promise,而 Navigator 下的方法仍基于回调。希望在 Navigator 下查找 getUserMedia() 的开发者请阅读那里的详细说明。

getSupportedConstraints 方法用于让应用判断 用户代理 支持哪些约束属性。应用可能需要这些信息以可靠地使用 必需约束或在 高级约束中获得可预期结果。

WebIDLpartial interface MediaDevices {
  MediaTrackSupportedConstraints getSupportedConstraints();
  Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints = {});
};

方法

getSupportedConstraints

返回一个字典,其成员是用户代理已知的可约束属性。受支持的属性必须被列出,用户代理不得在返回的字典中出现不支持的属性。返回值表示用户代理实际实现的内容,并且在一次浏览会话期间不会变化。

getUserMedia

弹窗请求用户允许使用其摄像头或其他视频、音频输入。

constraints 参数是 MediaStreamConstraints 类型的字典。

该方法返回一个 promise。promise 在 成功时会返回合适的 MediaStream 对象,如果用户接受有效轨道(如下描述)。

如果找不到有效轨道或用户拒绝授权,promise会 拒绝,具体情况如下。

当调用 getUserMedia() 方法时,用户代理 必须执行以下步骤:

  1. constraints 为方法的第一个参数。

  2. requestedMediaTypesconstraints 中值为字典或 true 的媒体类型集合。

  3. 如果 requestedMediaTypes 为空,则返回一个 拒绝的 promise,异常类型为 TypeError。 WebIDL 虽然参数是 optional,但调用必须传参才能成功。

  4. document相关全局对象关联 Document

  5. 如果 document 不是 完全激活,则返回一个 拒绝的 promise,异常类型为 DOMExceptionname 属性为 "InvalidStateError"。

  6. 如果 requestedMediaTypes 包含 "audio",且 document 不允许使用 "microphone" 权限名的功能, 跳到 权限失败 步骤。

  7. 如果 requestedMediaTypes 包含 "video",且 document 不允许使用 "camera" 权限名的功能, 跳到 权限失败 步骤。

  8. mediaDevicesthis

  9. isInViewis in view 算法的结果。

  10. p 为一个新的 promise。

  11. 并行执行以下步骤:

    1. isInViewfalse 时, 用户代理 必须等待,直到队列任务将 isInView 设为 is in view 算法结果为 true

    2. finalSet 为一个(初始为空的)集合。

    3. requestedMediaTypes 中的每种媒体类型 kind,执行:

      1. 对每种 kind 类型的每个可能源设备的每种可能配置,构造一个 候选项,作为最终 MediaStreamTrack 的占位符,包含源设备及其设置字典。

        称该候选集合为 candidateSet

        如果 candidateSet 为空,跳到 未找到失败 步骤。

      2. 如果 constraintskind 项值为 true,则 CS 为空约束集;否则用 constraintskind 项值。
      3. 移除 CS 中未为该类型 MediaStreamTrack 定义的约束属性。即在 "video" 内的音频约束和 "audio" 内的视频约束会被忽略,而不会导致 OverconstrainedError。
      4. 如果 CS 中有某个 必需约束,其名字不在 设备选择允许的必需约束列表里, 则 拒绝 p,异常类型为 TypeError,终止这些步骤。

      5. candidateSet 中每个候选项,使用 SelectSettings 算法及 CS。若结果为 undefined,移除该候选项。这样可排除不满足约束的设备。

        如果 candidateSet 为空,令 failedConstraint 为所有设置字典中 fitness distance 为无穷大的任何 必需约束,如没有则为 "",跳到 约束失败 步骤。

        此错误可透露底层设备无法提供哪些能力,用户尚未授权就能用于指纹识别。(这是一个指纹向量。)

      6. 读取所有 candidateSet 中未绑定到当前 Document 活动 MediaStreamTrack 的设备的权限状态。移除权限状态为 "denied" 的候选项。

        如果 candidateSet 为空,说明该类型设备都处于 "denied" 状态,跳到 权限失败 步骤。

      7. 可选地(如基于用户偏好、安全原因或平台限制),跳到 权限失败 步骤。

      8. candidateSet 中所有候选项加入 finalSet

    4. stream 为一个新的空 MediaStream 对象。

    5. requestedMediaTypes 中每种媒体类型 kind,并行执行以下子步骤:

      注意

      用户代理鼓励将不同类型媒体的并发请求合并为一个用户权限弹窗。

      1. 请求权限使用 PermissionDescriptorname 设置为 kind 关联的权限名(如 "camera" 对应 "video""microphone"对应 "audio"), 同时考虑所有绑定到当前 Document 活动并具有 同权限MediaStreamTrack, 权限状态为 "granted",最终获得一组媒体。 同权限指获取权限时要求和当前请求相同级别(如非隔离)的 MediaStreamTrack

        请求用户权限时,用户代理 必须披露权限是仅授予所选设备还是同类所有设备。

        注意

        如果用户未响应,算法会在此步骤停滞。

      2. 如果请求结果为 "denied",跳到 权限失败 步骤。

    6. hasSystemFocusfalse

    7. hasSystemFocusfalse 时, 用户代理 必须等待,直到队列任务根据 has system focus 算法将 hasSystemFocus 设为 true

    8. 设置设备信息暴露,参数为 mediaDevicesrequestedMediaTypestrue

    9. requestedMediaTypes 中每种媒体类型 kind,执行:

      1. finalCandidate 为所提供媒体,该媒体 必须finalSet 中精确的一个 kind 类型候选项。具体选择由 用户代理决定,可询问用户。

        用户代理 应当根据 SelectSettings 算法计算的 fitness distance 作为选择依据,也可用其他内部信息(如用户偏好)。

        用户代理鼓励默认选择用户的主用或系统默认设备(如果可能)。用户代理 可以允许用户使用任何媒体源,包括录制媒体文件。

      2. 请求结果为 "granted"。 如果硬件错误(如系统/程序/网页锁定)导致无法访问,则移除 finalSet 中对应候选项。 若 finalSet 中无该类型候选项,则 拒绝 p,异常类型为 DOMExceptionname 属性值为 "NotReadableError",终止这些步骤。否则,更新 finalSet 后重试。

        若设备访问因其他原因失败,则移除对应候选项。若 finalSet 无该类型候选项,则 拒绝 p,异常类型为 DOMExceptionname 属性值为 "AbortError",终止这些步骤。否则,更新 finalSet 后重试。

      3. grantedDevicefinalCandidate 的源设备。

      4. grantedDevice 的 deviceId deviceId, 将 mediaDevices.[[devicesLiveMap]][deviceId] 设为 true(如尚未为 true), 且 mediaDevices.[[devicesAccessibleMap]][deviceId] 也设为 true(如尚未为 true)。

      5. track创建 MediaStreamTrack 的结果,参数为 grantedDevicemediaDevicesMediaStreamTrack 的源 不得改变。

      6. track 加入 stream 的轨道集合。

    10. stream 中所有轨道,运行 ApplyConstraints 算法,并传入适当约束。若返回非 undefined,令 failedConstraint 为该结果,跳到 约束失败 步骤。

    11. stream 中每个 track将轨道源与 MediaDevices 绑定,参数为 track.[[Source]]mediaDevices

    12. resolve p,值为 stream,终止这些步骤。

    13. 未找到失败

      1. 如果 允许 getUserMedia 特定失败, 参数为 requestedMediaTypes,结果为 false,跳到 权限失败 步骤。

      2. 拒绝 p,异常类型为 DOMExceptionname 属性值为 "NotFoundError"。

    14. 约束失败

      1. 如果 允许 getUserMedia 特定失败, 参数为 requestedMediaTypes,结果为 false,跳到 权限失败 步骤。

      2. messageundefined 或信息性人类可读消息, constraintfailedConstraint(若 设备信息可暴露true),否则为 ""

      3. 拒绝 p,异常类型为 OverconstrainedError,通过 OverconstrainedError(constraint, message) 构造。

    15. 权限失败拒绝 p,异常类型为 DOMExceptionname 属性值为 "NotAllowedError"。

  12. 返回 p

要检查 允许 getUserMedia 特定失败, 给定 requestedMediaTypes,执行以下步骤:

  1. 如果 requestedMediaTypes 包含 "audio",读取描述符名为 "microphone" 的 权限状态。若结果为 "denied",返回 false

  2. 如果 requestedMediaTypes 包含 "video",读取描述符名为 "camera" 的 权限状态。若结果为 "denied",返回 false

  3. 返回 true

注意

在上述算法中,约束会检查两次——设备选择时和批准访问后。两次检查之间可能有时间间隔,选中的设备或许已不适用,这种情况会导致 NotReadableError。

设备选择允许的必需约束 包含以下约束名: widthheightaspectRatioframeRatefacingModeresizeModesampleRatesampleSizeechoCancellationautoGainControlnoiseSuppressionlatencychannelCountdeviceIdgroupId

MediaStreamConstraints 字典用于指示 用户代理MediaStreamTrack 中应包含哪些类型的轨道, 并通过 MediaStream 返回给 getUserMedia()

WebIDLdictionary MediaStreamConstraints {
  (booleanMediaTrackConstraints) video = false;
  (booleanMediaTrackConstraints) audio = false;
};

字典 MediaStreamConstraints 成员

video 类型为 (booleanMediaTrackConstraints), 默认值为 false

如果为 true,则请求返回的 MediaStream 包含一个视频轨道。 如果提供了 Constraints 结构,则进一步指定视频轨道的类型和设置。如果为 false,则 MediaStream 不得包含视频轨道。

audio 类型为 (booleanMediaTrackConstraints), 默认值为 false

如果为 true,则请求返回的 MediaStream 包含一个音频轨道。 如果提供了 Constraints 结构, 则进一步指定音频轨道的类型和设置。如果为 false,则 MediaStream 不得包含音频轨道。

10.3 传统 GetUserMedia 接口

本节为非规范性内容。

注意

本节中的 getUserMedia() 定义反映了最初提出的调用格式,仅为希望保留向后兼容性的浏览器记录。它与推荐接口有两个重要区别。

首先,getUserMedia() 方法的官方定义(也是推荐开发者使用的)现在在 MediaDevices 下。由于原始 API 仍可在 Navigator 对象下使用(为兼容旧代码),工作组承认早期用户常定义 getUserMedia 为 "var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;",以兼容不同浏览器版本。因此,为了功能等价,本节的方法以 MediaDevices 下的方法为准。

第二,规范中所有其他基于回调的方法都改为基于 Promise,因此 navigator.getUserMedia() 也需通过 navigator.mediaDevices.getUserMedia() 实现。由于 navigator.getUserMedia() 现在是规范中唯一剩余的回调方法,关于它是否应继续保留、是否应语法也改为 Promise,仍在讨论中,特别欢迎仍在使用该功能的开发者参与讨论。

其他从回调迁移到 Promise 的方法,因未被广泛实现,无需考虑遗留用法。

实现不需要实现该接口即可视为合规。

10.3.1 接口定义

WebIDLpartial interface Navigator {
  [SecureContext] undefined getUserMedia(MediaStreamConstraints constraints,
                                    NavigatorUserMediaSuccessCallback successCallback,
                                    NavigatorUserMediaErrorCallback errorCallback);
};
方法
getUserMedia

弹窗请求用户允许使用其摄像头或其他视频、音频输入。

constraints 参数是 MediaStreamConstraints 类型的字典。

如果用户接受有效轨道(参见 getUserMedia()MediaDevices), successCallback 会被调用并传入合适的 MediaStream 对象。

如果查找有效轨道失败或用户拒绝授权(参见 getUserMedia()MediaDevices), errorCallback 会被调用。

当调用 getUserMedia() 方法时, 用户代理 必须运行以下步骤:

  1. constraints 为方法的第一个参数。

  2. successCallback 为第二个参数指定的回调。

  3. errorCallback 为第三个参数指定的回调。

  4. getUserMedia() 算法 的步骤运行,参数为 constraints,并令 p 为得到的 promise。

  5. p 成功且值为 stream时,执行:

    1. stream 调用 successCallback

  6. p 失败且原因为 r时,执行:

    1. r 调用 errorCallback

10.4 实现建议

本节为非规范性内容。

最佳实践 1: 资源预留

用户代理建议在确定 getUserMedia() 调用会成功时预留资源。最佳做法是在 promise 解析之前预留资源。后续页面或其他应用再次调用 getUserMedia() 时,应将已分配的资源及其他应用占用的资源视为“忙碌”。这些被标记为“忙碌”的资源不应提供给当前网页使用,除非用户明确指定。可选地,用户代理可选择仅向同一源页面(即保持该资源忙碌的原始流对应的页面)提供来自忙碌源的流。

本规范建议在授权弹窗或设备选择界面(如果有)时,允许用户选择任何可用硬件作为页面请求的流源(前提是该资源能满足所有 必需约束)。虽然不是强制推荐,但有些 用户代理可能支持用本地文件或其他媒体替换视频或音频源。文件选择器可用于此功能。

本规范还建议将因先前调用 getUserMedia()(无论是本页还是其他仍存活的页面)而忙碌的所有资源展示给用户,并允许用户终止流并将资源用于当前页面。如果操作环境允许,还建议同样展示并处理其他应用当前占用的资源。如果用户选择此选项,必须移除受影响页面流中对应资源的轨道。

最佳实践 2: 存储权限

请求设备权限时,用户代理可以选择为同一来源存储该权限,避免用户后续再次授权。是否为每个设备单独存储、为同类设备存储,还是为所有设备存储权限,由 用户代理决定,但必须让用户明确知晓,并且存储权限时必须获得该集合全部设备的授权,例如存储所有摄像头的权限时,必须全部摄像头都授权而不仅是其中一个。

如上所述,本规范不强制授权后是否存储权限。若权限未存储,则权限仅在所有由该设备产生的 MediaStreamTrack 停止之前有效。

最佳实践 3: 多设备处理

MediaStream 可以包含多个视频和音频轨道。例如,可将两个或更多摄像头的视频合并到一个流对象中。但当前 API 不允许页面表达需要多个独立视频源的需求。

建议允许同一页面多次调用 getUserMedia(),以便页面请求多个独立的视频和/或音频流。

注意,如果页面多次调用 getUserMedia(),它们请求和分配资源的顺序不受本规范约束。

一次 getUserMedia() 调用总是返回一个最多包含一个音频轨道和一个视频轨道的流。如果脚本在页面稳定前多次调用 getUserMedia(),本规范建议 UI 设计者合并弹窗,让用户一次授权多个摄像头或媒体源。每次调用的约束可用于决定哪些流分配哪些源。

最佳实践 4: deviceId 生成

生成 deviceId 的高效做法是:用私钥 +(origin 或 origin+顶级 origin,视用户代理分区规则而定)+ salt + 驱动层设备底层硬件 id 生成加密哈希,并以字母数字串呈现。建议哈希长度不超过 32 位,但也不宜更短,以避免冲突风险。

另一种低熵方案是以存储空间换安全:为每个 origin 或 origin+顶级 origin(按 用户代理分区规则)每遇到一个新设备,随机分配 0~255 号,若号用尽则淘汰最久未使用的设备。

最佳实践 5: 用户代理发起的设备静音

为管理用户隐私,摄像头或麦克风源的轨道可被 用户代理随时强制静音。但这样做可能造成网页兼容性问题并泄露用户活动信息,因此需谨慎。

最佳做法是在以下情况下静音摄像头或麦克风轨道:

  • 发生操作系统级事件时,用户代理已全局暂停媒体播放但 JavaScript 未暂停。原因是如果捕获继续进行,用户可能会感到意外(除非已主动设置)。如果操作系统事件已导致轨道停止接收帧,则不会泄露新活动信息。即使非如此,告知捕获结束比在用户未察觉时继续捕获更合理。

  • 当页面未在视图中时, 重新启用所有源轨道均已禁用的轨道,以延迟恢复捕获,直到页面在视图中

最佳做法是在以下情况下取消静音之前被静音的摄像头或麦克风轨道:

  • 操作系统级事件恢复全局媒体播放且页面对用户可见(如未处于锁屏状态)。用户代理可酌情延迟操作,以确保用户意识到先前捕获会话。

  • 页面进入视图且含有一个或多个启用静音的轨道。

11. 可约束模式

可约束模式允许应用检查和调整实现该模式的对象(可约束对象)的属性。它被拆分为一组独立定义,以便其他规范引用。核心概念是“能力”,即对象的可约束属性及其可能值集合,这些值可以是范围或枚举。例如,摄像头可能支持帧率(属性)在每秒20到50帧之间(范围),也可能可以设置(属性)朝向用户、远离用户或向左/向右(枚举)。应用可通过 getCapabilities() 访问器检查可约束属性的支持能力。

应用可通过基本和/或高级约束集(ConstraintSet)及 applyConstraints() 方法选择对象能力的(范围)值。约束集包含对象的一个或多个属性名,以及每个属性的期望值(或期望值范围)。每对属性/值都可视为一个约束。例如,应用设置一个包含两个约束的约束集,第一个要求摄像头帧率在每秒30到40帧之间(范围),第二个要求摄像头朝向用户(具体值)。这些约束的交互方式取决于它们是在基本约束结构(即带有额外 'advanced' 属性的约束集)中,还是在高级列表中的约束集。行为如下:基本约束结构中的所有 'min'、'max'、'exact' 约束共同视为必需约束,如无法同时满足这些属性名的所有约束,用户代理必须拒绝返回的promise。否则,必须应用必需约束。接下来,将按顺序处理 advanced 列表中的每个约束集(即一起满足所有约束),若无法全部满足,则跳过该约束集。然后,用户代理必须分别尝试应用所有 'ideal' 约束或作为属性的裸值(称为可选基本约束)。对于这些属性,必须最大可能地满足它们,顺序不限。最后,用户代理必须resolve返回的promise。

注意
只有当指定的可约束属性被用户代理支持时,API中给出的约束才会被考虑。JS应用代码应先通过 getSupportedConstraints() 检查所用属性名是否被 用户代理支持。原因是 WebIDL 会丢弃约束字典中不支持的属性名,导致 用户代理不会处理这些属性,最后被静默忽略。这会造成编程混淆,JS代码设置了约束但 用户代理忽略它们。支持(识别)必需约束名称但无法满足的用户代理会抛出错误;不支持该属性的用户代理不会抛错。

以下示例有助于理解约束的工作方式。第一个是基本约束结构,给出了三个约束,用户代理会分别尝试满足。根据摄像头可用分辨率,可能无法同时满足三个约束。如果如此,用户代理会尽量满足两个,或只满足一个。如无法同时满足三个约束,可能有多个能满足两个约束的组合,此时由用户代理选择。

const stream = await navigator.mediaDevices.getUserMedia({
  video: {
    width: 1280,
    height: 720,
    aspectRatio: 3/2
  }
});

下一个示例略微复杂。width 和 height 仍给出理想值,但同时对它们和 frameRate 设定了最小要求。如果无法满足 frameRate、width 或 height 的最小值,则拒绝promise。否则,会尝试满足 width、height 和 aspectRatio 目标值,然后resolvepromise。

try {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      width: {min: 640, ideal: 1280},
      height: {min: 480, ideal: 720},
      aspectRatio: 3/2,
      frameRate: {min: 20}
    }
  });
} catch (error) {
  if (error.name != "OverconstrainedError") {
    throw error;
  }
  // 约束失败。 请尝试其他组合(未弹出授权框)
}

本例展示了通过 'advanced' 属性实现的约束结构的完全控制。此时,用户代理对必需约束的处理方式不变,但在尝试满足理想值前会处理 'advanced' 列表。此例的 'advanced' 列表包含两个约束集,第一个指定 width 和 height,第二个指定 aspectRatio。注意在 advanced 列表里,裸值视为 'exact' 值。本例表示:“我需要视频宽至少640、高至少480。优选精确1920x1280,但如果不行,优选4x3的宽高比,如果还不行,给我最接近1280x720的分辨率。”

try {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      width: {min: 640, ideal: 1280},
      height: {min: 480, ideal: 720},
      frameRate: {min: 30},
      advanced: [
        {width: 1920, height: 1280},
        {aspectRatio: 4/3},
        {frameRate: {min: 50}},
        {frameRate: {min: 40}}
      ]
    }
  });
} catch (error) {
  if (error.name != "OverconstrainedError") {
    throw error;
  }
  // 约束失败。 请尝试其他组合(未弹出授权框)
}

高级约束集的顺序很重要。在上例中,无法同时满足1920x1280和4x3约束集。由于1920x1280在列表前,用户代理会先尝试满足它。应用作者可通过为同一属性指定多个高级约束集实现回退策略。应用还指定了两个高级约束集,分别要求帧率大于50和大于40。如果用户代理能设置帧率大于50,则会设置,后续约束集会被自动满足。如果用户代理不能设置大于50,则会跳过该约束集,尝试设置大于40。如果用户代理都无法满足,则基本约束集的 'min' 值要求至少为30。也就是说,用户代理如果拿不到大于30的值就会完全失败,但会优先选大于50的,次选大于40的。

注意,与基本约束不同,高级列表中的约束集必须整体满足或整体跳过。因此,{width: 1920, height: 1280} 是请求该分辨率,而不是单独请求宽或高。可将基本约束视为“或”请求(非互斥),而高级约束集则是“与”请求。应用可通过 getConstraints() 访问器检查当前生效的完整约束集。

用户代理为可约束属性选择的具体值称为“设置”。例如,应用指定帧率必须至少30、最多40,则设置可以是32、35或37。应用可通过 getSettings() 访问器查询对象可约束属性的当前设置。

11.1 接口定义

虽然本规范正式将 ConstrainablePattern 定义为一个 WebIDL 接口,但实际上它是其他接口的模板或模式,不能直接继承, 因为方法的返回值需要被扩展,而 WebIDL 无法做到这一点。因此,每个希望使用此处定义功能的接口都必须提供其自身的 WebIDL 函数和接口副本。但它可以引用此处定义的语义,这些语义不会改变。示例请参见 MediaStreamTrack 接口定义

该模式依赖 constrainable object 定义三个内部槽:

  1. 一个 [[Capabilities]] 内部槽,初始化为一个 Capabilities 字典,描述所暴露的每个可约束属性的聚合允许值,如 Capabilities 所述,如果没有则为空字典。

  2. 一个 [[Constraints]] 内部槽,初始化为一个 为空的 Constraints 字典。

  3. 一个 [[Settings]] 内部槽,初始化为一个 Settings 字典,描述所暴露的每个可约束属性当前激活的设置值,如 Settings 所述,如果没有则为空字典。

模板:
WebIDL[Exposed=Window]
interface ConstrainablePattern {
  Capabilities  getCapabilities();
  Constraints   getConstraints();
  Settings      getSettings();
  Promise<undefined> applyConstraints(optional Constraints constraints = {});
};

方法

getCapabilities

getCapabilities() 方法返回对象所支持的可约束属性名称的字典。当被调用时,用户代理 必须返回 [[Capabilities]] 内部槽的值。

注意

底层硬件可能无法完全映射到所定义的可约束属性的范围。当出现这种情况时,条目 应当定义如何将硬件的设置转换并缩放到属性定义的值。例如,假设一个假设的 fluxCapacitance 属性范围是 -10(最小)到 10(最大),但常见硬件设备只支持 "off"、"medium" 和 "full" 三个值。 可约束属性定义可能会指定,对于这种硬件,用户代理 应该将范围值 -10 映射到 "off",10 映射到 "full",0 映射到 "medium"。也可能指明,如果 ConstraintSet 设置为严格值 3,用户代理 应尝试将硬件设置为 "medium",而 getSettings() 应返回 fluxCapacitance 为 0,因为这是与 "medium" 对应的值。

getConstraints

getConstraints() 方法 返回最近一次成功调用 ApplyConstraints 算法 时的 Constraints 参数,并保持指定顺序。注意,返回的一些高级 ConstraintSets 可能当前未满足。要检查哪些 ConstraintSets 当前有效, 应用应使用 getSettings。 用户代理 返回效果完全相同的约束集,而不是精确的约束。当被调用时, 用户代理 必须返回 [[Constraints]] 内部槽的值。

getSettings

getSettings() 方法 返回对象所有可约束属性的当前设置,无论是平台默认值还是通过 ApplyConstraints 算法 设置。注意,设置是符合约束的目标值, 因此有时可能与测量性能不同。当被调用时,用户代理 必须返回 [[Settings]] 内部槽的值。

applyConstraints

applyConstraints 模板方法 被调用时, 用户代理 必须执行以下步骤:

  1. object 为调用此方法的对象。

  2. newConstraints 为此方法的参数。

  3. p 为一个新的 promise。

  4. 并行运行以下步骤,如果此方法多次调用则保持调用顺序:

    1. failedConstraint 为运行 ApplyConstraints 算法, 参数为 newConstraints 的结果。

    2. successfulSettings 为上述步骤算法执行后 object 的当前设置。

    3. 队列一个任务,运行以下步骤:

      1. 如果 failedConstraint 不为 undefined,令 messageundefined 或有信息的人类可读消息,reject p,并以 OverconstrainedError 新建对象调用 OverconstrainedError(failedConstraint, message),然后终止这些步骤。此时原有约束保留。

      2. object[[Constraints]] 内部槽设置为 newConstraints 或一个与 newConstraints 在所有情况下效果相同的 Constraints 字典。

      3. object[[Settings]] 内部槽设置为 successfulSettings

      4. resolve p,值为 undefined

  5. 返回 p

ApplyConstraints 算法 说明如下。以下是算法中用到的预备定义:

我们用术语 settings dictionary 表示可能应用为对象设置值的集合。

对于字符串值约束,下文定义 “==” 为真,当序列中的某一值与被比较的值完全相同。

我们定义 fitness distance, 即一个 settings dictionary 与约束集 CS 之间的距离, 对每个成员(由 constraintNameconstraintValue 组成)在 CS存在 时,其值如下:

  1. 如果 constraintName 不被 用户代理 支持,则距离为 0。

  2. 如果约束 必需constraintValue 包含一个或多个 'min'、'max' 或 'exact' 成员, 或本身为裸值且裸值视为 'exact'),且 settings dictionaryconstraintName 成员值不满足约束或不存在,则距离为正无穷。

  3. 如果约束不适用于该类型对象,则距离为 0(即此约束不影响距离)。

  4. 如果 constraintValue 是布尔型,但可约束属性不是,则距离取决于 settings dictionaryconstraintName 是否 存在,公式如下:

    (constraintValue == exists) ? 0 : 1
  5. 如果 settings dictionaryconstraintName 成员 不存在,距离为 1。

  6. 如果没有指定理想值(constraintValue 没有 'ideal' 成员,或裸值视为 'ideal' 时不是裸值),距离为 0。
  7. 对所有正数约束(如 height、width、frameRate、aspectRatio、sampleRate 和 sampleSize),距离公式为
    (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|)
  8. 对所有字符串、枚举和布尔约束(如 deviceId、groupId、facingMode、resizeMode、echoCancellation),距离公式为
    (actual == ideal) ? 0 : 1

更多定义:

  • 我们将 ConstraintSet 的每个元素(除了特殊项 'advanced')称为“约束”,因为它用于将给定属性的可接受设置,从 ConstrainablePattern 对象的对应 Capability 中给出的完整列表或范围,限制为符合其指定范围或列表的值。
  • 我们将对象 O 的“有效 Capability”C,称为 C 的可能真子集(即 getCapabilities 返回的值),该子集会考虑环境限制和/或其他约束施加的限制。例如,假设有一个约束集限制了 aspectRatio、height 和 width 属性,对任意两个属性赋值都会限制第三个属性的有效 Capability。有效 Capability 集可能依赖于平台。例如,在资源受限设备上,可能无法将属性 P1 和 P2 都设置为 'high',而在另一台受限较少的设备上则可能可以。
  • 一个 settings dictionary,即对象 O 的可约束属性值集合,如果与约束集 CS 的 fitness distance 小于无穷,则视为满足 CS。
  • 一组 ConstraintSets CS1...CSn(n >= 1),如果能找到对象 O 的 settings dictionary 同时满足 CS1...CSn,则视为可被 O 满足。
  • 将一组 ConstraintSets CS1...CSn 应用于对象 O,就是选择一组能满足 CS1...CSn 的值,并将它们作为 O 属性的设置进行赋值。

我们定义 SelectSettings 算法如下:

  1. 每个 约束 为其属性指定一个或多个值(或一个值的范围)。某属性 可以 在 'advanced' ConstraintSets 列表中多次出现。如果约束的值为一个空列表,则 必须 视为未指定该约束(即,空约束 == 无约束)。

    注意,未知属性会被 WebIDL 丢弃,这意味着未知/不支持的必需约束会被悄悄忽略。为避免意外,建议应用作者先使用 getSupportedConstraints() 方法,如下方示例所示。

  2. object 为本算法应用的 ConstrainablePattern 对象。令 copyobject 的不受约束副本(即 copy 应如同所有 ConstraintSet 都被移除的 object)。
  3. copy 的所有可能的 settings dictionary,计算其 fitness distance,将属性的裸值视为理想值。令 candidates 为所有 fitness distance 有限的 settings dictionary 集合。

  4. 如果 candidates 为空,则返回 undefined 作为 SelectSettings 算法的结果。

  5. 按指定顺序迭代 newConstraints 内的 'advanced' ConstraintSets。对每个 ConstraintSet:
    1. 计算每个 candidates settings dictionary 与该 ConstraintSet 的 fitness distance,将属性的裸值视为精确值。

    2. 如果某些 candidates settings dictionary 的距离有限,则保留这些 settings dictionary,丢弃其他的。

      如果所有 candidates 的距离均为无穷,则忽略此 ConstraintSet。

  6. candidates 中选择一个 settings dictionary,作为 SelectSettings 算法的结果。用户代理 必须 使用距离最小的 settings dictionary(见第 3 步计算)。如果有多个最小距离,用户代理 根据系统默认属性值和 用户代理 默认属性值选择一个。

对于所选设备有系统默认值的属性,如果与上述算法兼容,则 应当 使用系统默认值。通常如 sampleRatesampleSize。其他属性如 echoCancellationresizeMode 通常没有系统默认值。用户代理 为这些属性定义自己的默认值。实现者需注意选择合适的默认值,因为它们常常影响媒体内容的生成。

注意

建议参考已有实现以选择有意义的默认值。注意,默认值可能因系统不同而异,例如桌面与移动设备。撰写本文时,用户代理实现通常采用如下默认值,这些值便于 RTCPeerConnection 作为 sink 使用:

  1. width 设为 640。

  2. height 设为 480。

  3. frameRate 设为 30。

  4. echoCancellation 设为 true

要对 object 应用 ApplyConstraints algorithm,给定参数 newConstraints用户代理 必须 执行以下步骤:

  1. successfulSettings 为以 newConstraints 为约束集运行 SelectSettings 算法的结果。

  2. 如果 successfulSettingsundefined,令 failedConstraint 为在执行 SelectSettings 算法时所有 settings dictionary 的距离为无穷的任一 必需约束,若没有则为 "",然后返回 failedConstraint 并终止这些步骤。

  3. 一次性移除 object 的现有约束,应用 newConstraints,并将 successfulSettings 作为当前设置。
  4. 返回 undefined
注意

如果 UA 释放了设备,比如轨道被 静音,应用设置不代表更改设备配置。此时,UA 会在重新获取设备时配置设备以匹配轨道设置,比如轨道被 取消静音时。

注意

任何与上述算法结果一致的实现都是允许的。例如,实现可以只跟踪约束下设置的最大最小值,而不是所有可能值。

注意

在选择 settings dictionary 时,UA 可以使用任何可用信息。例如,是否作为 getUserMedia 设备选择的一部分进行选择、摄像头的能耗是否因 settings dictionary 不同而变化,或者使用某个 settings dictionary 会导致设备驱动使用重采样等。

用户代理 可以 在任何时候为对象的可约束属性选择新设置。此时 必须 按上述算法尝试满足所有当前约束,令 successfulSettings 为所得新设置,并队列一个任务执行以下步骤:

  1. object 为一个或多个可约束属性的新设置已被更改的 ConstrainablePattern 对象。

  2. object[[Settings]] 内部槽设置为 successfulSettings

下面是一个可以传递给 applyConstraints() 或作为 constraints 返回值的 Constraints 示例。 它使用了为摄像头来源的 可约束属性 定义的约束, 适用于 MediaStreamTrack。 在此示例中,所有约束均为理想值,意味着结果是基于用户具体摄像头的“尽力而为”:

await track.applyConstraints({
  width: 1920,
  height: 1080,
  frameRate: 30,
});
const {width, height, frameRate} = track.getSettings();

console.log(`${width}x${height}x${frameRate}`); // 1920x1080x30,或者可能是例如
                                                // 1280x720x30,属于尽力而为

为了更细致的控制,应用可以强制要求精确匹配,只要能够处理失败:

try {
  await track.applyConstraints({
    width: {exact: 1920},
    height: {exact: 1080},
    frameRate: {min: 25, ideal: 30, max: 30},
  });
  const {width, height, frameRate} = track.getSettings();

  console.log(`${width}x${height}x${frameRate}`); // 1920x1080x25-30!

} catch (error) {
  if (error.name != "OverconstrainedError") {
    throw error;
  }
  console.log(`此摄像头无法满足请求的 ${error.constraint}。`);
}

约束也可以传递给 getUserMedia,不仅仅是初始化方便,还可以影响设备选择。 在这种情况下, 固有约束 也是可用的。

下面是一个示例,展示如何用约束优先选择上次访问时的特定摄像头和麦克风,并对分辨率和立体声有要求; 一旦授权,将应用这些约束,并在请求设备不再可用时(或在某些用户代理中被用户覆盖时)帮助寻找合适的替代设备:

try {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: localStorage.camId,
      width: {min: 800, ideal: 1024, max: 1280},
      height: {min: 600}
    },
    audio: {
      deviceId: localStorage.micId,
      channelCount: 2
    }
  });

  // 已授权。 存储设备ID以便下次使用
  localStorage.camId = stream.getVideoTracks()[0].getSettings().deviceId;
  localStorage.micId = stream.getAudioTracks()[0].getSettings().deviceId;

} catch (error) {
  if (error.name != "OverconstrainedError") {
    throw error;
  }
  // 约束不满足。 未找到 合适替代设备
}
注意

上述示例避免使用 {exact: deviceId},这样如果首选设备不可用,浏览器可以立即提供其他摄像头选择。

示例也会在每次授权时存储 deviceId,以便表示新的选择。

相对地,下面是一个用约束实现内容内摄像头选择器的示例。 此时我们使用 exact,并仅依赖于来自用户选择列表的 deviceId

async function switchCameraTrack(freshlyChosenDeviceId, oldTrack) {
  if (isMobile) {
    oldTrack.stop(); // 某些平台一次只能打开一个摄像头。
  }
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: {exact: freshlyChosenDeviceId}
    }
  });
  const [track] = stream.getVideoTracks();
  localStorage.camId = track.getSettings().deviceId;
  return track;
}

下面是一个申请手机后置摄像头的示例,理想情况下为720p,但也接受接近这个分辨率的值。 注意对分辨率约束是以横屏模式指定的:

async function getBackCamera() {
  return await navigator.mediaDevices.getUserMedia({
    video: {
      facingMode: {exact: 'environment'},
      width: 1280,
      height: 720
    }
  });
}

下面是一个“我想要接近720p的原生16:9分辨率,但即使没有原生支持,也要求精确帧率为10”的示例。 这需要分两步进行:先获取原生模式,再应用自定义帧率。 这也演示了如何从当前设置推导约束,注意可能发生旋转:

async function nativeResolutionButDecimatedFrameRate() {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      resizeMode: 'none', // 表示原生分辨率和帧率
      width: 1280,
      height: 720,
      aspectRatio: 16 / 9 // 宽高比可能不是完全精确
    }
  });
  const [track] = stream.getVideoTracks();
  const {width, height, aspectRatio} = track.getSettings();

  // 约束为横屏,设置可能被旋转(竖屏)
  if (width < height) {
    [width, height] = [height, width];
    aspectRatio = 1 / aspectRatio;
  }

  await track.applyConstraints({
    resizeMode: 'crop-and-scale',
    width: {exact: width},
    height: {exact: height},
    frameRate: {exact: 10},
    aspectRatio,
  });

  return stream;
}
注意
上述示例假设主方向 为横屏。

下面是一个展示如何使用 getSupportedConstraints 的示例, 用于应用无法容忍约束因用户代理不支持而被忽略的情形:

async function getFrontCameraRes() {
  const supports = navigator.mediaDevices.getSupportedConstraints();

  for (const constraint of ["facingMode", "aspectRatio", "resizeMode"]) {
    if (!(constraint in supports) {
      throw new OverconstrainedError(constraint, "Not supported");
    }
  }
  return await navigator.mediaDevices.getUserMedia({
    video: {
      facingMode: {exact: 'user'},
      advanced: [
        {aspectRatio: 16/9, height: 1080, resizeMode: "none"},
        {aspectRatio: 4/3, width: 1280, resizeMode: "none"}
      ]
    }
  });
}

11.2 约束类型

有效输入集的规范语法取决于值的类型。除了标准原子类型(boolean、long、double、DOMString)以外,有效值还包括任意原子类型的列表,以及如下定义的最小-最大范围。

列表值必须被解释为“或”关系。例如,如果相机属性 'facingMode' 被定义为有效值 ["left", "right", "user", "environment"],这意味着 'facingMode' 可以取 "left"、"right"、"environment" 和 "user"。类似地,Constraints 将 'facingMode' 限制为 ["user", "left", "right"],则表示 用户代理 应选择一个摄像头(如果可能的话让摄像头指向),使 "facingMode" 为 "user"、"left" 或 "right"。此约束请求摄像头不要背向用户,但允许 用户代理 让用户选择其他方向。

WebIDLdictionary DoubleRange {
  double max;
  double min;
};

字典 DoubleRange 成员

max 类型为 double

该属性的最大有效值。

min 类型为 double

该属性的最小值。

WebIDLdictionary ConstrainDoubleRange : DoubleRange {
  double exact;
  double ideal;
};

字典 ConstrainDoubleRange 成员

exact 类型为 double

该属性的精确要求值。

ideal 类型为 double

该属性的理想(目标)值。

WebIDLdictionary ULongRange {
  [Clamp] unsigned long max;
  [Clamp] unsigned long min;
};

字典 ULongRange 成员

max 类型为 unsigned long

该属性的最大有效值。

min 类型为 unsigned long

该属性的最小值。

WebIDLdictionary ConstrainULongRange : ULongRange {
  [Clamp] unsigned long exact;
  [Clamp] unsigned long ideal;
};

字典 ConstrainULongRange 成员

exact 类型为 unsigned long

该属性的精确要求值。

ideal 类型为 unsigned long

该属性的理想(目标)值。

WebIDLdictionary ConstrainBooleanParameters {
  boolean exact;
  boolean ideal;
};

字典 ConstrainBooleanParameters 成员

exact 类型为 boolean

该属性的精确要求值。

ideal 类型为 boolean

该属性的理想(目标)值。

WebIDLdictionary ConstrainDOMStringParameters {
  (DOMString or sequence<DOMString>) exact;
  (DOMString or sequence<DOMString>) ideal;
};

字典 ConstrainDOMStringParameters 成员

exact 类型为 (DOMString 或 sequence<DOMString>)

该属性的精确要求值。

ideal 类型为 (DOMString 或 sequence<DOMString>)

该属性的理想(目标)值。

WebIDLdictionary ConstrainBooleanOrDOMStringParameters {
  (boolean or DOMString) exact;
  (boolean or DOMString) ideal;
};

字典 ConstrainBooleanOrDOMStringParameters 成员

exact 类型为 (booleanDOMString)

该属性的精确要求值。

ideal 类型为 (booleanDOMString)

该属性的理想(目标)值。

WebIDLtypedef ([Clamp] unsigned long or ConstrainULongRange) ConstrainULong;
在本规范中,标识符 ConstrainULong 用于指代 ([Clamp] unsigned long 或 ConstrainULongRange) 类型。
WebIDLtypedef (double or ConstrainDoubleRange) ConstrainDouble;
在本规范中,标识符 ConstrainDouble 用于指代 (double 或 ConstrainDoubleRange) 类型。
WebIDLtypedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
在本规范中,标识符 ConstrainBoolean 用于指代 (boolean 或 ConstrainBooleanParameters) 类型。
WebIDLtypedef (DOMString or
         sequence<DOMString> or
         ConstrainDOMStringParameters) ConstrainDOMString;
在本规范中,标识符 ConstrainDOMString 用于指代 (DOMString 或 sequence<DOMString> 或 ConstrainDOMStringParameters) 类型。
WebIDLtypedef (boolean or DOMString or ConstrainBooleanOrDOMStringParameters) ConstrainBooleanOrDOMString;
在本规范中,标识符 ConstrainBooleanOrDOMString 用于指代 (boolean 或 DOMString 或 ConstrainBooleanOrDOMStringParameters) 类型。

11.3 能力

Capabilities 是一个包含一个或多个键值对的字典,其中每个键必须是可约束属性,每个值必须是该属性允许值集合的子集。值表达式的具体语法取决于属性的类型。Capabilities 字典指定了哪些可约束属性可以作为约束应用到 constrainable object。注意,一个 constrainable object 的 Capabilities 可以是 Web 平台定义的属性的子集,并且这些属性的值集合也是子集。注意 Capabilities 是由 用户代理返回给应用程序,不能由应用程序指定。但是,应用程序可以通过 Constraints 控制 用户代理为可约束属性选择的 Settings。

下面是一个 Capabilities 字典的示例。在此情况下,constrainable object 是一个可用能力非常有限的视频源。

{
  frameRate: {min: 1.0, max: 60.0},
  facingMode: ['user', 'left']
}

下一个示例指出,针对范围值的能力是为各个可约束属性分别给出范围,而不是组合。这对于视频宽度和高度尤其重要,因为宽度和高度的范围是分别报告的。在示例中,如果 constrainable object 只能提供 640x480 和 800x600 分辨率,则返回的相关能力如下:

{
  width: {min: 640, max: 800},
  height: {min: 480, max: 600},
  aspectRatio: {min: 4/3, max: 4/3}
}

注意上述示例中,aspectRatio 可以明确表示宽高不能任意组合,尽管它仍然暗示可用分辨率不止两个。

A 使用 Constrainable Pattern 的规范不应继承下面的字典,而应提供自己的定义。详见 MediaTrackCapabilities 示例。
模板:
WebIDLdictionary Capabilities {};

11.4 设置

Settings 是一个包含一个或多个键值对的字典。它必须包含在 getCapabilities() 返回的、在该对象类型上定义的每个属性的键;例如,一个音频 MediaStreamTrack 没有 "width" 属性。每个键必须有唯一值,并且值必须getCapabilities() 为该属性定义的集合成员。Settings 字典包含用户代理为对象可约束属性最终选择的实际值。值的具体语法取决于属性类型。

符合规范的用户代理必须支持本规范定义的所有可约束属性。

下面是一个 Settings 字典的示例。这个例子并不现实,因为用户代理实际上需要支持的可约束属性远不止这些。

{
  frameRate: 30.0,
  facingMode: 'user'
}
A 使用 Constrainable Pattern 的规范不应继承下面的字典,而应提供自己的定义。详见 MediaTrackSettings 示例。
模板:
WebIDLdictionary Settings {};

11.5 约束和约束集

由于 WebIDL 的限制,实现 Constrainable Pattern 的接口不能直接继承此处定义的 Constraints 和 ConstraintSet。它们必须提供遵循此模式的自己的定义。详见 MediaTrackConstraints 示例。

模板:
WebIDLdictionary ConstraintSet {};

ConstraintSet 的每个成员对应一个可约束属性,并指定该属性有效能力值集合的子集。应用 ConstraintSet 会指导 用户代理 将对应的可约束属性设置限制为指定的值或值范围。某属性可以同时出现在基本 Constraints 集和高级 ConstraintSets 列表中,并且可以在高级列表中的每个 ConstraintSet 中最多出现一次。

11.5.1 字典 Constraints 成员

advanced 类型为 sequence<ConstraintSet>

这是 用户代理必须尝试依次满足的 ConstraintSet 列表,只跳过无法满足的项。列表中这些 ConstraintSet 的顺序是有意义的。特别是,当它们作为 applyConstraints 参数时,用户代理必须按指定顺序尝试满足。如果高级 ConstraintSet C1 和 C2 各自可以满足但不能同时满足,则列表中靠前的 C1 或 C2 会被满足,另一个不会。用户代理必须尝试满足列表中所有 ConstraintSet,即使有的无法满足。因此,若约束 C3 在 C1 和 C2 之后指定,则用户代理将尝试满足 C3,即便 C2 无法满足。注意某属性名在每个 ConstraintSet 中只能出现一次,但可以出现在多个 ConstraintSet 中。

12. 示例

下面的示例代码展示了一个按钮。当点击时,按钮会被禁用,并提示用户提供一个媒体流。用户可以通过提供流(例如授予页面本地摄像头访问权限),然后禁用流(例如撤销该访问权限)来重新启用按钮。

<button id="startBtn">开始</button>
<script>
const startBtn = document.getElementById('startBtn');

startBtn.onclick = async () => {
  try {
    startBtn.disabled = true;
    const constraints = {
      audio: true,
      video: true
    };

    const stream = await navigator.mediaDevices.getUserMedia(constraints);

    for (const track of stream.getTracks()) {
      track.onended = () => {
        startBtn.disabled = stream.getTracks().some((t) => t.readyState == 'live');
      };
    }
  } catch (err) {
    console.error(err);
  }
};
</script>

此示例允许用户通过本地摄像头为自己拍照。注意,Image Capture 规范 [image-capture] 提供了更简单的实现方式。

<script>
window.onload = async () => {
  const video = document.getElementById('monitor');
  const canvas = document.getElementById('photo');
  const shutter = document.getElementById('shutter');

  try {
    video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});

    await new Promise(resolve => video.onloadedmetadata = resolve);
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    document.getElementById('splash').hidden = true;
    document.getElementById('app').hidden = false;

    shutter.onclick = () => canvas.getContext('2d').drawImage(video, 0, 0);
  } catch (err) {
    console.error(err);
  }
};
</script>

<h1>自拍亭</h1>

<section id="splash">
  <p id="errorMessage">加载中...</p>
</section>

<section id="app" hidden>
  <video id="monitor" autoplay></video>
  <button id="shutter">&#x1F4F7;</button>
  <canvas id="photo"></canvas>
</section>

13. 权限集成

本规范定义了两个强大功能,其名称为 names "camera""microphone"

它定义了以下类型和算法:

权限描述符类型
WebIDLdictionary CameraDevicePermissionDescriptor : PermissionDescriptor {
  boolean panTiltZoom = false;
};

一个权限涵盖对至少一种设备的访问。

描述符的语义是:它查询对该类任意设备的访问权限。 因此,如果查询 "camera" 权限返回 "granted", 客户端知道将可以访问一个摄像头且不会弹出权限提示;如果返回 "denied", 则知道所有摄像头的 getUserMedia 请求都不会成功。

如果用户代理认为对某些(但不是全部)此类设备已授权,查询将返回 "granted"。

如果用户代理认为对所有此类设备都拒绝了权限,查询将返回 "denied"。

{name: "camera", panTiltZoom: true}{name: "camera", panTiltZoom: false} 更强

注意

"granted" 权限不能保证 getUserMedia 一定成功。 它只表示用户不会被弹出权限提示。还有很多其他因素(如约束或摄像头正在被占用)会导致 getUserMedia 失败。

权限撤销算法
这是调用 设备权限撤销算法, 参数为 name 的结果。

14. 权限策略集成

本规范定义了两个策略控制功能 其字符串标识分别为 "camera""microphone"。 二者的默认允许列表"self"

注意

一个文档权限策略 决定该文档内的任何内容是否允许使用 getUserMedia 请求摄像头或麦克风。如果 在任一文档中禁用,则文档内任何内容都不允许使用 getUserMedia 请求摄像头或麦克风。 这由请求使用权限算法强制执行。

此外,enumerateDevices 只会枚举文档 允许使用的设备。

15. 隐私指示器要求

注意

本规范以单个 MediaDevices 对象视角表达隐私指示器要求。 鼓励实现者将这些原则外推以统一指示器展示,覆盖因 iframe 共存可在页面上存在的多个 MediaDevices 对象。

对于每一种 kind 设备,getUserMedia() 都会暴露:

定义 anyAccessible 为所有 any<kind>Accessible 的逻辑或。

定义 anyLive 为所有 any<kind>Live 的逻辑或。

则对用户代理有如下要求:

用户代理还鼓励如下行为:

16. 隐私和安全注意事项

本节为非规范性内容;不规定新行为,而是总结规范其他部分已有的信息。

本规范扩展了 Web 平台对媒体输入设备(特别是麦克风和摄像头)的管理能力。它也可能涉及其他媒体设备(如音频输出设备——扬声器和耳机)的信息暴露,但此类细节由其他规范规定。 从用户的麦克风和摄像头采集音视频会向应用暴露个人身份信息,本规范要求在共享前获得用户明确同意。

在摄像头或麦克风采集开始前,应用(“路过的网页”)只能获知用户是否有摄像头或麦克风(但不能知道数量)。设备标识符设计为不具有跨源跟踪用户的能力,但摄像头或麦克风能力的存在会为指纹表面增加两位。建议像对待其他持久化存储(如 Cookie)一样对待每源持久化标识符 deviceId

一旦开始摄像头或麦克风采集,本规范描述了如何访问并使用相关设备的媒体数据。这些数据可能敏感;建议应有指示器提示设备正在使用,但权限性质和设备使用指示器均由平台决定。

开始采集的权限可能是临时的,也可能是持久的。对于临时权限,重要的是用户可以“拒绝”,且不会因等待授权而导致界面阻塞——可以提供“永久拒绝”选项,或避免使用模态权限对话框。

一旦摄像头或麦克风采集开始,网页文档可以列出所有可用媒体采集设备及其标签。此能力持续到文档关闭,不能持久化。多数情况下,标签在不同源间是稳定的,因此有可能作为跨源追踪设备的手段。

注意

本规范暴露了未被使用设备的信息。这是为了兼容和历史原因。建议今后的规范不要采用这种模式,而应遵循 设备枚举设计原则中的最佳实践。

对于已开启或曾开启采集的开放网页文档,或处于可视状态的网页文档, 每当有新媒体设备被添加或移除时, devicechange 事件可能会同时在多个 navigable 和源上触发;用户代理可通过模糊事件触发时机或延迟到文档进入可视状态时触发,来降低跨源浏览活动关联的风险。

一旦网页文档获得采集设备的媒体流访问权限,也会获得关于设备的详细信息,包括其可操作能力范围(如摄像头可用分辨率)。这些操作能力在不同会话和源间大多是持久的,因此可作为跨时空、跨源追踪设备的手段。

一旦获得采集设备的视频流访问权限,该流极可能可用于唯一指纹识别设备(例如通过坏点检测)。同样地,获得音频流后也极可能可用于识别用户位置,甚至同一房间内不同用户(例如通过环境音频分析或设备扬声器播放的独特音频)。用户层面的缓解措施包括遮盖摄像头和/或麦克风或通过用户代理界面撤销权限。

可以利用约束让 getUserMedia 调用失败时返回有关系统设备的信息而不提示用户,这会增加指纹表面的暴露。用户代理应考虑限制 getUserMedia 失败调用的速率,以限制该暴露面。

对于存储的持久采集权限,重要的是用户能容易找到已授权列表并撤销不需要的权限。

权限被授予后,用户代理应让用户明确感知两点:

注意

带有持久权限的站点开发者应谨防权限被滥用。可使用 [permissions] API 撤销这些权限。

尤其不应允许自动将授权媒体设备的音视频流发送到第三方可选的终端。

例如,若某站点提供如下 URL: https://webrtc.example.org/?call=user 可自动建立呼叫并向 user 传输音视频,则可能被滥用为如下攻击场景:

已授予 https://webrtc.example.org/ 持久权限的用户,若点击或被重定向到 https://webrtc.example.org/?user=EvilSpy,就可能被诱导将自己的音视频流发送给攻击者 EvilSpy

17. 可扩展性

本节为非规范性内容。

虽然未来可能会发布新版本规范,也预计其他标准需要定义新的扩展能力以在本规范基础上构建。本节旨在为扩展制定者提供参考。

规范中所有 WebIDL 定义的接口、方法或属性均可扩展。两个常见扩展点是定义新的媒体类型和新的可约束属性。

17.1 定义新的媒体 kind(超越音频与视频)

至少需定义:

还应更新:

还可以包括:

17.2 定义新的可约束属性

需要思考并定义该属性的 Constraints、Capabilities 和 Settings(见 3. 术语)如何工作。相关内容请参考 MediaTrackSupportedConstraintsMediaTrackCapabilitiesMediaTrackConstraintsMediaTrackSettings4.3.8 可约束属性MediaStreamConstraints

扩展规范的制定者强烈建议在规范仓库通知规范维护者。
未来版本规范或 WebRTC 工作组制定的其他规范将考虑所有已知扩展,以减少潜在用法冲突。

17.3 MediaStreamTrackMediaStream定义新的 sink

其他规范可为 MediaStream 和/或 MediaStreamTrack 定义新的 sink。至少,一个新的 MediaStreamTrack 消费者需定义:

17.4 source 定义新的 MediaStreamTrack

其他规范可为 MediaStreamTrack 定义新的源。 至少,新的 MediaStreamTrack 源需:

A. 致谢

编辑们感谢工作组主席和团队联系人 Harald Alvestrand、Stefan Håkansson、Erik Lagerway 和 Dominique Hazaël-Massieux 的支持。本规范中的大量文本由许多人提供,包括 Jim Barnett、Harald Alvestrand、Travis Leithead、Josh Soref、Martin Thomson、 Jan-Ivar Bruaroey、Peter Thatcher、 Dominique Hazaël-Massieux 和 Stefan Håkansson。Dan Burnett 感谢 Voxeo 和 Aspect 在本规范开发过程中给予的重大支持。

B. 参考文献

B.1 规范性引用

[COOKIES]
HTTP 状态管理机制. A. Barth. IETF. 2011年4月. 推荐标准. URL: https://httpwg.org/specs/rfc6265.html
[dom]
DOM 标准. Anne van Kesteren. WHATWG. 实时标准. URL: https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript 语言规范. Ecma International. URL: https://tc39.es/ecma262/multipage/
[HTML]
HTML 标准. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 实时标准. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra 标准. Anne van Kesteren; Domenic Denicola. WHATWG. 实时标准. URL: https://infra.spec.whatwg.org/
[permissions]
权限. Marcos Caceres; Mike Taylor. W3C. 2025年6月24日. W3C 工作草案. URL: https://www.w3.org/TR/permissions/
[permissions-policy]
权限策略. Ian Clelland. W3C. 2025年5月6日. W3C 工作草案. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
RFC中用于指示需求级别的关键词. S. Bradner. IETF. 1997年3月. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc2119
[rfc4122]
通用唯一标识符 (UUID) URN 命名空间. P. Leach; M. Mealling; R. Salz. IETF. 2005年7月. 推荐标准. URL: https://www.rfc-editor.org/rfc/rfc4122
[RFC8174]
RFC2119关键词中的大小写歧义. B. Leiba. IETF. 2017年5月. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc8174
[WEBAUDIO]
Web 音频 API. Paul Adenot; Hongchan Choi. W3C. 2021年6月17日. W3C 推荐. URL: https://www.w3.org/TR/webaudio-1.0/
[WEBIDL]
Web IDL 标准. Edgar Chen; Timothy Gu. WHATWG. 实时标准. URL: https://webidl.spec.whatwg.org/
[WEBRTC]
WebRTC:浏览器中的实时通信. Cullen Jennings; Jan-Ivar Bruaroey; Henrik Boström; Florent Castelli. W3C. 2025年3月13日. W3C 推荐. URL: https://www.w3.org/TR/webrtc/

B.2 参考性引用

[image-capture]
MediaStream 图像采集. Miguel Casas-sanchez; Rijubrata Bhaumik. W3C. 2025年4月23日. W3C 工作草案. URL: https://www.w3.org/TR/image-capture/
[mediacapture-fromelement]
从 DOM 元素采集媒体. Martin Thomson; Miguel Casas-sanchez; Emircan Uysaler. W3C. 2025年2月12日. W3C 工作草案. URL: https://www.w3.org/TR/mediacapture-fromelement/
[mediastream-recording]
MediaStream 录制. Miguel Casas-sanchez. W3C. 2025年4月17日. W3C 工作草案. URL: https://www.w3.org/TR/mediastream-recording/