加密媒体扩展

W3C 工作草案

关于此文档的更多详细信息
此版本:
https://www.w3.org/TR/2026/WD-encrypted-media-2-20260611/
最新发布版本:
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/
编辑:
Xiaohan Wang (Google LLC)
Greg Freedman (Netflix Inc.)
前任编辑:
Joey Parrish (Google LLC) (Until June 2026)
Mark Watson (Netflix Inc.) (Until September 2019)
David Dorwin (Google Inc.) (Until September 2017)
Jerry Smith (Microsoft Corporation) (Until September 2017)
Adrian Bateman (Microsoft Corporation) (Until May 2014)
反馈:
GitHub w3c/encrypted-media (拉取请求新建议题未解决议题)
public-media-wg@w3.org 主题行为 [encrypted-media-2] … 消息主题 …存档

摘要

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

该 API 支持从简单的明文密钥解密到高价值视频的各种用例 (前提是用户代理具有适当的实现)。许可证/密钥交换由 应用控制,从而有助于开发支持一系列内容解密和保护技术的 健壮播放应用。

本规范并不定义内容保护或数字版权管理 系统。相反,它定义了一个通用 API,可用于发现、选择此类系统并与之交互, 也可用于发现、选择更简单的内容加密系统并与之交互。实现 数字版权管理并非符合本规范的要求:只要求实现 Clear Key 系统,作为共同基线。

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

本文档的状态

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

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

纳入其他特性超出了媒体工作组的范围。除 编辑性更新外,对本规范所做的其他实质性变更用于解决 针对本规范的维护议题:

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

本文档由 媒体工作组作为 工作草案发布,并使用 推荐标准 轨道

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

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

本文档由一个 按照 W3C 专利 政策运行的小组制作。 W3C 维护了一份 与该小组交付物有关的任何专利披露的公开列表; 该页面还包括 披露专利的说明。实际 知晓某项专利并认为该专利包含 必要权利要求的个人, 必须按照 W3C 专利政策第 6 节 披露该信息。

本文档受 2025年8月18日 W3C 流程文档约束。

1. 引言

本节为非规范性内容。

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

支持的内容按照特定于容器的“通用加密”规范进行加密, 从而能够跨密钥系统使用。支持的内容具有未加密容器,使 元数据能够提供给应用,并保持与其他 HTMLMediaElement 特性的兼容性。

实现者应注意本规范中描述的安全和隐私威胁 及顾虑的缓解措施。特别是,如果不了解 密钥系统及其实现的安全和隐私 属性,就无法满足本规范对安全和隐私的要求。8. 实现要求包含与底层密钥系统实现的 集成和使用相关的安全与隐私条款。10. 安全 关注外部威胁,例如输入数据或网络攻击。11. 隐私 关注用户特定信息的处理,以及为用户提供对自身隐私的充分 控制。

虽然本规范独立于媒体数据的来源,但作者应 了解,许多实现只支持解密通过 Media Source Extensions [MEDIA-SOURCE] 提供的媒体数据。

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

使用所提议 API 实现的通用栈

2. 定义

内容解密模块 (CDM)

内容解密模块 (CDM) 是为一个或多个密钥系统 提供功能(包括解密)的客户端组件。

实现可以将 CDM 的实现分离,也可以不分离;可以将它们视为 独立于用户代理,也可以不这样处理。这对 API 和应用是透明的。

密钥系统

密钥系统是解密机制和/或内容保护提供方的通用术语。 密钥系统字符串用于唯一标识一个密钥系统。用户代理使用它们来 选择一个 CDM,并标识与密钥相关的 事件来源。用户代理必须支持通用 密钥系统。用户代理可以还 提供附加的 CDM,并带有相应的密钥系统字符串。

密钥系统字符串始终是反向域名。密钥系统字符串使用区分大小写的匹配 进行比较。建议 CDM 使用简单的小写 ASCII 密钥系统字符串。

例如,"com.example.somesystem"。

在给定系统(示例中的 "somesystem")内,可以按密钥系统提供方确定的方式 定义子系统。例如,"com.example.somesystem.1" 和 "com.example.somesystem.1_5"。密钥系统提供方应记住,这些字符串将 用于比较和发现,因此它们应易于比较,且结构应保持合理简单。

密钥会话

密钥会话,或简称会话,为与 CDM 交换消息提供上下文,其结果是使密钥可供 CDM 使用。会话 体现为 MediaKeySession 对象。 每个密钥会话都与 generateRequest() 调用中提供的一个 初始化数据实例相关联。

每个密钥会话都与单个 MediaKeys 对象关联,并且只有与该 MediaKeys 对象关联的 媒体元素才能访问与 该会话关联的密钥。其他 MediaKeys 对象、CDM 实例以及媒体元素绝不能 访问该密钥会话或使用其密钥。会话一旦关闭,包括 MediaKeySession 对象 被销毁时,密钥会话及其包含的密钥就不再可用于解密

与未被显式存储的密钥会话关联的所有许可证和密钥,在密钥会话关闭时 必须被销毁。

密钥 ID 在一个会话内必须唯一。

会话 ID

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

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

每个会话 ID 应当在其创建所在的浏览上下文内唯一。 对于使是持久会话类型吗?算法返回 true 的会话类型,会话 ID 在一段时间内必须内唯一, 包括跨浏览会话。

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

密钥

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

最佳实践 1: 在 打包加密媒体时,使用一个 不同的密钥(以及密钥 ID)来加密每组 需要强制执行实质上不同策略的流。

例如,当两个视频分辨率之间的策略不同时,每组 流都会使用不同的密钥进行加密,以便可以 独立强制执行这些策略;类似地,当音频和视频流的 策略不同时,它们会使用不同的密钥。跨策略组共享密钥会阻止 独立强制执行,因此每个策略组使用不同的密钥是确保 在各客户端之间实现强制执行和兼容性的唯一方式。

可用于 解密

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

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

密钥 ID

一个密钥关联着一个密钥 ID,该 ID 是一个八位字节序列,并且 唯一 标识该密钥。容器指定能够解密媒体数据中某个块 或一组块的密钥的 ID。初始化数据 可以包含密钥 ID,以标识解密 媒体数据所需的密钥。 但是,并不要求初始化数据包含媒体数据媒体 资源中使用的任何或所有密钥 ID。提供给 CDM许可证将每个密钥与一个 密钥 ID 关联起来,从而使 CDM 在解密一个加密的媒体 数据块时能够选择适当的密钥。

已知密钥

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

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

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

许可证

许可证是特定于密钥系统的状态信息,其中包含一个或多个密钥 ——每个密钥都关联一个密钥 ID——以及可能的其他 关于密钥 使用的信息。

初始化数据
最佳实践 2: 在 生成初始化数据时,唯一标识解密内容所需的每个 密钥

密钥系统通常需要一个初始化数据块, 其中包含关于要解密的流的信息,然后才能构造许可证请求消息。 该块可以是简单的密钥或内容 ID,也可以是包含 此类信息的更复杂结构。应用以某种 特定于应用的方式或从媒体数据中获取初始化数据。

初始化数据是一个通用术语,指容器特定的数据,该数据由 CDM 用于生成许可证请求。

初始化数据的格式取决于容器的类型,并且 容器可以支持一种以上的初始化数据格式。 初始化数据类型是一个字符串, 表示随附的初始化数据的格式。初始化数据类型字符串始终 以区分大小写的方式匹配。建议初始化数据类型字符串 为 小写 ASCII 字符串。

Encrypted Media Extensions Initialization Data Format Registry 提供从初始化数据类型 字符串到每种格式规范的映射。

当用户代理在媒体数据中遇到初始化数据时,它会在 initData 属性中将 该初始化数据提供给应用,该属性属于 encrypted 事件。用户代理 不得存储初始化数据,也不得在遇到它时使用其内容。 应用通过 generateRequest()初始化数据提供给 CDM。 用户代理不得通过其他方式向 CDM 提供初始化数据

对于给定的一组流或媒体 数据,初始化数据必须是固定值。它必须只包含与播放给定的一组流或媒体数据所需密钥 相关的信息。它不得包含应用数据、客户端特定数据、用户特定 数据或可执行 代码。

初始化数据不应包含特定于密钥系统的数据或值。 实现必须支持 [EME-INITDATA-REGISTRY] 中为它们所支持的每个初始化数据类型 定义的通用格式。

不鼓励使用专有格式/内容,即 [EME-INITDATA-REGISTRY] 中未定义的格式,并且强烈不鼓励支持或 使用专有格式。专有格式只应与既有内容一起使用,或在不支持 通用格式的既有客户端设备上使用。

可关联值

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

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

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

  • 共享前缀或其他子集

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

  • 将来源与固定值进行 XOR(因为它可以轻易逆转)

相比之下,两个完全无关或在密码学上不同的值, 例如通过密码学强的不可逆哈希函数得到的值,是 不可关联的

如果引用的实体或实体集合可以在合理的时间和努力范围内 在没有其他实体参与的情况下将两个或更多标识符或其他值 相关联或建立关联,则称这些值可由某个 实体关联。否则,这些值是不可由某个实体 关联的

如果两个或更多标识符或其他值 不可由应用关联, 则它们是 不可由某个实体关联的,其中该实体 是包含该 应用、所有其他应用以及它们使用 或与之通信的服务器等其他实体的集合。否则,这些值会被视为 可由应用关联,这是被禁止的。

显著值

显著值是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,它为大量 用户或客户端设备所共享。显著值可以位于内存中,也可以 被持久化。

显著值的示例包括但不限于:

虽然显著值通常对用户或客户端设备是唯一的,但一个值 不需要严格唯一即可具有显著性。例如,由 少数用户共享的值仍可能具有显著性。

永久标识符

永久标识符是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,它在某种意义上不可磨灭, 或者用户不易移除、重置或更改。这包括但 不限于:

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

  • 在工厂中配置到硬件设备中的值

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

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

  • CDM 或其他软件 组件相关联或从其派生的值

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

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

显著永久 标识符是一个永久标识符,并且它是 显著的

当暴露到客户端之外时,显著永久标识符以及从其派生 或以其他方式与其相关的值必须加密。 显著永久标识符绝不能暴露给 应用,即使以 加密形式也不可以。

虽然显著永久标识符通常对用户或客户端 设备是唯一的,但显著永久标识符不需要严格唯一即可 具有显著性。例如,由少数 用户共享的显著永久标识符仍可能具有显著性。

显著永久标识符不是显著标识符,因为 它不是(在本规范范围内)派生或生成的。

distinctiveIdentifier 控制是否可以使用显著 永久标识符。具体来说,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时, 才可以使用显著永久标识符。

显著标识符

显著标识符是一个值,包括不透明或加密形式的值,对于该值, 客户端外部的任何实体都可能将这些值相关联或建立关联, 超出用户在 Web 平台上可能预期的范围(例如,cookie 和其他站点 数据)。例如,某些值对于应用以外的实体而言是可关联的, 跨越 a) 来源、 b) 浏览配置文件,或 c) 浏览会话,即使用户已经尝试通过清除浏览数据来保护其隐私,或者对于用户来说 不容易打破这种关联的值。特别是,如果 中央服务器(例如个性化 服务器)能够跨来源关联值,例如因为个性化请求 包含了公共值,或者因为在个性化请求中提供的值 即使在尝试 清除浏览数据后,仍可由此类服务器关联,则该值就是显著标识符。 造成这种情况的可能原因包括在个性化过程中使用显著 永久标识符

暴露给应用的显著标识符,即使以加密形式暴露,也 受标识符 要求约束,包括 被加密按来源和配置文件 唯一,以及 可清除

虽然显著标识符的实例化或使用是由 应用对本规范中定义的 API 的使用触发的,但该标识符不需要 被提供给应用,也可以触发与显著 标识符相关的条件。(显著永久 标识符不会提供给 应用,即使以不透明或加密形式也不会。)

