加密媒体扩展 (Encrypted Media Extensions)

W3C 工作草案

关于此文档的更多细节
此版本
https://w3org.cn/TR/2026/WD-encrypted-media-2-20260515/
最新发布版本
https://w3org.cn/TR/encrypted-media-2/
最新编辑草案
https://w3c.github.io/encrypted-media/
历史
https://w3org.cn/standards/history/encrypted-media-2/
提交历史
实现报告
https://w3c.github.io/test-results/encrypted-media/all.html
最新推荐标准
https://w3org.cn/TR/2017/REC-encrypted-media-20170918/
编辑
Joey Parrish (Google Inc.)
Greg Freedman (Netflix Inc.)
前任编辑
Mark Watson (Netflix Inc.) (至 2019 年 9 月)
David Dorwin (Google Inc.) (至 2017 年 9 月)
Jerry Smith (Microsoft Corporation) (至 2017 年 9 月)
Adrian Bateman (Microsoft Corporation) (至 2014 年 5 月)
反馈
GitHub w3c/encrypted-media (pull requests, 新建问题, 待处理问题)
public-media-wg@w3.org,邮件主题行需为 [encrypted-media-2] … 消息主题 … (归档)

摘要

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

该 API 支持从简单的明文密钥解密到高价值视频(假设用户代理实现了相应功能)等各种用例。许可证/密钥交换由应用程序控制,这有助于开发支持多种内容解密和保护技术的稳健播放应用程序。

本规范不定义内容保护或数字版权管理 (DRM) 系统。相反,它定义了一个通用 API,可用于发现、选择和交互此类系统以及更简单的内容加密系统。合规本规范并不要求实现数字版权管理:仅要求实现 Clear Key(明文密钥)系统作为通用基准。

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

本文档状态

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

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

其他功能的纳入超出了媒体工作组的范围。除了编辑更新外,本规范所做的其他实质性更改旨在解决针对本规范的维护问题:

有关自上一版本以来所做更改的完整列表,请参阅 提交记录 (commits)

本文件由 媒体工作组作为工作草案,通过 推荐标准轨道发布。

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

这是一份草案文件,可能随时被其他文件更新、替换或废弃。将其作为进展中的工作以外的引用是不恰当的。

本文件由遵循 W3C 专利政策的组别制定。W3C 维护了一份与该组交付成果相关的 专利披露公开列表;该页面还包含了披露专利的说明。任何知悉其认为包含 必要权利要求 (Essential Claim) 的个人,必须按照 W3C 专利政策第 6 节披露相关信息。

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

1. 简介

本节是非规范性的。

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

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

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

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

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

A generic stack implemented using the proposed APIs

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 必须 在一段时间内于 来源 (origin) 内是唯一的,包括跨浏览会话。

底层的内容保护协议不一定需要支持会话 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) 提供了从 初始化数据类型 字符串到每种格式规范的映射。

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

对于给定的流集合或 媒体数据,初始化数据 必须 是固定值。它 必须 仅包含与播放给定流集合或 媒体数据 所需密钥相关的信息。它 严禁 包含应用程序数据、客户端特定数据、用户特定数据或可执行代码。

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

不鼓励使用专有格式/内容,强烈不鼓励仅支持或使用专有格式。专有格式仅应与不支持通用格式的现有内容或现有客户端设备一起使用。

可关联值

如果两个或多个标识符或其他值相同或者可以通过合理的努力和时间来关联它们,则称它们是可关联的。否则,这些值是 不可关联的

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

  • 使用易于反转的哈希函数。

  • 共享前缀或其他子集。

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

  • 将来源 (origin) 与固定值进行 XOR 运算(因为这极易反转)。

相反,完全不相关或加密上不同的两个值(例如通过加密强不可逆哈希函数)是 不可关联的

如果引用实体或实体集在没有额外实体参与的情况下,可以通过合理的努力和时间来关联或关联它们,则称两个或多个标识符或其他值是 可被实体关联的。否则,这些值是 不可被实体关联的

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

独特值

独特值是指在大量用户或客户端设备之间共享的值、数据片段、拥有数据片段的含义,或可观察的行为或时序。独特值可能存在于内存中或被持久化。

独特值的示例包括但不限于:

虽然独特值通常对用户或客户端设备是唯一的,但值不必严格唯一即可成为独特的。例如,在少数用户之间共享的值仍然可以是独特的。

永久标识符

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

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

  • 工厂预置在硬件设备中的值。

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

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

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

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

  • 客户端或其他用户帐户值。

一个 独特永久标识符 是一个 独特永久标识符

当在客户端外部公开时,独特永久标识符及其衍生值或以其他方式与它们相关的值 必须加密。独特永久标识符 严禁 向应用程序公开,即使是加密形式。

虽然独特永久标识符通常对用户或客户端设备是唯一的,但独特永久标识符不需要严格唯一即可成为独特的。例如,在少数用户之间共享的独特永久标识符仍然可以是独特的。

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

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

独特标识符

独特标识符是一个值(包括不透明或加密形式),对于该值,任何客户端外部实体都有可能关联或关联超出用户在 Web 平台(例如 Cookie 和其他站点数据)所期望范围的值。例如,那些 可被应用程序以外的实体关联的 值,跨越 a) 来源 (origins),b) 浏览配置文件,或 c) 浏览会话,即使在用户尝试通过清除浏览数据来保护其隐私之后,或用户不容易打破这种关联的值。特别地,如果一个 中心服务器(例如个性化服务器)能够关联 跨来源的值,则该值就是独特标识符;例如,因为个性化请求包含了一个共同值,或者个性化请求中提供的值 可被此类服务器关联,即使在尝试清除浏览数据之后。造成这种情况的可能原因包括在个性化过程中使用了 独特永久标识符

向应用程序公开的独特标识符(即使是加密形式)受 标识符要求 的约束,包括必须 被加密针对每个来源和配置文件唯一,以及 可被清除

虽然独特标识符的实例化或使用是由应用程序使用本规范定义的 API 触发的,但不必将标识符提供给应用程序以触发与独特标识符相关的条件。(独特永久标识符甚至不以不透明或加密形式提供给应用程序。)

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

独特标识符是指满足以下所有条件的值、数据片段、拥有数据片段的含义,或可观察的行为或时序:

  • 它是 独特 的。

    虽然独特标识符通常对用户或客户端设备是唯一的,但标识符不需要严格唯一即可成为独特的。例如,在少数用户之间共享的标识符仍然可以是独特的。

  • 它、关于它的信息或从中派生或以其他方式与它相关的值,即使以加密形式,也会在客户端外部公开。这包括但不限于提供给应用程序和/或许可证、个性化 或其他服务器。

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

    规范明确禁止向应用程序公开的其他相关属性包括:

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

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

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

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

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

    • 用于多个但不一定是所有来源的单个值。

    • 用于域上所有来源的单个值。(标识符必须是针对每个 来源 (origin) 的。)

    • 预配置的源特定值。

    • 通过极易反转的方式生成的值,因此 可被应用程序关联,无论是在客户端生成还是涉及 个性化 过程。例如,XOR 运算或以其他方式将来源(的部分)与固定值集成。

