加密媒体扩展

W3C 工作草案

关于本文档的更多信息
本版本:
https://www.w3.org/TR/2025/WD-encrypted-media-2-20250821/
最新发布版本:
https://www.w3.org/TR/encrypted-media-2/
最新编辑草案:
https://w3c.github.io/encrypted-media/
历史:
https://www.w3.org/standards/history/encrypted-media-2/
提交历史
实施报告:
https://w3c.github.io/test-results/encrypted-media/all.html
最新推荐标准:
https://www.w3.org/TR/2017/REC-encrypted-media-20170918/
编辑:
Joey Parrish (Google公司)
Greg Freedman (Netflix公司)
前任编辑:
Mark Watson (Netflix公司)(截至2019年9月)
David Dorwin (Google公司)(截至2017年9月)
Jerry Smith (微软公司)(截至2017年9月)
Adrian Bateman (微软公司)(截至2014年5月)
反馈:
GitHub w3c/encrypted-media (拉取请求, 新建议题, 开放议题)
public-media-wg@w3.org 邮件主题请使用 [encrypted-media-2] … 消息主题 … (存档)

摘要

本规范扩展了 HTMLMediaElement [HTML] ,提供用于控制加密内容播放的 API。

该 API 支持从简单的 Clear Key 解密到高价值视频(取决于用户代理的实现)等用例。许可证/密钥交换由应用程序控制,便于开发支持多种内容解密和保护技术的健壮播放应用。

本规范不定义内容保护或数字版权管理(Digital Rights Management)系统。相反,它定义了一个通用的 API,可用于发现、选择并与这些系统以及更简单的内容加密系统交互。为了符合本规范,不要求实现数字版权管理:仅需将 Clear Key 系统作为通用基线来实现。

该通用 API 支持一组简单的内容加密能力,将诸如身份验证和授权之类的应用功能留给页面作者。通过要求由页面来中介内容保护系统特定的消息传递,而不是假定加密系统与许可证或其它服务器之间存在带外通信,从而实现这一点。

本文档状态

本节描述了本文档在其发布时的状态。当前的 W3C 出版物列表以及本技术报告的最新修订可在 W3C 标准与草案索引 中找到。

自 2017 年 9 月作为 W3C 推荐标准发布以来,新增了两个功能:

将其他功能纳入范围之外是媒体工作组的决定。除编辑更新外,对本规范所做的其他实质性更改主要为解决与本规范相关的维护问题:

有关自上一个版本以来所做更改的完整列表,请参见 提交

本文档由 媒体工作组(Media Working Group) 以 Recommendation track 的流程作为工作草案发布。

作为工作草案的发布并不意味着被 W3C 及其成员认可。

本文档为草案文件,可能随时被更新、替换或被其他文档废弃。不适宜将此文档作为除工作进展以外的引用来源。

本文档由一个根据 W3C 专利政策 运作的小组制作。 W3C 维护着一份与该小组成果相关的 任何专利披露的公开列表;该页面还包含披露专利的说明。如果个人确实知道某项专利并认为其中含有 必要权利要求(Essential Claim(s)),则该个人必须按照 W3C 专利政策第 6 节 的规定披露该信息。

本文档受 2025年8月18日 W3C 流程文件 管辖。

1. 介绍

本节为非规范性内容。

本规范允许脚本选择内容保护机制、控制许可证/密钥交换,并执行自定义许可证管理算法。它支持广泛的用例,而不要求每个用户代理针对每个用例进行客户端修改。这使内容提供者能够为所有设备开发单一的应用解决方案。

支持的内容按照容器特定的“通用加密(common encryption)”规范进行加密,从而在不同密钥系统之间可重用。支持的内容具有未加密的容器,便于将元数据提供给应用程序,并保持与其它 HTMLMediaElement 功能的兼容性。

实现者应关注本规范中描述的安全性和隐私威胁与关注点的缓解措施。特别地,安全与隐私的规范性要求在没有对 Key System 及其实现的安全与隐私属性的了解时无法满足。8. 实现要求 包含与底层 Key System 实现的集成和使用相关的安全与隐私条款。10. 安全性 侧重于外部威胁,例如输入数据或网络攻击。11. 隐私 侧重于用户特定信息的处理并为用户提供对其隐私的充分控制。

尽管本规范与媒体数据来源无关,但作者应意识到许多实现仅支持解密通过媒体源扩展(Media Source Extensions)提供的媒体数据 [MEDIA-SOURCE]。

使用该 API 实现的通用栈示例如下所示。该图显示了一个示例流程;也可以有其他组合的 API 调用和事件。

使用所提议 API 实现的通用栈示意图

2. 定义

内容解密模块(CDM)

内容解密模块(CDM)是客户端组件,提供一个或多个 Key Systems 的功能,包括解密。

实现可能会将 CDM 的实现分离开来,也可能将其视为与用户代理不可分。对此 API 和应用程序这是透明的。

Key System

Key System 是对解密机制和/或内容保护提供方的通用称呼。Key System 字符串为 Key System 提供唯一标识。用户代理使用它们来选择 CDM 并识别密钥相关事件的来源。用户代理 MUST 支持 常见密钥系统。用户代理 MAY 还可提供额外的具有对应 Key System 字符串的 CDMs

Key System 字符串始终为反向域名。Key System 字符串使用区分大小写的匹配进行比较。建议 CDMs 使用简单的小写 ASCII key system 字符串。

例如,“com.example.somesystem”。

在给定系统内(示例中的 “somesystem”),子系统由密钥系统提供方确定并定义。例如,“com.example.somesystem.1”和 “com.example.somesystem.1_5”。密钥系统提供方应记住这些字符串将用于比较和发现,因此应便于比较且结构保持相对简单。

Key Session

Key Session,或简称 Session,提供与 CDM 的消息交换上下文,结果是密钥被提供给 CDM。会话以 MediaKeySession 对象体现。每个 Key Session 与在 generateRequest() 调用中提供的单个 Initialization Data 相关联。

每个 Key Session 与单个 MediaKeys 对象相关联,且只有与该 MediaKeys 对象关联的媒体元素才可访问与该会话关联的密钥。其它 MediaKeys 对象、CDM 实例以及媒体元素 MUST NOT 访问该密钥会话或使用其密钥。会话及其包含的密钥在会话关闭后(包括当 MediaKeySession 对象被销毁时)不再 可用于解密

所有与 Key Session 相关且未被显式存储的许可证和密钥在 Key Session 关闭时 MUST 被销毁。

Key IDs MUST 在会话内唯一。

Session ID

Session ID 是由 CDM 生成的唯一字符串标识符,应用可用它来识别 MediaKeySession 对象。

每次当用户代理与 CDM 成功创建新会话时,都会生成一个新的 Session ID。

每个 Session ID 在创建它的浏览上下文中 SHALL 是唯一的。对于 Is persistent session type? 算法返回 true 的会话类型,Session ID 在时间上(包括跨浏览会话)在该 originMUST 保持唯一。

底层内容保护协议不一定需要支持 Session ID。

Key

除非另有说明,key 指可用于解密 媒体数据 中区块的解密密钥。每个此类密钥由 key ID 唯一标识。密钥与用于将其提供给 CDMsession 相关联。(相同密钥可存在于多个会话中。)这些密钥 MUST 仅通过 update() 调用提供给 CDM。(它们稍后可以作为存储会话数据的一部分通过 load() 加载。)

作者 SHOULD 对每组需要执行显著不同策略的流使用不同的密钥(和 key ID)。例如,如果两个视频分辨率之间的策略可能不同,则包含一种分辨率的流不应使用用于另一种分辨率的密钥进行加密。对于加密的情况,音频流 SHOULD NOT 与任何视频流使用相同的密钥。这是确保在客户端之间执行和兼容的唯一方法。

可用于解密

如果 CDM 确定某密钥当前可用于解密一个或多个 媒体数据 的区块,则该密钥被视为可用于解密。

例如,如果密钥的许可证已过期,则该密钥不可用于解密。即使许可证未过期,如果其它使用条件(例如输出保护)当前未满足,该密钥也不可用于解密。

Key ID

key 关联一个 key ID,该 key ID 是一系列八位字节并能唯一标识该密钥。容器指定可解密媒体数据中某区块或一组区块的密钥的 ID。Initialization Data MAY 包含用于识别解密媒体数据所需密钥的 key ID(s)。然而,并不要求 Initialization Data 包含媒体数据或媒体资源中使用的任何或所有 key ID。提供给 CDM许可证 将每个密钥与一个 key ID 关联,以便 CDM 在解密加密的媒体数据区块时选择适当的密钥。

已知密钥(Known Key)

如果 CDM 的会话实现包含关于某密钥的任何信息——特别是 key ID ——则该密钥被视为为会话所知,无论该密钥是否可用或其值是否已知。已知密钥通过 keyStatuses 属性暴露。

即使密钥变得不可用,例如由于 过期,或如果它们被移除但存在 许可证销毁记录,这些密钥仍被视为已知。只有当它们被显式从会话中移除并且任何许可证释放消息被确认后,密钥才变为未知。

例如,如果一次 update() 调用提供了不包含该密钥的新许可证并包含替换先前包含该密钥的许可证的指示,则该密钥可能变为未知。

许可证(License)

许可证是密钥系统特定的状态信息,包含一个或多个 密钥,每个密钥都与一个 key ID 关联,并可能包含关于密钥使用的其它信息。

Initialization Data

Key Systems 通常在构造许可证请求消息之前需要一段包含待解密流信息的初始化数据块。该块可以是一个简单的密钥或内容 ID,或包含此类信息的更复杂结构。它 SHOULD 始终允许唯一标识解密内容所需的 key(s)。该初始化信息 MAY 通过某些应用特定方式获取或随媒体数据提供。

Initialization Data 是一个通用术语,表示容器特定的数据,CDM 用它来生成许可证请求。

初始化数据的格式取决于容器类型,且容器 MAY 支持多种初始化数据格式。Initialization Data Type 是一个字符串,用于指示随附 Initialization Data 的格式。Initialization Data Type 字符串始终区分大小写匹配。建议 Initialization Data Type 字符串使用小写 ASCII 字符串。

Encrypted Media Extensions Initialization Data Format Registry [EME-INITDATA-REGISTRY] 提供了从 Initialization Data Type 字符串到每种格式规范的映射。

当用户代理在 媒体数据 中遇到 Initialization Data 时,它将该 Initialization Data 提供给应用,置于 initData 属性中,作为 encrypted 事件的一部分。用户代理 MUST NOT 在遇到时存储 Initialization Data 或使用其 内容。应用通过在 generateRequest() 调用中将 Initialization Data 提供给 CDM。用户代理 MUST NOT 通过其它方式将 Initialization Data 提供给 CDM

Initialization Data 对于给定的一组流或媒体数据 MUST 是固定值。它 MUST 仅包含与播放给定一组流或媒体数据所需密钥相关的信息。它 MUST NOT 包含应用数据、客户端特定数据、用户特定数据或可执行代码。

Initialization Data SHOULD NOT 包含 Key System 特定的数据或值。实现 MUST 支持 [EME-INITDATA-REGISTRY] 中为其支持的每种 Initialization Data Type 定义的通用格式。

不鼓励使用专有格式/内容,且强烈不建议仅支持或仅使用专有格式。专有格式应仅在预先存在的内容或不支持通用格式的预先存在的客户端设备上使用。

可关联值(Associable Value)

如果两个或多个标识符或其它值是相同的,或可以在合理的时间和努力范围内将它们相关联或关联起来,则称它们为可关联的。否则,这些值为 不可关联

例如,以下方式创建的值被认为是 可关联

  • 使用可被轻易逆转的哈希函数。

  • 共享前缀或其它子集。

  • 将随机值 N 替换为 N+10。

  • 将 origin 与固定值进行 XOR(因为这是可以轻易逆转的)。

相比之下,两个完全不相关或在加密学上不可逆(例如使用强加密哈希函数) 的值为 不可关联

如果在合理时间和努力范围内,所述实体或实体集合能够在不依赖其它实体参与的情况下将它们相关联或关联起来,则称两个或多个标识符或其它值为 可被某实体关联。否则,这些值为 不可被某实体关联

如果它们为 不可被某实体关联,且该实体为包含应用、所有其它应用以及它们使用或通信的服务器在内的集合,则称两个或多个标识符或其它值为 应用不可关联。否则,这些值将被视为 可被应用关联,这是被禁止的。

Distinctive Value(区别性值)

Distinctive Value 是一种值、数据片段、持有某数据的含义或一种可观察的行为或时序,不在大量用户或客户端设备间共享。Distinctive Value 可以存在于内存中或被持久化。

Distinctive Value 的示例包括但不限于:

虽然 Distinctive Value 通常对用户或客户端设备是唯一的,但值不必严格唯一才算区别性。例如,在少量用户之间共享的值仍然可能是区别性的。

永久标识符(Permanent Identifier)

永久标识符是一种以某种方式不可磨灭或对用户而言非平凡以移除、重置或更改的值、数据片段、持有某数据的含义或可观察的行为或时序。这包括但不限于:

  • 硬件或基于硬件的标识符

  • 在出厂时在硬件中预置的值

  • 与操作系统安装实例相关或从其派生的值

  • 与用户代理安装实例相关或从其派生的值

  • CDM 或其它软件组件相关的值

  • 配置文件或类似半永久数据中的值,即使在客户端生成

  • 客户端或其它用户账户值

区别性永久标识符 是一种 永久标识符,且为 区别性 的。

当在客户端之外暴露时,区别性永久标识符及其派生或相关值 MUST加密。区别性永久标识符 MUST NOT 以任何形式(即使是加密形式)暴露给应用。

虽然区别性永久标识符通常对用户或客户端设备是唯一的,但它不必严格唯一才算区别性。例如,在少量用户之间共享的区别性永久标识符仍可能是区别性的。

区别性永久标识符并不是 区别性标识符,因为它不是在本规范范围内派生或生成的。

distinctiveIdentifier 控制是否允许使用区别性永久标识符。具体地,只有当用于创建 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时,才允许使用区别性永久标识符。

区别性标识符(Distinctive Identifier)

区别性标识符是一个值(包括不透明或加密形式),外部实体可以将该值与其它值相关联或匹配,从而超出用户在 Web 平台上可能预期的关联范围(例如 cookie 和其它站点数据)。例如,在以下情形下的值即为区别性标识符:可被应用以外的实体跨 a) origins、b) 浏览配置文件 或 c) 浏览会话 关联,即使用户尝试通过清除浏览数据来保护其隐私,或该值不易被用户打破这种关联。特别地,如果某中心服务器(例如个性化服务器)能够将值在各 origin 间关联,比如因为个性化请求包含了共同的值,或者因为个性化请求中提供的值即使在尝试清除浏览数据后仍可被该服务器关联,那么该值即为区别性标识符。可能导致此类情况的原因包括在个性化过程中使用了 区别性永久标识符

暴露给应用的区别性标识符,即使以加密形式,MUST 遵守 标识符要求,包括被 加密在每个 origin 与配置文件中唯一,并且可 被清除

虽然区别性标识符的实例化或使用由应用对本规范中定义的 API 的使用触发,但触发与区别性标识符相关的条件并不要求该标识符必须被提供给应用。(区别性永久标识符 MUST NOT 以任何形式提供给应用,包括不透明或加密形式。)

distinctiveIdentifier 控制是否允许使用区别性标识符。具体地,仅当用于创建 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时,才允许使用区别性标识符。

区别性标识符是满足以下所有条件的值、数据片段、持有某数据的含义或可观察的行为或时序:

  • 它是 区别性 的。

    虽然区别性标识符通常对单个用户或客户端设备是唯一的,但标识符不必严格唯一才算区别性。例如,少量用户共享的标识符仍可能是区别性的。

  • 它、关于它的信息或从它派生或与它相关的值被暴露,即使是加密形式,也会在客户端之外被暴露。这包括但不限于将其提供给应用和/或许可证、个性化 或其它服务器。

  • 它具有下列一种或多种属性:

    • 它是从一个或多个 区别性永久标识符 派生的。

    • 生成、个性化、预置或产生该值的其它过程涉及一个或多个 区别性永久标识符 或另一个区别性标识符。

    • 它是 可清除 的,但不是与 cookies 和其它站点数据一并清除的那种。

      例如,通过某个用户代理外部的机制,例如操作系统级别的机制。

    公开给应用的值在规范性上被禁止的其它属性包括但不限于:

    此类在规范上被禁止的值的示例包括但不限于:

    • 用于所有 origin 的单一基于硬件的值。

    • 用于所有 origin 的单一基于随机的值。

    • 从个性化过程获得的单一值,在所有 origin 中使用。

    • 包含上述全部或部分内容的值。

    • 用于多个但不是全部 origin 的单一值。

    • 用于域上所有 origin 的单一值。(标识符必须为每个 origin 的。)

    • 预置的特定 origin 的值。

    • 通过可轻易逆转的方式生成的值,因此被视为可被应用 关联,无论是在客户端生成还是涉及 个性化 过程。例如,将 origin 与固定值进行 XOR 或以其它方式整合(部分)。

虽然区别性标识符通常可被生成它们的实体所 关联,但它们 MUST 对应用而言 不可关联。换句话说,此类关联或匹配仅可由最初生成区别性标识符值的实体(例如 个性化 服务器)完成。拥有访问 区别性永久标识符 的实体 MUST NOT 将此能力暴露给应用,否则会使生成的区别性标识符对应用变得 可关联,并且 SHOULD 注意避免向其它实体或第三方暴露此类关联能力。

区别性标识符的示例包括但不限于:

  • 在密钥请求中包含的一系列字节,该序列与其它客户端设备包含的不同,并基于或直接/间接使用了 区别性永久标识符

  • 在密钥请求中包含的公钥,该公钥与其它客户端设备请求中包含的公钥不同,并基于或直接/间接使用了 区别性永久标识符

  • 证明拥有私钥(例如,通过签署某些数据),其它客户端设备没有该私钥,并基于或直接/间接使用了 区别性永久标识符

  • 该私钥的标识符。

  • 用于派生另一个即使第一个值不直接暴露仍会被暴露的值。

  • 从另一个区别性标识符派生的值。

  • 在与 个性化 服务器一起报告的随机值,或在提供了 区别性永久标识符 后由此类服务器提供的值。

  • 从出厂时预置在硬件设备中的唯一值派生的值。

  • 从硬件设备中的唯一硬件值(例如 MAC 地址或序列号)或软件值(例如操作系统安装实例或操作系统用户帐户名)派生的值。

  • 从包含在 CDM 二进制文件或 CDM 使用的其它文件中的唯一值派生的值。