distinctiveIdentifier 控制是否可以使用显著 标识符。具体来说,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时, 才可以使用显著标识符。

显著标识符是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,并且满足以下所有 条件:

  • 它是显著的

    虽然显著标识符通常对用户或客户端设备是唯一的,但 标识符不需要严格唯一即可具有显著性。例如,由 少数用户共享的标识符仍可能具有显著性。

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

  • 它具有以下一项或多项属性:

    对于暴露给应用的值,其他受关注且被规范性禁止的属性包括:

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

    • 用于所有来源的单个基于硬件的值。

    • 用于所有来源的单个基于随机数的值。

    • 个性化过程中获得并 用于 所有来源的单个值。

    • 包含上述任何值的全部或一部分的值。

    • 用于多个但并非所有来源的单个值。

    • 用于一个域上所有来源的单个值。(标识符必须 按来源区分。)

    • 预配置的、特定于来源的值。

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

虽然显著标识符通常可由生成它们的实体关联, 但它们必须不可由应用关联的。换 句话说,这种相关或关联只能由最初生成 显著标识符值的实体完成,例如 个性化服务器。 有权访问显著永久 标识符的实体不得 将这种能力暴露给应用,因为这会使生成的显著 标识符可由 应用关联,并且谨慎避免向 其他实体或第三方暴露 这种关联。

显著标识符的示例包括但不限于:

  • 包含在密钥请求中的一系列字节,不同于其他客户端设备包含的 字节序列,并且基于或直接或 间接使用显著永久 标识符获得。

  • 包含在密钥请求中的公钥,该公钥不同于其他客户端设备请求中 包含的公钥,并且基于或直接 或间接使用显著永久 标识符获得。

  • 证明拥有其他客户端设备不具备的私钥(例如,通过对某些数据签名), 并且该私钥基于或直接或 间接使用显著永久 标识符获得。

  • 此类密钥的标识符。

  • 使用此类值来派生另一个被暴露的值,即使第一个 值未被直接暴露。

  • 从另一个显著标识符派生的值。

  • 显著永久 标识符一起报告给(例如,个性化)服务器的随机值, 或在提供显著永久 标识符后由此类服务器提供的随机值。

  • 从工厂中配置到硬件设备中的唯一值派生的值。

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

  • 从嵌入在 CDM 二进制文件或其他供 CDM 使用的文件中的唯一值派生的值。

不属于显著标识符的事物示例:

  • 如果安装基数很大,则给定 CDM 版本的所有副本 共享的公钥。

  • 一个随机数或临时密钥,它是唯一的但只在一个会话中使用。

  • 未以任何派生或类似方式暴露到客户端之外的值, 包括通过个性化 或类似方式。

  • 用于例如视频管线与 CDM 之间证明的设备唯一密钥, 当前提是 CDM 不让 这些证明继续流向 应用,而是使用不构成显著标识符的密钥自行生成新的证明。

  • 一个随浏览数据(例如 cookie)完全清除/可清除的值,清除后它将被一个不可关联的值替换] (不仅仅是不可由 应用关联),即使对于中央服务器(例如 个性化 服务器)也是如此,并且满足以下一项或多项:

    • 没有显著 永久标识符或显著标识符 参与该值的生成。

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

    • 它是由服务器在未使用或不知道另一个 显著标识符的情况下提供的值。

显著标识符和 显著永久 标识符的使用

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

如果一个实现、配置、实例或对象在其生命周期内或相关此类实体的 生命周期内的任何时候,将一个或多个 显著永久 标识符、关于它们的信息,或从它们派生 或以其他方式与它们相关的值暴露到客户端之外,即使以加密形式暴露, 则该实现、配置、实例或对象使用 显著永久标识符。这包括但不限于 将此类值提供给个性化服务器。此类 值不得 提供给应用。

如果一个实现、配置、实例或对象 使用显著标识符和/或使用显著 永久标识符,则该实现、配置、实例或对象 使用显著标识符或显著永久标识符

distinctiveIdentifier 控制是否可以使用显著标识符显著 永久标识符。具体来说,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时,才可以使用此类 标识符。

跨源限制

在播放期间,嵌入的媒体数据会暴露给嵌入来源中的脚本。 为了让 API 能够在 encrypted 事件中提供初始化数据媒体 数据必须与嵌入页面 CORS 同源。 如果媒体 数据与嵌入文档是跨源的,作者 HTMLMediaElement 上使用 crossOrigin 属性,并在媒体数据响应上使用 CORS 标头,以使其 CORS 同源

混合 内容限制

在播放期间,嵌入的媒体数据会暴露给嵌入来源中的脚本。 为了让 API 能够在 encrypted 事件中提供初始化数据媒体 数据不得混合内容 [MIXED-CONTENT]。

时间

时间必须等价于 ECMAScript Language Specification 中表示的时间。

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

Time 通常表示一个具有毫秒精度的时间瞬点;然而,仅这一点 并不足以构成充分的定义。 ECMAScript 语言 规范中的“时间值与时间范围”一节添加了其他重要要求。

过期时间

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

浏览配置文件

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

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

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

本节定义了获取对密钥系统访问的机制。请求中包含 能力也可以实现特性检测。

拒绝 promise 时,算法的步骤总是会被中止。

3.1 Permissions Policy 集成

requestMediaKeySystemAccess() 是一个由字符串 encrypted-media 标识的策略控制 特性。 它的默认 允许列表'self' [PERMISSIONS-POLICY]。

3.3 MediaKeySystemConfiguration 字典

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

MediaKeysRequirement 枚举定义如下:

枚举描述
required
在调用 requestMediaKeySystemAccess() 时使用
返回的对象必须支持此特性。
MediaKeySystemAccess 对象返回时
由该对象创建的 CDM 实例可以使用此特性。
optional
在调用 requestMediaKeySystemAccess() 时使用
返回的对象可以支持并使用此特性。
MediaKeySystemAccess 对象返回时
此值不能且绝不能出现在这样的 对象中。
not-allowed
在调用 requestMediaKeySystemAccess() 时使用
返回的对象必须在不使用此 特性的情况下工作,并且绝不能在任何时候使用 它。
MediaKeySystemAccess 对象返回时
由该对象创建的 CDM 实例绝不能使用此特性。
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 中, 该对象由 MediaKeySystemAccessgetConfiguration() 方法返回。
initDataTypes 类型为 sequence<DOMString>, 默认值为 []
受支持的初始化数据类型 名称列表。如果列表为空,或包含一个或多个与所有其他成员一起受支持的值 (由算法确定),则认为此对象的初始化数据类型 能力受支持。序列中的值不得为空字符串。
audioCapabilities 类型为 sequence<MediaKeySystemMediaCapability>, 默认值为 []
受支持的音频类型和能力对列表。如果列表为空,或包含一个或多个与所有其他 成员一起受支持的值(由算法确定),则认为此 对象的音频能力受支持。当值之间存在 冲突时,将选择较早的值。空列表表示 不支持音频能力。在这种情况下, videoCapabilities 元素不得为空。
videoCapabilities 类型为 sequence<MediaKeySystemMediaCapability>, 默认值为 []
受支持的视频类型和能力对列表。如果列表为空,或包含一个或多个与所有其他 成员一起受支持的值(由算法确定),则认为此 对象的视频能力受支持。当值之间存在 冲突时,将选择较早的值。空列表表示 不支持视频能力。在这种情况下, audioCapabilities 元素不得为空。
distinctiveIdentifier 类型为 MediaKeysRequirement, 默认值为 "optional"
是否要求使用可区别标识符

当此成员为 "not-allowed" 时, 实现绝不能在与从此配置创建的任何对象相关联的任何 操作中使用 可区别标识符或可区别永久标识符

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

当此成员为 "not-allowed" 时, CDM 绝不能持久化任何与 应用或此对象的 Document相关的状态。

就此成员而言,持久化状态不包括由密钥系统 实现控制的持久化唯一 标识符(可区别 标识符)。distinctiveIdentifier 独立地 反映此要求。

当不支持持久化 状态时,只能创建 "temporary" 会话。

对于 "temporary" 会话, 存储状态的需要和能力是密钥系统实现特定的,并且可能随所使用的 特性而变化。

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

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

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

实现不应向此字典添加成员。如果添加成员, 它们必须MediaKeysRequirement 类型,并且建议它们具有 "optional" 的默认值,以支持最广泛的 应用和客户端组合。

用户代理实现无法识别的字典成员会根据 [WEBIDL] 被忽略,并且不会在 requestMediaKeySystemAccess() 算法中予以考虑。如果应用使用非标准字典成员,它绝不能 依赖 用户代理实现会拒绝包含此类字典 成员的配置。

此字典绝不能用于向 CDM 传递状态或数据。

3.4 MediaKeySystemMediaCapability 字典

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

3.4.1 字典 MediaKeySystemMediaCapability 成员

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

媒体资源MIME 类型

encryptionScheme 类型为 DOMString,默认值为 null

与内容类型关联的加密方案。null 或 不存在的值会向用户代理表明,应用不要求特定加密方案, 因而任何加密方案都是可接受的。

空字符串不同于 null 或不存在,因此会被视为 无法识别的加密方案。

encryptionScheme 的知名值包括:

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

与内容类型关联的鲁棒性级别。空字符串表示 任何解密和解码该内容类型的能力都是可接受的。 实现必须配置 CDM,以至少支持用于 创建 MediaKeys 对象的 MediaKeySystemAccess 对象配置中指定的 鲁棒性 级别。

为了让此对象表示的能力被视为受支持, contentType 绝不能为空字符串,并且其整个 值,包括所有编解码器,都必须robustness 一起受支持。

如果一组编解码器中的任意一个都可接受,则对 每个编解码器使用此字典的单独实例。

最佳实践 3: 当 将 MediaKeySystemMediaCapability 传给 requestMediaKeySystemAccess() 时, 在 MIME 类型中指定编解码器和编解码器约束 (例如,根据 [RFC6381]), 除非容器在规范上蕴含它们, 并将 encryptionScheme 设置为某个特定值,而不是 依赖默认的 null。

不同的加密方案通常彼此不兼容。null 的默认值 以及将 null 解释为“任意”的做法,为 不知情的应用提供了向后兼容性,并为较旧用户代理提供了一条 polyfill 路径。

4. MediaKeySystemAccess 接口

MediaKeySystemAccess 对象提供对密钥系统的访问。

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

4.1 属性

keySystem 类型为 DOMString,只读
标识正在使用的密钥系统

4.2 方法

getConfiguration()

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

返回的对象是第一个可满足的 MediaKeySystemConfiguration 配置的非严格子集(加上任何隐含默认值),该配置传递给 requestMediaKeySystemAccess() 调用,而该调用返回的 promise 以此对象 resolve。它不包含未在该单一配置中指定的能力值 (隐含默认值除外),因此可能不会反映 密钥系统实现的所有能力。配置中的所有值 可以以任意组合使用。类型为 MediaKeysRequirement 的成员反映 该能力是否为任意组合所要求。它们不会具有 "optional" 值。

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

  1. 返回此对象的 configuration 值。

    这会导致每次调用此方法时,都从 configuration 创建并初始化一个新对象。

    如果应用没有给出 encryptionScheme, 累积的 configuration 必须 仍然包含一个值为 nullencryptionScheme 字段,以便 polyfill 可以在不指定具体值的情况下检测用户代理对该 字段的支持。

createMediaKeys()