虽然独特标识符通常 可被生成它们的实体关联,但它们 必须不可被应用程序关联的。换句话说,此类相关或关联仅由最初生成独特标识符值的实体(例如个性化服务器)实现。有权访问 独特永久标识符 的实体 严禁 向应用程序公开此功能,因为这会使产生的独特标识符 可被应用程序关联,并 应该 注意避免将此类关联暴露给其他实体或第三方。

独特标识符的示例包括但不限于:

  • 包含在密钥请求中的一系列字节,与其他客户端设备包含的字节序列不同,并且是基于或通过 独特永久标识符 直接或间接获取的。

  • 密钥请求中包含的公钥,与其他客户端设备请求中包含的公钥不同,并且是基于或通过 独特永久标识符 直接或间接获取的。

  • 证明私钥持有(例如,通过对某些数据签名),这是其他客户端设备没有的,且是基于或通过 独特永久标识符 直接或间接获取的。

  • 此类密钥的标识符。

  • 用于派生另一个被公开值的值,即使第一个值没有直接公开。

  • 派生自另一个独特标识符的值。

  • 一个随机值,该值在提供 独特永久标识符 的同时被报告给(例如 个性化)服务器,或在提供 独特永久标识符 后由此类服务器提供。

  • 派生自工厂预置在硬件设备中的唯一值的值。

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

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

不是 独特标识符的事物示例:

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

  • 唯一但仅在一次会话中使用的随机数 (nonce) 或临时密钥。

  • 未在客户端外部公开的值,即使通过派生或类似方式,包括通过 个性化 或类似方式。

  • 例如,当 CDM 不让这些证明进一步流向应用程序,而是使用不构成独特标识符的密钥自行进行新证明时,视频管道和 CDM 之间证明中使用的设备唯一密钥。

  • 连同浏览数据(例如 Cookie)一起被完全清除/可清除的值,之后它将被替换为一个 不可关联的(不仅是 不可被应用程序关联的)值,即使是由中央服务器(例如 个性化 服务器)关联,并且满足以下一项或多项:

    • 值的生成过程中没有涉及 独特永久标识符 或独特标识符。

    • 它是使用来自系统输入而生成的随机值。

    • 它是服务器在不使用或不知晓另一个独特标识符的情况下提供的值。

独特标识符和独特永久标识符的使用

如果一个实现、配置、实例或对象在其生命周期或相关实体的生命周期内的任何时间,在客户端外部公开(即使是加密形式)了一个或多个 独特标识符、关于它们的信息、或从中派生或以其他方式与它们相关的值,则该实现、配置、实例或对象 使用独特标识符。这包括但不限于向应用程序和/或许可证、个性化 或其他服务器提供此类值。

如果一个实现、配置、实例或对象在其生命周期或相关实体的生命周期内的任何时间,在客户端外部公开(即使是加密形式)了一个或多个 独特永久标识符、关于它们的信息、或从中派生或以其他方式与它们相关的值,则该实现、配置、实例或对象 使用独特永久标识符。这包括但不限于向 个性化 服务器提供此类值。此类值 严禁 提供给应用程序。

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

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

跨来源限制

在播放过程中,嵌入的媒体数据会公开给嵌入 来源 (origin) 中的脚本。为了使 API 能够在 encrypted 事件中提供 初始化数据媒体数据 必须 与嵌入页面 CORS-同源。如果 媒体数据 与嵌入文档跨来源,作者 应该HTMLMediaElement 上使用 crossOrigin 属性,并在 媒体数据 响应中使用 CORS 头,使其 CORS-同源

混合内容限制

在播放过程中,嵌入的媒体数据会公开给嵌入 来源 (origin) 中的脚本。为了使 API 能够在 encrypted 事件中提供 初始化数据媒体数据 严禁 成为 混合内容 [MIXED-CONTENT]。

时间

时间 必须 等同于 ECMAScript 语言规范中表示的时间。

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

时间通常代表具有毫秒精度的某个瞬间;然而,仅凭这一点并不足以作为定义。定义的“时间值和时间范围”参考增加了其他重要要求。

过期时间

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

浏览配置文件

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

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

3. 获取密钥系统访问权限

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

算法的步骤在拒绝 promise 时总是会被终止。

3.1 权限策略集成

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

3.3 MediaKeySystemConfiguration 字典

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

MediaKeysRequirement 枚举定义如下

枚举描述
required(必须)
当在对 requestMediaKeySystemAccess() 的调用中使用时
返回的对象 必须 支持此特性。
当由 MediaKeySystemAccess 对象返回时
由该对象创建的 CDM 实例 可以 使用此特性。
可选
当在对 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,默认为 ""
一个可选标签,它将被保留在从 MediaKeySystemAccessgetConfiguration() 方法返回的 MediaKeySystemConfiguration 中。
initDataTypes 类型为 sequence<DOMString>,默认为 []
受支持的 初始化数据类型 名称列表。如果列表为空或包含一个或多个与所有其他成员兼容的值(由算法确定),则该对象的 初始化数据类型 能力被视为受支持。序列中的值 必须 不为空字符串。
audioCapabilities 类型为 sequence<MediaKeySystemMediaCapability>,默认为 []
受支持的音频类型和能力对的列表。如果列表为空或包含一个或多个与所有其他成员兼容的值(由算法确定),则该对象的音频能力被视为受支持。当值之间存在冲突时,将选择较早的值。空列表表示不支持任何音频能力。在这种情况下,videoCapabilities 元素不能留空。
videoCapabilities 类型为 sequence<MediaKeySystemMediaCapability>,默认为 []
受支持的视频类型和能力对的列表。如果列表为空或包含一个或多个与所有其他成员兼容的值(由算法确定),则该对象的视频能力被视为受支持。当值之间存在冲突时,将选择较早的值。空列表表示不支持任何视频能力。在这种情况下,audioCapabilities 元素不能留空。
distinctiveIdentifier 类型为 MediaKeysRequirement,默认为 "optional"
是否需要使用 特有标识符当此成员为 "not-allowed" 时,实现 必须 不为由此配置创建的任何对象的任何操作 使用特有标识符或特有永久标识符
persistentState 类型为 MediaKeysRequirement,默认为 "optional"
是否需要持久化状态的能力。这包括会话数据和任何其他类型的状态。当此成员为 "not-allowed" 时,CDM 必须 不持久化任何与该对象的 Document 的应用程序或 origin(源)相关的状态。

就此成员而言,持久状态不包括由 密钥系统 实现控制的持久唯一标识符(特有标识符)。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 下受到支持。

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

最佳实践 3MediaKeySystemMediaCapability 传递给 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() 算法选择的配置选项的受支持组合。