并非 Distinctive Identifier 的示例包括:

  • 在给定 CDM 版本的所有副本中共享的公钥(如果已安装基数很大)。

  • 唯一但仅在一个会话中使用的 nonce 或短期密钥。

  • 未被暴露(即使是派生或类似方式)到客户端之外的值,包括通过 个性化 或类似方式。

  • 在视频管道与 CDM 之间用于证明的设备唯一密钥,当 CDM 不允许这些证明继续流向应用,而是使用不会构成区分性标识符的新密钥自行做新的证明时,该值不构成区别性标识符。

  • 与浏览数据(例如 cookies)一并完全清除/可清除的值,之后它将被一个 不可关联 的值替换(不仅仅是对应用 不可关联),并且满足下列一项或多项条件:

    • 在生成该值时未涉及任何 区别性永久标识符 或区别性标识符。

    • 它是一个在没有系统输入的情况下生成的随机值。

    • 它是由服务器提供的值,且在生成时未使用或未知任何其它区别性标识符。

区别性标识符与区别性永久标识符的使用

如果某实现、配置、实例或对象在其生命周期内或相关实体的生命周期内在客户端之外暴露(即使以加密形式)一个或多个 区别性标识符、有关它们的信息或从它们派生或与它们相关的值,则称其 使用了区别性标识符。这包括但不限于将此类值提供给应用和/或许可证、个性化 或其它服务器。

如果某实现、配置、实例或对象在其生命周期内或相关实体的生命周期内在客户端之外暴露(即使以加密形式)一个或多个 区别性永久标识符、有关它们的信息或从它们派生或与它们相关的值,则称其 使用了区别性永久标识符。此类值 MUST NOT 提供给应用。

如果某实现、配置、实例或对象 使用了区别性标识符 和/或 使用了区别性永久标识符,则称其 使用了区别性标识符或区别性永久标识符

distinctiveIdentifier 控制是否允许使用 区别性标识符区别性永久标识符。具体地,只有当用于创建 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时,才允许使用这些标识符。

跨源限制(Cross Origin Limitations)

在播放期间,嵌入的媒体数据会在嵌入页面的 origin 下暴露给脚本。为了能在 Initialization Dataencrypted 事件中被提供,媒体数据 MUST 与嵌入页面保持 CORS-same-origin。如果媒体数据与嵌入文档为跨源,作者 SHOULDHTMLMediaElement 上使用 crossOrigin 属性,并在媒体数据响应上使用 CORS 头,使其为 CORS-same-origin

混合内容限制(Mixed Content Limitations)

在播放期间,嵌入的媒体数据会在嵌入页面的 origin 下暴露给脚本。为了能在 Initialization Dataencrypted 事件中被提供,媒体数据 MUST NOT混合内容 [MIXED-CONTENT]。

时间(Time)

时间 MUST 等价于 ECMAScript 中所表示的时间值(参见 ECMAScript Time Values and Time Range)[ECMA-262]。

如果不存在这样的时间或时间不确定,时间将等于 NaN。它不应具有 Infinity 的值。

时间通常表示一个具有毫秒精度的时刻;然而这并不是充分的定义。已定义的时间值与时间范围引用添加了其它重要要求。

到期时间(Expiration Time)

指密钥在该时间之后不再 可用于解密时间

浏览配置文件(Browsing Profile)

在给定机器上的用户代理可能支持在多种不同上下文、模式或临时状态下执行,这些上下文或模式在对应用可见的状态和数据方面应当独立。特别地,所有存储数据应当是独立的。在本规范中,我们将此类独立上下文或模式称为“浏览配置文件”。

此类独立上下文的示例包括用户代理在不同操作系统用户帐户中运行,或用户代理提供在单个帐户下定义多个独立配置文件的能力。

3. 获取对密钥系统的访问

本节定义获取 Key System 访问的机制。在请求中包含能力也允许进行功能检测。

当 promise 被拒绝时,算法的步骤将始终中止。

3.1 权限策略集成

requestMediaKeySystemAccess() 是由字符串 encrypted-media 标识的 策略控制功能。其 默认允许列表'self' [PERMISSIONS-POLICY].

3.3 MediaKeySystemConfiguration 字典

WebIDLenum MediaKeysRequirement {
  "required",
  "optional",
  "not-allowed"
};

MediaKeysRequirement 枚举定义如下:

枚举说明
required
用于调用 requestMediaKeySystemAccess()
返回的对象 MUST 必须支持此特性。
MediaKeySystemAccess 对象返回时
该对象创建的 CDM 实例 MAY 可以使用此特性。
optional
用于调用 requestMediaKeySystemAccess()
返回的对象 MAY 可以支持并使用此特性。
MediaKeySystemAccess 对象返回时
此值不能且 MUST NOT 出现在此类对象中。
not-allowed
用于调用 requestMediaKeySystemAccess()
返回的对象 MUST 必须在任何时候都不使用此特性,并且 MUST NOT 使用它。
MediaKeySystemAccess 对象返回时
该对象创建的 CDM 实例 MUST NOT 使用此特性。
WebIDLdictionary MediaKeySystemConfiguration {
  DOMString                               label = "";
  sequence<DOMString>                     initDataTypes = [];
  sequence<MediaKeySystemMediaCapability> audioCapabilities = [];
  sequence<MediaKeySystemMediaCapability> videoCapabilities = [];
  MediaKeysRequirement                    distinctiveIdentifier = "optional";
  MediaKeysRequirement                    persistentState = "optional";
  sequence<DOMString>                     sessionTypes;
};

字典 MediaKeySystemConfiguration 包含以下成员:

label 类型为 DOMString,默认值为 ""
一个可选标签,会在 MediaKeySystemConfiguration 通过 getConfiguration() 方法由 MediaKeySystemAccess 返回时保留。
initDataTypes 类型为 sequence<DOMString>, 默认值为 []
支持的 初始化数据类型(Initialization Data Type) 名称列表。此对象的 初始化数据类型 能力被视为支持,如果列表为空或包含一个或多个与所有其它成员(由算法决定)均兼容的值。序列中的值 MUST 不得为空字符串。
audioCapabilities 类型为 sequence<MediaKeySystemMediaCapability>, 默认值为 []
支持的音频类型与能力对的列表。该对象的音频能力被视为支持,如果列表为空或包含一个或多个与所有其它成员(由算法决定)均兼容的值。当值间冲突时,优先选择前面的值。空列表表示不支持任何音频能力,此时 videoCapabilities 元素不能为空。
videoCapabilities 类型为 sequence<MediaKeySystemMediaCapability>, 默认值为 []
支持的视频类型与能力对的列表。该对象的视频能力被视为支持,如果列表为空或包含一个或多个与所有其它成员(由算法决定)均兼容的值。当值间冲突时,优先选择前面的值。空列表表示不支持任何视频能力,此时 audioCapabilities 元素不能为空。
distinctiveIdentifier 类型为 MediaKeysRequirement, 默认值为 "optional"
是否要求使用 区别性标识符

当该成员为 "not-allowed" 时,实现 MUST NOT 在从此配置创建的任何对象的任何操作中 使用区别性标识符或区别性永久标识符

persistentState 类型为 MediaKeysRequirement,默认值为 "optional"
是否要求具备持久化状态的能力,包括会话数据及其它类型的状态。

当该成员为 "not-allowed" 时,CDM MUST NOT 持久化与应用或该对象的 origin 相关的任何状态。

就此成员而言,持久化状态不包括由 Key System 实现控制的持久化唯一标识符(区别性标识符)。distinctiveIdentifier 独立反映该要求。

当不支持持久化状态时,仅允许创建 "temporary" 会话。

对于 "temporary" 会话,是否需要及能否存储状态取决于 Key System 的具体实现,并可能随所使用的功能而异。

如果应用打算创建非 "temporary" 会话,应在调用 requestMediaKeySystemAccess() 时,将该成员设置为 "required"。

sessionTypes 类型为 sequence<DOMString>
必须支持的 MediaKeySessionType 列表。所有值都必须被支持。

如果此成员在字典传递给 requestMediaKeySystemAccess()不存在 [Infra],则该字典会被视为已设置为 [ "temporary" ]

实现 SHOULD NOT 向该字典添加成员。如确需添加,MUST 类型为 MediaKeysRequirement,并且强烈建议(RECOMMENDED)默认值为 "optional",以支持最广泛的应用与客户端组合。

用户代理实现未识别的字典成员会被 [WEBIDL] 忽略,不会在 requestMediaKeySystemAccess() 算法中考虑。应用如使用非标准字典成员,MUST NOT 依赖用户代理实现会因包含此类成员而拒绝配置。

此字典 MUST NOT 用于向 CDM 传递状态或数据。

3.4 MediaKeySystemMediaCapability 字典

WebIDLdictionary MediaKeySystemMediaCapability {
  DOMString contentType = "";
  DOMString? encryptionScheme = null;
  DOMString robustness = "";
};

3.4.1 字典 MediaKeySystemMediaCapability 成员

contentType 类型为 DOMString,默认值为 ""

媒体资源MIME 类型

应用 SHOULD 确保 MIME 类型显式指定 codec 及 codec 约束(例如,参见 [RFC6381]),除非这些已被容器规范性蕴含。

encryptionScheme 类型为 DOMString,默认值为 null

与 contentType 关联的加密方案。值为 null 或未提供表示应用未指定特定加密方案,因此任何加密方案均可接受。

了解此字段的应用 SHOULD 指定所需的加密方案,因为不同加密方案通常彼此不兼容。应用实际接受“任何”加密方案并不现实,但默认值 null 及将 null 解释为“任何”可为未适配的应用提供向后兼容性,并为旧版用户代理提供 polyfill 路径。

空字符串与 null 或未提供不同,因此会被视为未识别的加密方案。

encryptionScheme 的常用值包括:

  • cenc: “cenc”模式,定义见 [CENC],第 4.2a 节。AES-CTR 模式全样本和视频 NAL 子样本加密。
  • cbcs: “cbcs”模式,定义见 [CENC],第 4.2d 节。AES-CBC 模式部分视频 NAL 模式加密。对于视频,规范允许各种加密模式。
  • cbcs-1-9: 与 "cbcs" 模式相同,但视频加密模式为 1:9(加 1 跳 9),推荐见 [CENC] 第 10.4.2 节。
robustness 类型为 DOMString,默认值为 ""

与 contentType 关联的健壮性级别。空字符串表示可接受任何解密和解码 contentType 的能力。

实现 MUST 配置 CDM 至少支持用于创建 MediaKeysMediaKeySystemAccess 对象配置中的健壮性级别。具体如何配置 CDM 取决于实现,且实现 MAY 可选择在配置中使用最高健壮性级别,即使有更高健壮性可用。如果仅指定空字符串,实现 MAY 可配置为使用最低健壮性级别。

应用 SHOULD 指定所需的健壮性级别,以避免意外的客户端不兼容。

为使该对象表示的能力被视为受支持,contentType MUST NOT 为空字符串,且其全部值(包括所有 codec)MUSTrobustness 一起被支持。

如果可接受任何一组 codec,应为每个 codec 使用该字典的独立实例。

4. MediaKeySystemAccess 接口

MediaKeySystemAccess 对象用于访问 Key System

WebIDL[Exposed=Window, SecureContext] interface MediaKeySystemAccess {
  readonly attribute DOMString keySystem;
  MediaKeySystemConfiguration  getConfiguration ();
  Promise<MediaKeys>           createMediaKeys ();
};

4.1 属性

keySystem 类型为 DOMString,只读
标识正在使用的 Key System

4.2 方法

getConfiguration()

返回由 requestMediaKeySystemAccess() 算法选择的受支持配置选项组合。

返回的对象是第一个可满足的 MediaKeySystemConfiguration 配置(加上任何隐含默认值)的非严格子集,来自于传递给 requestMediaKeySystemAccess() 的参数,该参数返回了被解析为本对象的 promise。它不包含未在该单一配置中指定的能力(除隐含默认值外),因此可能不反映 Key System 实现的所有能力。配置中的所有值都可以任意组合使用。类型为 MediaKeysRequirement 的成员反映该能力是否在任何组合中是必需的。它们不会有值 "optional"。

调用此方法时,用户代理 MUST 运行以下步骤:

  1. 返回此对象的 configuration 值。

    每次调用该方法都会创建并从 configuration 初始化一个新对象。

    如果应用未指定 encryptionScheme,累计的 configuration MUST 仍包含 encryptionScheme 字段且值为 null,这样 polyfill 可以检测用户代理对该字段的支持,无需指定具体值。

createMediaKeys()

keySystem 创建一个新的 MediaKeys 对象。

调用此方法时,用户代理 MUST 运行以下步骤:

  1. promise 为一个新的 promise。

  2. 并行运行下列步骤:

    1. configuration 为此对象的 configuration 值。

    2. configurationdistinctiveIdentifier 成员值为 "required",则 use distinctive identifiertrue,否则为 false

    3. configurationpersistentState 成员值为 "required",则 persistent state allowedtrue,否则为 false

    4. 如有必要,加载并初始化由此对象的 cdm implementation 值表示的 Key System 实现。

    5. instance 为此对象 cdm implementation 值表示的 Key System 实现的新实例。

    6. configuration 初始化 instance,以启用、禁用和/或选择 Key System 功能。

    7. 如果 use distinctive identifierfalse,则禁止 instance 使用区别性标识符或区别性永久标识符

    8. 如果 persistent state allowedfalse,则禁止 instance 持久化与应用或本对象 origin 相关的任何状态。

    9. 如果上述任何步骤失败,则以合适的 error name 拒绝 promise,并创建新的 DOMException

    10. media keys 为一个新的 MediaKeys 对象,并按如下方式初始化:

      1. use distinctive identifier 值为 use distinctive identifier

      2. persistent state allowed 值为 persistent state allowed

      3. supported session types 值为 configurationsessionTypes 成员的值。

      4. cdm implementation 值为此对象的 cdm implementation 值。

      5. cdm instance 值为 instance

    11. media keys 解析 promise

  3. 返回 promise

5. MediaKeys 接口

MediaKeys 对象表示一组密钥,相关的 HTMLMediaElement 可以在播放期间使用这些密钥来解密 媒体数据。它也表示一个 CDM 实例。

MediaKeys 对象不再可访问时,用户代理可能会销毁该对象。

例如,当没有脚本引用且没有附加的媒体元素时。

对于返回 promise 的方法,所有错误都通过拒绝返回的 Promise 异步报告。这包括 [WEBIDL] 类型映射错误。

当 promise 被拒绝时,算法的步骤将始终中止。

WebIDLenum MediaKeySessionType {
  "temporary",
  "persistent-license"
};

MediaKeySessionType 枚举定义如下:

枚举说明
temporary

一种会话,其许可证、密钥及与会话相关的记录或数据不被持久化。

应用无需管理此类存储。对该会话类型的支持为 必需(REQUIRED)

persistent-license

一种会话,其许可证(以及可能的其它与会话相关的数据)将被持久化。当许可证及其包含的密钥被销毁时,必须持久保存一份 许可证销毁记录。该“许可证销毁记录”是一个由 Key System 指定的、表明许可证及其密钥在客户端不再可用的证明。对该会话类型的支持为 可选(OPTIONAL)

只有当创建此对象的 MediaKeySystemAccess 对象的配置中,persistentState 的值为 "required" 时,才可以创建此类会话。该会话在成功调用 update() 后,必须能够通过其 Session ID 被加载。一条类型为 "license-release" 的 message,其中包含 许可证销毁记录,将在调用 remove() 时生成,直到该记录通过传给 update() 的响应被确认。

应用负责确保当不再需要这些会话的持久化数据时将其移除。参见 会话存储与持久化

WebIDL[Exposed=Window, SecureContext] interface MediaKeys {
    MediaKeySession createSession (optional MediaKeySessionType sessionType = "temporary");
    Promise<MediaKeyStatus> getStatusForPolicy (optional MediaKeysPolicy policy = {});
    Promise<boolean> setServerCertificate (BufferSource serverCertificate);
};

5.1 方法

createSession()

返回一个新的 MediaKeySession 对象。

sessionType 参数会影响返回对象的行为。

调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 supported session types 值不包含 sessionType,则 抛出 [WEBIDL] 一个 NotSupportedError

    对于那些 Is persistent session type? 算法返回 truesessionType,如果此对象的 persistent state allowed 值为 false,则会失败。

  2. 如果实现不支持在当前状态下执行 MediaKeySession 操作,则 抛出 [WEBIDL] 一个 InvalidStateError

    有些实现直到该 MediaKeys 对象通过 setMediaKeys() 与媒体元素关联后,才可执行 MediaKeySession 算法。此步骤使应用在尝试执行此类操作之前能够检测到这种不常见的行为。

  3. session 为一个新的 MediaKeySession 对象,并按如下方式初始化:

    1. sessionId 属性为一个空字符串。

    2. expiration 属性为 NaN

    3. closed 属性 为一个新的 promise。

    4. key status 为一个新的空的 MediaKeyStatusMap 对象,并按如下方式初始化:

      1. size 属性为 0。

    5. session type 值为 sessionType

    6. uninitialized 值为 true。

    7. callable 值为 false。

    8. closing or closed 值为 false。

    9. use distinctive identifier 值为此对象的 use distinctive identifier 值。

    10. cdm implementation 值为此对象的 cdm implementation

    11. cdm instance 值为此对象的 cdm instance

  4. 返回 session

getStatusForPolicy()

返回给定 MediaKeyStatus 对应于指定的 MediaKeysPolicy

WebIDLdictionary MediaKeysPolicy {
    DOMString minHdcpVersion;
};

MediaKeysPolicy 字典仅由可选属性组成。每个属性表示一项策略要求。如果 CDM 会基于所有这些要求允许呈现解密后的媒体数据,则该策略被认为已满足。

HDCP 策略由 minHdcpVersion 表示。如果系统能够启用所指定或更高版本的 HDCP,则该策略将导致返回的 MediaKeyStatus 为 "usable"。[EME-HDCP-VERSION-REGISTRY] 提供了从 minHdcpVersion 值到 HDCP 规范的映射。

HDCP 状态的判定应以 CDM 在播放期间执行此类限制时的方式一致。通过这种方式,应用开发者可以获得合理的提示,以便优化他们为开始播放而获取的内容。

调用此方法时,用户代理 MUST 运行以下步骤:

  1. If policy 没有任何 存在的 字典成员,则返回一个以新建 TypeError 被拒绝的 promise。
  2. promise 为一个新的 promise。

  3. 排队一个任务 来运行以下步骤:

    1. policy 的每个 字典成员 运行下列步骤:

      1. 如果键不是有效的 MediaKeysPolicy 成员或值的类型不正确,则以 TypeError 拒绝 promise 并中止这些步骤。

    2. policy 的每个 字典成员 运行下列步骤:

      1. 如果 CDM 无法确定该字典成员对应的 MediaKeyStatus,则以 NotSupportedError 拒绝 promise 并中止这些步骤。

    3. policy 的每个 字典成员 运行下列步骤:

      1. 如果 CDM 会阻止就该字典成员呈现解密后的媒体数据,则用 "output-restricted" 解析 promise

    4. 用 "usable" 解析 promise

  4. 返回 promise

setServerCertificate()

提供一个服务器证书,用于加密发送到许可证服务器的消息。