keySystem 创建一个新的 MediaKeys 对象。

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

  1. promise 为一个新的 promise。

  2. 并行运行以下步骤:

    1. configuration 为此对象的 configuration 值的值。

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

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

    4. 必要时加载并初始化此对象的 cdm implementation 值所表示的 密钥系统实现。

    5. instance 为此对象的 cdm implementation 值所表示的密钥系统 实现的新实例。

    6. 使用 configuration 初始化 instance,以启用、禁用和/或选择 密钥系统特性。

    7. 如果 use distinctive identifierfalse,则阻止 instance 使用 可区别标识符和可区别永久标识符

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

    9. 如果前面的任何步骤失败,则以一个新的 DOMException 拒绝 promise,其名称为适当的错误 名称

    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 resolve promise

  3. 返回 promise

5. MediaKeys 接口

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

MediaKeys 对象不再可访问时, 用户代理可以销毁它。

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

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

拒绝 promise 时,算法的步骤总是会被中止。

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

MediaKeySessionType 枚举定义如下:

枚举描述
temporary

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

应用无需担心管理此类存储。对此 会话类型的支持是必需的

persistent-license

一种会话,其许可证(以及可能的其他与会话相关的数据) 将被持久化。当许可证及其包含的密钥被销毁时,许可证销毁记录被持久化。 许可证 销毁记录是特定于密钥系统的证明, 表明许可证及其包含的密钥不再可由客户端使用。对此会话 类型的支持是可选的

只有当创建此对象的 MediaKeySystemAccess 对象 关联的配置具有 persistentState 值 "required" 时, 才能创建此类型的会话。一旦 update() 成功调用,该会话必须可通过其会话 ID加载。当调用 remove() 时, 会生成一个类型为 "license-release" 且包含许可证销毁记录message, 直到该记录被传递给 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 参数会影响返回对象的行为。

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

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

    如果此对象的 persistent state allowed 值为 false,则 是持久会话类型吗? 算法返回 truesessionType 值会失败。

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

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

  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()

返回给定 MediaKeysPolicyMediaKeyStatus

WebIDLdictionary MediaKeysPolicy {
    DOMString minHdcpVersion;
};

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

HDCP 策略由 minHdcpVersion 表示。如果 系统可以启用指定的 HDCP 版本或更高版本,则该策略会产生 "usable" 的 MediaKeyStatus。 [EME-HDCP-VERSION-REGISTRY] 提供从 minHdcpVersion 值 到 HDCP 规范的映射。

HDCP 状态的确定应当以与 CDM 在播放期间强制执行此类限制相同的方式进行。这样,应用 开发者可以获得合理提示,从而优化他们为开始播放而获取的内容。

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

  1. 如果 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" resolve promise 并中止这些步骤。

    4. 用 "usable" resolve promise

  4. 返回 promise

setServerCertificate()

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

对于使用此类证书的密钥系统,应用应当调用 setServerCertificate() 来显式设置服务器证书。 否则,许可证交换可能失败。

某些密钥系统也支持通过 排队一个 "message" 事件算法 从服务器请求证书。然而, 并非所有使用此类证书的密钥系统都支持这一点。

服务器证书内容特定于密钥系统。它绝不能包含 可执行代码。

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

  1. 如果此对象的 cdm implementation 值所表示的密钥系统实现不支持服务器证书,则返回一个以 false 兑现的 promise。

  2. 如果 serverCertificate 为空,则返回一个以新创建的 TypeError 拒绝的 promise。

  3. certificate获取所持 字节副本 serverCertificate 的结果。

  4. promise 为一个新的 promise。

  5. 并行运行以下步骤:

    1. sanitized certificatecertificate 的经验证和/或清理的版本。

      用户代理在将证书传递给 CDM 之前应彻底验证该证书。这可能包括 验证值是否处于合理 限制内、剥离无关数据或字段、预解析它、清理它, 和/或生成一个完全清理后的版本。用户代理应检查 字段的长度和值是否合理。未知字段应被 拒绝或移除。

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

    3. 如果前一步失败,则以一个新的 DOMException 拒绝 promise,其名称为适当的错误 名称

    4. true 兑现 promise

  6. 返回 promise

5.2 算法

5.2.1 是持久会话类型吗?

运行“是持久会话类型吗?”算法以确定指定的 会话类型是否支持任何形式的持久性。运行此算法的请求包括一个 MediaKeySessionType 值。

运行以下步骤:

  1. session type 为指定的 MediaKeySessionType 值。

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

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

5.2.2 CDM 不可用

CDM 实例 变为不可用时,运行“CDM 不可用”算法,以关闭与 MediaKeys 对象 media keys 关联的所有 MediaKeySession 对象。 运行此算法的请求包括一个 MediaKeySessionClosedReason 值。

运行以下步骤:

  1. reason 为指定的 MediaKeySessionClosedReason 值。

  2. 对于由 media keys 创建且未 关闭的每个 MediaKeySession排队一个 任务,以在该会话上以原因 reason 运行 会话已关闭 算法。

5.3 存储与持久性

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

如果 MediaKeys 对象的 persistent state allowed 值为 false,则该对象的 cdm instance不得由于 对此对象或其创建的任何会话进行操作而持久化状态或 访问先前持久化的状态。

如果 MediaKeys 对象的 persistent state allowed 值为 true,则该对象的 cdm instance可以由于 对此对象或其创建的任何会话进行操作而持久化状态或访问 先前持久化的状态。

持久化数据必须始终以只有此对象的 Document可以访问它的方式存储。 此外,该数据必须只能由当前 浏览配置文件访问;其他浏览配置文件、用户代理和 应用绝不能 访问所存储的数据。见存储在 用户设备上的信息

在支持持久性存储时,另请参见10. 安全11. 隐私中的其他考虑事项。

6. MediaKeySession 接口

MediaKeySession 对象表示一个密钥会话

当且仅当 MediaKeySession 对象的 closed 属性已被 resolve 时, 该对象才是已关闭的。

对于每个未关闭MediaKeySession 对象,用户代理持续执行监视 CDM 状态变化算法。 监视 CDM 状态变化算法必须与主事件循环并行运行,但不得与本规范中定义为并行运行的 其他过程并行运行。

如果 MediaKeySession 对象未关闭,且创建它的 MediaKeys 对象仍然可访问,则该 MediaKeySession 对象不得 被销毁,并且继续接收事件。否则,不再可访问的 MediaKeySession 对象不得 再接收事件,并且可以被销毁。

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

如果 MediaKeySession 对象在变为页面不可访问时 尚未关闭,则 CDM 关闭与该 对象关联的密钥会话

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

最佳实践 4: 当 你需要在执行另一个操作之前确保密钥会话已经关闭时,调用 close() 并等待返回的 promise; 不要依赖自动会话关闭的时机。

密钥会话具体何时关闭是实现细节。

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

拒绝 promise 时,算法的以下步骤总是会被中止。

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

MediaKeySessionClosedReason 枚举定义如下:

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

这可能由于许多原因发生,包括设备休眠、监视器 配置更改等。硬件上下文重置的确切原因取决于 实现。

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

这意味着应用遇到了特定于设备的会话资源限制。 如果已关闭的会话由于某种原因仍然需要,应用开发者应考虑某种策略来减少 所需会话数量,或主动关闭不需要的会话。

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,只读

此对象及其关联密钥或许可证的会话 ID

expiration 类型为 unrestricted double, 只读

会话中所有密钥的过期时间,或者如果不存在这样的 时间,或许可证明确永不过期,则为 NaN,具体由 CDM 确定。 此值可能在会话生命周期内发生变化,例如当某个操作触发 窗口的开始时。

closed 类型为 Promise<MediaKeySessionClosedReason>, 只读

表示对象因运行会话已关闭算法而变为已关闭的时间。此 promise 只能被 fulfilled,且永不会被拒绝。

keyStatuses 类型为 MediaKeyStatusMap, 只读

指向一个只读映射的引用,该映射把会话已知密钥 ID映射到关联密钥的当前 状态。每个条目必须具有唯一的密钥 ID。

每当事件循环运转时,映射条目及其值都可能被更新。该 映射永远不会处于不一致或部分更新的状态,但如果两次访问之间 事件循环发生运转,则它可能在访问之间发生变化。密钥 ID 可能会作为 load()update() 调用的结果而被添加。 密钥 ID 可能会作为移除 现有密钥知识(或用一组新密钥替换现有密钥集)的 update() 调用 的结果而被移除。如果密钥变得不可用,例如由于过期,则不会移除密钥 ID。相反,此类 密钥必须被赋予适当的状态,例如“expired”。

某些较旧的平台可能包含不暴露 密钥 ID 的密钥系统实现, 从而无法提供符合要求的用户代理实现。在 这种情况下,为了最大化互操作性,当非空列表 合适时(例如,当此对象表示的密钥会话可以包含 密钥时),该映射会填充为一个单独的键值对, 其中包含单字节密钥 ID 0,以及最适合此对象聚合状态的 MediaKeyStatus

onkeystatuseschange 类型为 EventHandler

keystatuseschange 事件的 事件处理器。

onmessage 类型为 EventHandler

message 事件的 事件处理器。

6.2 方法

generateRequest()

基于 initData 生成许可证请求。如果算法成功且 promise 被兑现, 则始终会排队一个类型为 "license-request" 或 "individualization-request" 的 message

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

  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 值表示的密钥系统实现不支持将 initDataType 作为 初始化数据 类型,则返回一个以 NotSupportedError 拒绝的 promise。 字符串比较区分大小写。

  7. init data获取由 initData持有的字节副本的结果。

  8. session type 为此对象的 session type

  9. promise 为一个新的 promise。

  10. 并行运行以下步骤:

    1. 如果 init datainitDataType 无效,则用新创建的 TypeError 拒绝 promise

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

      用户代理必须在将初始化 数据传递给 CDM 之前对其进行彻底验证。这包括验证 字段的长度和值是否合理,验证值是否在合理限制内, 并剥离无关、不受支持或未知的数据或字段。 建议用户代理预解析、清理 和/或生成初始化数据的完全清理版本。如果由 initDataType 指定的初始化 数据格式支持多个条目, 用户代理应该移除 CDM 不需要的条目。用户代理不得重新排序初始化 数据中的条目。

    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. 如果 cdm 不支持 sanitized init data, 则用 NotSupportedError 拒绝 promise

      2. 按以下列表中 session type 值对应的步骤操作:

        "temporary"

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

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

        "persistent-license"

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

      3. session id 为唯一的会话 ID 字符串。

        如果在 session type 上运行是否为持久会话 类型?算法的结果为 true,则该 ID 必须在此对象的 Document中随时间保持唯一,包括 跨 Document 和浏览会话。

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

          CDM cdm 不得使用任何未通过 sanitized init data 提供的流特定 数据,包括媒体 数据

          CDM cdm 此时不应该 存储会话数据, 包括会话 ID。参见会话存储和 持久化

        2. message type 为 "license-request"。

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

          在随后对 update() 的调用中,CDM 必须基于 sanitized init datarequested license type 生成许可证请求,该数据按 initDataType 解释。

        2. message type 反映 message 的类型,即 "license-request" 或 "individualization-request"。

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

      1. 如果前述任一步骤因资源不足而失败,则用 QuotaExceededError 拒绝 promise

      2. 如果前述任一步骤因任何其他原因失败,则用一个新的 DOMException 拒绝 promise,其名称为适当的错误名称

      3. sessionId 属性设置为 session id

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

      5. undefined 兑现 promise

      6. session 上运行排队一个 "message" 事件 算法,并提供 message typemessage

  11. 返回 promise