返回的对象是解析此对象时所调用的 requestMediaKeySystemAccess() 调用中传递的第一个可满足的 MediaKeySystemConfiguration 配置的非严格子集(加上任何隐含的默认值)。它不包含未在该单一配置中指定的任何能力的值(隐含的默认值除外),因此可能无法反映 密钥系统 实现的所有能力。配置中的所有值可以以任何组合方式使用。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. 初始化 instance 以使用 configuration 启用、禁用和/或选择 密钥系统 特性。

    7. 如果 use distinctive identifierfalse,则防止 instance 使用特有标识符和特有永久标识符

    8. 如果 persistent state allowedfalse,则防止 instance 持久化任何与该对象的 Document 的应用程序或 origin(源)相关的状态。

    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 解决 promise

  3. 返回 promise

5. MediaKeys 接口

MediaKeys 对象代表一组相关的 HTMLMediaElement 可用于回放期间解密 媒体数据 的密钥。它还代表一个 CDM 实例。

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

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

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

算法的步骤在拒绝 promise 时总是会被终止。

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

MediaKeySessionType 枚举定义如下

枚举描述
temporary

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

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

persistent-license

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

此类会话只有在与创建此对象的 MediaKeySystemAccess 对象关联的配置具有 "required" 的 persistentState 值时才能创建。一旦成功调用 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 算法,直到使用 setMediaKeys() 将此 MediaKeys 对象与媒体元素关联。此步骤使应用程序能够在尝试执行此类操作之前检测到这种不常见的行为。

  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" 解决 promise 并中止这些步骤。

    4. 用 "usable" 解决 promise

  4. 返回 promise

setServerCertificate()

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

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

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

服务器证书内容是 密钥系统 特有的。它 必须 不包含可执行代码。

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

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

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

  3. certificateserverCertificate 参数内容的一个副本。

  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 创建的且未处于 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) 文档(Document) 能够访问的方式进行存储。此外,该数据 必须(MUST)仅能由当前的 浏览配置(browsing profile) 访问;其他浏览配置、用户代理和应用程序 不得(MUST NOT)能够访问所存储的数据。参见 用户设备上存储的信息

有关支持持久化存储时的其他注意事项,请参阅 10. 安全11. 隐私

6. MediaKeySession 接口

MediaKeySession 对象代表一个 密钥会话

当且仅当 MediaKeySession 对象的 closed 属性已被解析时,该对象才被视为 关闭(closed)

对于每个未 关闭MediaKeySession 对象,用户代理 应(SHALL)持续执行 Monitor for CDM State Changes(监控 CDM 状态变更)算法。监控 CDM 状态变更算法 必须(MUST)与主事件循环并行运行,但不能与本规范中定义为并行运行的其他程序并行。

如果 MediaKeySession 对象未 关闭 且创建它的 MediaKeys 对象仍然可访问,则该对象 不得(SHALL NOT)被销毁,并应继续接收事件。否则,无法再访问的 MediaKeySession 对象 不得(SHALL NOT)接收进一步的事件,且 可以(MAY)被销毁。

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

如果 MediaKeySession 对象在页面无法访问时未 关闭,则 CDM 应(SHALL)关闭与该对象关联的 密钥会话

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

最佳实践 4当您需要在采取其他操作前确保密钥会话已确实关闭时,请调用 close() 并等待返回的期约;不要依赖自动会话关闭的时间点。

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

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

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

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 实例上创建新会话。

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

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

会话中所有密钥的 过期时间;如果不存在此类时间,或者根据 CDM 的判断,许可证明确永不过期,则该值为 NaN。此值可在会话生命周期内更改,例如当某个操作触发某个时间窗口的开始时。

closed,类型为 Promise<MediaKeySessionClosedReason>,只读

会话关闭 算法运行导致对象变为 已关闭 状态时发出信号。该期约仅能被履行(fulfilled),永不被拒绝(rejected)。

keyStatuses,类型为 MediaKeyStatusMap,只读

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

映射条目及其值可能会在每次事件循环旋转时更新。该映射 不得(MUST NOT)出现不一致或部分更新的情况,但如果在两次访问之间事件循环发生了旋转,它在访问之间可能会发生变化。密钥 ID 可能会因 load()update() 的调用而添加。密钥 ID 可能会因删除了现有密钥知识(或用新集合替换现有密钥集合)的 update() 调用而移除。密钥 ID 不得(MUST NOT)因不可用(如由于过期)而被移除。相反,此类密钥 必须(MUST)被赋予适当的状态,如 "expired"。

某些较旧的平台可能包含不暴露密钥 ID 的 Key System 实现,使得无法提供符合规范的用户代理实现。在这种情况下,为了最大限度地提高互操作性,映射会填充一个包含单字节密钥 ID 0 和该对象在存在非空列表时最适合聚合状态的 MediaKeyStatus 的对(例如,当该对象表示的 密钥会话 可以包含 密钥 时)。

onkeystatuseschange,类型为 EventHandler

keystatuseschange 事件的事件处理程序。

onmessage,类型为 EventHandler

message 事件的事件处理程序。

6.2 方法

generateRequest()

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

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

  1. 如果此对象的 closing or closed 值(正在关闭或已关闭)为 true,则返回一个以 InvalidStateError 拒绝的期约。

  2. 如果此对象的 uninitialized(未初始化)值为 false,则返回一个以 InvalidStateError 拒绝的期约。

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

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

  5. 如果 initData 为空数组,则返回一个以新创建的 TypeError 拒绝的期约。

  6. 如果此对象 cdm implementation 值所代表的 Key System 实现不支持将 initDataType 用作 Initialization Data Type(初始化数据类型),则返回一个以 NotSupportedError 拒绝的期约。字符串比较区分大小写。

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

  8. session type 为此对象的 session type

  9. promise 为一个新的 Promise。

  10. 并行执行以下步骤

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

    2. sanitized init datainit data 的经过验证和净化的版本。

      用户代理在将 初始化数据 传递给 CDM 之前,必须(MUST)彻底验证该数据。这包括验证字段的长度和数值是否合理、验证数值是否在合理范围内,以及剥离无关、不受支持或未知的数据或字段。用户代理 建议(RECOMMENDED)预解析、净化和/或生成 初始化数据 的完全净化版本。如果 initDataType 指定的 初始化数据 格式支持多个条目,用户代理 应(SHOULD)移除 CDM 不需要的内容。用户代理 不得(MUST NOT)重新排列 初始化数据 中的条目。

    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 必须(MUST)在此对象 来源(origin) 文档(Document) 的整个生命周期(包括跨文档和浏览会话)中保持唯一。

      4. 如果能够基于 sanitized init data 生成针对 requested license type 的许可证请求
        1. message 为基于 sanitized init data(按 initDataType 解读)生成的针对 requested license type 的许可证请求。

          cdm 不得(MUST NOT)使用任何非通过 sanitized init data 提供的流特定数据,包括 媒体数据

          cdm 不应(SHOULD NOT)在此阶段存储会话数据,包括会话 ID。参见 会话存储与持久化

        2. message type 为 "license-request"。

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

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

        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 上运行 Queue a "message" Event(排队“message”事件)算法,并提供 message typemessage

  11. 返回 promise