使用此类证书的 Key Systems 必须也支持通过 排队 "message" 事件 算法从服务器请求该证书。

此方法允许应用主动向支持该功能的实现提供服务器证书,以避免当 CDM 请求证书时额外的往返。该方法用于优化,应用不必一定使用它。

服务器证书内容为 Key System 特定内容。它 MUST NOT 包含可执行代码。

调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 cdm implementation 值所表示的 Key System 实现不支持服务器证书,则返回一个解析为 false 的 promise。

  2. 如果 serverCertificate 是空数组,则返回一个以新建 TypeError 被拒绝的 promise。

  3. certificateserverCertificate 参数内容的副本。

  4. promise 为一个新的 promise。

  5. 并行运行下列步骤:

    1. sanitized certificate 为对 certificate 的验证和/或清理后的版本。

      用户代理应在将证书传递给 CDM 之前对其进行彻底验证。此过程可能包括验证字段值在合理范围内、剥离无关数据或字段、预解析、对其进行清理,和/或生成完全清理后的版本。用户代理应检查字段的长度和值是否合理。未知字段应被拒绝或移除。

    2. 使用此对象的 cdm instance 来处理 sanitized certificate

    3. 如果前一步失败,则以合适的 DOMException(其 name 为相应的 错误名)拒绝 promise

    4. true 解析 promise

  6. 返回 promise

5.2 算法

5.2.1 是否为持久会话类型?

Is persistent session type? 算法用于确定指定的会话类型是否支持任何形式的持久化。请求运行此算法时包含一个 MediaKeySessionType 值。

运行下列步骤:

  1. session type 为指定的 MediaKeySessionType 值。

  2. session type 的值从下列列表中执行对应步骤:

    "temporary"
    返回 false
    "persistent-license"
    返回 true

5.2.2 CDM 不可用

CDM Unavailable 算法用于在 MediaKeySession 对象关联的 MediaKeys 对象 media keys 上关闭所有会话,当相应的 CDM 实例变为不可用时。请求运行此算法时包含一个 MediaKeySessionClosedReason 值。

运行下列步骤:

  1. reason 为指定的 MediaKeySessionClosedReason 值。

  2. 对于由 media keys 创建且尚未被 closed 的每个 MediaKeySession,排队一个任务以在该会话上使用原因 reason 运行 Session Closed 算法。

5.3 存储与持久化

本节描述与存储和持久化相关的一般要求。

如果一个 MediaKeys 对象的 persistent state allowed 值为 false,则该对象的 cdm instance 不得(SHALL NOT) 因该对象或其创建的任何会话的操作而持久化状态或访问先前持久化的状态。

如果一个 MediaKeys 对象的 persistent state allowed 值为 true,则该对象的 cdm instance 可以(MAY) 因该对象或其创建的任何会话的操作而持久化状态或访问先前持久化的状态。

持久化的数据 必须(MUST) 始终以仅由该对象的 origin 的文档访问的方式存储。此外,该数据 必须(MUST) 仅能被当前的 浏览配置文件 访问;其它浏览配置文件、用户代理和应用不得访问存储的数据。参见 用户设备上存储的信息

在支持持久化存储时,请参阅 10. 安全11. 隐私 以获取更多考虑事项。

6. MediaKeySession 接口

MediaKeySession 对象表示一个 key session

当且仅当对象的 closed 属性已被解析时,MediaKeySession 对象才被视为 closed

用户代理 SHALL 持续为每个未被 closedMediaKeySession 对象执行 Monitor for CDM State Changes 算法。Monitor for CDM State Changes 算法 MUST 与主事件循环并行运行,但不应与本规范中也被定义为并行运行的其它过程并行执行。

一个 MediaKeySession 对象 SHALL NOT 被销毁,并且在它未被 closed 且创建它的 MediaKeys 对象仍然可访问时 SHALL 继续接收事件。否则,一个已不再可访问的 MediaKeySession 对象 SHALL NOT 再接收进一步的事件并且 MAY 被销毁。

Note

上述规则意味着在所有与该 CDM 实例相关的 MediaKeys 对象和 MediaKeySession 对象都被销毁之前,不得销毁该 CDM 实例。

如果在 MediaKeySession 对象在变得对页面不可访问时仍未被 closed,则相应的 CDM 必须关闭与该对象关联的 key session

Note

关闭 key session 会导致销毁任何未被显式存储的许可证和密钥。

Note

何时精确关闭 key session 属于实现细节,应用不应依赖具体的时序。 想要在采取其它操作之前确保会话已关闭的应用 SHOULD 调用 close() 并等待返回的 promise 被解析。

对于返回 promise 的方法,所有错误都通过拒绝返回的 Promise 异步报告。这也包括 [WEBIDL] 的类型映射错误。

当拒绝 promise 时,算法的下列步骤将始终中止。

WebIDLenum MediaKeySessionClosedReason {
  "internal-error",
  "closed-by-application",
  "release-acknowledged",
  "hardware-context-reset",
  "resource-evicted"
};

MediaKeySessionClosedReason 枚举定义如下:

枚举说明
internal-error 该会话因 CDM 中发生不可恢复错误而被关闭。发生此情况时,应用 MUST NOT 在该 MediaKeys 实例上创建新的会话。
closed-by-application 该会话是因为应用显式调用会话的 close() 方法而被关闭。
release-acknowledged 该会话因为 CDM 收到许可证销毁记录的确认而被关闭。
hardware-context-reset 该会话因为 CDM 的原始硬件上下文被重置而被关闭。 发生此情况时,用户代理 MUST 允许应用在该 MediaKeys 实例上创建新会话。
Note

这可能由多种原因引起,包括设备休眠、显示器配置更改等。硬件上下文重置的具体原因取决于实现。

resource-evicted 该会话因系统需要回收资源以允许创建其它会话而被关闭。
Note

这表示应用已达到设备特定的会话资源限制。如果被关闭的会话仍然需要,应用开发者应考虑减少所需会话数或主动关闭不需要的会话等策略。

WebIDL[Exposed=Window, SecureContext] interface MediaKeySession : EventTarget {
  readonly        attribute DOMString                            sessionId;
  readonly        attribute unrestricted double                  expiration;
  readonly        attribute Promise<MediaKeySessionClosedReason> closed;
  readonly        attribute MediaKeyStatusMap                    keyStatuses;
                  attribute EventHandler                         onkeystatuseschange;
                  attribute EventHandler                         onmessage;
  Promise<undefined>    generateRequest (DOMString initDataType, BufferSource initData);
  Promise<boolean> load (DOMString sessionId);
  Promise<undefined>    update (BufferSource response);
  Promise<undefined>    close ();
  Promise<undefined>    remove ();
};

6.1 属性

sessionId 类型为 DOMString, 只读

此对象及其关联的 key(s) 或 license(s) 的 Session ID

expiration 类型为 unrestricted double, 只读

会话中所有 key(s) 的 到期时间,如果不存在此类时间或 CDM 明确指示许可证永不过期,则为 NaN,由 CDM 决定。

此值在会话生命周期内 可能(MAY) 发生变化,例如某个操作触发了窗口开始时间。

closed 类型为 Promise<MediaKeySessionClosedReason>, 只读

当因运行 Session Closed 算法而导致该对象被视为 closed 时发出信号。此 promise 只能被 fulfilled,不会被 rejected。

keyStatuses 类型为 MediaKeyStatusMap, 只读

对只读映射的引用,该映射将 key IDs(对会话已知)映射到关联密钥的当前状态。每个条目 必须(MUST) 具有唯一的 key ID。

映射条目及其值可能在事件循环运行时被更新。映射 不得(MUST NOT) 出现不一致或部分更新,但在两次访问之间如果事件循环运行过,映射可能发生变化。Key IDs 可能因调用 load()update() 而被添加。Key IDs 可能因调用 update() 并移除对现有密钥的知识(或用新的一组密钥替换现有集合)而被移除。Key IDs 不得(MUST NOT) 因变得不可用(例如到期)而被移除。相反,此类密钥 必须(MUST) 被赋予适当的状态,例如 "expired"。

某些旧平台上的 Key System 实现可能不暴露 key IDs,从而无法提供完全符合规范的用户代理实现。为了最大化互操作性,暴露此类 CDMs 的用户代理实现 应(SHOULD) 如下实现此成员:每当应有非空列表(例如该对象表示的 key session 可能包含 key(s) 时),用一对条目填充映射,条目使用单字节 key ID 0 和最适合该对象聚合状态的 MediaKeyStatus

onkeystatuseschange 类型为 EventHandler

用于处理 keystatuseschange 事件的事件处理器。

onmessage 类型为 EventHandler

用于处理 message 事件的事件处理器。

6.2 方法

generateRequest()

基于 initData 生成许可证请求。如果算法成功并且 promise 被解析,则类型为 "license-request" 或 "individualization-request" 的 message 将始终被排入队列。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 closing or closed 值为 true,则返回一个以 InvalidStateError 拒绝的 promise。

  2. 如果此对象的 uninitialized 值为 false,则返回一个以 InvalidStateError 拒绝的 promise。

  3. 将此对象的 uninitialized 值设为 false。

  4. 如果 initDataType 是空字符串,则返回一个以新建的 TypeError 拒绝的 promise。

  5. 如果 initData 是空数组,则返回一个以新建的 TypeError 拒绝的 promise。

  6. 如果此对象的 cdm implementation 值所表示的 Key System 实现不支持将 initDataType 作为一种 Initialization Data Type,则返回一个以 NotSupportedError 拒绝的 promise。字符串比较区分大小写。

  7. init datainitData 参数内容的副本。

  8. session type 为此对象的 session type

  9. promise 为一个新的 promise。

  10. 并行运行下列步骤:

    1. 如果 init data 对于 initDataType 无效,则以新建的 TypeError 拒绝 promise

    2. sanitized init data 为经验证和清理后的 init data 版本。

      用户代理 MUST 在将 Initialization Data 传递给 CDM 之前对其进行彻底验证。这包括验证字段的长度和值是否合理、验证值是否在合理范围内,以及剥离无关、不支持或未知的数据或字段。建议用户代理对 Initialization Data 进行预解析、清理和/或生成完全清理后的版本。如果 initDataType 指定的 Initialization Data 格式支持多条目,用户代理 SHOULD 移除 CDM 不需要的条目。用户代理 MUST NOT 重新排序 Initialization Data 中的条目。

    3. 如果前一步失败,则以新建的 TypeError 拒绝 promise

    4. 如果 sanitized init data 为空,则以 NotSupportedError 拒绝 promise

    5. session id 为空字符串。

    6. message 为 null。

    7. message type 为 null。

    8. cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。

    9. 使用 cdm 执行下列步骤:

      1. 如果 sanitized init data 不被 cdm 支持,则以 NotSupportedError 拒绝 promise

      2. 按下列列表中与 session type 值相对应的条目执行步骤:

        "temporary"

        requested license type 设为一个临时的不可持久化的许可证。

        返回的许可证不得为可持久化的,也不得要求持久化与其相关的信息。

        "persistent-license"

        requested license type 设为可持久化的许可证。

      3. session id 为一个唯一的 Session ID 字符串。

        如果对 session type 运行 Is persistent session type? 算法的结果为 true,则该 ID MUST 在此对象的 origin 范围内随时间保持唯一,包括跨越不同的 Documents 和浏览会话。

      4. 如果可基于 sanitized init data 生成针对 requested license type 的许可证请求:
        1. message 为基于按照 initDataType 解释的 sanitized init data 所生成的、用于 requested license type 的许可证请求。

          cdm MUST NOT 使用任何流特定数据,包括未通过 sanitized init data 提供的 media data

          cdm SHOULD NOT 在此时存储会话数据,包括会话 ID。参见 会话存储与持久化

        2. message type 为 "license-request"。

        否则:
        1. message 为在生成可基于 sanitized init datarequested license type 许可证请求之前需要被处理的请求。

          在随后对 update() 的调用中,CDM MUST 基于按照 initDataType 解释的 sanitized init data 生成针对 requested license type 的许可证请求。

        2. message type 表示 message 的类型,为 "license-request" 或 "individualization-request"。

    10. 排队一个任务(Queue a task) 来运行下列步骤:

      1. 如果前述任何步骤因资源短缺而失败,则以 QuotaExceededError 拒绝 promise

      2. 如果前述任何步骤因其它原因失败,则以一个新的 DOMException(其 name 为相应的 error name) 拒绝 promise

      3. sessionId 属性设为 session id

      4. 将此对象的 callable 值设为 true。

      5. undefined 解析 promise

      6. session 上运行 Queue a "message" Event 算法,提供 message typemessage

  11. 返回 promise

load()

将为指定会话存储的数据加载到此对象中。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 closing or closed 值为 true,则返回一个以 InvalidStateError 拒绝的 promise。

  2. 如果此对象的 uninitialized 值为 false,则返回一个以 InvalidStateError 拒绝的 promise。

  3. 将此对象的 uninitialized 值设为 false。

  4. 如果 sessionId 是空字符串,则返回一个以新建的 TypeError 拒绝的 promise。

  5. 如果对本对象的 session type 运行 Is persistent session type? 算法的结果为 false,则返回一个以新建的 TypeError 拒绝的 promise。

  6. origin 为此对象的 origin

  7. promise 为一个新的 promise。

  8. 并行运行下列步骤:

    1. sanitized session IDsessionId 的经验证和/或清理后的版本。

      用户代理在将 sessionId 值传递给 CDM 之前应对其进行彻底验证。至少应检查长度和值是否合理(例如,不超过几十个字符且为字母数字)。

    2. 如果前一步失败,或 sanitized session ID 为空,则以新建的 TypeError 拒绝 promise

    3. 如果在本对象的 Document 中存在一个尚未被 closedMediaKeySession 对象,且其 sessionId 属性等于 sanitized session ID,则以 QuotaExceededError 拒绝 promise

      换言之,如果在此浏览上下文中已存在一个针对该 sanitized session ID 的未关闭会话(不论类型),则不要创建新会话。

    4. expiration timeNaN

    5. message 为 null。

    6. message type 为 null。

    7. cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。

    8. 使用 cdm 执行下列步骤:

      1. 如果在 origin 中不存在针对 sanitized session ID 的存储数据,则以 false 解析 promise 并中止这些步骤。

      2. 如果存储的会话的 session type 与当前本对象的 MediaKeySessionsession type 不同,则以新建的 TypeError 拒绝 promise

      3. session data 为在 origin 下为 sanitized session ID 存储的数据。此数据 MUST NOT 包含来自其它 origin 的数据或与 origin 无关的数据。

      4. 如果存在一个在任一 Document 中尚未被 closed 且表示该 session dataMediaKeySession 对象,则以 QuotaExceededError 拒绝 promise

        换言之,如果在任何浏览上下文中已存在针对该 sanitized session ID 的未关闭持久会话,则不要创建新会话。

      5. 加载 session data

      6. 如果 session data 指示了该会话的某个 expiration time,则将 expiration time 设为该到期时间。

      7. 如果需要发送消息,则执行下列步骤:

        1. message 为基于 session data 生成的消息。

        2. message type 为该消息的适当 MediaKeyMessageType

    9. 排队一个任务(Queue a task) 来运行下列步骤:

      1. 如果前述任何步骤失败,则以适当的 error name 拒绝 promise

      2. sessionId 属性设为 sanitized session ID

      3. 将此对象的 callable 值设为 true。

      4. 如果已加载的会话包含有关任何密钥的信息(存在 known keys),则对 session 运行 Update Key Statuses 算法,提供每个密钥的 key ID 及相应的 MediaKeyStatus

        如果确定某密钥的状态需要额外处理,可使用 "status-pending"。一旦对一个或多个密钥的额外处理完成,再次运行 Update Key Statuses 算法并提供实际状态。

      5. session 上运行 Update Expiration 算法,提供 expiration time

      6. true 解析 promise

      7. 如果 message 非 null,则在 session 上运行 Queue a "message" Event 算法,提供 message typemessage

  9. 返回 promise

update()

CDM 提供消息(包括许可证)。

response 参数包含要提供给 CDM 的消息。其内容依赖于 Key System,且 MUST NOT 包含可执行代码。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 closing or closed 值为 true,则返回一个以 InvalidStateError 拒绝的 promise。

  2. 如果此对象的 callable 值为 false,则返回一个以 InvalidStateError 拒绝的 promise。

  3. 如果 response 是空数组,则返回一个以新建的 TypeError 拒绝的 promise。

  4. response copyresponse 参数内容的副本。

  5. promise 为一个新的 promise。

  6. 并行运行下列步骤:

    1. sanitized response 为对 response copy 经验证和/或清理后的版本。

      用户代理在将响应传递给 CDM 之前应对其进行彻底验证。这可能包括验证字段值在合理范围内、剥离无关数据或字段、预解析、清理和/或生成完全清理后的版本。用户代理应检查字段的长度和值是否合理。未知字段应被拒绝或移除。

    2. 如果前一步失败,或 sanitized response 为空,则以新建的 TypeError 拒绝 promise

    3. message 为 null。

    4. message type 为 null。

    5. session closed 为 false。

    6. cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。

    7. 使用 cdm 执行下列步骤:

      1. 如果 sanitized response 的格式在任何方面无效,则以新建的 TypeError 拒绝 promise

      2. 处理 sanitized response,并按下列列表中首个匹配条件的规定执行:

        如果 sanitized response 包含许可证或密钥

        这包括初始许可证、更新许可证以及许可证续订消息。

        处理 sanitized response,并按下列列表中首个匹配条件的规定执行:

        如果 sessionType 为 "temporary" 且 sanitized response 未指定应存储其包含的会话数据(包括任何许可证、密钥或类似会话数据)
        处理 sanitized response,但不存储任何会话数据。
        如果 sessionType 为 "persistent-license" 且 sanitized response 包含可持久化的许可证
        处理 sanitized response,并存储 sanitized response 中包含的许可证/密钥及相关会话数据。此类数据 MUST 以仅能由此对象的 origin 访问的方式存储。
        否则

        以新建的 TypeError 拒绝 promise

        另见 会话存储与持久化

        每个会话的状态信息(包括密钥) MUST 以这样一种方式存储:关闭一个会话不会影响其它会话中可观察到的状态,即使这些会话包含重叠的 key ID。

        sanitized response 包含密钥和/或相关数据时,cdm 很可能在内存中按 key ID 存储该密钥和相关数据。

        会话内部的替换算法依赖于 Key System

        建议 CDM 实现支持每个 MediaKeySession 对象的标准且合理的最小密钥数目(包含标准替换算法),以及标准且合理的最小 MediaKeySession 对象数目。这使得在用户代理之间能够实现合理数量的密钥轮换算法,并可能在涉及同一元素中多路流(例如自适应流、不同音视频轨道)使用不同密钥的用例中减少播放中断的可能性。

        如果 sanitized response 包含 许可证销毁记录 的确认且 sessionType 为 "persistent-license"

        运行下列步骤:

        1. 关闭 key session 并清除与此对象关联的 所有 存储会话数据,包括 sessionId许可证销毁记录

          随后的对 load() 的调用,若使用此对象的 sessionId 值,将会失败,因为该会话 ID 不再有存储的数据。

        2. session closed 设为 true。

        否则
        处理 sanitized response,但不存储任何会话数据。

        例如,sanitized response 可能包含将用于生成另一个 message 事件的信息。在这种情况下,无需验证其内容是否与 sessionType 匹配。

      3. 如果需要发送消息,执行下列步骤:

        1. message 为该消息。

        2. message type 为该消息的适当 MediaKeyMessageType

    8. 排队一个任务(Queue a task) 来运行下列步骤:

      1. 如果 session closed 为 true:

        在此对象上以原因 "release-acknowledged" 运行 Session Closed 算法。

        否则:

        运行下列步骤:

        1. 如果此对象的 CDM 对该对象已知的密钥集合变化或任何密钥的状态发生变化,则对 session 运行 Update Key Statuses 算法,提供每个已知密钥的 key ID 及相应的 MediaKeyStatus

          如果确定某密钥的状态需要额外处理,可使用 "status-pending"。一旦额外处理完成,再次运行 Update Key Statuses 算法并提供实际状态。

        2. 如果会话的 expiration time 发生变化,则在 session 上运行 Update Expiration 算法,提供新的到期时间。

        3. 如果前述任一步骤失败,则以新的 DOMException(其 name 为相应的 error name) 拒绝 promise

        4. 如果 message 非 null,则在 session 上运行 Queue a "message" Event 算法,提供 message typemessage

      2. undefined 解析 promise

  7. 返回 promise