load()

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

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

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

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

  3. 令此对象的 uninitialized 值为 false。

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

  5. 如果在此对象的 session type 上运行是否为持久会话类型? 算法的结果为 false,则返回一个以新创建的 TypeError 拒绝的 promise。

  6. origin 为此对象的 Document

  7. promise 为一个新的 promise。

  8. 并行运行以下步骤:

    1. sanitized session ID 为经过验证和/或清理的 sessionId 版本。

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

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

    3. 如果此对象的 Document 中存在一个尚未关闭MediaKeySession 对象, 且其 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 dataorigin 中为 sanitized session ID 存储的数据。此数据不得包含 来自其他源的数据或不与任何源关联的数据。

      4. 如果任何 Document 中存在一个尚未关闭MediaKeySession 对象,并且该对象表示 session data,则用 QuotaExceededError 拒绝 promise

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

      5. 加载 session data

      6. 如果 session data 指示会话的过期 时间,则令 expiration time 为该过期时间。

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

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

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

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

      1. 如果前述任一步骤失败,则用适当的错误名称 拒绝 promise

      2. sessionId 属性设置为 sanitized session ID

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

      4. 如果已加载会话包含有关任何密钥的信息(存在 已知密钥),则在 session 上运行更新 密钥状态算法,并提供每个密钥的密钥 ID 以及适当的 MediaKeyStatus

        如果需要额外处理才能确定密钥状态, 则使用 "status-pending"。 一旦一个或多个密钥的额外处理完成, 再次运行更新 密钥状态算法,并使用实际状态。

      5. session 上运行更新过期时间算法, 并提供 expiration time

      6. true 兑现 promise

      7. 如果 message 不为 null,则在 session 上运行排队一个 "message" 事件 算法,并提供 message typemessage

  9. 返回 promise

update()

CDM 提供消息,包括许可证。

response 参数包含要提供给 CDM 的消息。 其内容是密钥系统特定的。它不得 包含可执行代码。

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

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

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

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

  4. response copy获取由 response持有的字节副本的结果。

  5. promise 为一个新的 promise。

  6. 并行运行以下步骤:

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

      用户代理在将 response 传递给 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 中包含的许可证/密钥 以及相关会话数据。此类数据必须 以只有此对象的 Document 能够访问的方式存储。
        否则

        用新创建的 TypeError 拒绝 promise

        另见会话存储和 持久化

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

        sanitized response 包含密钥和/或 相关数据时,cdm 很可能会(在内存中)存储以密钥 ID 索引的密钥 及相关数据。

        会话内的替换算法依赖于密钥 系统

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

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

        运行以下步骤:

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

          之后用此对象的 sessionId 的值调用 load() 将会失败,因为 没有为该会话 ID 存储任何数据。

        2. session closed 设置为 true。

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

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

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

        1. message 为该消息。

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

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

      1. 如果 session closed 为 true:

        在此对象上运行会话已关闭 算法,原因为 "release-acknowledged"。

        否则:

        运行以下步骤:

        1. 如果此对象的 CDM 已知密钥集合发生变化,或任何密钥的状态发生变化,则在 session 上运行更新密钥 状态算法, 并提供每个已知密钥的密钥 ID 以及适当的 MediaKeyStatus

          如果需要额外处理才能 确定密钥状态,则使用 "status-pending"。 一旦一个或多个密钥的额外 处理完成,则再次运行 更新密钥 状态算法,并使用实际 状态。

        2. 如果会话的过期时间 发生变化,则在 session 上运行 更新 过期时间算法, 并提供新的过期时间。

        3. 如果前述任一步骤失败,则用一个新的 DOMException 拒绝 promise, 其名称为适当的 错误名称

        4. 如果 message 不为 null,则在 session 上运行排队一个 "message" 事件算法,并提供 message typemessage

      2. undefined 兑现 promise

  7. 返回 promise

close()

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

当请求已被处理时,返回的 promise 会被兑现;当会话关闭时, closed 属性 promise 会以 "closed-by-application" 兑现。

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

  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 关闭与此对象关联的密钥会话

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

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

      1. undefined 兑现 promise

      2. 在此对象上运行会话 已关闭算法,原因为 "closed-by-application"。

  6. 返回 promise

remove()

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

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

  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. 排队 一个任务以运行以下步骤:

      1. session 上运行更新密钥状态 算法, 并提供会话中所有密钥 ID 以及每个密钥对应的 "released" MediaKeyStatus 值。

      2. session 上运行更新过期时间算法, 并提供 NaN

      3. 如果前述任一步骤失败,则用一个新的 DOMException 拒绝 promise, 其名称为适当的错误名称

      4. message type 为 "license-release"。

      5. undefined 兑现 promise

      6. 如果 message 不为 null,则在 session 上运行排队一个 "message" 事件算法,并提供 message typemessage

  5. 返回 promise

6.3 MediaKeyStatusMap 接口

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

密钥的状态独立于该密钥当前是否正在使用,也独立于媒体 数据。

例如,如果某个密钥有当前无法满足的输出要求,则该密钥的 状态应根据情况为 "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, 只读

已知密钥的数量。

6.3.2 方法

has()

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

get()

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

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

要迭代的值对是由所有已知密钥密钥 ID及关联的 MediaKeyStatus 值组成的对集合的快照, 并按密钥 ID排序。密钥 ID 按如下方式比较:对于长度为 m 的密钥 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 确定该密钥当前可用于解密
当前可能不可用于解密的密钥不得具有 此状态。
expired 该密钥因其过期时间已 过去而不再可用于解密
expiration 属性表示的时间必须 早于当前时间。会话中的所有其他密钥必须 具有此状态。
released 密钥本身不再可供 CDM 使用,但关于 该密钥的信息,例如许可证销毁记录, 是可用的。
output-restricted 存在与该密钥关联且当前无法满足的输出限制。 使用此密钥解密的媒体数据在必要时可根据输出限制被阻止呈现。 应用应避免使用会触发与该密钥关联的输出限制的流。
output-downscaled 存在与该密钥关联且当前无法满足的输出限制。 使用此密钥解密的媒体数据在必要时可根据输出限制以较低质量(例如 分辨率)呈现。应用 应避免使用会触发与该密钥关联的输出限制的流。
支持降级缩放是可选的。应用不应该依赖降级缩放来 确保在无法满足输出要求时不中断播放。
usable-in-future 该密钥尚未可用于解密, 因为开始时间在 未来。当达到其开始时间时,该密钥将变得可用。
status-pending 该密钥的状态尚未知,且正在确定中。一旦确定, 该状态将用实际状态更新。
internal-error 该密钥因 CDM 中与其他值无关的错误而当前不可用于解密。此值 不能由应用采取行动处理。

MediaKeyMessageEvent 对象用于 message 事件。

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

MediaKeyMessageType 定义如下:

枚举描述
license-request 消息包含对新许可证的请求。
license-renewal 消息包含对现有许可证续订的请求。
license-release 消息包含许可证销毁记录
individualization-request 消息包含对应用辅助个体化(或 重新个体化)的请求。
与所有其他消息一样,消息中的任何标识符必须 每源且每配置文件可区别,并且 绝不能可区别永久 标识符
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,只读
消息的类型。

实现绝不能要求应用处理消息 类型。 实现必须支持不区分消息的应用,并且 绝不能要求应用处理消息类型。 具体而言,密钥系统必须支持 将所有类型的消息传递给单个 URL。

此属性允许应用在不解析消息的情况下区分消息。 它旨在启用可选的应用和/或服务器优化, 但应用不需要使用它。

message 类型为 ArrayBuffer, 只读
来自 CDM 的消息。消息特定于密钥 系统

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" 事件

“排队一个 "message" 事件”算法会将一个 message 事件排队到 MediaKeySession 对象。运行此算法的请求包括目标 MediaKeySession 对象、 一个 message type 和一个 message

message绝不能包含可区别永久标识符, 即使是以加密形式。若 MediaKeySession 对象的 use distinctive identifier 值为 false,则 message绝不能包含可区别标识符,即使 是以加密形式。

运行以下步骤:

  1. session 为指定的 MediaKeySession 对象。

  2. 排队一个 任务,以使用 MediaKeyMessageEvent 接口创建一个名为 message 的事件; 该事件不冒泡且不可取消,其 type 属性设置为 message,其 isTrusted 属性初始化为 true,并将其分派到 session

    事件接口 MediaKeyMessageEvent 具有:

6.6.2 更新 密钥状态

“更新密钥状态”算法会更新 MediaKeySession已知密钥集合, 或更新一个或多个密钥的状态。运行此 算法的请求包括目标 MediaKeySession 对象,以及一个由 密钥 ID 和关联的 MediaKeyStatus 组成的对序列。

该算法总是在任务中运行。

运行以下步骤:

  1. session 为关联的 MediaKeySession 对象。

  2. input statuses 为密钥 ID 和关联的 MediaKeyStatus 对序列。

  3. statusessessionkeyStatuses 属性。

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

    1. 清空 statuses

    2. input statuses 中的每个对。

      1. pair 为该对。

      2. statuses 插入一个以 pair 的密钥 ID 为键的条目, 其值为 pairMediaKeyStatus 值。

    这些步骤的效果是,sessionkeyStatuses 属性内容会被替换,而不会使对该属性的 现有引用失效。从脚本的角度看,此替换是原子的。 也就是说,脚本永远不会看到部分填充的序列。

  5. 排队一个 任务,以在 session触发一个事件,其名称为 keystatuseschange

  6. 排队一个 任务,以在每个媒体元素上运行 必要时尝试恢复播放算法; 这些媒体元素的 mediaKeys 属性是 创建该 sessionMediaKeys 对象。

6.6.3 更新 到期时间

“更新到期时间”算法会更新 MediaKeySession到期时间。 运行此算法的请求包括目标 MediaKeySession 对象和新的 到期时间,该时间可以是 NaN

该算法总是在任务中运行。

运行以下步骤:

  1. session 为关联的 MediaKeySession 对象。

  2. expiration timeNaN

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

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

6.6.4 会话 已关闭

会话已关闭算法会在密钥会话已被 CDM 关闭后,更新 MediaKeySession 状态。运行此算法的请求包括一个 目标 MediaKeySession 对象 和一个 MediaKeySessionClosedReason

该算法始终在一个任务中运行。

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

CDM 可以在任何时候关闭会话,例如当该 会话不再 需要时,或当系统资源丢失时。在这种情况下,监视 CDM 状态变化算法会检测该变化并运行此算法。

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

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

运行以下步骤:

  1. session 为关联的 MediaKeySession 对象。

  2. promisesessionclosed 属性。

  3. 如果 promise 已兑现,则中止这些步骤。

  4. sessionclosing or closed 值设置为 true。

  5. session 上运行更新密钥状态 算法,并提供一个 空序列。

  6. session 上运行更新过期时间算法, 并提供 NaN

  7. 用所提供的原因兑现 promise

6.6.5 监视 CDM 状态变化

监视 CDM 状态变化算法会在 CDM 状态的各个 方面发生变化时执行所需步骤。

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

该算法始终与主事件循环并行运行。

运行以下步骤:

  1. sessionMediaKeySession 对象。

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

  3. 如果 cdm 有尚未发送的外发消息,则排队一个 任务以执行以下步骤:

    1. message typemessage 分别为消息类型和 消息。

    2. 运行排队一个 "message" 事件算法,传入 sessionmessage typemessage

  4. 如果 cdm 已更改 session 已知的密钥集合,或 一个或多个密钥的状态,则排队一个 任务以执行以下 步骤:

    1. statuses 为密钥 ID 和 MediaKeyStatus 值对的列表, 其中包含 session 已知的每个密钥 对应的一个值对。

    2. 运行更新密钥 状态算法,传入 sessionstatuses

  5. 如果 cdm 已更改 session过期时间, 则排队一个 任务以执行以下步骤:

    1. expiration timesession 的新过期时间。

    2. 运行更新过期时间 算法,传入 sessionexpiration time

  6. 如果 cdm 已关闭 session,则排队一个 任务以在 session 上运行 会话已关闭算法, 并提供适当的 MediaKeySessionClosedReason 值。

  7. 如果 cdm 因硬件上下文重置而变得不可用,则排队一个 任务以运行CDM 不可用算法, 原因为 "hardware-context-reset"。

  8. 如果 cdm 因任何其他原因而变得不可用,则排队一个 任务以 运行CDM 不可用算法,原因为 "internal-error"。