load()

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

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

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

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

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

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

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

  6. origin 为此对象 文档(Document)来源(origin)

  7. promise 为一个新的 Promise。

  8. 并行执行以下步骤

    1. sanitized session IDsessionId 的经过验证和/或净化的版本。

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

    2. 如果上述步骤失败,或者 sanitized session ID 为空,则以新创建的 TypeError 拒绝 promise

    3. 如果在此对象 文档(Document) 中存在一个未 关闭sessionId 属性为 sanitized session IDMediaKeySession 对象,则以 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 与当前 MediaKeySession session type 不相同,则以新创建的 TypeError 拒绝 promise

      3. session data 为存储在 origin 中用于 sanitized session ID 的数据。此数据 不得(MUST NOT)包含来自其他来源的数据或未与任何来源关联的数据。

      4. 如果存在任何 文档(Document) 中有一个未 关闭 且表示该 session dataMediaKeySession 对象,则以 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 运行 Update Key Statuses(更新密钥状态)算法,并提供每个密钥的 密钥 ID 以及相应的 MediaKeyStatus

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

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

      6. true 解析 promise

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

  9. 返回 promise

update()

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

response 参数包含要提供给 CDM 的消息。其内容特定于 Key System。它 不得(MUST NOT)包含可执行代码。

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

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

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

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

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

  5. promise 为一个新的 Promise。

  6. 并行执行以下步骤

    1. sanitized responseresponse 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) 文档(Document) 能够访问的方式进行存储。
        否则

        以新创建的 TypeError 拒绝 promise

        另参见 会话存储与持久化

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

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

        会话内的替换算法取决于 Key System

        CDM 实现 建议(RECOMMENDED)支持每个 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

        对该对象运行 Session Closed(会话关闭)算法,原因设为 "release-acknowledged"。

        否则

        运行以下步骤

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

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

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

        3. 如果上述任何步骤失败,则用一个新的 DOMException 拒绝 promise,其名称是相应的 错误名称

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

      2. undefined 解析 promise

  7. 返回 promise

close()

指示应用程序不再需要该会话,CDM 应释放与该会话关联的任何资源并将其关闭。不应释放或清除持久化数据。

当请求被处理时,返回的期约被解析;当会话关闭时,closed 属性期约以 "closed-by-application" 被解析。

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

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

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

  3. promise 为一个新的 Promise。

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

  5. 并行执行以下步骤

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

    2. 使用 cdm 关闭与此对象关联的 密钥会话

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

    3. 加入队列一个任务以运行以下步骤

      1. undefined 解析 promise

      2. 对该对象运行 Session Closed(会话关闭)算法,原因设为 "closed-by-application"。

  6. 返回 promise

remove()

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

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

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

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

  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 上运行 Update Key Statuses(更新密钥状态)算法,提供会话中所有的 密钥 ID,并为每个 ID 提供 "released" MediaKeyStatus 值。

      2. session 上运行 Update Expiration(更新过期时间)算法,并提供 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,如果未标识该密钥的状态,则返回 undefined

此接口具有由 可迭代(iterable) [WebIDL] 带来的 entrieskeysvaluesforEach@@iterator 方法。