close()

表示应用不再需要该会话,且 CDM 应释放与会话关联的任何资源并关闭该会话。已持久化的数据不应被释放或清除。

当请求被处理完毕时,返回的 promise 将被解析;当会话关闭时,closed 属性的 promise 将被解析为 "closed-by-application"。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 closing or closed 值为 true,则返回一个解析为 undefined 的 promise。

  2. 如果此对象的 callable 值为 false,则返回一个以 InvalidStateError 拒绝的 promise。

  3. promise 为一个新的 promise。

  4. 将此对象的 closing or closed 值设为 true。

  5. 并行运行下列步骤:

    1. cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。

    2. 使用 cdm 关闭与此对象关联的 key session

      关闭 key session 会导致销毁任何未被显式存储的许可证和密钥。

    3. 排队一个任务(Queue a task) 来运行下列步骤:

      1. undefined 解析 promise

      2. 在此对象上以原因 "closed-by-application" 运行 Session Closed 算法。

  6. 返回 promise

remove()

移除与该会话关联的所有许可证和密钥。对于 持久会话类型,其它会话数据将在释放消息确认被 update() 处理后按每种会话类型的定义被清除。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 closing or closed 值为 true,则返回一个以 InvalidStateError 拒绝的 promise。

  2. 如果此对象的 callable 值为 false,则返回一个以 InvalidStateError 拒绝的 promise。

  3. promise 为一个新的 promise。

  4. 并行运行下列步骤:

    1. cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。

    2. message 为 null。

    3. message type 为 null。

    4. 使用 cdm 执行下列步骤:

      1. 如果有任何许可证和/或密钥与该会话相关联:

        1. 销毁与该会话关联的许可证和/或密钥。

          这意味着无论许可证和/或密钥是在内存中、持久存储中或两者中,都将被销毁。

        2. 按下列列表中与此对象的 session type 值相对应的条目执行步骤:

          "temporary"

          继续执行下列步骤。

          "persistent-license"
          1. record of license destruction 为表示此对象所代表许可证的 许可证销毁记录

          2. 存储该 record of license destruction

          3. message 为包含或反映该 record of license destruction 的消息。

    5. 排队一个任务(Queue a task) 来运行下列步骤:

      1. session 上运行 Update Key Statuses 算法,为会话中的所有 key ID 提供 "released" 的 MediaKeyStatus 值。

      2. session 上运行 Update Expiration 算法,提供 NaN

      3. 如果前述任一步骤失败,则以新的 DOMException(其 name 为相应的 error name) 拒绝 promise

      4. message type 为 "license-release"。

      5. undefined 解析 promise

      6. 如果 messagenull,则在 session 上运行 Queue a "message" Event 算法,提供 message typemessage

  5. 返回 promise

6.3 MediaKeyStatusMap 接口

MediaKeyStatusMap 对象是一个只读映射, 将 key IDs 映射到关联密钥的当前状态。

密钥的状态与该密钥当前是否正在被使用以及与媒体数据无关。

例如,如果某个密钥具有当前无法满足的输出要求,则无论该密钥是否已被或当前被用来解密媒体数据,其状态都应相应地为 "output-downscaled" 或 "output-restricted"。

WebIDL[Exposed=Window, SecureContext] interface MediaKeyStatusMap {
  iterable<BufferSource,MediaKeyStatus>;
  readonly attribute unsigned long size;
  boolean has (BufferSource keyId);
  (MediaKeyStatus or undefined) get (BufferSource keyId);
};

6.3.1 属性

size 类型为 unsigned long, 只读

已知的 known keys 的数量。

6.3.2 方法

has()

如果已知由 keyId 标识的密钥的状态,则返回 true

get()

返回由 keyId 标识的密钥的 MediaKeyStatus, 如果由 keyId 标识的密钥的状态未知,则返回 undefined

此接口具有由 iterable [WebIDL] 提供的 entrieskeysvaluesforEach@@iterator 方法。

要迭代的键值对是针对所有 known keys 所形成的 key ID 与其关联的 MediaKeyStatus 值的快照,按 key ID 排序。Key ID 的比较方式如下:对于长度为 m 的 key ID A 和长度为 nB,令 m <= n,当且仅当 Am 个八位字节在字典序中小于 B 的前 m 个八位字节,或这些八位字节相等且 m < n 时,判定 A < B

WebIDLenum MediaKeyStatus {
  "usable",
  "expired",
  "released",
  "output-restricted",
  "output-downscaled",
  "usable-in-future",
  "status-pending",
  "internal-error"
};

MediaKeyStatus 枚举定义如下:

枚举说明
usable CDM 确定该密钥当前可用于 解密
可能当前不可用于 解密 的密钥 MUST NOT 被标示为此状态。
expired 该密钥因其 到期时间 已过而不再可用于 解密
expiration 属性表示的时间 MUST 早于当前时间。会话中的所有其他密钥 MUST 具有此状态。
released 该密钥本身不再可供 CDM 使用, 但有关该密钥的信息(例如 许可证销毁记录)仍然可用。
output-restricted 与该密钥相关的输出限制当前无法满足。使用此密钥解密的媒体数据可能会在必要时被阻止呈现以遵守输出限制。应用应避免使用会触发该密钥相关输出限制的流。
output-downscaled 与该密钥相关的输出限制当前无法满足。根据输出限制的需要,使用此密钥解密的媒体数据可能以较低质量(例如分辨率)呈现。应用应避免使用会触发该密钥相关输出限制的流。
对下采样(downscaling)的支持为 可选(OPTIONAL)。应用 SHOULD NOT 依赖下采样以在输出要求无法满足时确保播放不中断。
usable-in-future 该密钥尚未可用于 解密,因为其开始时间在将来。该密钥将在其开始时间到达时变为可用。
status-pending 该密钥的状态尚不确定,正在确定中。一旦确定,将使用实际状态进行更新。
internal-error 由于 CDM 中发生了与其它值无关的错误,该密钥当前不可用于 解密。此值对应用不可行动(non-actionable)。

MediaKeyMessageEvent 对象用于 message 事件。

WebIDLenum MediaKeyMessageType {
  "license-request",
  "license-renewal",
  "license-release",
  "individualization-request"
};

MediaKeyMessageType 定义如下:

枚举说明
license-request 消息包含对新许可证的请求。
license-renewal 消息包含对现有许可证的续订请求。
license-release 消息包含一份 许可证销毁记录
individualization-request 消息包含对 App-Assisted Individualization(或重新个性化)的请求。
与其它消息一样,消息中的任何标识符 MUSTorigin 与 profile 范围内保持区别性,且 MUST NOTDistinctive Permanent Identifiers
WebIDL[Exposed=Window, SecureContext]
interface MediaKeyMessageEvent : Event {
  constructor(DOMString type, MediaKeyMessageEventInit eventInitDict);
  readonly attribute MediaKeyMessageType messageType;
  readonly attribute ArrayBuffer         message;
};

6.4.1 属性

messageType 类型为 MediaKeyMessageType, 只读
消息的类型。

实现 MUST NOT 要求应用处理消息类型。实现 MUST 支持不区分消息类型的应用, 且 MUST NOT 要求应用必须处理消息类型。具体来说,Key Systems MUST 支持把所有类型的消息都传递到同一个 URL。

该属性允许应用在不解析消息的情况下区分消息类型。其旨在支持可选的应用和/或服务器优化,但应用不必使用它。

message 类型为 ArrayBuffer, 只读
来自 CDM 的消息。消息是 Key System 特定的。

6.4.2 MediaKeyMessageEventInit

WebIDLdictionary MediaKeyMessageEventInit : EventInit {
  required MediaKeyMessageType messageType;
  required ArrayBuffer         message;
};
6.4.2.1 字典 MediaKeyMessageEventInit 成员
messageType 类型为 MediaKeyMessageType
消息的类型。
message 类型为 ArrayBuffer
消息内容。

6.5 事件摘要

本节为非规范性内容。

事件名称 接口 触发时...
keystatuseschange Event 会话中的密钥或其状态发生了变化。
message MediaKeyMessageEvent CDM 已为该会话生成了一条消息。

6.6 算法

6.6.1 排队一个 "message" 事件

Queue a "message" Event 算法将一个 message 事件排入指定的 MediaKeySession 对象的事件队列。请求运行此算法时包括一个目标 MediaKeySession 对象、一个 message type 和一个 message

message MUST NOT 包含 Distinctive Permanent Identifier(s),即使是以加密形式也不得包含。若 MediaKeySession 对象的 use distinctive identifier 值为 false,则 message MUST NOT 包含 Distinctive Identifier(s),即使是以加密形式也不得包含。

运行下列步骤:

  1. session 为指定的 MediaKeySession 对象。

  2. 排队一个任务(Queue a task) 来创建一个名为 message 的事件,该事件不冒泡且不可取消,使用 MediaKeyMessageEvent 接口,\ 将其 type 属性设为 message,并将其 isTrusted 属性初始化为 true,然后在 session 上派发该事件。

    该事件接口 MediaKeyMessageEvent 具有:

6.6.2 更新密钥状态(Update Key Statuses)

Update Key Statuses 算法用于更新某个 known 密钥集合,或更新一个或多个密钥的状态,该会话为 MediaKeySession。请求运行此算法时包括一个目标 MediaKeySession 对象以及一系列 key ID 与其对应的 MediaKeyStatus 对。

该算法总是在一个 task 中运行。

运行下列步骤:

  1. session 为相关联的 MediaKeySession 对象。

  2. input statuses 为由 key ID 与对应 MediaKeyStatus 对组成的序列。

  3. statusessessionkeyStatuses 属性。

  4. 运行下列步骤以替换 statuses 的内容:

    1. 清空 statuses

    2. input statuses 中的每一对进行处理。

      1. pair 为该对。

      2. statuses 中插入一项,其键为 pair 的 key ID,值为 pairMediaKeyStatus 值。

    该步骤的效果是以原子方式替换 sessionkeyStatuses 属性内容,而不会使对该属性的现有引用失效。从脚本角度看,这种替换是原子的;脚本 MUST NOT 看到部分填充的序列。

  5. 排队一个任务(Queue a task)触发一个事件(fire an event),事件名为 keystatuseschange,在 session 上触发。

  6. 排队一个任务(Queue a task) 来在每个其 mediaKeys 属性为创建该 sessionMediaKeys 对象的媒体元素上运行 Attempt to Resume Playback If Necessary 算法。

6.6.3 更新到期时间(Update Expiration)

Update Expiration 算法用于更新某个 expiration time,目标为某个 MediaKeySession。请求运行此算法时包括一个目标 MediaKeySession 对象和新的到期时间,该时间可以为 NaN

该算法总是在一个 task 中运行。

运行下列步骤:

  1. session 为相关联的 MediaKeySession 对象。

  2. expiration timeNaN

  3. 如果新的到期时间不是 NaN,则令 expiration time 为该到期时间。

  4. sessionexpiration 属性设置为以 time 表示的 expiration time

6.6.4 会话已关闭(Session Closed)

Session Closed 算法在 key session 被相应的 CDM 关闭后,更新对应的 MediaKeySession 状态。请求运行此算法时包括一个目标 MediaKeySession 对象和一个 MediaKeySessionClosedReason

该算法总是在一个 task 中运行。

当会话被关闭时,与之关联的许可证和密钥不再可用于解密 媒体数据。在运行此算法之后,所有 MediaKeySession 的方法将失败,且不会再为该对象排入新的事件。

CDM 可能在任何时刻关闭会话,例如当会话不再需要或系统资源丢失时。在这种情况下,Monitor for CDM State Changes 算法会检测到该变化并运行此算法。

其它会话中的密钥 MUST 不受影响,即使它们具有重叠的 key ID。

在此算法运行后,由该算法排入队列的事件的事件处理器将被执行,但不能再排入更多事件。因此,关闭会话不会导致 CDM 发送更多消息。

运行下列步骤:

  1. session 为相关联的 MediaKeySession 对象。

  2. promisesessionclosed 属性。

  3. 如果 promise 已被解析,则中止这些步骤。

  4. sessionclosing or closed 值设为 true。

  5. session 上运行 Update Key Statuses 算法,传入一个空序列。

  6. session 上运行 Update Expiration 算法,传入 NaN

  7. 用提供的原因解析 promise

6.6.5 监视 CDM 状态变化(Monitor for CDM State Changes)

Monitor for CDM State Changes 算法执行在 CDM 状态发生各种变化时所需的步骤。

该算法仅适用于那些未被其它算法覆盖的 CDM 状态变化。例如,update() 可能导致消息、密钥状态变化和/或到期时间变化,但这些都在该算法内部处理。

该算法总是与主事件循环并行运行。

运行下列步骤:

  1. sessionMediaKeySession 对象。

  2. cdm 为由 sessioncdm instance 值表示的 CDM 实例。

  3. 如果 cdm 有一个尚未发送的外发消息(outgoing message),则 排队一个任务(Queue a task) 来执行下列步骤:

    1. message typemessage 分别为该消息的类型与消息内容。

    2. 运行 Queue a "message" Event 算法,传入 sessionmessage typemessage

  4. 如果 cdm 更改了对 session 已知的密钥集合(known keys)或更改了一个或多个密钥的状态,则 排队一个任务(Queue a task) 来执行下列步骤:

    1. statuses 为一个包含键值对的列表,每项为对 session 已知的一个 key ID 与其对应的 MediaKeyStatus 值。

    2. 运行 Update Key Statuses 算法,传入 sessionstatuses

  5. 如果 cdm 更改了 sessionexpiration time,则 排队一个任务(Queue a task) 来执行下列步骤:

    1. expiration timesession 的新到期时间。

    2. 运行 Update Expiration 算法,传入 sessionexpiration time

  6. 如果 cdm 已关闭 session,则 排队一个任务(Queue a task) 来在 session 上运行 Session Closed 算法,并提供一个适当的 MediaKeySessionClosedReason 值。

  7. 如果 cdm 因硬件上下文重置而变为不可用,则 排队一个任务(Queue a task) 来运行 CDM Unavailable 算法,原因设为 "hardware-context-reset"。

  8. 如果 cdm 因其它原因变为不可用,则 排队一个任务(Queue a task) 来运行 CDM Unavailable 算法,原因设为 "internal-error"。

6.7 异常

方法通过拒绝返回的 promise,并抛出 简单异常 [WEBIDL] 或 DOMException 来报告错误。下列 [WEBIDL] 中定义的 简单异常DOMException 名称 会在算法中使用。算法中指定的原因会列在每个名称旁边,但这些名称 MAY 也可用于其他原因。

名称 可能原因(非详尽)
TypeError 参数为空。
初始化数据无效。
响应格式无效。
为 "temporary" 会话提供了持久许可证。
NotSupportedError 现有 MediaKeys 对象无法移除。
Key System 不被支持。
初始化数据类型不被 Key System 支持。
会话类型不被 Key System 支持。
初始化数据不被 Key System 支持。
操作不被 Key System 支持。
InvalidStateError 现有 MediaKeys 对象目前无法移除。
会话已经被使用。
会话尚未初始化。
会话已关闭。
QuotaExceededError MediaKeys 对象无法与更多 HTMLMediaElement 一起使用。
针对该 sessionId 已存在一个未关闭的会话。
资源不足,无法创建新会话或许可证请求。

6.8 会话存储与持久化

本节提供了与算法互补的会话存储与持久化概述。

存储与持久化 的要求外,下列要求也适用。

如果对本对象的 session type 运行 Is persistent session type? 算法结果为 false,则用户代理和 CDM MUST NOT 在任何时候持久化与该会话相关的记录或数据。这包括许可证、密钥、许可证销毁记录,以及 Session ID

本节剩余内容适用于 Is persistent session type? 算法返回 true 的会话类型。

CDM SHOULD NOT 在首次调用 update() 前存储会话数据(包括 Session ID)。具体来说,CDM SHOULD NOTgenerateRequest() 算法期间存储会话数据。这可确保应用知晓会话的存在,并知道需要最终移除它。

与会话相关的 所有 数据在会话被清除时 MUST 被清除,例如在处理 update() 时收到 许可证销毁记录确认。参见 持久数据

CDM MUST 确保某个会话的数据只存在于一个未被 closedMediaKeySession 对象中,无论在哪个 Document。换言之,当已经存在一个由 generateRequest() 创建且仍活动的对象,或已通过 load() 加载到另一个对象中时,针对该 sessionId 的 MediaKeySessionload() MUST 失败。只有所有曾经表示该会话的对象都已 closed 时,会话才 MAY 被再次加载。

应用在使用 Is persistent session type? 算法返回 true 的类型创建会话后,SHOULD 之后通过先用 remove() 启动移除过程,然后确保移除过程(可能涉及消息交换)成功完成来删除存储的数据。CDM MAY 也会酌情移除会话,但应用 SHOULD NOT 依赖此行为。

支持持久化存储时参见 10. 安全11. 隐私 以获取更多考虑事项。

7. HTMLMediaElement 扩展

本节规定了当支持加密媒体扩展(Encrypted Media Extensions)时,HTMLMediaElement [HTML] 所增加和修改的内容。

以下内部值被添加到 HTMLMediaElement

HTMLMediaElement 的行为做如下修改:

对于返回 promise 的方法,所有错误都通过拒绝返回的 Promise 异步报告。这包括 [WEBIDL] 类型映射错误。