6.7 异常

这些方法通过使用简单异常 [WEBIDL] 或 DOMException 拒绝返回的 promise 来报告错误。算法中使用了以下来自 [WEBIDL] 的 简单 异常DOMException 名称。 算法中指定的原因列在每个名称旁边,尽管这些名称也可以 因其他原因使用。

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

6.8 会话存储与持久性

本节提供会话存储和持久化的概述,以补充这些 算法。

除了存储和持久化中的要求外,还适用以下要求。

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

本节其余部分适用于是否为持久会话类型?算法返回 true 的会话类型。

CDM 不应该存储会话数据,包括 会话 ID,直到 首次调用 update()。具体而言,CDM 不应该generateRequest() 算法期间存储会话数据。这 确保应用知道该会话,并知道它最终需要 移除该会话。

当会话被清除时,与该会话关联的所有数据必须被清除, 例如在 update() 中处理 许可证销毁记录 确认时。参见持久化 数据

CDM 必须确保给定会话的数据 只存在于一个尚未在任何 Document关闭MediaKeySession 对象中。 换言之,当已经有一个 MediaKeySession 表示由 sessionId 参数指定的会话时,load() 必须失败, 这可能是因为通过 generateRequest() 创建它的对象仍处于活动状态,或者它已通过 load() 加载到另一个 对象中。只有曾表示该会话的所有对象都已关闭时,会话才可以再次加载。

使用是否为持久会话类型?算法返回 true 的类型创建会话的应用,应该随后移除已存储的数据,方法是先 使用 remove() 发起移除过程,然后确保 该移除过程成功完成,该过程可能涉及消息交换。CDM 也可以在适当时移除会话,但应用不应该依赖 这一点。

支持持久化存储时的其他考虑事项,参见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,只读,可空

在为此媒体元素解密加密媒体数据时使用的 MediaKeys

onencrypted 类型为 EventHandler

encrypted 事件的事件处理器。所有 HTMLMediaElement必须同时支持它作为内容属性和 IDL 属性。

onwaitingforkey 类型为 EventHandler

waitingforkey 事件的事件处理器。 所有 HTMLMediaElement必须同时支持它作为内容属性和 IDL 属性。

7.2 方法

setMediaKeys()

提供在播放期间解密媒体数据时要使用的 MediaKeys

在播放期间支持清除或替换关联的 MediaKeys 对象 是实现质量问题。在许多情况下,这会导致糟糕的用户 体验或被拒绝的 promise。

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

  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 实例已被 另一个媒体元素使用

      • 用户代理无法将其与此元素一起使用

      则运行以下步骤:

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

        1. 令此对象的 attaching media keys 值为 false。

        2. QuotaExceededError 拒绝 promise

      2. 中止这些步骤。

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

      1. 如果用户代理或 CDM 不支持 移除关联,则运行 以下步骤:

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

          1. 令此对象的 attaching media keys 值 为 false。

          2. NotSupportedError 拒绝 promise

        2. 中止这些步骤。

      2. 如果当前无法移除该关联,则运行以下步骤:

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

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

          1. 令此对象的 attaching media keys 值 为 false。

          2. InvalidStateError 拒绝 promise

        2. 中止这些步骤。

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

      4. 如果前一步失败,则运行以下步骤:

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

          1. 令此对象的 attaching media keys 值 为 false。

          2. 用适当的错误名称拒绝 promise

        2. 中止这些步骤。

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

      1. 将由 mediaKeys 表示的 CDM 实例与 媒体元素关联,以解密媒体 数据

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

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

          1. mediaKeys 属性设置为 null。

          2. 令此对象的 attaching media keys 值 为 false。

          3. 用一个新的 DOMException 拒绝 promise,其名称 为适当的错误 名称

        2. 中止这些步骤。

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

      1. mediaKeys 属性设置为 mediaKeys

      2. 令此对象的 attaching media keys 值为 false。

      3. undefined 兑现 promise

      4. 如果 mediaKeys 不为 null,则在媒体元素上运行必要时尝试 恢复播放算法。

  6. 返回 promise

7.3 MediaEncryptedEvent 接口

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,只读
指示 initData 属性中包含的 初始化数据初始化数据类型
initData 类型为 ArrayBuffer, 只读,可空
该事件的初始化数据

7.3.2 MediaEncryptedEventInit

WebIDLdictionary MediaEncryptedEventInit : EventInit {
  DOMString    initDataType = "";
  ArrayBuffer? initData = null;
};
7.3.2.1 字典 MediaEncryptedEventInit 成员
initDataType 类型为 DOMString, 默认值为 ""
初始化数据 类型
initData 类型为 ArrayBuffer, 可空,默认值为 null
初始化数据

7.4 事件概要

本节为非规范性内容。

事件名称 接口 分派时机... 前提条件
encrypted MediaEncryptedEvent 用户代理在初始化数据中遇到媒体数据 该元素的 readyState 等于或大于 HAVE_METADATA

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

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

7.5 算法

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

“媒体数据可能包含加密块”算法会在用户代理要求在播放媒体数据之前指定 MediaKeys 对象时暂停播放。 运行此算法的请求包括目标 HTMLMediaElement 对象。

运行以下步骤:

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

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

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

    1. media element 上运行等待密钥算法。

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

7.5.2 遇到初始化数据

“遇到初始化数据”算法会为在媒体 数据中遇到的 初始化数据排队一个 encrypted 事件。运行此算法的请求包括一个目标 HTMLMediaElement 对象。

运行以下步骤:

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

  2. initDataType 为空字符串。

  3. initData 为 null。

  4. 如果媒体数据CORS 同源不是 混合内容,则运行以下步骤:

    1. initDataType 为表示初始化数据的初始化 数据类型的字符串。

    2. initData 为初始化数据。

    虽然媒体元素可以允许加载“可升级内容” [MIXED-CONTENT],但用户代理不得将此类 媒体数据中的初始化数据暴露给应用。

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

    事件接口 MediaEncryptedEvent 具有:

    readyState 不会改变,也没有算法 被中止。此事件仅提供信息。

    如果媒体数据不是 CORS 同源 或是混合内容,则 initData 属性 将为 null。应用可以 从备用来源获取初始化数据。

7.5.3 遇到加密块

“遇到加密块”算法会将一个加密媒体数据块排队等待 解密,并在可能时尝试解密。运行此算法的请求包括 目标 HTMLMediaElement 对象。

运行以下步骤:

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

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

  3. block 添加到 media elementencrypted block queue 末尾。

  4. 如果 media elementdecryption blocked waiting for key 值为 false,则运行尝试解密算法。

7.5.4 尝试 解密

“尝试解密”算法会尝试解密已排队等待 解密的媒体数据。运行此算法的请求包括一个目标 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. 运行媒体 数据已损坏 步骤,该步骤属于资源 获取算法

      2. media keys 上运行CDM 不可用算法;若为硬件上下文重置,则原因为 "hardware-context-reset", 否则为 "internal-error"。

      3. 中止这些步骤。

    4. 如果至少存在一个由 media keys 创建且未关闭MediaKeySession,则运行以下 步骤:

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

      1. blockmedia elementencrypted block queue 中的第一个条目。

      2. block key IDblock 的密钥 ID。

        密钥 ID 通常由容器指定。

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

        1. available keys 为由 media keys 创建的会话中 密钥的并集。

        2. block key 为 null。

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

          如果多个会话包含可用于 block key ID解密的密钥, 要使用哪个会话和密钥取决于密钥 系统

        4. 如果运行前一步导致 available keys 中任何密钥的状态发生变化, 则排队 一个任务,在每个受影响的 session 上运行 更新密钥状态 算法,并提供该会话中的所有密钥 ID以及每个密钥对应的 适当 MediaKeyStatus 值。

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

          1. 使用 cdm,用 block key 解密 block

          2. 按照以下列表中第一个匹配条件的步骤执行:

            如果解密失败
            1. 运行媒体 数据 已损坏步骤,该步骤属于资源 获取算法

            2. 如果 cdm 不再可用,则在 media keys 上运行CDM 不可用算法;对于硬件上下文重置,原因为 "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密钥时,会到达此步骤。

    一旦用户代理已渲染无法解密的块之前的块(尽其所能,例如所有完整的视频帧), 它将运行等待密钥算法。

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

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

  1. encrypted 为 false。

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

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

  4. 提供该帧用于渲染。

7.5.5 等待 密钥

“等待密钥”算法会排队一个 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. 排队一个 任务,以在 media element触发一个事件,其名称为 waitingforkey

  6. 暂停播放。

7.5.6 必要时尝试恢复播放

“必要时尝试恢复播放”算法会在媒体元素因等待密钥而被阻塞且所需密钥当前 可用于解密时恢复播放。运行此 算法的请求包括目标 HTMLMediaElement 对象。

运行以下步骤:

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

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

  3. media element 上运行尝试解密 算法。

  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 事件不会(或不太可能) 考虑当前密钥之外的密钥可用性。

      就绪状态的变化还可能导致 HTMLMediaElement 事件按此处所述 被触发。

7.6 媒体元素限制

本节为非规范性内容。

CDM 处理的媒体数据可以无法通过 Web 平台 API 以通常方式获得(例如使用 CanvasRenderingContext2DdrawImage() 方法,以及 AudioContext MediaElementAudioSourceNode)。 本规范不定义这种媒体数据不可用的条件;不过,如果媒体数据无法通过这类 API 获得,则它们可以表现得好像根本不存在媒体数据。

在不是由 UA 执行媒体渲染的情况下,例如基于硬件的媒体管线, 可能无法使用完整的 HTML 渲染能力,例如 CSS Transforms。一种可能的限制是,视频媒体 可以被 限制为只出现在边与窗口边缘平行且方向正常的矩形区域中。

8. 实现要求

本节定义实现要求——适用于用户代理和密钥系统,包括 CDM 和服务器——这些要求可能未在 算法中明确说明。这里以及整个规范中的要求适用于所有实现, 无论 CDM 是独立于用户代理,还是用户代理的一部分。

8.1 CDM 约束

用户代理实现者必须确保 CDM 不访问任何并非使用本规范特性播放受保护媒体 合理所需的信息、 存储 或系统能力。具体而言,CDM 不得

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

8.2 消息与通信

所有发往和来自 CDM 的消息和通信,例如 CDM 与 许可证服务器之间的通信,必须经由用户代理传递。CDM 不得发起直接的 带外网络请求。除直接个性化中描述的那些以外的所有消息和通信必须 通过本规范中定义的 API 经由应用传递。具体而言,所有 包含应用、或 内容特定信息的通信,或者 发送到由应用指定或基于其源的 URL 的通信,必须通过 这些 API。这包括所有许可证交换消息。

8.3 持久化数据

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

8.3.1 使用特定于源和特定于浏览配置文件的密钥系统 存储

可能以应用或许可证服务器可见的方式影响消息或行为的持久化数据,必须以特定于 和特定于浏览配置文件的方式存储,并且绝不能泄漏到私密浏览会话或从私密浏览会话泄漏。 具体而言但不限于此,会话数据、许可证、密钥和每源标识符必须以及按浏览配置文件存储。

会话存储与持久性

8.3.2 允许清除持久化数据

使用持久化数据的实现必须允许用户清除该数据, 使其无论是在外部(例如通过本规范定义的 API)还是在客户端设备上 都不再可检索。