要迭代的键值对是所有 已知密钥密钥 ID 和关联的 MediaKeyStatus 值所组成的对集合的一个快照,按 密钥 ID 排序。密钥 ID 的比较方式如下:对于长度为 m 的密钥 ID A 和长度为 n 的密钥 ID B(假设 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 存在与该密钥关联的、目前无法满足的输出限制。如果根据输出限制有必要,使用此密钥解密的媒体数据可能会以较低的质量(例如分辨率)进行显示。应用程序应避免使用会触发与该密钥关联的输出限制的流。对降级的支持是 可选(OPTIONAL)的。应用程序 不应(SHOULD NOT)依赖降级来确保输出要求无法满足时播放不中断。
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 该消息包含对 App 辅助个性化(或重新个性化)的请求。与所有其他消息一样,消息中的任何标识符 必须(MUST)每个来源和每个配置文件的区分标识符,且 不得(MUST NOT)区分永久标识符
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 事件摘要

本节是非规范性的。

事件名称 Interface 调度时间:当……时
keystatuseschange Event 会话中的密钥或其状态发生了变化。
message MediaKeyMessageEvent CDM 已为该会话生成了一条消息。

6.6 算法

6.6.1 排队“message”事件

“排队‘message’事件”算法将 message 事件排队到 MediaKeySession 对象。运行此算法的请求需包含目标 MediaKeySession 对象、message typemessage

message 不得(MUST NOT)包含 区分永久标识符,即使是加密形式。如果 MediaKeySession 对象的 use distinctive identifier 值为 false,则 message 不得(MUST NOT)包含 区分标识符,即使是加密形式。

运行以下步骤

  1. session 为指定的 MediaKeySession 对象。

  2. 排队一个任务,创建一个名为 message 的事件,该事件不冒泡且不可取消,使用 MediaKeyMessageEvent 接口,其 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 属性的内容,而不使对该属性的现有引用无效。此替换从脚本的角度来看是原子的。也就是说,脚本 绝不能(MUST NOT)看到部分填充的序列。

  5. 排队一个任务,在 session触发 名为 keystatuseschange 的事件。

  6. 排队一个任务,在每个媒体元素上运行 Attempt to Resume Playback If Necessary(必要时尝试恢复播放)算法,其 mediaKeys 属性是创建该 sessionMediaKeys 对象。

6.6.3 更新过期时间

“更新过期时间”算法更新 MediaKeySession过期时间。运行此算法的请求需包含目标 MediaKeySession 对象和新的过期时间(可能是 NaN)。

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

运行以下步骤

  1. session 为关联的 MediaKeySession 对象。

  2. expiration timeNaN

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

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

6.6.4 会话关闭

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

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

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

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

即使密钥 ID 有重叠,其他会话中的密钥 必须(MUST)不受影响。

在本算法运行后,将执行此算法排队的事件的事件处理程序,但无法再排队进一步的事件。因此,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 状态变更

“监控 CDM 状态变更”算法执行当 CDM 状态的各个方面发生变化时所需的步骤。

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

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

运行以下步骤

  1. sessionMediaKeySession 对象。

  2. cdmsessioncdm instance 值所代表的 CDM 实例。

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

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

    2. 运行 Queue a "message" Event(排队“message”事件)算法,并传递 sessionmessage typemessage

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

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

    2. 运行 Update Key Statuses(更新密钥状态)算法,传递 sessionstatuses

  5. 如果 cdm 改变了 session过期时间排队一个任务以执行以下步骤

    1. expiration timesession 的新过期时间。

    2. 运行 更新过期时间 (Update Expiration) 算法,并传入 sessionexpiration time

  6. 如果 cdm 已关闭 session,则 排队一个任务,以便在 session 上运行 会话关闭 (Session Closed) 算法,并附带适当的 MediaKeySessionClosedReason 值。

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

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

6.7 异常

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

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

6.8 会话存储与持久化

本节概述了对算法起补充作用的会话存储和持久化。

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

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

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

CDM 不应 存储包括会话 ID 在内的会话数据,直到首次调用 update()。具体而言,CDM 不应generateRequest() 算法期间存储会话数据。这确保了应用程序能够感知会话,并知道最终需要将其删除。

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

CDM 必须 确保给定会话的数据仅存在于一个未 关闭MediaKeySession 对象中(在任何 文档 (Document) 中)。换句话说,当已存在一个表示由 sessionId 参数指定的会话的 MediaKeySession 时,load() 必须 失败,无论是因为通过 generateRequest() 创建它的对象仍处于活动状态,还是已通过 load() 加载到另一个对象中。只有在所有曾表示该会话的对象都 关闭 后,会话 方可 再次加载。

使用 是持久化会话类型吗? 算法返回 true 的类型来创建会话的应用程序 在稍后通过首先使用 remove() 发起移除过程,并确保该移除过程(可能涉及消息交换)成功完成,从而移除存储的数据。CDM 也可 在适当情况下移除会话,但应用程序 不应 依赖此行为。

有关支持持久化存储时的其他注意事项,请参阅 10. 安全11. 隐私

7. HTMLMediaElement 扩展

本节规定了在支持加密媒体扩展时对 HTMLMediaElement [HTML] 的增加和修改。

以下内部值被添加到 HTMLMediaElement 中:

  • 正在附加媒体密钥 (attaching media keys),其 为布尔值。

  • 加密块队列 (encrypted block queue),其 为等待解密的加密块队列。

  • 解密因等待密钥而受阻 (decryption blocked waiting for key),其 为布尔值。

  • 播放因等待密钥而受阻 (playback blocked waiting for key),其 为布尔值。

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. 如果此对象的 正在附加媒体密钥 值为 true,则返回一个被 InvalidStateError 拒绝的 Promise。

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

  3. 令此对象的 正在附加媒体密钥 值为 true。

  4. promise 为一个新的 Promise。

  5. 并行执行以下步骤

    1. 如果满足以下所有条件:

      • mediaKeys 不为 null,

      • mediaKeys 表示的 CDM 实例已被另一个媒体元素使用

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

      则令此对象的 正在附加媒体密钥 值为 false,并使用 QuotaExceededError 拒绝 promise

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

      1. 如果用户代理或 CDM 不支持移除此关联,则令此对象的 正在附加媒体密钥 值为 false,并使用 NotSupportedError 拒绝 promise

      2. 如果当前无法移除此关联,则令此对象的 正在附加媒体密钥 值为 false,并使用 InvalidStateError 拒绝 promise

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

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

      4. 如果上述步骤失败,则令此对象的 正在附加媒体密钥 值为 false,并使用适当的 错误名称 拒绝 promise

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

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

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

        1. mediaKeys 属性设置为 null。

        2. 令此对象的 正在附加媒体密钥 值为 false。

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

      3. 排队一个任务,以便在媒体元素上运行 必要时尝试恢复播放 (Attempt to Resume Playback If Necessary) 算法。

    4. mediaKeys 属性设置为 mediaKeys

    5. 令此对象的 正在附加媒体密钥 值为 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,只读
指示包含在 initData 属性中的 初始化数据 (Initialization Data)初始化数据类型 (Initialization Data Type)
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 事件摘要

本节是非规范性的。

事件名称 Interface 调度时间:当……时 前提条件
encrypted MediaEncryptedEvent 用户代理在 媒体数据 中遇到 初始化数据 元素的 readyState 大于或等于 HAVE_METADATA

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

waitingforkey Event 播放因等待密钥而受阻。 readyState 小于或等于 HAVE_CURRENT_DATA。该元素的 播放因等待密钥而受阻 值刚变为 true

7.5 算法

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

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

运行以下步骤

  1. 媒体元素 (media element) 为指定的 HTMLMediaElement 对象。

  2. 如果 媒体元素mediaKeys 属性为 null,且实现要求在解码潜在的加密 媒体数据 之前指定一个 MediaKeys 对象,请运行以下步骤:

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

    1. 媒体元素 上运行 等待密钥 算法。

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

7.5.2 遇到初始化数据 (Initialization Data Encountered)

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

运行以下步骤

  1. 媒体元素 为指定的 HTMLMediaElement 对象。

  2. initDataType 为空字符串。

  3. initData 为 null。

  4. 如果 媒体数据CORS-same-origin不是 混合内容 (mixed content),请运行以下步骤:

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

    2. initData 为该初始化数据。

    虽然媒体元素可能允许加载“可升级内容” [MIXED-CONTENT],但用户代理 不得 向应用程序公开来自此类媒体数据的初始化数据。

  5. 排队一个任务,以创建一个名为 encrypted 的事件,该事件不冒泡且不可取消,使用 MediaEncryptedEvent 接口,其 type 属性设置为 encrypted,其 isTrusted 属性初始化为 true,并在 媒体元素 上分发它。

    事件接口 MediaEncryptedEvent 具有:

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

    如果媒体数据 不是 CORS-same-origin 或属于 混合内容,则 initData 属性将为 null。应用程序可以从其他来源获取初始化数据。

7.5.3 遇到加密块 (Encrypted Block Encountered)

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

运行以下步骤

  1. 媒体元素 为指定的 HTMLMediaElement 对象。

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

  3. block 添加到 媒体元素加密块队列 末尾。

  4. 如果 媒体元素解密因等待密钥而受阻 值为 false,请运行 尝试解密 (Attempt to Decrypt) 算法。

7.5.4 尝试解密 (Attempt to Decrypt)

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

运行以下步骤

  1. 媒体元素 为指定的 HTMLMediaElement 对象。

  2. 如果 媒体元素加密块队列 为空,则中止这些步骤。

  3. 如果 媒体元素mediaKeys 属性不为 null,请运行以下步骤:

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

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

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

      1. 运行 资源获取算法媒体数据已损坏 (media data is corrupted) 步骤。

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

      3. 中止这些步骤。

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

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

      1. block媒体元素加密块队列 中的第一个条目。

      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 上运行 更新密钥状态 (Update Key Statuses) 算法,并为每个会话提供其中所有的 密钥 ID 以及相应的 MediaKeyStatus 值。

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

          1. 使用 cdmblock key 解密 block

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

            如果解密失败:
            1. 运行 资源获取算法媒体数据已损坏 (media data is corrupted) 步骤。

            2. 如果 cdm 不再可用,对 media keys 运行 CDM 不可用 算法,硬件上下文重置的原因设为 "hardware-context-reset",否则设为 "internal-error"。

            3. 中止这些步骤。

            否则
            1. 媒体元素加密块队列 前端移除 block

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

              换句话说,解码该块。

            3. 返回此算法的开头。

            并非所有的解密问题(例如使用错误的密钥)都会导致解密失败。在这种情况下,这里不会触发错误,但在解码过程中可能会触发错误。

          否则,没有任何会话包含用于 block key ID 的密钥,因此继续。

  4. 媒体元素解密因等待密钥而受阻 值设置为 true

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

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

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

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

  1. encrypted 为 false。

  2. 检测帧是否已加密。

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

  4. 提供该帧以进行渲染。

7.5.5 等待密钥 (Wait for Key)

“等待密钥”算法排队一个 waitingforkey 事件并更新 readyState。它仅应在 HTMLMediaElement 对象处于 潜在播放 (potentially playing) 状态且其 readyState 大于或等于 HAVE_FUTURE_DATA 时调用。运行此算法的请求包含一个目标 HTMLMediaElement 对象。

运行以下步骤

  1. 媒体元素 为指定的 HTMLMediaElement 对象。

  2. 如果 媒体元素播放因等待密钥而受阻 值为 true,则中止这些步骤。

  3. 媒体元素播放因等待密钥而受阻 值设置为 true

    上述步骤的结果是,如果媒体元素尚未成为 受阻媒体元素,则它将成为受阻媒体元素。在这种情况下,媒体元素将停止播放。

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

    如果 当前播放位置 的数据可用:

    媒体元素readyState 设置为 HAVE_CURRENT_DATA

    否则

    媒体元素readyState 设置为 HAVE_METADATA

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

  5. 排队一个任务,以便在 媒体元素触发一个名为 waitingforkey 的事件。

  6. 暂停播放。

7.5.6 必要时尝试恢复播放 (Attempt to Resume Playback If Necessary)

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

运行以下步骤

  1. 媒体元素 为指定的 HTMLMediaElement 对象。

  2. 如果 媒体元素播放因等待密钥而受阻false,则中止这些步骤。

  3. 媒体元素 上运行 尝试解密 算法。

  4. 如果用户代理可以在 播放方向 上推进 当前播放位置

    1. 媒体元素解密因等待密钥而受阻 值设置为 false

    2. 媒体元素播放因等待密钥而受阻 值设置为 false

      上述步骤的结果是,媒体元素可能不再是 受阻媒体元素,因此可以恢复播放。

    3. 根据 适当的情况,将 媒体元素readyState 值设置为 HAVE_CURRENT_DATAHAVE_FUTURE_DATAHAVE_ENOUGH_DATA

      超出 HAVE_CURRENT_DATA 的状态和 canplaythrough 事件不(或不太可能)考虑超出当前密钥的密钥可用性。

      就绪状态的改变也可能导致 HTMLMediaElement 事件被触发,如 此处 所述。

7.6 媒体元素限制

本节是非规范性的。

CDM 处理的媒体数据 可能 无法通过 Web 平台 API 以通常的方式(例如使用 CanvasRenderingContext2D drawImage() 方法和 AudioContext MediaElementAudioSourceNode)获取。本规范未定义此类媒体数据不可用的条件,但是,如果媒体数据无法通过此类 API 获取,则它们 可能 的行为就像根本不存在媒体数据一样。

如果媒体渲染不由 UA 执行,例如在基于硬件的媒体流水线的情况下,完整的 HTML 渲染功能(例如 CSS 变换) 可能 不可用。一个可能的限制是视频媒体 可能 被限制为仅出现在与窗口边缘平行且具有正常方向的矩形区域中。

8. 实现要求

本节定义了针对用户代理和 密钥系统 的实现要求,包括 CDM 和服务器,这些要求可能未在算法中明确解决。此处的以及贯穿本规范的要求适用于所有实现,无论 CDM 是与用户代理分离还是属于用户代理的一部分。

8.1 CDM 约束

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

  • 访问网络资源(无论是本地还是远程),除非通过本规范明确允许的用户代理。

  • 访问存储(例如磁盘或内存),除非使用本规范功能播放受保护媒体时合理需要。

  • 访问除 CDM 状态和 持久化数据 之外的用户数据。

  • 访问硬件组件或设备,除非使用本规范功能播放受保护媒体时合理需要。

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

8.2 消息与通信

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

8.3 持久化数据

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

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

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

请参阅 会话存储和持久化

8.3.2 允许清除持久化数据

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

用户代理

  • 像处理其他站点数据(如 Cookie [COOKIES])一样处理持久化数据。具体而言:

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

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

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

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

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

  • 确保清除持久化数据的操作足够原子化,以防止通过依赖未同时清除的其他类型本地存储数据,来重新关联新旧标识符的“Cookie 复活”式重新关联。请参阅 数据清除不完整

  • 以一种帮助用户理解 数据清除不完整 的可能性,并使他们能够删除与所有持久化数据的特性(包括 Cookie [COOKIES] 和 Web 存储)关联的数据的方式呈现这些界面。

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

  • 允许用户通过 来源 和/或所有来源专门删除持久化数据。

8.3.3 加密或混淆持久化数据

用户代理 将持久化数据视为潜在敏感数据;此信息的发布很有可能损害用户隐私。为此,用户代理 确保持久化数据被安全存储,并在删除数据时,立即从底层存储中删除。

8.4 向应用程序公开的值

暴露给应用程序或可由其推断出的值(例如通过 CDM 使用),即使并非设计为标识符,也可能被用于识别客户端或用户。本节定义了避免或至少减轻此类关注的要求。对于 标识符 还有额外的要求。

8.4.1 使用源特定和配置文件特定的值

暴露给应用程序或可由其推断出的所有 独特值 (Distinctive Values) 必须 在每个 来源浏览配置文件 内是唯一的。也就是说,用于一个 来源 的使用本规范定义 API 的值 必须 与用于任何其他使用这些 API 的来源的值不同,并且在一个 浏览配置文件 中使用的值 必须 与用于任何其他配置文件的值不同,无论来源如何。此类值 不得 泄漏到私人浏览会话中或从中泄漏。

跨来源和配置文件的值 必须应用程序不可关联的 (non-associable by applications),这意味着 不得 可能关联来自多个来源或配置文件的值,例如确定它们来自同一个客户端或用户。具体而言,从独立于来源和/或独立于配置文件的值派生每个来源值的实现,必须 以确保上述不可关联性属性的方式进行,例如使用具有适当不可逆特性的派生函数。

8.4.2 允许清除值

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

一旦清除,在后续需要值时 必须 生成新的 应用程序不可关联的 值。

8.5 标识符

实现使用标识符,特别是 独特标识符或独特永久标识符,带来了隐私问题。本节定义了避免或至少减轻此类问题的要求。暴露给应用程序的值 的要求也适用于暴露给应用程序的标识符。

8.5.1 限制或避免使用独特标识符和永久标识符

8.5.2 加密标识符

特征性标识符 (Distinctive Identifiers)特征性永久标识符 (Distinctive Permanent Identifiers) 在客户端外部暴露时,必须在消息交换层面进行加密。所有其他标识符在客户端外部暴露时,应当在消息交换层面进行加密。加密必须确保任何两个标识符密文实例只能由拥有解密密钥的实体进行关联

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

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

服务器不得特征性标识符 暴露给发送该标识符的 CDM 以外的任何实体。

具体而言,不应将其提供给应用程序,或在发送给 CDM 的消息中明文包含。这可以通过加密该标识符或消息来实现,使其只能由特定的 CDM 解密。

8.5.3 使用源特定和配置文件特定的标识符

特征性永久标识符 外,所有标识符必须针对每个 源 (origin)浏览配置文件 保持唯一。请参阅 8.4.1 使用每个源每个配置文件的值

8.5.4 使用不可关联的标识符

所有由实现向应用程序暴露的标识符(包括 特征性标识符,即使是加密形式),必须满足在不同 浏览配置文件 以及 清除标识符应用程序无法关联

8.5.5 允许清除标识符

作为 允许清除持久化数据 中各项要求的结果,除 特征性永久标识符 外,所有潜在标识符或 特征性值 必须是可以清除的,以确保在客户端设备外部(如通过本规范定义的 API)和客户端设备内部,这些值均不再可检索、可观察或可推断。

使用特征性标识符 的实现必须允许用户清除 特征性标识符使用特征性永久标识符 的实现必须允许用户清除与 特征性永久标识符 关联的值。

一旦清除,当随后需要诸如 特征性标识符 等值时,必须生成新的 应用程序无法关联 的值。

8.6 个性化 (Individualization)

标识符(尤其是 特征性标识符)有时是通过一个称为“个性化”或“配置”的过程生成或获取的。生成的标识符必须 对应用程序不可关联,并且其 使用 必须仅暴露给 来自单个配置文件的单个源。此过程可以多次执行,例如在标识符被 清除 之后。

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

distinctiveIdentifier 控制是否可以 使用 特征性标识符特征性永久标识符(包括用于个性化)。具体而言,仅当用于创建 MediaKeys 对象的 MediaKeySystemAccessdistinctiveIdentifier 成员值为 "required" 时,才可以 使用 此类标识符。

8.6.1 直接个性化 (Direct Individualization)

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

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

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

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

  • 不得CDM 直接执行。

  • 不得通过本规范定义的 API 传递给或通过应用程序。

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

  • 必须加密所有 特征性标识符特征性永久标识符

  • 必须使用 TLS。

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

8.6.2 应用辅助个性化 (App-Assisted Individualization)

应用辅助个性化是在 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] 功能,例如 定位 (seeking)