当 promise 被拒绝时,算法的步骤始终中止。

WebIDL[Exposed=Window] partial interface HTMLMediaElement {
  [SecureContext] readonly        attribute MediaKeys?   mediaKeys;
                                  attribute EventHandler onencrypted;
                                  attribute EventHandler onwaitingforkey;
  [SecureContext] Promise<undefined> setMediaKeys (MediaKeys? mediaKeys);
};

7.1 属性

mediaKeys 类型为 MediaKeys,只读,可为 null

当前 media element 解密加密 媒体数据 时所用的 MediaKeys

onencrypted 类型为 EventHandler

用于 encrypted 事件的事件处理器。所有 HTMLMediaElementMUST 同时支持作为内容属性和 IDL 属性。

onwaitingforkey 类型为 EventHandler

用于 waitingforkey 事件的事件处理器。所有 HTMLMediaElementMUST 同时支持作为内容属性和 IDL 属性。

7.2 方法

setMediaKeys()

提供用于播放时解密媒体数据的 MediaKeys

在播放过程中清除或替换关联的 MediaKeys 对象的支持属于实现质量问题。在许多情况下会导致不良的用户体验或 promise 被拒绝。

当调用此方法时,用户代理 MUST 运行以下步骤:

  1. 如果此对象的 attaching media keys 值为 true,则返回一个以 InvalidStateError 拒绝的 promise。

  2. 如果 mediaKeysmediaKeys 属性是同一个对象,则返回一个解析为 undefined 的 promise。

  3. 将此对象的 attaching media keys 值设为 true。

  4. promise 为一个新的 promise。

  5. 并行运行下列步骤:

    1. 如果以下条件全部成立:

      • mediaKeys 不为 null,

      • mediaKeys 所代表的 CDM 实例已被其他媒体元素使用,

      • 用户代理无法与当前元素一起使用它,

      则将此对象的 attaching media keys 值设为 false,并以 QuotaExceededError 拒绝 promise

    2. 如果 mediaKeys 属性不为 null,则运行以下步骤:

      1. 如果用户代理或 CDM 不支持解除关联,则将 attaching media keys 设为 false,并以 NotSupportedError 拒绝 promise

      2. 如果当前无法解除关联,则将 attaching media keys 设为 false,并以 InvalidStateError 拒绝 promise

        例如,某些实现可能不允许在播放期间解除关联。

      3. 停止使用 CDM 实例(由 mediaKeys 属性表示)来解密 媒体数据,并解除与媒体元素的关联。

      4. 如果前述步骤失败,则将 attaching media keys 设为 false,并以合适的 错误名 拒绝 promise

    3. 如果 mediaKeys 不为 null,则运行以下步骤:

      1. mediaKeys 所代表的 CDM 实例与媒体元素关联,以用于解密 媒体数据

      2. 如果前述步骤失败,运行以下步骤:

        1. mediaKeys 属性设为 null。

        2. attaching media keys 设为 false。

        3. 以新的 DOMException(其 name 为合适的 错误名)拒绝 promise

      3. 排队一个任务,在该媒体元素上运行 Attempt to Resume Playback If Necessary 算法。

    4. mediaKeys 属性设为 mediaKeys

    5. attaching media keys 设为 false。

    6. undefined 解析 promise

  6. 返回 promise

MediaEncryptedEvent 对象用于 encrypted 事件。

WebIDL[Exposed=Window]
interface MediaEncryptedEvent : Event {
    constructor(DOMString type, optional MediaEncryptedEventInit eventInitDict = {});
    readonly        attribute DOMString    initDataType;
    readonly        attribute ArrayBuffer? initData;
};

7.3.1 属性

initDataType 类型为 DOMString,只读
指示 Initialization Data Type, 由 Initialization Data 所包含, 并存储在 initData 属性中。
initData 类型为 ArrayBuffer,只读,可为 null
该事件的 Initialization Data

7.3.2 MediaEncryptedEventInit

WebIDLdictionary MediaEncryptedEventInit : EventInit {
  DOMString    initDataType = "";
  ArrayBuffer? initData = null;
};
7.3.2.1 字典 MediaEncryptedEventInit 成员
initDataType 类型为 DOMString,默认值为 ""
Initialization Data Type
initData 类型为 ArrayBuffer,可为 null,默认值为 null
Initialization Data

7.4 事件摘要

本节为非规范性内容。

事件名称 接口 触发时... 前置条件
encrypted MediaEncryptedEvent 用户代理在 Initialization Data媒体数据 中被发现时触发。 元素的 readyState 等于或大于 HAVE_METADATA

元素可能正在播放或已经播放过。

waitingforkey Event 播放因等待密钥而被阻塞。 readyState 等于或小于 HAVE_CURRENT_DATA。 元素的 playback blocked waiting for key 值刚刚变为 true

7.5 算法

7.5.1 媒体数据可能包含加密块

Media Data May Contain Encrypted Blocks 算法在用户代理要求在播放媒体数据之前指定 MediaKeys 对象时暂停播放。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. 如果 media elementmediaKeys 属性为 null, 且实现要求在解码可能被加密的 媒体数据 之前指定 MediaKeys 对象,则运行下列步骤:

    当应用在调用 setMediaKeys() 来提供 MediaKeys 对象之前就提供媒体数据时,可能会到达这些步骤。选择 CDM 可能会影响使用的管线和/或解码器,因此一些实现可能会延迟播放可能包含加密块的媒体数据,直到通过向 setMediaKeys() 传入一个 MediaKeys 对象来指定 CDM 为止。

    1. media element 上运行 Wait for Key 算法。

    2. 等待恢复播放的信号。

7.5.2 遇到 Initialization Data

Initialization Data Encountered 算法为在 Initialization Data 中遇到的项排入一个 encrypted 事件。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. initDataType 为空字符串。

  3. initData 为 null。

  4. 如果媒体数据是 CORS-same-origin不是 混合内容受限,则运行下列步骤:

    1. initDataType 为表示该 Initialization Data 的 Initialization Data Type 的字符串。

    2. initData 为该 Initialization Data。

    虽然媒体元素可能允许加载“可升级内容”(Upgradeable Content),但用户代理 MUST NOT 将来自此类媒体数据的 Initialization Data 暴露给应用程序。

  5. 排队一个任务 来创建一个名为 encrypted 的事件,该事件不冒泡且不可取消,使用 MediaEncryptedEvent 接口,将其 type 属性设为 encrypted 并将 isTrusted 初始化为 true,然后在 media element 上派发该事件。

    该事件接口 MediaEncryptedEvent 包含:

    readyState 不会被改变,且不会中止任何算法。此事件仅提供信息。

    如果媒体数据不是 CORS-same-origin 或为 混合内容,则 initData 属性将为 null。应用可以从其它来源检索 Initialization Data。

7.5.3 遇到加密块

Encrypted Block Encountered 算法将一块加密的媒体数据排入解密队列,并在可能的情况下尝试解密。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. block 为该块加密的媒体数据。

  3. block 添加到 media elementencrypted block queue 的末端。

  4. 如果 media elementdecryption blocked waiting for key 值为 false,则运行 Attempt to Decrypt 算法。

7.5.4 尝试解密

Attempt to Decrypt 算法尝试解密已排队等待解密的媒体数据。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. 如果 media elementencrypted block queue 为空,则中止这些步骤。

  3. 如果 media elementmediaKeys 属性不为 null,则运行下列步骤:

    1. media keys 为该属性所引用的 MediaKeys 对象。

    2. cdmmedia keyscdm instance 值所表示的 CDM 实例。

    3. 如果 cdm 因任何原因不再可用,运行下列步骤:

      1. 运行 资源获取算法 的 “media data is corrupted” 步骤。

      2. media keys 上以原因为硬件上下文重置或其他适当原因运行 CDM Unavailable 算法。

      3. 中止这些步骤。

    4. 如果由 media keys 创建且尚未被 closed 的至少一个 MediaKeySession 存在,则运行下列步骤:

      该检查确保 cdm 已完成加载,并且是匹配密钥可用的先决条件。

      1. blockmedia elementencrypted block queue 中的第一项。

      2. block key IDblock 的 key ID。

        key ID 通常由容器指定。

      3. 使用 cdm 执行下列步骤:

        1. available keys 为由 media keys 创建的 session 中所有密钥的并集。

        2. block key 为 null。

        3. 如果任何 available keys 对应于 block key ID 且可 用于解密,则令 session 为包含该密钥的 MediaKeySession 对象,并令 block key 为该密钥。

          如果多个会话包含对于 block key ID用于解密 的密钥,选择使用哪个会话和密钥取决于 Key System

        4. 如果在运行前述步骤后任何 available keys 的状态发生变化,则为每个受影响的 session 排队一个任务来运行 Update Key Statuses 算法,提供该会话中所有的 key ID 及其对应的 MediaKeyStatus 值。

        5. 如果 block key 不为 null,则运行下列步骤:

          1. 使用 cdmblock key 解密 block

          2. 按下列列表中首个匹配的情况执行步骤:

            如果解密失败
            1. 运行资源获取算法的 “media data is corrupted” 步骤。

            2. 如果 cdm 不再可用,则在 media keys 上以相应原因运行 CDM Unavailable 算法(硬件上下文重置时使用 hardware-context-reset,否则使用 internal-error)。

            3. 中止这些步骤。

            否则
            1. media elementencrypted block queue 前端移除 block

            2. 正常处理解密后的块。

              换言之,对该块进行解码。

            3. 返回到本算法的开头。

            并非所有解密问题(例如使用了错误的密钥)都会导致解密失败。在此类情况下,此处不会触发错误,但在解码期间可能会触发错误。

          否则,表示在任何会话中都不存在该 block key ID 的密钥,则继续后续流程。

  4. media elementdecryption blocked waiting for key 值设为 true

    当对于 block 不存在可 用于解密 的密钥时,将到达此步骤。

    一旦用户代理已呈现位于该无法解密块之前的块(尽可能多,例如所有完整的视频帧),它将运行 Wait for Key 算法。

    在此处不直接运行该算法是为了允许实现提前对当前播放位置之前的媒体数据进行解密和解码,而不影响可见行为。

对于基于帧的加密,当媒体元素作为资源获取算法的一部分尝试解码一个帧时,可按如下方式实现:

  1. encrypted 为 false。

  2. 检测该帧是否被加密。

    如果该帧被加密
    运行上述步骤。
    否则
    继续。
  3. 解码该帧。

  4. 提供该帧用于渲染。

7.5.5 等待密钥

Wait for Key 算法排入一个 waitingforkey 事件并更新 readyState。该算法仅应在 HTMLMediaElement 对象处于可播放状态并且其 readyState 大于或等于 HAVE_FUTURE_DATA 时调用。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. 如果 media elementplayback blocked waiting for key 值为 true,则中止这些步骤。

  3. media elementplayback blocked waiting for key 值设为 true

    由于上述步骤,媒体元素如果尚未成为受阻塞的媒体元素,将变为一个受阻塞的媒体元素。在这种情况下,媒体元素将停止播放。

  4. 按下列列表中首个匹配条件的规定执行步骤:

    如果可获得用于当前播放位置的即时数据

    media elementreadyState 设为 HAVE_CURRENT_DATA

    否则

    media elementreadyState 设为 HAVE_METADATA

    换言之,如果当前播放位置的视帧和音频数据因为未加密和/或已成功解密而已被解码,则将 readyState 设为 HAVE_CURRENT_DATA。否则(包括此前情况但数据不再可用的情形),将 readyState 设为 HAVE_METADATA

  5. 排队一个任务 来触发名为 waitingforkey 的事件于 media element

  6. 挂起播放。

7.5.6 必要时尝试恢复播放

Attempt to Resume Playback If Necessary 算法在媒体元素因等待密钥而被阻塞且所需密钥当前 可用于解密 时恢复播放。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行下列步骤:

  1. media element 为指定的 HTMLMediaElement 对象。

  2. 如果 media elementplayback blocked waiting for keyfalse,则中止这些步骤。

  3. media element 上运行 Attempt to Decrypt 算法。

  4. 如果用户代理能够沿播放方向推进当前播放位置:

    1. media elementdecryption blocked waiting for key 值设为 false

    2. media elementplayback blocked waiting for key 值设为 false

      由于上述步骤,媒体元素可能不再是一个受阻塞的媒体元素,从而播放可能恢复。

    3. media elementreadyState 值设为 HAVE_CURRENT_DATAHAVE_FUTURE_DATAHAVE_ENOUGH_DATA,视情况而定。

      超过 HAVE_CURRENT_DATA 的状态以及 canplaythrough 事件通常不会(或不太可能)考虑当前密钥之外的密钥可用性。

      readyState 的变化也可能导致如规范中所述触发相应的 HTMLMediaElement 事件。

7.6 媒体元素限制

本节为非规范性内容。

CDM 处理的媒体数据在常规方式下(例如通过 CanvasRenderingContext2DdrawImage() 方法或通过 AudioContextMediaElementAudioSourceNode)可能无法通过 Web 平台 API 获取。该规范并不定义媒体数据不可用的具体条件,但如果媒体数据对这些 API 不可用,则这些 API MAY 表现得像根本不存在媒体数据一样。

在媒体呈现并非由用户代理执行的情况下(例如使用硬件媒体管线),完整的 HTML 呈现能力(例如 CSS 变换)MAY 无法使用。一个可能的限制是视频媒体MAY 被限制为仅在与窗口边缘平行且方向正常的矩形区域中显示。

8. 实现要求

本节定义实现要求——针对用户代理和 Key Systems(包括 CDM 与服务器)——这些要求可能未在算法中明确涉及。此处及整份规范中的要求适用于所有实现,无论 CDM 是独立于用户代理还是作为其一部分。

8.1 CDM 约束

用户代理实现者 MUST 确保 CDMs 不得访问任何对使用本规范功能播放受保护媒体并非合理必要的信息、存储或系统能力。具体而言,CDM SHALL NOT

用户代理实现者可使用各种技术来满足上述要求。例如,自行实现 CDM 的用户代理实现者可以将上述要求作为该组件的设计要求。使用第三方 CDM 的用户代理实现者可以确保其在受限环境(例如“沙箱”)中执行,从而无法访问被禁止的信息和组件。

8.2 消息与通信

CDM 之间的所有消息与通信(例如 CDM 与许可证服务器之间)MUST 通过用户代理传递。CDM MUST NOT 发起直接的带外网络请求。除 Direct Individualization 所述外,所有消息与通信 MUST 通过应用程序并使用本规范定义的 API 传递。具体而言,所有包含应用、origin 或内容特定信息,或发送到由应用指定或基于其 origin 的 URL 的通信 MUST 通过这些 API。包括所有许可证交换消息在内。

8.3 持久数据

持久化数据包括由 CDM 或代表 CDM 由用户代理存储的、在 MediaKeys 对象销毁后仍然存在的所有数据。具体包括任何标识符(包括 Distinctive Identifier(s))、许可证、密钥、key ID 或 许可证销毁记录,这些由 CDM 或代表 CDM 的用户代理存储。

8.3.1 使用特定于来源和浏览配置文件的 Key System 存储

可能影响应用或许可证服务器可见消息或行为的持久化数据 MUSTorigin-特定且 浏览配置文件-特定的方式存储,且MUST NOT 泄漏到私人浏览会话或从私人浏览会话泄漏。具体但不限于,会话数据、许可证、密钥和按 origin 的标识符 MUST 按 origin 和按浏览配置文件存储。

参见 Session Storage and Persistence

8.3.2 允许清除持久数据

使用持久化数据的实现 MUST 允许用户清除这些数据,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索。

用户代理 SHOULD

  • 将持久化数据如同其他站点数据(例如 cookies)处理。具体包括:

    • 允许用户与清除 cookies 一并清除持久化数据。

    • 允许用户作为清除浏览历史功能的一部分清除持久化数据。

    • 将持久化数据包含在“删除所有数据”的功能中。

    • 在与其它站点数据相同的 UI 位置展示持久化数据。

  • 允许用户按 origin 和按 浏览配置文件 清除持久化数据,尤其是在“忘记此站点”功能中一并忘记与该站点关联的 cookies、数据库等。

  • 确保清除持久化数据的操作具有足够的原子性,以防止通过未同时清除的其他本地存储数据重新关联新旧标识符(“cookie resurrection”)。参见 incomplete clearing of data

  • 在界面中以有助于用户理解 incomplete clearing of data 可能性的方式呈现这些接口,并使他们能够同时删除与所有持久化功能关联的数据(包括 cookies 和 Web 存储)。

  • 以便于用户理解 incomplete clearing of data 可能性的方式呈现用于禁用和重新启用 Key System 的界面,并使他们能够同时删除所有此类持久化存储中的数据。

  • 允许用户针对特定 origin 或所有 origin 专门删除持久化数据。

8.3.3 对持久数据进行加密或混淆

用户代理 SHOULD 将持久化数据视为潜在敏感数据;这些信息的泄露可能严重危及用户隐私。因此,用户代理 SHOULD 确保持久化数据被安全存储,并在删除数据时及时从底层存储中清除。

8.4 暴露给应用的值

暴露给应用或可被应用推断出的值(例如通过其被 CDM 使用)可能被用来识别客户端或用户,无论这些值是否被设计为标识符。本节定义了避免或至少缓解此类问题的要求。关于 Identifiers 有额外要求。

8.4.1 使用每来源每配置文件的值

所有暴露给或可被应用推断出的 Distinctive Values MUST 在每个 origin 和每个 浏览配置文件 范围内唯一。也就是说,使用本规范定义的 API 为一个 origin 生成的值 MUST 与为任何其他 origin 生成的值不同,且在一个浏览配置文件中使用的值 MUST 与其它配置文件的值不同。此类值 MUST NOT 泄漏到私人浏览会话或从私人浏览会话泄漏。

跨 origin 与配置文件的值 MUST应用不可关联的,即应用不应能将来自多个 origin 或配置文件的值关联起来以判断它们是否来自同一客户端或用户。具体而言,那些从与 origin 无关或配置文件无关的值派生 per-origin 值的实现 MUST 采用确保上述不可关联性的方法,例如使用具备非可逆性质的派生函数。

8.4.2 允许清除这些值

根据 Allow Persistent Data to Be Cleared 的要求,所有持久化并暴露给应用的值 MUST 可被清除,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索、观察或推断。

一旦被清除,当再次需要值时 MUST 生成新的、对应用不可关联的值。

8.5 标识符

实现中使用标识符(尤其是 Distinctive Identifier(s) 或 Distinctive Permanent Identifier(s))会带来隐私问题。本节定义了避免或至少缓解这些问题的要求。Values Exposed to the Application 中的要求同样适用于暴露给应用的标识符。

概要如下:

8.5.1 限制或避免使用明显标识符和永久标识符

8.5.2 对标识符加密

Distinctive IdentifiersDistinctive Permanent Identifiers MUST 在暴露到客户端外部时在消息交换层被加密。所有其他标识符 SHOULD 在暴露到客户端外部时在消息交换层被加密。所使用的加密 MUST 确保任何两次同一标识符密文实例仅能被持有相应解密密钥的实体 关联