用户代理应当

  • 将持久化数据视为其他站点数据,例如 cookie [COOKIES]。 具体而言:

    • 允许用户将持久化数据与 cookie [COOKIES] 和其他 站点数据一起清除。

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

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

    • 在与其他站点数据相同的 UI 位置呈现持久化数据。

  • 允许用户按每个 和每个浏览配置文件清除持久化数据,尤其是作为 “忘记此站点”功能的一部分,该功能会忘记与特定站点相关联的 cookie [COOKIES]、数据库等。

  • 确保清除持久化数据的操作具有足够的原子性,以防止出现 “cookie 复活”类型的情形,即依赖另一种未同时清除的本地存储数据, 将新的标识符与旧标识符重新关联。见数据清除不完整

  • 以帮助用户理解数据清除不完整可能性的方式呈现这些接口, 并使他们能够同时删除与所有持久化数据功能相关联的数据,包括 cookie [COOKIES] 和 web storage。

  • 以帮助用户理解数据清除不完整可能性的方式呈现 禁用和重新启用密钥系统 的接口,并使他们能够同时删除所有持久化存储功能中的 所有此类数据。

  • 允许用户按 和/或针对所有 源专门删除持久化数据。

8.3.3 加密或混淆持久化数据

用户代理应当将持久化数据视为潜在敏感数据;释放这些信息 很可能会损害用户隐私。为此, 用户代理应当确保安全存储持久化数据,并且在删除 数据时,将其及时从底层存储中删除。

8.4 暴露给应用的值

暴露给应用或可由应用推断的值(例如通过 CDM 的使用), 可能被用于标识客户端或用户,无论它们是否设计为 标识符。本节定义了避免或至少缓解此类问题的要求。对于标识符还有其他要求。

8.4.1 使用每源每配置文件值

暴露给应用或可由应用推断的所有可区别值必须按每个 浏览配置文件保持唯一。也就是说,一个 使用本规范定义的 API 时所使用的值,必须不同于任何其他 源使用这些 API 时所使用的值;并且一个浏览 配置文件中使用的值,必须不同于任何其他配置文件中使用的值, 无论源如何。此类值绝不能泄漏到私密浏览会话或从私密浏览会话泄漏。

跨源和跨配置文件的值必须不能被应用关联,这意味着 绝不能能够关联来自多个源或多个配置文件的值,例如 用来确定它们来自同一客户端或用户。具体而言,从与源无关和/或与配置文件无关的值 派生每源值的实现,必须以确保上述不可关联属性的方式执行, 例如使用具有适当不可逆属性的派生函数。

8.4.2 允许清除值

作为允许清除持久化数据中要求的结果, 暴露给应用的所有持久化值必须可清除, 使这些值无论是在外部(例如通过本规范定义的 API)还是在客户端 设备上都不再可检索、可观察或可推断。

一旦被清除,当随后需要这些值时,必须生成新的不能被应用关联的值。

8.5 标识符

实现使用标识符,尤其是可区别 标识符或可区别永久标识符,会带来隐私 问题。本节定义了避免或至少缓解此类问题的要求。 暴露给应用的值的要求也适用于 暴露给应用的标识符。

8.5.1 限制或避免使用可区别标识符和永久标识符

8.5.2 加密标识符

显著标识符显著永久 标识符在暴露到客户端外部时,必须在消息交换层面加密。 所有其他标识符在暴露到客户端外部时,在消息交换层面加密。 该 加密必须确保标识符密文的任何两个实例 只有拥有解密密钥的实体才能 关联

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

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

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

  • 作为个性化的一部分。

CDM 必须验证加密密钥 属于其密钥系统的有效服务器。对于暴露给 应用的标识符,这可以使用 服务器证书来实现。

服务器不得向发送该显著标识符CDM 以外的任何实体暴露该标识符。

具体而言,不应将其提供给应用,也不应在发往 CDM 的消息中以未加密形式包含它。这可以通过加密该标识符或 使用该标识符加密消息来实现,或者使其只能由该特定的 CDM 解密。

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

可区别永久标识符外, 所有标识符必须按每个 浏览配置文件保持唯一。见 8.4.1 使用每源每配置文件值

8.5.4 使用不可关联标识符

实现暴露给应用的所有标识符,包括可区别标识符, 即使是以加密形式,必须在跨浏览配置文件以及标识符清除后,都不能被应用关联

8.5.5 允许清除标识符

作为允许 清除持久数据中要求的结果,除显著永久 标识符之外,所有潜在标识符或显著值 必须是可清除的,使这些值无论是在外部(例如通过本规范中 定义的 API),还是在客户端设备上,都不再可检索、可观察或可推断。

使用显著 标识符的实现必须允许用户清除 显著标识符使用显著 永久标识符的实现必须允许用户清除与显著永久标识符相关联的值。

一旦被清除,当随后需要诸如显著标识符之类的值时, 必须生成新的不可由 应用关联的值。

8.6 个体化

标识符,尤其是显著标识符,有时 通过称为个性化或配置的过程生成或获得。所得到的标识符必须 不可由应用关联,并且对它们的使用必须仅 暴露给来自单一配置文件的单一 来源。该过程可以执行多次,例如 在标识符被清除之后。

该过程必须用户代理直接执行,或通过应用执行。两种个性化类型的机制、 流程和限制不同,如以下各节所述。使用哪种方法取决于 CDM 实现以及 本规范要求的应用,尤其是以下要求。

distinctiveIdentifier 控制是否可以使用显著标识符显著永久 标识符, 包括用于 个性化。具体来说,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccessdistinctiveIdentifier 成员的值为 "required" 时,才可以使用此类标识符。

8.6.1 直接个体化

直接个性化在 CDM 与一个源无关且 应用无关的服务器之间执行。尽管该服务器与源无关,个性化的结果 使 CDM 能够按照本规范的其他要求提供特定于源的标识符。 该过程必须由用户 代理执行,并且不得使用本规范中定义的 API。

例如,这样的过程可以通过与由用户代理或 CDM 供应商托管的预定服务器通信, 初始化客户端设备和/或为每个源获取一个可清除的标识符,该标识符用于单个浏览配置文件,可能使用 显著永久标识符或来自 客户端设备的其他永久标识符

对于此类个性化,所有消息交换:

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

  • 不得CDM 直接执行。

  • 不得通过本规范中定义的 API 传递给应用或经由应用 传递。

  • 必须发送到独立于任何源和 应用选择的 URL。

  • 必须加密所有显著标识符显著 永久标识符

  • 必须使用 TLS。

实现不得暴露、 源或 应用特定信息,或与源可关联的值给 集中式服务器,即使以加密形式也不行,因为这可能会创建用户或设备访问过的 所有源的集中记录。

8.6.2 应用辅助个体化

应用辅助个性化在 CDM 与应用之间执行, 包括应用选择的服务器,并产生一个按来源区分的标识符。该 过程必须通过本规范中定义的 API 执行,并且 不得 涉及其他通信方法。与 API 的所有其他使用一样,该过程 可以使用一个或多个显著 标识符,但它 不得使用显著永久 标识符或非特定于来源的值, 即使以加密形式也不可以。如果该过程使用一个或多个显著 标识符,则所得标识符按定义也是一个 显著标识符

对于此类个性化,所有消息交换:

可关联值(包括显著标识符)在该过程中被使用时,实现不得向集中式服务器暴露来源、 来源或应用特定信息,或与来源可关联的值, 即使以加密形式也不可以,因为这可能 创建一个记录用户或设备访问过的所有来源的中央记录。

在采取适当预防措施的情况下,此类个性化可以提供比 直接个性化更好的隐私保护,尽管不如 不使用显著 标识符的模型。为了保留这种设计的好处,并避免 引入其他隐私问题,此类实现以及支持它们的应用 避免将个性化消息延迟或转发到 中央服务器或其他不受应用作者控制的服务器。

8.7 支持多个密钥

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

如何支持多个密钥的机制属于实现细节,但它必须 对应用和本规范定义的 API 保持透明。

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

8.8 初始化数据类型支持

8.8.1 生成的许可证独立于内容类型

实现应当允许使用它们支持的任何初始化数据类型 生成的许可证与任何内容类型一起使用。

否则,requestMediaKeySystemAccess() 算法可能会,例如,因为某个 initDataTypes 不支持与某个 videoCapabilities 一起使用,而拒绝一个 MediaKeySystemConfiguration

8.8.2 支持从媒体数据中提取

对于任何可能出现在受支持容器中的、受支持的初始化数据类型, 用户代理必须支持从每个此类受支持容器中提取 该类型的初始化数据

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

8.9 支持的媒体

本节定义了本规范实现所支持的内容(媒体 资源)的属性。

8.9.1 未加密容器

媒体容器绝不能被加密。本规范依赖于用户代理 无需解密任何媒体数据即可解析媒体容器的能力。这包括 遇到加密块遇到初始化数据 算法,以及支持标准 HTMLMediaElement [HTML] 功能,例如跳转

8.9.2 可互操作加密

媒体资源,包括所有 轨道,必须按容器特定的“common encryption”规范 加密和打包,该规范允许在提供一个或多个密钥时,以完全指定且兼容的方式 解密内容。

Encrypted Media Extensions 流 格式注册表 提供了对此类流格式的引用。

8.9.3 未加密的带内支持内容

带内支持内容,例如字幕、描述性音频和文字记录,不应 被加密。

此类轨道的解密——尤其是使其能被提供回用户代理的解密——通常不受实现支持。 因此,加密此类轨道会阻止它们在用户代理实现中被广泛用于无障碍功能。

为确保无障碍信息以可用形式提供,对于选择支持加密带内支持内容的实现: a) CDM 必须将 解密后的数据提供给用户代理,并且 b) 用户代理必须以与等效未加密支持内容 相同的方式处理它,例如将其暴露为 定时文本轨道 [HTML]。

9. 公共密钥 系统

所有用户代理必须支持本节所述的公共密钥系统

这确保所有用户代理(包括完全开源的用户代理)都保证支持一个公共的基本功能级别。 因此,只需要基本解密的内容提供者可以构建简单应用, 它们无需与任何内容保护提供者合作即可在所有平台上运行。

9.1 Clear Key

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

9.1.1 能力

以下内容描述 Clear Key 如何支持密钥系统特定能力:

9.1.2 行为

以下内容描述 Clear Key 如何实现密钥系统特定行为:

  • generateRequest() 算法中:

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

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

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

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

  • expiration 属性 始终为 NaN

  • update() 算法中:

    • response 参数要么是如许可证格式中所述的 JWK Set,要么是 如许可证释放确认格式中所述的按 UTF-8 编码的 JSON 对象。

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

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

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

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

9.1.3 许可证请求格式

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

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

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

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

9.1.3.1 示例

本节为非规范性内容。

以下示例是针对两个密钥 ID 的临时许可证请求。 (换行仅为便于阅读。)

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

9.1.4 许可证格式

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

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

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

"kty"(密钥类型)
"oct"(八位字节序列)。
"k"(密钥值)
包含对称密钥值的八位字节序列的 base64url 编码。
"kid"(密钥 ID)
包含密钥 ID值的八位字节序列的 base64url 编码。

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

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

9.1.4.1 示例

本节为非规范性内容。

以下示例是一个包含单个对称密钥的 JWK Set。(换行 仅为便于阅读。)