8.9.2 互操作性加密

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

加密媒体扩展流格式注册表 提供了对此类流格式的引用。

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() 算法中,反映 许可证销毁记录message 是一个 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

当包含在 MediaKeyMessageEvent 对象的 ArrayBuffer message 属性中时,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 编码。

当包含在 MediaKeyMessageEvent 对象的 ArrayBuffer message 属性中时,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 编码”术语定义以及“关于实现无填充 base64url 编码的说明”。具体而言,没有 '=' 填充,并且必须分别使用字符 '-' 和 '_' 代替 '+' 和 '/'。

10. 安全性

10.1 输入数据攻击和漏洞

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

如果 CDM 运行的上下文(例如 DOM)与用户代理不同(沙盒化),则此类验证尤为重要。

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

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

10.2 CDM 攻击和漏洞

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

利用未完全沙盒化和/或使用平台功能的 CDM 实现,可能允许攻击者访问操作系统或平台功能、提升权限(例如以系统或 root 身份运行),和/或访问驱动程序、内核、固件、硬件等。此类功能、软件和硬件可能并未针对敌意软件或基于网络的攻击进行加固,并且可能不会像用户代理那样及时更新安全修复程序。对 CDM 实现中的安全漏洞修复缺乏、不频繁或缓慢的更新增加了风险。此类 CDM 实现及暴露它们的用户代理在所有安全领域都必须特别小心,包括对 所有数据 的解析。