标识符可能通过以下方式暴露:

  • 通过 message 事件暴露给应用。

  • 在来自服务器的消息中,例如作为传递给 update() 的消息的一部分。

  • 作为 individualization 的一部分。

CDM MUST 验证用于加密的密钥属于其 Key System 的有效服务器。对于暴露给应用的标识符,这可通过 服务器证书 实现。

服务器 MUST NOTDistinctive Identifier 暴露给除发送该标识符的 CDM 之外的任何实体。

具体来说,不应将其提供给应用或在发送给 CDM 的消息中以未加密形式包含。可以通过对标识符进行加密或将其包含在仅能被特定 CDM 解密的加密信封中来实现。

这意味着:

  • 使用设备特定或用户特定密钥进行的每个签名 MUST 即使对相同明文也应不同。

  • 与设备或用户特定密钥相关的标识符、密钥或证书 MUST 为许可证或 individualization 服务器加密。

  • 来自许可证服务器发送到 CDM 的消息 MUST NOT 在加密信封外部暴露接收者唯一的标识符(例如预期解密密钥的 ID)。

8.5.3 使用每来源每配置文件标识符

Distinctive Permanent Identifiers 外,所有标识符 MUST 在每个 origin 与每个 浏览配置文件 范围内唯一。参见 8.4.1 Use Per-Origin Per-Profile Values

这包括但不限于 Distinctive Identifiers

Distinctive Permanent Identifiers MUST NOT 暴露给应用或 origin。

8.5.4 使用不可关联的标识符

所有暴露给应用的标识符,包括 Distinctive Identifiers,即使为加密形式,MUST 对应用保持 不可关联,跨 origins浏览配置文件 和清除标识符的情形均应如此。

对于所有此类标识符,一个或多个应用(包括相关的许可证或其它服务器)MUST NOT 能够实现对此类值的关联或匹配。

8.5.5 允许清除标识符

根据 Allow Persistent Data to Be Cleared 的要求,除 Distinctive Permanent Identifiers 外,所有潜在标识符或 Distinctive Values MUST 可被清除,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索、观察或推断。

使用 Distinctive Identifiers 的实现 MUST 允许用户清除这些 Distinctive Identifier(s)。使用 Distinctive Permanent Identifier(s) 的实现 MUST 允许用户清除与这些 Distinctive Permanent Identifier(s) 相关联的值。

一旦清除,当再次需要诸如 Distinctive Identifiers 等值时,必须生成新的不可被应用关联的值。

8.6 个性化

标识符,尤其是 Distinctive Identifiers,有时通过称为 individualization 或 provisioning 的流程生成或获取。由此产生的标识符 MUST 满足 non-associable by applications 要求,并且对它们的 使用 MUST 仅在来自单一配置文件的单一 origin的范围内暴露。该过程 MAY 被重复执行,例如在标识符被 清除 之后。

该过程 MUST用户代理直接执行通过应用执行。两种 individualization 类型的机制、流程和限制不同,下面的各节对其作了说明。采用哪种方法取决于 CDM 的实现以及对本规范要求的应用,尤其是下文所述的要求。

Note

distinctiveIdentifier 控制是否可以对 Distinctive Identifiers 以及 Distinctive Permanent Identifiers 进行包括 individualization 在内的使用。具体地,只有当用于创建 MediaKeys 对象的 distinctiveIdentifier 成员的值为 "required" 时,才允许使用这些标识符。

8.6.1 直接个性化

Direct Individualization 在 CDM 与一个与 origin 和应用无关的服务器之间进行。尽管该服务器与 origin 无关,individualization 的结果仍允许 CDM 根据本规范的其他要求提供 origin 特定的标识符。该过程 MUST 由用户代理执行,且 MUST NOT 使用本规范中定义的 API。

Note

例如,该过程可能通过与用户代理或 CDM 厂商托管的预定服务器通信来初始化客户端设备和/或为 单个浏览配置文件获取一个 可清除的 per-origin 标识符,可能使用客户端设备的 Distinctive Permanent Identifier(s) 或其他 Permanent Identifier(s)

对于此类 individualization,所有消息交换:

  • MUST 由用户代理处理并通过用户代理的网络栈执行。

  • MUST NOTCDM 直接执行。

  • MUST NOT 通过本规范定义的 API 传递给或经由应用程序。

  • MUST 发送到独立于任何 origin 和应用的 URL。

  • MUST 对所有 Distinctive IdentifiersDistinctive Permanent Identifiers 进行加密。

  • MUST 使用 TLS。

实现 MUST NOT 将 origin(s)、origin 或应用特定信息,或可与 origin(s) associable 的值暴露给集中式服务器(即便是以加密形式),因为这可能会创建用户或设备访问过的所有 origin 的集中记录。

8.6.2 应用辅助的个性化

App-Assisted Individualization 在 CDM 与应用之间进行(可包括应用选择的服务器),并产生一个 per-origin 标识符。该过程 MUST 通过本规范定义的 API 执行,且 MUST NOT 涉及其它通信方式。与所有通过这些 API 的用法一样,该过程 MAY 使用一个或多个 Distinctive Identifier(s),但 MUST NOT 使用 Distinctive Permanent Identifier(s) 或 非 origin 特定的值,即便是以加密形式。若该过程 使用了一个或多个 Distinctive Identifier(s),则生成的标识符也将按定义成为 Distinctive Identifier

对于此类 individualization,所有消息交换:

当在流程中使用可 associable 的值,包括 Distinctive Identifier(s) 时,实施者 MUST NOT 将 origin(s)、origin 或应用特定信息,或可与 origin(s) associable 的值暴露给集中服务器(即便是以加密形式),因为这同样可能创建用户或设备访问过的所有 origin 的集中记录。

经过适当的防护,此类 individualization 可以比 Direct Individualization 提供更好的隐私,尽管仍不及不使用 Distinctive Identifier(s) 的模型。为保持此设计的隐私优势并避免引入其他隐私问题,此类实现及支持它们的应用 SHOULD 避免将 individualization 消息延后或转发到由应用作者不控制的集中服务器或其他服务器。

8.7 支持多个密钥

实现 MUST 在每个 MediaKeySession 对象中支持多个密钥。

Note

多个密钥如何被支持属于实现细节,但这一点 MUST 对应用和本规范定义的 API 透明。

实现 MUST 支持在播放期间在密钥之间无缝切换。这包括同一 MediaKeySession 中的密钥以及位于不同 MediaKeySession 对象中的密钥。

8.8 初始化数据类型支持

8.8.1 生成的许可证与内容类型无关

实现 SHOULD 允许用其支持的任何 Initialization Data Type 生成的许可证被用于任何内容类型。

Note

否则,requestMediaKeySystemAccess() 算法可能例如会因为某个 MediaKeySystemConfiguration 中的某个 initDataTypes 与某个 videoCapabilities 不兼容而拒绝该配置。

8.8.2 支持从媒体数据中提取

对于任何可能出现在受支持容器中的受支持的 Initialization Data Type,用户代理 MUST 支持从每种此类受支持的容器中 提取 该类型的 Initialization Data

Note

换言之,表明支持某个 Initialization Data Type 意味着既支持 CDM 生成许可证请求,也(对于容器特定类型)支持用户代理从容器中提取该类型的数据。这并不意味着实现必须能够从任意受支持的内容类型中解析任意受支持的 Initialization Data

8.9 支持的媒体

本节定义了本规范实现所支持的内容(media resource)的属性。

8.9.1 未加密容器

媒体容器 MUST NOT 被加密。本规范依赖于用户代理在无需解密任何媒体数据的情况下解析媒体容器的能力。这包括 Encrypted Block EncounteredInitialization Data Encountered 算法,以及支持标准的 HTMLMediaElement 功能,例如 seeking

8.9.2 可互操作加密

Media resources(包括所有轨道)MUST 按照允许在提供密钥时以完全规范且兼容的方式解密内容的容器特定 “common encryption” 规范进行加密与打包。

Note

Encrypted Media Extensions Stream Format Registry [EME-STREAM-REGISTRY] 提供了此类流格式的参考。

8.9.3 未加密的带内支持内容

带内支持内容(例如字幕、描述音轨和转录)SHOULD NOT 被加密。

Note

对这些轨道进行解密——尤其是要将其提供回用户代理——通常不被实现支持。因此,对这些轨道加密将阻止它们在用户代理实现中广泛用于无障碍功能。

为确保可访问性信息以可用的形式提供,对于选择支持加密的带内支持内容的实现:a) CDM MUST 将解密后的数据提供给用户代理,且 b) 用户代理 MUST 以与等效未加密支持内容相同的方式对其进行处理,例如将其作为 timed text tracks 暴露 [HTML]。

9. 通用密钥系统

所有用户代理 MUST 支持本节中描述的通用 密钥系统

这确保了在所有用户代理中都有一个通用的基线功能级别,包括那些完全开源的用户代理。因此,仅需要基本解密的内容提供商可以构建简单的应用程序,在所有平台上工作,而无需与任何内容保护提供商合作。

9.1 Clear Key(明文密钥)

"org.w3.clearkey" 密钥系统 使用明文(未加密)的密钥来解密源内容。不需要额外的客户端侧内容保护。该 密钥系统 在下文描述。

9.1.1 功能

下文描述了 Clear Key 如何支持与 密钥系统 相关的特定功能:

9.1.2 行为

下文描述了 Clear Key 如何实现与 密钥系统 相关的特定行为:

  • generateRequest() 算法中:

    • 生成的 message 是一个 JSON 对象,按 许可请求格式 所述以 UTF-8 编码。

    • 请求通过从 sanitized init data 中提取密钥 ID 来生成。

    • "type" 成员的值是 sessionType 参数的值。

  • sessionId 属性是可由 32 位整数表示的数值。

  • expiration 属性始终为 NaN

  • update() 算法中:

    • response 参数要么是一个按 许可格式 所述的 JWK 集,要么是一个按 许可释放确认格式 所述以 UTF-8 编码的 JSON 对象。

    • 在第一种情况下,如果 sanitized response 不是具有至少一个针对音频/视频类型长度有效的有效 JWK 密钥的有效 JWK 集,则被视为无效。在第二种情况下,如果 sanitized response 不是有效的 JSON 对象,则被视为无效。

  • 对于类型为 "persistent-license" 的会话,在 remove() 算法中,反映 record of license destructionmessage 是一个按 许可释放格式 所述以 UTF-8 编码的 JSON 对象。

  • keyStatuses 属性最初包含通过 update() 提供的所有密钥 ID,状态为 "usable"。当执行 remove() 算法时,keyStatuses 属性将被设置为空列表。

  • 初始化数据:实现 MAY 支持注册的初始化数据类型的任意组合 [EME-INITDATA-REGISTRY]。实现 SHOULD 支持 "keyids" 类型 [EME-INITDATA-KEYIDS] 及其他适合用户代理支持的内容类型的类型。

9.1.3 许可请求格式

本节描述通过 message 事件的 message 属性提供给应用的许可请求的格式。

该格式是一个 JSON 对象,包含以下成员:

"kids"
一个 密钥 ID 的数组。数组的每个元素都是包含该密钥 ID 值的字节序列的 base64url 编码。
"type"
请求的 MediaKeySessionType

当包含在 message 属性的 ArrayBuffer 中,作为 MediaKeyMessageEvent 对象的一部分时,JSON 字符串按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextDecoder 接口将 ArrayBuffer 的内容解码为 JSON 字符串。

9.1.3.1 示例

本节为非规范性内容。

下例是针对两个密钥 ID 的临时许可的许可请求。(换行仅为可读性)

{
  "kids": [
    "LwVHf8JLtPrv2GUXFW2v_A",
    "0DdtU9od-Bh5L3xbv0Xf_A"
  ],
  "type": "temporary"
}

9.1.4 许可格式

本节描述作为 update() 方法的 response 参数提供的许可的格式。

该格式是一个包含对称密钥表示的 JSON Web Key (JWK) 集,用于解密,定义见 JSON Web Key (JWK) 规范 [RFC7517]。

对于集合中的每个 JWK,其参数值如下:

"kty" (key type)
"oct"(八位元组序列)。
"k" (key value)
包含对称 key 值的字节序列的 base64url 编码。
"kid" (key ID)
包含 密钥 ID 值的字节序列的 base64url 编码。

JSON 对象 MAY 有一个可选的 "type" 成员值,该值 MUSTMediaKeySessionType 值之一。 如果未指定,则使用默认值 "temporary"。update() 算法将此值与 sessionType 进行比较。

当作为 ArrayBuffer response 参数传递给 update() 方法时,JSON 字符串 MUST 按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextEncoder 接口对 JSON 字符串进行编码。

9.1.4.1 示例

本节为非规范性内容。

下例是一个包含单个对称密钥的 JWK 集。(换行仅为可读性)

{
  "keys": [{
    "kty": "oct",
    "k": "tQ0bJVWb6b0KPL6KtZIy_A",
    "kid": "LwVHf8JLtPrv2GUXFW2v_A"
  }],
  "type": "temporary"
}

9.1.5 许可释放格式

本节描述通过 message 事件的 message 属性提供的许可释放消息的格式。

该格式是一个 JSON 对象。对于类型为 "persistent-license" 的会话,该对象应包含以下成员:

"kids"
一个 密钥 ID 的数组。数组的每个元素都是包含该密钥 ID 值的字节序列的 base64url 编码。

当包含在 message 属性的 ArrayBuffer 中,作为 MediaKeyMessageEvent 对象的一部分时,JSON 字符串按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextDecoder 接口将 ArrayBuffer 的内容解码为 JSON 字符串。

9.1.5.1 反映许可销毁记录的示例消息

本节为非规范性内容。

下例是包含两个密钥的 "persistent-license" 会话的许可释放示例。(换行仅为可读性)

{
  "kids": [ "LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A" ]
}

9.1.6 许可释放确认格式

本节描述作为 update() 方法的 response 参数提供的许可释放确认的格式。

该格式是一个 JSON 对象,包含以下成员:

"kids"
一个 密钥 ID 的数组。数组的每个元素都是包含该密钥 ID 值的字节序列的 base64url 编码。

当作为 ArrayBuffer response 参数传递给 update() 方法时,JSON 字符串 MUST 按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextEncoder 接口对 JSON 字符串进行编码。

9.1.6.1 示例

本节为非规范性内容。

下例是针对两个密钥 ID 的临时许可的许可请求。(换行仅为可读性)

{
  "kids": [
    "LwVHf8JLtPrv2GUXFW2v_A",
    "0DdtU9od-Bh5L3xbv0Xf_A"
  ]
}

9.1.7 使用 base64url

本节为非规范性内容。

有关 base64url 及其使用的更多信息,请参阅 RFC7515 中的 "Base64url Encoding" 术语定义和 "Notes on implementing base64url encoding without padding"。具体来说,不使用 '=' 填充,并且字符 '-' 和 '_' MUST 分别替代 '+' 和 '/'。

10. 安全

10.1 输入数据攻击和脆弱性

User Agent 和 Key System 实现必须media dataInitialization Data、传入 update() 的数据、许可证、 密钥数据,以及应用提供的所有其他数据视为不受信任的内容和潜在攻击向量。它们应当使用适当的防护措施来减轻任何相关的威胁,并在解析、解密等处理此类数据时谨慎。User Agents 应该 在将数据传递给 CDM 之前验证数据。

如果 CDM 并未在与例如 DOM 相同的(沙箱化的)上下文中运行,则这种验证尤为重要。

实现不得向应用返回会影响程序控制流的活动内容或被动内容。

例如,暴露可能来自媒体数据的信息(例如传入 Initialization DatagenerateRequest() 的情况)中的 URL 或其他信息并不安全。应用必须确定要使用的 URL。messageType 属性可被应用用于在适用时从一组 URL 中进行选择。

10.2 CDM 攻击和脆弱性

User Agents 负有为用户提供安全浏览网络方式的责任。该责任适用于 User Agents 使用的任何功能,包括第三方功能。User agent 实现者必须Key System 实现者处获取足够的信息,以使其能够正确评估与集成该 Key System 的安全影响。User agent 实现者 必须确保 CDM 实现提供和/或支持足够的控制,以便 user agent 能够为用户提供安全保障。User agent 实现者 必须 确保 CDM 实现能够并且会在发现安全脆弱性时快速且积极地进行更新。

利用未完全沙箱化和/或使用平台特性的 CDM 实现,攻击者可能访问操作系统或平台特性、提升权限(例如以 system 或 root 身份运行),以及/或访问驱动、内核、固件、硬件等。这类特性、软件和硬件可能没有针对敌对软件或基于 Web 的攻击进行健壮性编写,并且可能不会像 user agent 那样频繁获得安全修复。CDM 实现中缺乏、稀少或缓慢的安全修复更新会增加风险。此类 CDM 实现及暴露它们的 UA 在所有安全领域都必须特别谨慎,包括解析 所有数据

当使用作为客户端操作系统、平台和/或硬件的一部分或由其提供的 CDM 或底层机制时,user agents 应格外谨慎。

如果 user agent 选择支持无法被充分沙箱化或无法以其他方式加固的 Key System 实现,则 user agent 应该在加载或调用该实现之前确保用户已充分获知并/或给予明确同意

向未验证的来源授予权限等同于在存在网络攻击者的情况下向任何来源授予该权限。参见 持久同意的滥用

10.3 网络攻击

10.3.1 潜在攻击

本节为非规范性。

潜在的网络攻击及其影响包括:

  • DNS 欺骗攻击:无法保证声称属于某一域名(origin)的主机确实来自该域名。

  • 被动网络攻击:无法保证客户端与服务器之间传输的数据(包括 Distinctive IdentifiersDistinctive Permanent Identifiers)不会被其他实体查看。参见 用户追踪

  • 主动网络攻击:无法保证页面不会被注入额外脚本或 iframe(无论页面是否使用本规范中定义的 API)。其后果包括:

  • 持久同意的滥用:无法保证请求使用本规范中定义的 API 的主机就是用户先前提供同意的主机。其后果是,在存在网络攻击者的情况下,向未验证来源授予权限等同于向任何来源授予权限。

10.3.2 缓解措施

以下技术可以缓解这些风险:

使用 TLS

使用 TLS 的应用可以确保只有用户、代表用户工作的软件,以及使用 TLS 并具有识别为同一域的证书的其他页面,能够与该应用交互。此外,结合安全源(origin)的基于 origin 的权限,可确保授予给应用的权限不能被网络攻击者滥用。

本规范中定义的 API 仅在安全上下文中暴露。另见 安全源和传输

阻止混合内容

User agents 必须正确处理混合内容 [MIXED-CONTENT],包括阻止“可阻止内容”[MIXED-CONTENT],以避免暴露于不安全内容。这种暴露可能会破坏其他缓解措施,例如 TLS 的使用。