{
  "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 编码。 应用可以使用 TextDecoder 接口 [ENCODING] 将 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 字符串必须按 Encoding 规范 [ENCODING] 中指定的 UTF-8 编码。 应用可以使用 TextEncoder 接口 [ENCODING] 编码 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”。 具体而言,不存在 '=' 填充,并且字符 '-' 和 '_' 必须分别用于替代 '+' 和 '/'。

10. 安全

10.1 输入数据攻击与漏洞

用户代理和密钥系统实现必须媒体 数据初始化数据、传递给 update() 的数据、许可证、 密钥数据以及应用提供的所有其他数据视为不可信内容和 潜在攻击向量。它们必须使用适当的防护措施来缓解任何 相关威胁,并注意安全地解析、解密等此类数据。用户代理应当 在将数据传递给 CDM 之前对其进行验证。

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

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

例如,暴露可能来自媒体数据的 URL 或其他信息并不安全,比如传递给 generateRequest()初始化数据就是这种情况。 应用必须确定要使用的 URL。 messageType 属性 可由应用在适用时用于从一组 URL 中进行选择;该属性属于 message 事件。

10.2 CDM 攻击与漏洞

用户代理负责为用户提供安全的 Web 浏览方式。此 责任适用于用户代理使用的任何功能,包括 来自第三方的功能。用户代理实现者必须密钥系统实现者处获取 足够的 信息,使其能够恰当地评估与密钥系统集成的安全影响。用户代理实现者 必须确保 CDM 实现提供和/或支持 足够的控制能力,以便 用户代理为用户提供安全性。用户代理实现者必须 确保 CDM 实现在出现安全漏洞时能够且将会被快速、主动地更新。

利用未完全沙盒化和/或使用平台 特性的 CDM 实现,可能允许攻击者访问 OS 或平台功能、提升权限 (例如,以 system 或 root 身份运行),和/或访问驱动程序、内核、固件、硬件等。 此类功能、软件和硬件可能并未被编写为能够抵御恶意 软件或基于 Web 的攻击,并且可能不会通过安全修复进行更新,尤其是 与用户代理相比。CDM 实现中安全 漏洞修复更新缺失、不频繁或缓慢会增加风险。此类 CDM 实现以及暴露它们的 UA 必须在 安全性的所有方面格外谨慎,包括解析所有数据

当使用属于客户端 OS、平台和/或硬件的一部分,或由其提供的 CDM 或底层机制时, 用户代理应格外谨慎。

如果用户代理选择支持一个无法被充分沙盒化或以其他方式保护的密钥系统实现, 则用户代理应该在加载或调用它之前确保用户已被充分告知和/或给出明确同意

向未经认证的源授予权限,在存在网络攻击者的情况下等同于 向任何源授予这些权限。参见对 持久化同意的滥用

10.3 网络攻击

10.3.1 潜在攻击

本节为非规范性内容。

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

  • DNS 欺骗攻击:无法保证声称位于某个 域()中的主机 确实来自该域。

  • 被动网络攻击:无法保证在客户端和服务器之间传输的数据,包括 可区别 标识符可区别永久 标识符,不会被其他实体查看。见用户 跟踪

  • 主动网络攻击:无法保证额外脚本或 iframe 不会被注入到页面中 (无论是出于合法目的使用本规范中定义 API 的页面, 还是不使用这些 API 的页面)。其后果是:

    • 对本规范中定义的 API 的调用可以被注入到任何页面中。

    • 来自出于合法原因使用这些 API 的页面的本规范所定义 API 调用可能 被操纵,包括修改所请求的 功能、修改或添加调用,以及修改或注入数据。另见 输入数据攻击与漏洞

    • 在客户端和服务器之间传输的数据,包括可区别标识符可区别永久 标识符,可能被其他实体查看和/或修改。见用户跟踪

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

10.3.2 缓解措施

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

使用 TLS

使用 TLS 的应用可以确信,只有用户、代表用户工作的软件, 以及其他使用 TLS 且具有标识其来自同一域证书的页面, 能够与该应用交互。此外, 特定 权限与安全源结合,确保授予应用的 权限不能被网络攻击者滥用。

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

阻止混合内容

用户代理必须正确处理混合内容 [MIXED-CONTENT],包括 阻止 “Blockable Content” [MIXED-CONTENT],以避免 潜在暴露于 不安全内容。此类暴露可能破坏其他缓解措施,例如使用 TLS。

用户代理可以选择阻止所有混合内容,包括 “Optionally-blockable Content” [MIXED-CONTENT],以通过防止不可信媒体数据 被传递给 CDM 来进一步提高安全性(见CDM 攻击与漏洞)。

用户代理应当确保在具有比其他用户代理功能 (例如 DOM 内容)更大安全顾虑的 密钥系统可以被某个 访问之前,用户已被充分告知和/或给出明确同意。

此类机制必须按每个设置, 以避免有效使用启用后续 恶意访问,并且必须按每个浏览配置文件设置。

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

10.4 iframe 攻击

10.4.1 潜在攻击

本节为非规范性内容。

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

10.4.2 缓解措施

告知用户或要求同意的用户代理, 包括出于安全和/或隐私原因的情况,应当 将 UI 和同意的持久化基于顶层 Document与使用本规范中定义 API 的 的组合。 这可确保用户被告知发出请求的主文档,并确保 为一个(合法)组合持久化权限不会无意中允许 恶意使用不被检测到。

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

10.5 跨目录攻击

本节为非规范性内容。

不同作者共享一个主机名,例如在 geocities.com 上托管内容的用户,都共享同一个。用户代理 不提供按路径名限制 API 访问的功能。

在共享主机上使用本规范中定义的 API 会破坏用户代理实现的基于源的 安全和隐私缓解措施。例如,每源的 可区别标识符会被同一主机名上的所有作者 共享,并且持久化数据可能会被该主机上的任何作者访问和操作。 如果例如修改或删除此类数据可能抹除用户对特定内容的 权利,则后者尤其重要。

即使用户代理提供了路径限制功能,通常的 DOM 脚本安全模型也会使绕过这种保护并从任何路径访问 数据变得轻而易举。

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

11. 隐私

用户设备上存在或使用密钥系统会引发若干 隐私问题,分为两类:(a) 可能由 EME 接口本身或在密钥系统消息中 披露的用户特定信息,以及 (b) 可能持久存储在用户设备上的用户特定信息。

用户代理必须负责为用户提供对其自身隐私的充分控制。 由于用户代理可能会与第三方 CDM 实现集成, CDM 实现者必须向用户代理实现者提供足够的信息和控制,以使他们能够实现 适当技术,确保用户能控制自己的隐私,包括但不限于 下文所述技术。

11.1 EME 和密钥系统披露的信息

关于 EME 和密钥系统披露信息的顾虑分为 两类:(a) 关于非特定信息的顾虑,尽管这些信息仍可能有助于 对用户代理或设备进行指纹识别;以及 (b) 可直接用于用户跟踪的 用户特定信息。

11.2 指纹识别

恶意应用可能能够通过检测或枚举所支持的 密钥系统列表及相关信息,对用户或用户代理进行指纹识别。 如果未提供适当的源保护,这可能包括检测已访问的站点以及为这些站点存储的信息。 特别是,密钥系统 必须不在不同之间共享密钥或其他数据。

本规范中的若干特性会暴露可能对指纹识别略有贡献的能力信息:

11.3 信息泄漏

11.3.1 顾虑

本节为非规范性内容。

CDM,尤其是在用户代理外部实现的 CDM,可能不具有与 Web 平台相同的 基本隔离。重要的是应采取措施避免信息泄漏,尤其是跨源泄漏。 这包括内存中数据和 已存储数据。如果不这样做,可能导致信息泄漏到私密 浏览会话或从其中泄漏、跨浏览配置文件(包括跨 操作系统用户 账户)泄漏,甚至跨不同浏览器或应用泄漏。

11.3.2 缓解措施

为避免此类问题,用户代理和 CDM 实现必须确保:

  • CDM 具有 CDM 实例的概念,该实例与 MediaKeys 对象一一关联。

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

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

  • 会话数据不会与未关联到创建该会话的 MediaKeys 对象的媒体元素共享。 除其他事项外,这意味着会话的密钥不得用于解密由 mediaKeys 属性 不是该 MediaKeys 对象的 媒体元素加载的内容。

  • MediaKeys 对象及其底层 实现不会向外暴露信息。

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

  • 只能加载由请求 存储的数据。

  • 无法从 CDM 中提取、派生或推断出 本规范中未明确描述,且未经用户许可也无法通过其他 Web 平台 API 提供给页面的信息。这适用于暴露到客户端设备外部或暴露给应用的任何 信息,包括例如 CDM 消息中的信息。

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

    • 位置,包括地理位置

    • 显著标识符以外的凭据或标识符

    • OS 账户名和其他潜在 PII

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

    • 本地网络详细信息(例如,设备的本地 IP 地址)

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

    • 不与本规范中定义的 API 相关联,也不是因其而存储的用户状态。

11.4 用户跟踪

11.4.1 顾虑

本节为非规范性内容。

第三方宿主(或任何能够将内容 分发到多个站点的实体,例如广告商)可以使用由 CDM 存储或代表其存储的显著标识符或持久化 数据(包括许可证、密钥、密钥 ID,或许可证销毁记录), 跨多个会话(包括跨浏览配置文件)跟踪用户,构建用户 活动或兴趣的画像。此类跟踪会削弱 Web 平台其余部分提供的隐私保护, 并且例如可能实现原本不可能的高度定向 广告。如果结合一个知道 用户真实身份的站点(例如需要 已认证凭据的内容提供商或电子商务站点),这可能使压迫性团体能够 比在纯匿名 Web 使用的世界中更准确地锁定个人。

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

本规范提出了一个特定的关注点,因为此类信息通常 存储在用户代理(以及关联的浏览 配置文件存储)之外,通常在 CDM 中。

由于许可证和许可证销毁记录的内容是密钥系统特定的,并且由于密钥 ID 可以包含任何值, 这些数据项可能被 滥用来存储可识别用户的信息。

密钥系统可以访问或创建针对 设备或设备用户的持久化或半持久化标识符。在某些情况下,这些标识符可能以安全方式绑定到特定 设备。如果这些标识符出现在密钥系统 消息中, 则设备和/或用户可能被跟踪。如果未应用下述缓解措施, 这可能包括随时间跟踪用户/设备,以及关联给定设备的多个 用户。

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

如果未加以缓解,此类跟踪可能会根据 密钥系统的设计采取三种形式:

  • 在所有情况下,预计此类标识符可供完全支持 密钥系统的站点和/或服务器使用(因此能够 解释密钥系统 消息),从而使此类站点能够跟踪。

  • 如果由密钥系统暴露的标识符不是 源特定的,则两个 完全支持该密钥系统的站点 和/或服务器可以串通来跟踪用户。

  • 如果密钥系统消息以 一致的方式包含从用户标识符派生的信息, 例如特定内容项的初始密钥系统 消息中的某一部分不会随时间变化且依赖于 用户标识符,则任何应用都可以使用该信息来 随时间跟踪设备或用户。

此外,如果密钥系统允许密钥或其他数据被存储并在 源之间重用,则两个源可能能够串通,通过记录它们访问同一密钥的能力来跟踪 唯一用户。

最后,如果任何用于用户控制密钥系统的用户界面 将数据与 HTTP 会话 cookies [COOKIES] 或 持久化存储中的数据分开呈现,则 用户很可能会修改其中一种的站点授权或删除其中一种的数据,而不修改其他数据。 这会允许站点将这些不同功能用作彼此的冗余备份, 从而挫败用户保护其隐私的尝试。

除了站点和其他第三方可能跟踪用户之外,用户 代理实现者、CDM 供应商或设备供应商也可以构建用户 活动或兴趣的画像,例如用户访问的使用本规范中定义的 API 的站点。 此类跟踪会削弱 Web 平台其余部分提供的隐私保护, 尤其是与源隔离相关的保护。

标识符,例如显著标识符, 可以从由 CDM 供应商运营或提供的服务器获得,例如通过个性化 过程。该过程可能包括向服务器提供客户端标识符,包括 显著永久 标识符。为了生成 每源标识符,也可能提供一个表示该源的值。

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

以下章节描述了可在未经用户同意的跟踪风险方面进行缓解的技术。

11.4.2 缓解措施

不要使用 显著标识符或显著永久标识符

密钥系统实现尽可能避免使用 显著标识符和显著永久标识符,并且只有在它们能够有意义地 提高实现的健壮性时才使用它们。参见限制或 避免使用显著标识符和永久标识符

不要将显著 永久标识符暴露给应用

实现不得显著永久 标识符暴露给 应用或来源。

加密显著标识符

显著标识符密钥系统消息中必须被 加密,并附带时间戳或随机数,使密钥系统 消息始终 不同。这会防止使用密钥系统 消息进行跟踪,除非由 完全支持该密钥系统的服务器进行。参见加密标识符

像 cookie / Web Storage 一样对待显著标识符 和密钥系统存储的数据

用户代理以一种将显著 标识符密钥系统存储的数据与 HTTP 会话 cookie [COOKIES] 强关联的方式呈现给用户, 包括将其纳入“移除 所有数据”,并在相同的 UI 位置呈现 它。这可能会鼓励用户以合理的怀疑态度看待此类标识符。 用户代理帮助用户避免数据清除不完整

不要向无关实体暴露按来源的信息

不要向个性化服务器或其他与该来源无关的实体提供来源 或与来源可关联的值。如果实现使用了这样的 过程,请遵循个性化一节中的要求和建议。

使用不可关联的按来源、按配置文件的值和标识符

对于暴露给应用的所有显著值, 实现必须为每个来源浏览配置文件使用 不同的不可由 应用关联的值。参见8.4.1 使用按来源、按配置文件的值

这对于使用显著标识符的实现尤其重要。参见 8.5.3 使用按来源、按配置文件的标识符

使用特定于来源和特定于浏览配置文件的密钥系统 存储

CDM 使用的任何数据,如果可能以应用或许可证服务器可见的方式影响 消息或行为,则必须来源浏览配置文件进行分区,并且不得 泄漏到私密浏览会话或从私密浏览会话泄漏。这 包括内存中数据和持久化数据。具体但非穷尽地说, 会话数据、许可证、密钥和按来源的标识符必须来源 和按浏览配置文件进行分区。参见8.3.1 使用特定于来源和特定于浏览配置文件的密钥系统存储8.4.1 使用按来源、按配置文件的值

提供用户删除持久数据的能力,包括显著标识符

用户代理必须为用户提供清除由密钥 系统维护的任何 持久数据(包括显著 标识符)的能力。参见允许清除持久数据

使存储的数据过期

用户代理可以在一段时间后自动删除 显著标识符和/或其他 密钥系统数据,并且这种方式可以由用户配置。

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

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

然而,如果用户并未充分理解此类过期的影响, 这也可能使用户对内容的访问,尤其是已购买或 租赁内容的访问,面临风险。

阻止第三方访问

用户代理可以将对密钥系统和/或特性的访问限制为 源自浏览上下文的顶级Document来源的脚本。 例如,requestMediaKeySystemAccess() 可以拒绝 来自在 iframe 中运行的其他来源页面对某些配置的请求。

用户代理必须确保用户在使用 显著标识符或显著永久标识符之前充分知情和/或给出 明确同意。

此类机制必须来源设置, 以避免有效使用导致后续 恶意访问,并且必须浏览配置文件设置。

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

提供用户控件,用于禁用密钥系统密钥系统对标识符的使用

用户代理为用户提供一个全局控制项,用于控制某个 密钥系统 是否启用,和/或密钥系统显著 标识符或显著永久标识符的使用是否启用(如果 密钥系统支持)。用户代理帮助用户避免数据清除不完整

要求对每个密钥系统的访问进行特定于站点的白名单授权

用户代理可以要求用户在站点能够使用每个密钥系统和/或某些特性之前,明确授权访问它。用户代理 允许用户临时或永久撤销此授权。

使用共享黑名单

用户代理可以允许用户共享来源 和/或密钥系统的黑名单。这将允许社区共同 保护其隐私。

虽然这些建议阻止了本规范中定义的 API 被轻易用于 用户跟踪,但它们并不能完全阻止这种情况。在单个来源内,站点可以 在会话期间继续跟踪用户,然后可以将所有这些信息连同 该站点获得的任何识别信息(姓名、信用卡号、 地址)一起传递给第三方。如果第三方与多个站点合作以 获取此类信息,并且标识符不是 按来源和配置文件唯一,则仍然可以创建画像。

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

11.5.1 顾虑

本节为非规范性内容。

密钥系统可能会在用户设备上存储信息,或者用户 代理可能会代表密钥系统存储信息。这可能向同一设备的另一个用户揭示 某个用户的信息,包括可能曾使用特定密钥系统(即访问过的站点),甚至是 使用某个密钥系统解密过的内容。

如果一个源存储的信息影响另一个源的密钥系统运行, 则一个用户在某个站点上访问过的站点或观看过的内容,可能会被泄露给另一个 可能恶意的站点。

如果客户端设备上为一个浏览配置文件存储的信息影响 其他浏览 配置文件或浏览器的密钥系统运行, 则一个配置文件中访问过的站点或观看过的内容,可能被另一个浏览配置文件泄露或与其相关联, 甚至包括不同操作 系统用户账户或浏览器。

11.5.2 缓解措施

缓解这些顾虑的要求定义在8.3 持久化数据中。

11.6 数据清除不完整

11.6.1 顾虑

本节为非规范性内容。

如果在清除显著标识符 和已存储数据和/或禁用密钥系统时,未同时清除和/或 禁用所有此类数据和 功能,以及 cookie [COOKIES] 和其他 站点数据,则用户保护其隐私的尝试可能会失败。例如:

  • 如果用户清除 cookie 或其他持久存储,而没有同时清除 显著标识符和密钥系统存储的数据, 站点就可以通过将各种特性彼此作为冗余备份来挫败这些 尝试。

  • 如果用户清除显著 标识符,而没有同时清除 密钥系统存储的数据(包括持久会话),以及 cookie 和其他 持久存储,站点就可以通过使用剩余数据将旧标识符和新标识符 关联起来,从而挫败这些尝试。

  • 如果用户禁用密钥系统,尤其是针对某个特定来源, 而没有 同时清除 cookie 或其他持久存储,站点就可以通过使用剩余特性来挫败这些 尝试。

  • 如果用户禁用密钥系统,之后又决定 启用该密钥系统,而没有同时清除 cookie 或其他 持久存储、显著 标识符,以及密钥 系统存储的数据,则站点可能能够将 禁用之前的数据与该密钥系统 重新启用之后的数据和行为关联起来。

11.6.2 缓解措施

缓解这些顾虑的建议定义在8.3 持久化数据中。

11.7 私密浏览模式

用户代理可以支持一种旨在保护用户匿名性和/或确保浏览活动记录不会 持久化到客户端的操作模式(例如私密浏览)。前几节讨论的隐私顾虑 对使用此类模式的用户而言可能尤其令人担忧。

支持此类模式的用户代理实现者应当仔细考虑 是否应在这些模式中禁用对密钥系统的访问。例如,此类模式 可以禁止创建支持或使用 MediaKeySystemAccess 对象, 这些对象支持或使用 persistentStatedistinctiveIdentifier (无论是作为 CDM 实现的一部分,还是因为应用指明它们为 "required")。如果实现不禁止此类创建, 则它们应当在允许使用之前,告知用户此类模式的预期隐私属性所面临的影响和潜在后果。

11.8 安全源与传输

本规范中定义的 API 仅在安全源上受支持,从而保护前几节讨论的信息。 标识符还会按加密标识符中的规定进行加密。

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

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

12. 一致性

除标记为非规范性的节外,本规范中的所有编写指南、图表、示例和注均为非规范性内容。 本规范中的其他所有内容均为规范性内容。

本文档中的关键词 可以必须绝不能可选建议必需不得应当不应 应按 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 选择受支持的密钥系统并使用来自 "encrypted" 事件的初始化数据

此示例使用 requestMediaKeySystemAccess() 方法选择一个受支持的密钥系统,然后使用来自媒体数据初始化数据 来生成许可证请求并将其发送到 适当的许可证服务器。受支持的密钥系统之一使用 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 事件, 初始化会简单得多。这可以通过以其他方式提供 初始化数据,或在 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() 调用的响应中(如果需要多次 往返),以及密钥系统可能因任何其他原因需要发送消息时。

<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" autoplay onencrypted='handleInitData(event)'></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 对本规范的贡献。也感谢通过参与 邮件列表和 issue 等方式为本规范作出贡献的许多 其他人。

A. 参考文献

A.1 规范性参考文献

[COOKIES]
HTTP 状态管理机制. A. Barth. IETF. 2011 年 4 月. 提议标准. URL:https://httpwg.org/specs/rfc6265.html
[dom]
DOM 标准. Anne van Kesteren. WHATWG. 现行标准. URL:https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript 语言规范. Ecma International. URL:https://tc39.es/ecma262/multipage/
[EME-INITDATA-KEYIDS]
"keyids" 初始化数据 格式. Joey Parrish; Greg Freedman. W3C. 2024 年 8 月 20 日. W3C 工作组说明. URL:https://www.w3.org/TR/eme-initdata-keyids/
[EME-INITDATA-REGISTRY]
Encrypted Media Extensions 初始化数据格式注册表. Joey Parrish; Greg Freedman. W3C. 2026 年 5 月 7 日. DRY. URL:https://www.w3.org/TR/eme-initdata-registry/
[ENCODING]
Encoding 标准. Anne van Kesteren. WHATWG. 现行标准. URL:https://encoding.spec.whatwg.org/
[HTML]
HTML 标准. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 活 标准. URL:https://html.spec.whatwg.org/multipage/
[Infra]
Infra 标准. Anne van Kesteren; Domenic Denicola. WHATWG. 现行标准. URL:https://infra.spec.whatwg.org/
[mimesniff]
MIME 嗅探标准. Gordon P. Hemsley. WHATWG. 现行标准. URL:https://mimesniff.spec.whatwg.org/
[MIXED-CONTENT]
混合内容. Emily Stark; Mike West; Carlos IbarraLopez. W3C. 2023 年 2 月 23 日. CRD. URL:https://www.w3.org/TR/mixed-content/
[PERMISSIONS-POLICY]
权限策略. Ian Clelland. W3C. 2025 年 10 月 6 日. W3C 工作草案. URL:https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
用于 RFC 以指示要求级别的 关键词. S. Bradner. IETF. 1997 年 3 月. 最佳当前实践. URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC6381]
“Bucket” 媒体类型的 'Codecs' 和 'Profiles' 参数. 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]
RFC 2119 关键词中大写与小写的 歧义. B. Leiba. IETF. 2017 年 5 月. 最佳当前实践. URL:https://www.rfc-editor.org/rfc/rfc8174
[WEBIDL]
Web IDL 标准. Edgar Chen; Timothy Gu. WHATWG. 现行标准. URL:https://webidl.spec.whatwg.org/

A.2 资料性参考文献

[CENC]
ISO/IEC 23001-7:2016, 信息技术 — MPEG 系统技术 — 第 7 部分:ISO Base Media File Format 文件中的通用 加密. 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 版本注册表. Joey Parrish; Greg Freedman. W3C. 2026 年 5 月 7 日. DRY. URL:https://www.w3.org/TR/eme-hdcp-version-registry/
[EME-STREAM-REGISTRY]
Encrypted Media Extensions 流格式 注册表. Joey Parrish; Greg Freedman. W3C. 2026 年 5 月 7 日. DRY. URL:https://www.w3.org/TR/eme-stream-registry/
[MEDIA-SOURCE]
Media Source Extensions™. Jean-Yves Avenard; Mark Watson. W3C. 2025 年 11 月 4 日. W3C 工作草案. URL:https://www.w3.org/TR/media-source-2/
[RFC6838]
媒体类型规范与注册 程序. 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 1.1. Paul Adenot; Hongchan Choi. W3C. 2024 年 11 月 5 日. FPWD. URL:https://www.w3.org/TR/webaudio-1.1/