当使用属于或由客户端操作系统、平台和/或硬件提供的 CDM 或底层机制时,用户代理应当特别勤勉。

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

授予未经身份验证的源权限等同于在网络攻击者存在的情况下向任何源授予权限。请参阅 滥用持久化同意

10.3 网络攻击

10.3.1 潜在攻击

本节是非规范性的。

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

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

  • 被动网络攻击:无法保证在客户端和服务器之间传输的数据(包括 特征性标识符特征性永久标识符)不被其他实体查看。请参阅 用户追踪

  • 主动网络攻击:无法保证额外的脚本或 iframe 不会被注入到页面中(包括那些为了合法目的使用本规范定义 API 的页面以及不使用它们的页面)。其后果是

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

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

    • 传输的数据(包括 特征性标识符特征性永久标识符)可以被其他实体查看和/或修改。请参阅 用户追踪

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

10.3.2 缓解措施

以下技术可以缓解风险

使用 TLS

使用 TLS 的应用程序可以确信只有用户、代表用户运行的软件,以及拥有证书标识它们来自同一域的其他 TLS 页面才能与该应用程序交互。此外,与安全源相结合的 特定于源 的权限确保授予应用程序的权限不会被网络攻击者滥用。

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

阻止混合内容

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

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

用户代理应当确保在 访问可能比其他用户代理功能(例如 DOM 内容)带来更大安全问题的 密钥系统 之前,用户完全知情和/或给予明确同意。

此类机制必须基于 以避免合法的用途启用随后的恶意访问,并且必须基于 浏览配置文件

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

10.4 iframe 攻击

10.4.1 潜在攻击

本节是非规范性的。

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

10.4.2 缓解措施

包括出于安全和/或 隐私原因告知用户或需要同意 的用户代理,应当将同意的 UI 和持久性建立在顶级 文档 与使用本规范定义 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 拥有与 MediaKeys 对象一一关联的 CDM 实例的概念。

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

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

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

  • MediaKeys 对象和底层实现不会在 之外暴露信息。

  • 持久化会话数据(如果适用)按 进行存储。

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

  • 不可能从 CDM 中提取、导出或推断除本规范中明确描述或在未经用户许可的情况下通过其他 Web 平台 API 提供给页面的信息。这适用于在客户端设备外部或向应用程序暴露的任何信息,例如在 CDM 消息中的信息。

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

    • 位置,包括地理位置

    • 特征性标识符 外的凭据或标识符

    • 操作系统帐户名和其他潜在的 PII(个人身份信息)

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

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

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

    • 未与本规范定义的 API 相关联或非因其结果而存储的用户状态。

11.4 用户追踪

11.4.1 问题

本节是非规范性的。

第三方主机(或任何能够将内容分发到多个站点的实体,例如广告商)可以使用由 CDM 代表其存储的 特征性标识符 或持久化数据(包括许可证、密钥、密钥 ID 或 许可证销毁记录)来跨多个会话(包括跨 浏览配置文件)追踪用户,从而建立用户活动或兴趣的配置文件。此类追踪将破坏 Web 平台其他部分提供的隐私保护,例如可能启用其他方式无法实现的精准定向广告。结合知晓用户真实身份的站点(例如需要认证凭据的内容提供商或电子商务站点),这可能使压迫性团体能比在完全匿名网络使用的世界中更准确地针对个人。

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

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

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

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

值得注意的是,此类标识符,特别是那些不可清除、非 特定或 永久 的标识符,其追踪影响超过了现有技术,例如 Cookie [COOKIES] 或嵌入在 URL 中的会话标识符。

如果不加缓解,根据 密钥系统 的设计,此类追踪可能采取三种形式

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

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

  • 如果 密钥系统 消息以一致的方式包含从用户标识符派生的信息,例如使得特定内容项目的初始 密钥系统 消息的一部分随时间保持不变并且依赖于用户标识符,那么任何应用程序都可以使用此信息来随时间追踪该设备或用户。

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

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