User agents 可以选择阻止所有混合内容,包括“可选择阻止内容”[MIXED-CONTENT],以通过防止将不受信任的媒体数据传递给 CDM 来进一步提高安全性(参见 CDM 攻击和脆弱性)。

User agents 应该在允许可能带来比其他 user agent 功能(例如 DOM 内容)更大安全风险的 Key System 被某个 origin 访问之前,确保用户已充分获知并/或给予明确同意。

这些机制必须origin 进行,以避免合法用途导致随后被恶意访问滥用,并且必须browsing profile 进行。

将本规范中定义的 API 限制在安全上下文中,可确保网络攻击者无法利用向未验证来源授予的权限。参见 持久同意的滥用

10.4 iframe 攻击

10.4.1 潜在攻击

本节为非规范性。

恶意页面可能将合法应用托管在 iframe 中,以尝试隐藏攻击或欺骗用户关于来源的信息,例如使其看起来来自合法内容提供者。这对那些会告知用户或要求同意的实现尤为相关,例如出于安全和/或隐私原因。除了网络攻击之外,攻击者还可能通过在 iframe 中托管合法使用来尝试利用本规范中定义的 API。通过让合法应用执行这些操作,攻击者可以重用已授予的权限(或白名单),并/或看起来像是合法的请求或使用。

10.4.2 缓解措施

对于那些告知用户或要求同意的 user agents,包括出于安全和/或隐私原因的情况,应该 将 UI 和同意的持久化基于顶层文档的 origin 与使用本规范中定义 API 的 origin 的组合。 这可确保用户被告知发起请求的主文档,并且为一种(合法)组合持久化权限不会无意中允许恶意使用不被发现。

作者应当防止其他实体在 iframe 中托管其应用。必须出于合法应用设计原因需要支持被托管的应用,不应允许托管文档向 CDM 提供任何数据 —— 无论是通过本规范中定义的 API 还是作为媒体数据 —— 并且不应允许托管的框架调用本规范中定义的 API。

10.5 跨目录攻击

本节为非规范性。

例如,不同作者在同一主机名上托管内容(如用户在 geocities.com 上托管内容)时共享同一 origin。User agents 不提供按路径名限制 API 访问的功能。

在共享主机上使用本规范中定义的 API 会破坏 user agents 实现的基于 origin 的安全与隐私缓解措施。例如,按 origin 的 Distinctive Identifiers 会被同一主机名下的所有作者共享,且持久化的数据可能被主机上的任何作者访问和操纵。如果例如修改或删除此类数据可能会抹去用户对特定内容的访问权,则后者尤为重要。

即使 user agents 提供了路径限制功能,通常的 DOM 脚本安全模型也会使绕过该保护并从任何路径访问数据变得非常容易。

因此,建议共享主机上的作者避免使用本规范中定义的 API,因为这样做会破坏 user agents 中基于 origin 的安全与隐私缓解措施。

11. 隐私

在用户设备上存在或使用 Key System(s) 会引发许多隐私问题,主要分为两类: (a) 可能由 EME 接口本身或在 Key System 消息中披露的与用户相关的信息;以及 (b) 可能在用户设备上持久存储的与用户相关的信息。

User Agents 必须 承担为用户提供对其隐私的充分控制的责任。由于 User Agents 可能与第三方的 CDM 实现集成,CDM 实现者 必须 向 user agent 实现者提供足够的信息和控制,以便他们能够实施适当的技术,确保用户能控制其隐私,包括但不限于下面描述的技术。

11.1 EME 与 Key Systems 披露的信息

关于 EME 和 Key Systems 披露的信息的关切分为两类: (a) 关于非特指的信息的担忧,尽管这些信息并不具体但仍可能促成对用户代理或设备的指纹识别;以及 (b) 可能被直接用于 用户追踪 的与用户相关的信息。

11.2 指纹识别(Fingerprinting)

恶意应用可能通过检测或枚举所支持的 Key Systems 列表及相关信息来为用户或用户代理创建指纹。如果没有提供适当的 origin 保护,这可能包括检测已访问过的站点以及为这些站点存储的信息。尤其是,Key Systems 必须 不在 origins 之间共享密钥或其他数据。

11.3 信息泄露

11.3.1 关切

本节为非规范性。

CDMs,尤其是那些在 user agent 之外实现的,可能没有 Web 平台的相同基本隔离措施。采取措施以避免信息泄露尤为重要,特别是跨 origin 的泄露。这包括内存中的数据和持久存储的数据。如果不这样做,可能会导致与私密浏览会话之间的信息泄露、跨 browsing profiles(包括跨操作系统用户帐户)之间的泄露,甚至在不同浏览器或应用之间发生泄露。

11.3.2 缓解措施

为避免此类问题,user agent 和 CDM 实现 必须 确保:

  • CDMs 具有将一个 CDM 实例与一个 MediaKeys 对象一一关联的概念。

  • 密钥、许可证、其他会话数据以及会话的存在状况被限制在与创建该会话的 MediaKeys 对象关联的 CDM 实例内。

  • 会话数据不得在 MediaKeys 对象或 CDM 实例之间共享。

  • 会话数据不得与未与创建该会话的 MediaKeys 对象关联的媒体元素共享。此要求意味着,例如,某个会话的密钥 必须 不得用于解密由 mediaKeys 属性不是该 MediaKeys 对象加载的内容。

  • MediaKeys 对象及其底层实现不得在 origin 之外暴露信息。

  • 如果适用,持久化的会话数据应按 origin 存储。

  • 仅可加载由请求的 origin 存储的数据。

  • 不得从 CDM 中提取、推导或推断出本规范未明确描述的,或在未经用户许可情况下通过其他 Web 平台 API 无法获得的信息。此要求适用于任何暴露给客户端设备外部或应用的信息,包括例如在 CDM 消息中所携带的信息。

    该要求涵盖的信息类型包括但不限于:

    • 位置,包括地理位置

    • 凭据或标识符(Distinctive Identifiers 除外)

    • 操作系统帐户名及其他可能的 PII

    • 本地目录路径,可能包含类似信息

    • 本地网络详情(例如设备的本地 IP 地址)

    • 本地设备,包括但不限于蓝牙、USB 和用户媒体

    • 与本规范定义的 API 无关或未由其存储的用户状态

11.4 用户追踪

11.4.1 关切

本节为非规范性。

第三方主机(或任何能够将内容分发到多个站点的实体,例如广告商)可能使用 Distinctive Identifier 或持久化数据(包括许可证、密钥、key ID,或由或代表 CDM 存储的记录)来在多个会话间跟踪用户(包括跨 originsbrowsing profiles),从而构建用户活动或兴趣的档案。这样的追踪会破坏 Web 平台提供的隐私保护,例如可能使高度定向广告成为可能。结合能够识别用户真实身份的网站(例如需要身份验证凭证的内容提供者或电子商务站点),这可能使压迫性组织比在纯匿名 Web 环境中更精确地定位个人。

通过本规范的实现可获得的用户或客户端特定信息包括:

本规范带来特别关注的原因在于,此类信息通常存储在 user agent 之外(且不与 browsing profile 存储)——通常在 CDM 中。

由于许可证的内容和 records of license destructionKey System-特定的,并且 key ID 可能包含任意值,这些数据项可能被滥用来存储可识别用户的信息。

Key Systems 可能为设备或设备用户访问或创建持久或半持久的标识符。在某些情况下,这些标识符可能以安全方式绑定到特定设备。如果这些标识符出现在 Key System 消息中,则设备和/或用户可能会被跟踪。如果不采取下述缓解措施,这可能包括随时间跟踪用户/设备以及关联同一设备的多个用户。

需要注意的是,此类标识符,尤其是那些不可清除、非 origin 特定或被视为永久的(permanent),其跟踪影响超过现有技术(例如 cookies [COOKIES] 或嵌入 URL 的会话标识符)。

若未缓解,此类跟踪可能有三种形式,取决于 Key System 的设计:

  • 在所有情况下,预期这些标识符可被完全支持该 Key System 的站点和/或服务器获得(因此能够解释 Key System 消息),使得这些站点能够进行追踪。

  • 如果 Key Systems 所暴露的标识符并非 origin 特定,则两个完全支持该 Key System 的站点和/或服务器可能串通以跟踪用户。

  • 如果 Key System 消息包含从用户标识符派生出的信息并以一致的方式呈现,例如对特定内容项的初始 Key System 消息的一部分随时间不变且依赖于用户标识符,则任何应用都可利用该信息随时间跟踪设备或用户。

此外,如果某个 Key System 允许跨 origins 存储并重用密钥或其他数据,那么两个 origin 可能通过记录它们访问某个公共密钥的能力而串通追踪唯一用户。

最后,如果任何用于用户控制 Key Systems 的用户界面将数据与 HTTP 会话 cookie [COOKIES] 或持久存储中的数据分开显示,则用户很可能在修改站点授权或删除数据时只在其中一处进行操作而不是同时在所有位置进行。这将使站点能够将各种功能互为备份,从而破坏用户保护其隐私的尝试。

除了站点和其他第三方可能跟踪用户的潜在风险外,user agent 实现者、CDM 供应商或设备供应商也可能构建用户活动或兴趣的档案,例如用户访问的使用本规范定义 API 的站点。这种跟踪会破坏 Web 平台其余部分所提供的隐私保护,尤其是与 origin 隔离相关的保护。

例如 Distinctive Identifiers 等标识符,可能从由 CDM 供应商运营或提供的服务器获取,例如通过 individualization 过程。该过程可能包括向服务器提供客户端标识符(包括 Distinctive Permanent Identifier(s))。为生成 per-origin 标识符,可能还会提供表示 origin 的值。

在这种实现中,CDM 供应商可能能够跟踪用户的活动,例如访问过的 origin 数量或需要新标识符的次数。如果在标识符请求中提供了 origin 或与 origin associable 的值,则 CDM 供应商可以跟踪用户访问的网站或设备用户。

下节描述了可以在不征得用户同意的情况下缓解跟踪风险的技术。

11.4.2 缓解措施

不要 use Distinctive Identifiers or Distinctive Permanent Identifiers

Key System 的实现 应该 尽可能避免 using Distinctive Identifiers and Distinctive Permanent Identifiers,仅在它们能显著增强实现的健壮性时才使用。参见 Limit or Avoid use of Distinctive Identifiers and Permanent Identifiers

不要向应用暴露 Distinctive Permanent Identifiers

实现 不得 向应用或 origin 暴露 Distinctive Permanent Identifiers

Distinctive Identifiers 进行加密

Distinctive Identifiers 出现在 Key System 消息中时,必须 将其与时间戳或随机数一起加密,从而使得 Key System 消息始终不同。这样可以防止使用 Key System 消息进行跟踪,除非由完全支持该 Key System 的服务器协作。参见 Encrypt Identifiers

Distinctive Identifiers 和 Key System 存储的数据视作 cookies / web storage

User agents 应该 以与 HTTP 会话 cookie [COOKIES] 强相关的方式向用户展示 Distinctive IdentifiersKey Systems 存储的数据的存在情况,包括将其纳入“删除所有数据”操作,并在相同的 UI 位置展示。这可能促使用户对这类标识符持更谨慎的态度。User agents 应该 帮助用户避免 Incomplete Clearing of Data

不要向无关实体暴露按 origin 分类的信息

不要向个别化(individualization)服务器或与该 origin 无关的其他实体提供 origin 或与 origin associable 的值。如果实现使用了个别化流程,请遵循 Individualization 部分中的要求和建议。

使用不可被关联的按 origin、按 profile 的值和标识符

对于向应用暴露的所有 Distinctive Values,实现 必须 为每个 origin 和每个 browsing profile 使用不同的 non-associable by applications 值。参见 8.4.1 Use Per-Origin Per-Profile Values

对于那些 使用 Distinctive Identifier(s) 的实现,这一点尤为重要。参见 8.5.3 Use Per-Origin Per-Profile Identifiers

使用按 origin 和按浏览配置文件区分的 Key System 存储

任何可能以应用或许可证服务器可见的方式影响消息或行为的由 CDM 使用的数据 必须originbrowsing profile 分区,并且 不得 泄露到私人浏览会话或从私人浏览会话泄露。这包括内存中的数据和持久化的数据。具体但不限于,会话数据、许可证、密钥和按 origin 的标识符 必须origin 和按 browsing profile 分区。参见 8.3.1 Use origin-specific and browsing profile-specific Key System storage8.4.1 Use Per-Origin Per-Profile Values

向用户提供删除持久数据的功能,包括 Distinctive Identifiers

User agents 必须 提供用户清除任何由 Key Systems 维护的持久数据(包括 Distinctive Identifiers)的能力。参见 Allow Persistent Data to Be Cleared

使存储的数据过期

User agents 可以(例如以用户可配置的方式)在一段时间后自动删除 Distinctive Identifiers 和/或其他 Key System 数据。

Note

例如,用户代理可以配置为将此类数据视为仅限会话的存储,在用户关闭所有可访问该数据的浏览上下文后删除该数据。

这可以限制站点跟踪用户的能力,因为站点则仅能在用户对站点进行身份验证(例如购买或登录服务)时跨多个会话跟踪用户。

但是,如果用户不了解此类过期的含义,这也可能危及用户对内容(尤其是已购买或租赁内容)的访问权。

阻止第三方访问

User agents 可以 限制对 Key Systems 和/或功能的访问,仅允许来自顶层文档 origin 的脚本访问。例如,requestMediaKeySystemAccess() 可能会拒绝来自在 iframe 中运行的其他 origin 页面对某些配置的请求。

User agents 必须 确保在 using Distinctive Identifier(s) or Distinctive Permanent Identifier(s) 之前,用户已被充分告知并/或已给予明确同意。

此类机制 必须 针对每个 origin 进行,以避免合法用途使后续的恶意访问成为可能,并且 必须 针对每个 browsing profile 进行。

Note

将本规范中定义的 API 限制在安全上下文中,可以确保 网络攻击者 无法利用授予给未验证 origin 的权限。参见 abuse of persisted consent

提供用户控件以禁用 Key SystemsKey System 对标识符的使用

User Agents 应该 为用户提供一个全局控制,允许用户决定是否启用某个 Key System,和/或是否启用 Key Systemuse of Distinctive Identifier(s) or Distinctive Permanent Identifier(s)(如果该 Key System 支持)。User agents 应该 帮助用户避免 Incomplete Clearing of Data

要求对每个 Key System 的站点特定白名单授权

User agents 可以 要求用户在站点使用某个 Key System(和/或其某些功能)之前明确授权访问。User agents 应该 允许用户临时或永久撤销该授权。

使用共享黑名单

User agents 可以 允许用户共享 origins 和/或 Key Systems 的黑名单。这样社区可以共同保护其隐私。

Note

虽然这些建议可以防止本规范中定义的 API 被用于明显的用户跟踪,但并不能完全阻止跟踪。在单一 origin 内,站点仍可以在会话期间跟踪用户,然后将所有这些信息与站点获得的任何标识信息(姓名、信用卡号、地址)一并传递给第三方。如果第三方与多个站点合作获取此类信息,并且标识符不是 按 origin 和 profile 唯一,那么仍然可以创建用户档案。

11.5 存储在用户设备上的信息

11.5.1 关切

This section is non-normative.

Key Systems 可能在用户设备上存储信息,或者 user agents 可能代 Key Systems 存储信息。这样可能会向同一设备的其他用户泄露有关某用户的信息,包括可能使用过特定 origins 的站点(即访问过的站点)或甚至用 Key System 解密过的内容。

如果一个 origin 存储的信息会影响另一个 origin 的 Key System 的操作,那么用户在某个站点访问过的站点或查看过的内容可能会被另一个(可能是恶意的)站点透露出来。

如果为客户端设备上的某个 browsing profile 存储的信息会影响其他 browsing profiles 或浏览器上 Key System 的行为,那么一个配置文件中访问的站点或查看的内容可能会被另一个 browsing profile 揭示或与之相关联,甚至可能涉及不同的操作系统用户账户或不同的浏览器。

11.5.2 缓解措施

缓解这些关切的要求在 8.3 Persistent Data 中有定义。

11.6 数据未完全清除

11.6.1 关切

This section is non-normative.

用户通过清除 Distinctive Identifiers 和已存储的数据和/或禁用某个 Key System 来保护其隐私的尝试,可能会在未同时清除和/或禁用所有相关数据、功能、cookies [COOKIES] 及其他站点数据时被破坏。例如:

  • 如果用户清除 cookies 或其他持久存储,但没有同时清除 Distinctive Identifiers 和 Key Systems 存储的数据,站点可以通过将这些功能互为冗余备份来挫败用户的清除行为。

  • 如果用户清除 Distinctive Identifiers,但没有同时清除由 Key Systems 存储的数据(包括持久会话),以及 cookies 和其他持久存储,站点可以利用剩余的数据将旧标识与新标识关联,从而挫败清除。

  • 如果用户禁用某个 Key System(尤其是针对特定 origin),但没有同时清除 cookies 或其他持久存储,站点可以利用剩余的功能来挫败这些尝试。

  • 如果用户禁用某个 Key System,之后又决定重新启用该 Key System,但没有同时清除 cookies、其他持久存储、Distinctive Identifiers 以及 Key Systems 存储的数据,则站点可能能够将禁用前的数据与重新启用后的数据和行为相关联。

11.6.2 缓解措施

缓解这些关切的建议在 8.3 Persistent Data 中有定义。

11.7 私人浏览模式

User agents 可能支持一种旨在保持用户匿名和/或确保不在客户端保留浏览活动记录的操作模式(例如私密浏览)。对于使用这些模式的用户,上述各节讨论的隐私问题可能尤为令人关注。

支持此类模式的 user agent 实现者 应该 仔细考虑在这些模式下是否应禁用对 Key Systems 的访问。例如,这些模式 可以 禁止创建支持或使用 MediaKeySystemAccess 对象,这些对象支持或使用 persistentStatedistinctiveIdentifier(无论是作为 CDM 实现的一部分,还是因为应用将其标记为 "required")。如果实现不禁止此类创建,则在允许使用之前,应该 向用户告知其对这些模式预期隐私属性的影响和潜在后果。

11.8 安全来源与传输

本规范中定义的 API 仅在安全的 origins 上支持,从而保护前述各节中讨论的信息。标识符也按 Encrypt Identifiers 中规定的方式进行加密。

应用(包括其使用的服务器)应该 对所有包含或涉及 CDM 的数据或消息的流量使用安全传输,这包括但不限于来自 message 事件传出的所有数据,以及传递给 update() 的数据。

所有 user agents 必须 正确处理混合内容 [MIXED-CONTENT],以避免在用户代理或应用希望强制执行安全 origin 和传输时暴露于不安全的内容或传输。

12. 符合性

除标记为非规范性的各节之外,本规范中的所有撰写指南、图表、示例和注释均为非规范性。规范性要求适用于本规范中的其他所有内容。

文档中以全大写出现的关键字 MAYMUSTMUST NOTOPTIONALRECOMMENDEDREQUIREDSHALLSHALL NOTSHOULDSHOULD NOT 的解释,应当按照 BCP 14 [RFC2119] [RFC8174] 的规定进行,且仅当这些关键字以全部大写形式出现时适用,如上所示。

13. 示例

本节为非规范性。

本节包含使用所提议扩展的各种用例的示例解决方案。这些并非对这些用例的唯一解决方案。示例中使用了 video 元素,但相同方法同样适用于所有媒体元素。在某些情况下(例如使用同步 XHR),示例被简化以便突出扩展本身。

13.1 页面加载时已知源和密钥(Clear Key)

在此简单示例中,源文件和 明文许可证 在页面中为硬编码。只会创建一个会话。

<script>
  function onLoad() {
    var video = document.getElementById('video');

    if (!video.mediaKeys) {
      navigator.requestMediaKeySystemAccess('org.w3.clearkey', [
        { initDataTypes: ['webm'],
          videoCapabilities: [{ contentType: 'video/webm; codecs="vp8"' }] }
      ]).then(
        function(keySystemAccess) {
          var promise = keySystemAccess.createMediaKeys();
          promise.catch(
            console.error.bind(console, 'Unable to create MediaKeys')
          );
          promise.then(
            function(createdMediaKeys) {
              return video.setMediaKeys(createdMediaKeys);
            }
          ).catch(
            console.error.bind(console, 'Unable to set MediaKeys')
          );
          promise.then(
            function(createdMediaKeys) {
              var te = new TextEncoder();
              var initData = te.encode( '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}');
              var keySession = createdMediaKeys.createSession();
              keySession.addEventListener("message", handleMessage, false);
              return keySession.generateRequest('keyids', initData);
            }
          ).catch(
            console.error.bind(console, 'Unable to create or initialize key session')
          );
        }
      );
    }
  }

  function handleMessage(event) {
    var keySession = event.target;
    var te = new TextEncoder();
    var license = te.encode('{"keys":[{"kty":"oct","k":"tQ0bJVWb6b0KPL6KtZIy_A","kid":"LwVHf8JLtPrv2GUXFW2v_A"}],"type":"temporary"}');
    keySession.update(license).catch(
      console.error.bind(console, 'update() failed')
    );
  }
</script>

<body onload='onLoad()'>
  <video src='foo.webm' autoplay id='video'></video>
</body>

13.2 选择受支持的 Key System 并使用来自 "encrypted" 事件的初始化数据

本示例使用 Key SystemrequestMediaKeySystemAccess() 方法选择一个受支持的 Key System,然后使用来自 Initialization Data (来自media data)来生成许可证请求并将其发送到相应的许可证服务器。所支持的某个 key system 使用了 serverCertificate,该证书已被预先提供。

<script>
  var licenseUrl;
  var serverCertificate;

  // Returns a Promise<MediaKeys>.
  function createSupportedKeySystem() {
    someSystemOptions = [
     { initDataTypes: ['keyids', 'webm'],
       audioCapabilities: [
         { contentType: 'audio/webm; codecs="opus"' },
         { contentType: 'audio/webm; codecs="vorbis"' }
       ],
       videoCapabilities: [
         { contentType: 'video/webm; codecs="vp9"' },
         { contentType: 'video/webm; codecs="vp8"' }
       ]
     }
    ];
    clearKeyOptions = [
     { initDataTypes: ['keyids', 'webm'],
       audioCapabilities: [
         { contentType: 'audio/webm; codecs="opus"' },
         { contentType: 'audio/webm; codecs="vorbis"' }
       ],
       videoCapabilities: [
         { contentType: 'video/webm; codecs="vp9"',
           robustness: 'foo' },
         { contentType: 'video/webm; codecs="vp9"',
           robustness: 'bar' },
         { contentType: 'video/webm; codecs="vp8"',
           robustness: 'bar' },
       ]
     }
    ];

    return navigator.requestMediaKeySystemAccess('com.example.somesystem', someSystemOptions).then(
      function(keySystemAccess) {
        // Not shown:
        // 1. Use both attributes of keySystemAccess.getConfiguration().audioCapabilities[0]
        //    and both attributes of keySystemAccess.getConfiguration().videoCapabilities[0]
        //    to retrieve appropriate stream(s).
        // 2. Set video.src.

        licenseUrl = 'https://license.example.com/getkey';
        serverCertificate = new Uint8Array([ ... ]);
        return keySystemAccess.createMediaKeys();
      }
    ).catch(
      function(error) {
        // Try the next key system.
        navigator.requestMediaKeySystemAccess('org.w3.clearkey', clearKeyOptions).then(
          function(keySystemAccess) {
            // Not shown:
            // 1. Use keySystemAccess.getConfiguration().audioCapabilities[0].contentType
            //    and keySystemAccess.getConfiguration().videoCapabilities[0].contentType
            //    to retrieve appropriate stream(s).
            // 2. Set video.src.

            licenseUrl = 'https://license.example.com/clearkey/request';
            return keySystemAccess.createMediaKeys();
          }
        );
      }
    ).catch(
      console.error.bind(console, 'Unable to instantiate a key system supporting the required combinations')
    );
  }

  function handleInitData(event) {
    var video = event.target;
    if (video.mediaKeysObject === undefined) {
      video.mediaKeysObject = null; // Prevent entering this path again.
      video.pendingSessionData = []; // Will store all initData until the MediaKeys is ready.
      createSupportedKeySystem().then(
        function(createdMediaKeys) {
          video.mediaKeysObject = createdMediaKeys;

          if (serverCertificate)
            createdMediaKeys.setServerCertificate(serverCertificate);

          for (var i = 0; i < video.pendingSessionData.length; i++) {
            var data = video.pendingSessionData[i];
            makeNewRequest(video.mediaKeysObject, data.initDataType, data.initData);
          }
          video.pendingSessionData = [];

          return video.setMediaKeys(createdMediaKeys);
        }
      ).catch(
        console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
      );
    }
    addSession(video, event.initDataType, event.initData);
  }

  function addSession(video, initDataType, initData) {
    if (video.mediaKeysObject) {
      makeNewRequest(video.mediaKeysObject, initDataType, initData);
    } else {
      video.pendingSessionData.push({initDataType: initDataType, initData: initData});
    }
  }

  function makeNewRequest(mediaKeys, initDataType, initData) {
    var keySession = mediaKeys.createSession();
    keySession.addEventListener("message", licenseRequestReady, false);
    keySession.generateRequest(initDataType, initData).catch(
      console.error.bind(console, 'Unable to create or initialize key session')
    );
  }

  function licenseRequestReady(event) {
    var request = event.message;

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.keySession = event.target;
    xmlhttp.open("POST", licenseUrl);
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4) {
        var license = new Uint8Array(xmlhttp.response);
        xmlhttp.keySession.update(license).catch(
          console.error.bind(console, 'update() failed')
        );
      }
    }
    xmlhttp.send(request);
  }
</script>

<video autoplay onencrypted='handleInitData(event)'></video>

13.3 在加载媒体之前创建 MediaKeys

如果在 MediaKeys 初始化期间不需要处理 encrypted 事件,则初始化要简单得多。这可以通过以其他方式提供 Initialization Data 或在创建 MediaKeys 对象之后再设置源来实现。本示例采用后者。

<script>
  var licenseUrl;
  var serverCertificate;
  var mediaKeys;

  // See the previous example for implementations of these functions.
  function createSupportedKeySystem() { ... }
  function makeNewRequest(mediaKeys, initDataType, initData) { ... }
  function licenseRequestReady(event) { ... }

  function handleInitData(event) {
    makeNewRequest(mediaKeys, event.initDataType, event.initData);
  }

  createSupportedKeySystem().then(
    function(createdMediaKeys) {
      mediaKeys = createdMediaKeys;
      var video = document.getElementById("v");
      video.src = 'foo.webm';
      if (serverCertificate)
        mediaKeys.setServerCertificate(serverCertificate);
      return video.setMediaKeys(mediaKeys);
    }
  ).catch(
    console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
  );
</script>

<video id="v" autoplay onencrypted='handleInitData(event)'></video>

13.4 使用所有事件

这是一个更完整的示例,演示了如何使用所有事件。

注意 handleMessage() 可能会被多次调用,包括在需要多次往返的情况下对 update() 调用的响应中,或出于 Key System 可能需要发送消息的任何其他原因。

<script>
  var licenseUrl;
  var serverCertificate;
  var mediaKeys;

  // See previous examples for implementations of these functions.
  // createSupportedKeySystem() additionally sets renewalUrl.
  function createSupportedKeySystem() { ... }
  function handleInitData(event) { ... }

  // This replaces the implementation in the previous example.
  function makeNewRequest(mediaKeys, initDataType, initData) {
    var keySession = mediaKeys.createSession();
    keySession.addEventListener('message', handleMessage, false);
    keySession.addEventListener('keystatuseschange', handlekeyStatusesChange, false);
    keySession.closed.then(
      function(reason) {
        console.log('Session', this.sessionId, 'closed, reason', reason);
      }.bind(keySession)
    );
    keySession.generateRequest(initDataType, initData).catch(
      console.error.bind(console, 'Unable to create or initialize key session')
    );
  }

  function handleMessageResponse(keySession, response) {
    var license = new Uint8Array(response);
    keySession.update(license).catch(
      function(err) {
        console.error('update() failed: ' + err);
      }
    );
  }

  function sendMessage(type, message, keySession) {
    var url = licenseUrl;
    if (type == "license-renewal")
      url = renewalUrl;
    xmlhttp = new XMLHttpRequest();
    xmlhttp.keySession = keySession;
    xmlhttp.open('POST', url);
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4)
        handleMessageResponse(xmlhttp.keySession, xmlhttp.response);
    }
    xmlhttp.send(message);
  }

  function handleMessage(event) {
    sendMessage(event.messageType, event.message, event.target);
  }

  function handlekeyStatusesChange(event) {
    // Evaluate the current state using one of the map-like methods exposed by
    // event.target.keyStatuses.
    // For example:
    event.target.keyStatuses.forEach(function(status, keyId) {
      switch (status) {
        case "usable":
          break;
        case "expired":
          // Report an expired key.
          break;
        case "status-pending":
          // The status is not yet known. Consider the key unusable until the status is updated.
          break;
        default:
          // Do something with |keyId| and |status|.
      }
    })
  }

  createSupportedKeySystem().then(
    function(createdMediaKeys) {
      mediaKeys = createdMediaKeys;
      var video = document.getElementById("v");
      video.src = 'foo.webm';
      if (serverCertificate)
        mediaKeys.setServerCertificate(serverCertificate);
      return video.setMediaKeys(mediaKeys);
    }
  ).catch(
    console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
  );
</script>

<video id='v' src='foo.webm' autoplay></video>

13.5 存储的许可证

此示例请求一个用于将来使用的持久许可证并将其存储。它还提供了用于之后检索该许可证和销毁它的函数。

<script>
  var licenseUrl;
  var serverCertificate;
  var mediaKeys;

  // See the previous examples for implementations of these functions.
  // createSupportedKeySystem() additionally sets persistentState: "required" in each options dictionary.
  function createSupportedKeySystem() { ... }
  function sendMessage(message, keySession) { ... }
  function handleMessage(event) { ... }

  // Called if the application does not have a stored sessionId for the media resource.
  function makeNewRequest(mediaKeys, initDataType, initData) {
    var keySession = mediaKeys.createSession("persistent-license");
    keySession.addEventListener('message', handleMessage, false);
    keySession.closed.then(
      function(reason) {
        console.log('Session', this.sessionId, 'closed, reason', reason);
      }.bind(keySession)
    );
    keySession.generateRequest(initDataType, initData).then(
      function() {
        // Store this.sessionId in the application.
      }.bind(keySession)
    ).catch(
      console.error.bind(console, 'Unable to request a persistent license')
    );
  }

  // Called if the application has a stored sessionId for the media resource.
  function loadStoredSession(mediaKeys, sessionId) {
    var keySession = mediaKeys.createSession("persistent-license");
    keySession.addEventListener('message', handleMessage, false);
    keySession.closed.then(
      function(reason) {
        console.log('Session', this.sessionId, 'closed, reason', reason);
      }.bind(keySession)
    );
    keySession.load(sessionId).then(
      function(loaded) {
        if (!loaded) {
          console.error('No stored session with the ID ' + sessionId + ' was found.');
          // The application should remove its record of |sessionId|.
          return;
        }
      }
    ).catch(
      console.error.bind(console, 'Unable to load or initialize the stored session with the ID ' + sessionId)
    );
  }

  // Called when the application wants to stop using the session without removing the stored license.
  function closeSession(keySession) {
    keySession.close();
  }

  // Called when the application wants to remove the stored license.
  // The stored session data has not been completely removed until the promise returned by remove() is fulfilled.
  // The remove() call may initiate a series of messages to/from the server that must be completed before this occurs.
  function removeStoredSession(keySession) {
    keySession.remove().then(
      function() {
        console.log('Session ' + this.sessionId + ' removed');
        // The application should remove its record of this.sessionId.
      }.bind(keySession)
    ).catch(
      console.error.bind(console, 'Failed to remove the session')
    );
  }

  // This replaces the implementation in the previous example.
  function handleMessageResponse(keySession, response) {
    var license = new Uint8Array(response);
    keySession.update(license).then(
      function() {
        // If this was the last required message from the server, the license is
        // now stored. Update the application state as appropriate.
      }
    ).catch(
      console.error.bind(console, 'update() failed')
    );
  }

  createSupportedKeySystem().then(
    function(createdMediaKeys) {
      mediaKeys = createdMediaKeys;
      var video = document.getElementById("v");
      if (serverCertificate)
        mediaKeys.setServerCertificate(serverCertificate);
      return video.setMediaKeys(mediaKeys);
    }
  ).catch(
    console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
  );
</script>

<video id='v' src='foo.webm' autoplay></video>

13.6 使用 HDCP 策略预取媒体

如果某些媒体的许可证中包含 HDCP 策略,应用可以在预取之前检查该策略版本。

const status = await video.mediaKeys.getStatusForPolicy({
  minHdcpVersion: '1.4'
});

if (status === 'usable') {
  // Pre-fetch HD content.
} else {  // 'output-restricted'
  // Pre-fetch SD content.
}

14. 致谢

编辑们感谢 Aaron Colwell、Alex Russell、Anne van Kesteren、Bob Lund、 Boris Zbarsky、Chris Needham、Chris Pearce、David Singer、Domenic Denicola、Frank Galligan、 Glenn Adams、Henri Sivonen、Jer Noble、Joe Steele、Joey Parrish、John Simmons、Mark Vickers、Pavel Pergamenshchik、Philip Jägenstedt、Pierre Lemieux、Robert O'Callahan、Ryan Sleevi、Steve Heffernan、Steven Robertson、Theresa O'Connor、Thomás Inskip、Travis Leithead, 以及 Xiaohan Wang 对本规范的贡献。也感谢通过参与邮件列表和问题等方式为本规范做出贡献的许多其他人员。

A. 参考文献

A.1 规范性参考文献

[COOKIES]
HTTP State Management Mechanism。A. Barth。IETF。2011 年 4 月。建议标准。URL: https://httpwg.org/specs/rfc6265.html
[dom]
DOM Standard。Anne van Kesteren。WHATWG。 现行标准。URL: https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript Language Specification。 Ecma International。URL: https://tc39.es/ecma262/multipage/
[EME-INITDATA-KEYIDS]
"keyids" Initialization Data Format。Joey Parrish;Greg Freedman。W3C。2024 年 8 月 20 日。W3C 工作组说明。URL: https://www.w3.org/TR/eme-initdata-keyids/
[EME-INITDATA-REGISTRY]
Encrypted Media Extensions Initialization Data Format Registry。Joey Parrish;Greg Freedman。W3C。2024 年 7 月 18 日。DRY。URL: https://www.w3.org/TR/eme-initdata-registry/
[ENCODING]
Encoding Standard。Anne van Kesteren。 WHATWG。现行标准。URL: https://encoding.spec.whatwg.org/
[HTML]
HTML Standard。Anne van Kesteren; Domenic Denicola;Dominic Farolino;Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。现行标准。URL: https://html.spec.whatwg.org/multipage/
[Infra]
Infra Standard。Anne van Kesteren;Domenic Denicola。WHATWG。现行标准。URL: https://infra.spec.whatwg.org/
[mimesniff]
MIME Sniffing Standard。Gordon P. Hemsley。WHATWG。现行标准。URL: https://mimesniff.spec.whatwg.org/
[MIXED-CONTENT]
Mixed Content。Emily Stark;Mike West;Carlos IbarraLopez。W3C。2023 年 2 月 23 日。CRD。URL: https://www.w3.org/TR/mixed-content/
[PERMISSIONS-POLICY]
Permissions Policy。Ian Clelland。W3C。2025 年 8 月 6 日。W3C 工作草案。URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels。S. Bradner。IETF。1997 年 3 月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC6381]
The 'Codecs' and 'Profiles' Parameters for "Bucket" Media Types。R. Gellens;D. Singer;P. Frojdh。IETF。2011 年 8 月。 建议标准。URL: https://www.rfc-editor.org/rfc/rfc6381
[RFC7517]
JSON Web Key (JWK)。M. Jones。 IETF。2015 年 5 月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc7517
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words。B. Leiba。IETF。2017 年 5 月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc8174
[WEBIDL]
Web IDL Standard。Edgar Chen;Timothy Gu。 WHATWG。现行标准。URL: https://webidl.spec.whatwg.org/

A.2 参考资料(非规范性)

[CENC]
ISO/IEC 23001-7:2016, Information technology — MPEG systems technologies — Part 7: Common encryption in ISO Base Media File Format files。ISO/IEC。国际标准。URL: https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1
[EME-HDCP-VERSION-REGISTRY]
Encrypted Media Extensions HDCP Version Registry。Joey Parrish;Greg Freedman。W3C。2024 年 7 月 18 日。DRY。URL: https://www.w3.org/TR/eme-hdcp-version-registry/
[EME-STREAM-REGISTRY]
Encrypted Media Extensions Stream Format Registry。Joey Parrish;Greg Freedman。W3C。2024 年 7 月 18 日。DRY。URL: https://www.w3.org/TR/eme-stream-registry/
[MEDIA-SOURCE]
Media Source Extensions™。Jean-Yves Avenard;Mark Watson。W3C。2025 年 4 月 17 日。W3C 工作草案。URL: https://www.w3.org/TR/media-source-2/
[RFC6838]
Media Type Specifications and Registration Procedures。N. Freed;J. Klensin;T. Hansen。IETF。2013 年 1 月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc6838
[RFC7515]
JSON Web Signature (JWS)。M. Jones;J. Bradley;N. Sakimura。IETF。2015 年 5 月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc7515
[webaudio]
Web Audio API。Paul Adenot;Hongchan Choi。W3C。2021 年 6 月 17 日。W3C 推荐。URL: https://www.w3.org/TR/webaudio-1.0/