除了站点和其他第三方追踪用户的潜力外,用户代理实现者、CDM 供应商或设备供应商也可以构建用户的活动或兴趣的配置文件,例如用户访问的使用本规范定义 API 的站点。此类追踪将破坏 Web 平台其他部分提供的隐私保护,特别是与源隔离相关的部分。

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

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

以下部分描述了可以在未经用户同意的情况下缓解追踪风险的技术。

11.4.2 缓解措施

不要 使用特征性标识符或特征性永久标识符

密钥系统 实现应当尽可能避免 使用特征性标识符和特征性永久标识符,仅在它们对实现的健壮性有实质性贡献时使用它们。请参阅 限制或避免使用特征性标识符和永久标识符

不要将 特征性永久标识符 暴露给应用程序

实现不得特征性永久标识符 暴露给应用程序或源。

加密 特征性标识符

密钥系统 消息中的 特征性标识符 必须与时间戳或随机数一起加密,以确保 密钥系统 消息始终不同。这防止了除了完全支持该 密钥系统 的服务器外,使用 密钥系统 消息进行追踪。请参阅 加密标识符

像处理 Cookie / Web 存储一样处理 特征性标识符 和密钥系统存储数据

用户代理应当以强烈关联 HTTP 会话 Cookie [COOKIES] 的方式,向用户呈现 特征性标识符 和由 密钥系统 存储的数据的存在,将其包含在“删除所有数据”中,并将其呈现在相同的 UI 位置。这可能会鼓励用户以合理的怀疑态度看待此类标识符。用户代理应当帮助用户避免 不完全的数据清除

不要将特定于源的信息暴露给不相关的实体

不要向个性化服务器或与源无关的其他实体提供 或与源 相关联 的值。如果实现使用了此类过程,请遵循 个性化 部分中的要求和建议。

使用不可关联的每个源每个配置文件的值和标识符

对于暴露给应用程序的所有 特征性值,实现必须为每个 浏览配置文件 使用不同的 应用程序无法关联 的值。请参阅 8.4.1 使用每个源每个配置文件的值

这对于 使用特征性标识符 的实现尤为重要。请参阅 8.5.3 使用每个源每个配置文件的标识符

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

CDM 所使用的任何可能以应用程序或许可证服务器可见的方式影响消息或行为的数据,必须来源 (origin)浏览配置文件 (browsing profile) 进行分区,并且不得泄漏给隐私浏览会话或从中泄漏。这包括内存中数据和持久化数据。具体而言(但不限于),会话数据、许可证、密钥和每个来源的标识符必须来源和按浏览配置文件进行分区。请参阅 8.3.1 使用特定于来源和浏览配置文件的 密钥系统 (Key System) 存储 以及 8.4.1 使用按来源按配置文件的值

提供用户删除持久化数据的功能,包括 特征标识符 (Distinctive Identifiers)

用户代理必须为用户提供清除由 密钥系统 (Key Systems) 维护的任何持久化数据的能力,包括 特征标识符。请参阅 允许清除持久化数据

过期存储的数据

用户代理可以(可能以用户配置的方式)在一段时间后自动删除 特征标识符 和/或其他密钥系统数据。

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

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

然而,如果用户不完全理解此类过期带来的影响,这也可能使用户对内容的访问(特别是购买或租赁的内容)处于风险之中。

阻止第三方访问

用户代理可以限制对 密钥系统 和/或功能的访问,仅限于源自浏览上下文顶层 文档 (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 隐私浏览模式

用户代理可以支持旨在保护用户匿名性和/或确保浏览活动记录不在客户端持久化的操作模式(例如,隐私浏览)。前几节中讨论的隐私问题对于采用此类模式的用户来说可能尤为令人担忧。

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

11.8 安全源和传输

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

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

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

12. 一致性

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

本文档中的关键字 MAYMUSTMUST NOTOPTIONALRECOMMENDEDREQUIREDSHALLSHALL NOTSHOULDSHOULD NOT 仅在全部大写时(如此处所示),根据 BCP 14 [RFC2119] [RFC8174] 进行解释。

13. 示例

本节是非规范性的。

本节包含使用提议的扩展进行各种用例的示例解决方案。这些并不是这些用例的唯一解决方案。视频元素在示例中使用,但同样适用于所有媒体元素。在某些情况下,例如使用同步 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 初始化期间处理加密事件,初始化会简单得多。这可以通过以其他方式提供 初始化数据 或在创建 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 对本规范的贡献。感谢其他许多为本规范做出贡献的人,包括通过参与邮件列表和问题讨论。

A. 参考资料

A.1 规范性参考资料

[COOKIES]
HTTP 状态管理机制. A. Barth. IETF. 2011年4月. 提议标准. URL: https://httpwg.org/specs/rfc6265.html
[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. 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://w3org.cn/TR/eme-initdata-keyids/
[EME-INITDATA-REGISTRY]
加密媒体扩展初始化数据格式注册表. Joey Parrish; Greg Freedman. W3C. 2026年5月7日. DRY. URL: https://w3org.cn/TR/eme-initdata-registry/
[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.whatwg.cn/multipage/
[Infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. 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://w3org.cn/TR/mixed-content/
[PERMISSIONS-POLICY]
权限策略. Ian Clelland. W3C. 2025年10月6日. W3C 工作草案. URL: https://w3org.cn/TR/permissions-policy-1/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. 1997年3月. Best Current Practice. 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 Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/

A.2 参考性参考资料

[CENC]
ISO/IEC 23001-7:2016, 信息技术 — MPEG 系统技术 — 第 7 部分:ISO 基础媒体文件格式文件中的通用加密. ISO/IEC. 国际标准. URL: https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1
[EME-HDCP-VERSION-REGISTRY]
加密媒体扩展 HDCP 版本注册表. Joey Parrish; Greg Freedman. W3C. 2026年5月7日. DRY. URL: https://w3org.cn/TR/eme-hdcp-version-registry/
[EME-STREAM-REGISTRY]
加密媒体扩展流格式注册表. Joey Parrish; Greg Freedman. W3C. 2026年5月7日. DRY. URL: https://w3org.cn/TR/eme-stream-registry/
[MEDIA-SOURCE]
媒体源扩展™. Jean-Yves Avenard; Mark Watson. W3C. 2025年11月4日. W3C 工作草案. URL: https://w3org.cn/TR/media-source-2/
[RFC6838]
媒体类型规范和注册程序。N. Freed; J. Klensin; T. Hansen. IETF. 2013年1月. 当前最佳实践. URL: https://www.rfc-editor.org/rfc/rfc6838
[RFC7515]
JSON 网络签名 (JWS). M. Jones; J. Bradley; N. Sakimura. IETF. 2015年5月. 建议标准. URL: https://www.rfc-editor.org/rfc/rfc7515
[webaudio]
Web 音频 API 1.1. Paul Adenot; Hongchan Choi. W3C. 2024年11月5日. FPWD. URL: https://w3org.cn/TR/webaudio-1.1/