版权所有 © 2026 万维网联盟 (W3C)。W3C® 法律免责声明、商标及宽松文档许可规则适用。
本规范扩展了 HTMLMediaElement [HTML],提供了控制加密内容播放的 API。
该 API 支持从简单的明文密钥解密到高价值视频(假设用户代理实现了相应功能)等各种用例。许可证/密钥交换由应用程序控制,这有助于开发支持多种内容解密和保护技术的稳健播放应用程序。
本规范不定义内容保护或数字版权管理 (DRM) 系统。相反,它定义了一个通用 API,可用于发现、选择和交互此类系统以及更简单的内容加密系统。合规本规范并不要求实现数字版权管理:仅要求实现 Clear Key(明文密钥)系统作为通用基准。
该通用 API 支持一套简单的内容加密功能,将身份验证和授权等应用功能留给页面作者。这是通过要求内容保护系统特定的消息传递由页面中介,而不是假设加密系统与许可证或其他服务器之间存在带外通信来实现的。
本节描述了本文件发布时的状态。当前的 W3C 出版物列表和本技术报告的最新版本可以在 W3C 标准和草案索引中找到。
自 2017 年 9 月以 W3C 推荐标准发布以来,新增了两项功能:
encryptionScheme 属性进行的加密方案能力检测。getStatusForPolicy() 方法查询与 HDCP 策略关联的密钥状态的能力。其他功能的纳入超出了媒体工作组的范围。除了编辑更新外,本规范所做的其他实质性更改旨在解决针对本规范的维护问题:
encrypted-media 标识符与权限策略(Permissions Policy)集成。MediaKeyStatus 中添加 "usable-in-future",用于尚未可用于解密的密钥。QuotaExceededError。MediaKeySessionClosedReason 以描述会话关闭的可能原因,并相应地调整算法。有关自上一版本以来所做更改的完整列表,请参阅 提交记录 (commits)。
发布为工作草案并不意味着 W3C 及其成员的认可。
这是一份草案文件,可能随时被其他文件更新、替换或废弃。将其作为进展中的工作以外的引用是不恰当的。
本文件由遵循 W3C 专利政策的组别制定。W3C 维护了一份与该组交付成果相关的 专利披露公开列表;该页面还包含了披露专利的说明。任何知悉其认为包含 必要权利要求 (Essential Claim) 的个人,必须按照 W3C 专利政策第 6 节披露相关信息。
本文件受 2025 年 8 月 18 日 W3C 流程文档约束。
本节是非规范性的。
本规范使脚本能够选择内容保护机制、控制许可证/密钥交换并执行自定义许可证管理算法。它支持广泛的用例,而无需在每个用户代理中针对每个用例进行客户端修改。这使得内容提供商能够为所有设备开发单一的应用程序解决方案。
受支持的内容根据容器特定的“通用加密”规范进行加密,从而实现跨密钥系统的使用。受支持的内容具有未加密的容器,使元数据能够提供给应用程序,并保持与其他 HTMLMediaElement 功能的兼容性。
实现者应注意本规范中描述的安全和隐私威胁及问题的缓解措施。特别是,如果不了解 密钥系统及其实现的安全和隐私属性,则无法满足规范的安全和隐私要求。8. 实现要求包含了与底层 密钥系统实现集成和使用相关的安全和隐私条款。10. 安全性侧重于外部威胁,例如输入数据或网络攻击。11. 隐私侧重于处理特定用户信息并为用户提供对其自身隐私的充分控制。
虽然本规范独立于媒体数据的来源,但作者应意识到许多实现仅支持解密通过媒体源扩展 (Media Source Extensions) [MEDIA-SOURCE] 提供的媒体数据。
下面展示了一个使用该 API 实现的通用堆栈。此图显示了一个示例流程;其他 API 调用和事件组合也是可能的。
内容解密模块 (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 是由 CDM 生成的唯一字符串标识符,应用程序可使用它来识别 MediaKeySession 对象。
每当用户代理和 CDM 成功创建一个新会话时,就会生成一个新的会话 ID。
每个会话 ID 应当 在其创建的浏览上下文中是唯一的。对于 是持久会话类型吗? 算法返回 true 的会话类型,会话 ID 必须 在一段时间内于 来源 (origin) 内是唯一的,包括跨浏览会话。
底层的内容保护协议不一定需要支持会话 ID。
除非另有说明,密钥指可用于解密 媒体数据 中块的解密密钥。每个此类密钥由一个唯一的 密钥 ID 标识。密钥与用于将其提供给 CDM 的 会话 关联。(同一个密钥可能存在于多个会话中。)此类密钥 必须 仅通过 update() 调用提供给 CDM。(它们稍后可能由 load() 作为存储会话数据的一部分加载。)
例如,当两个视频分辨率之间的策略不同时,每组流使用不同的密钥加密,以便可以独立执行策略;同样,当音频和视频流的策略不同时,它们使用不同的密钥。跨策略组共享密钥会阻止独立执行,因此每个策略组使用不同的密钥是确保跨客户端执行和兼容性的唯一方法。
如果 CDM 确信密钥当前可用于解密一个或多个 媒体数据 块,则该密钥被认为是可用于解密的。
例如,如果密钥的许可证已过期,则该密钥不可用于解密。即使许可证未过期,如果其他使用条件(例如输出保护)当前未满足,密钥也不可用于解密。
一个 密钥 与一个密钥 ID 关联,该 ID 是八位字节序列,用于唯一标识密钥。容器指定了可以解密 媒体数据 中某个块或一组块的密钥 ID。初始化数据 可以 包含密钥 ID 以标识解密媒体数据所需的密钥。但是,不要求初始化数据包含 媒体数据 或 媒体资源 中使用的任何或所有密钥 ID。提供给 CDM 的 许可证 将每个密钥与一个密钥 ID 关联,以便 CDM 在解密加密的媒体数据块时可以选择适当的密钥。
如果 CDM 的会话实现包含关于密钥的任何信息(特别是 密钥 ID),则该密钥被视为会话已知,无论实际 密钥 是否可用或其值是否已知。已知密钥通过 keyStatuses 属性公开。
密钥即使在变得不可用后仍被视为已知,例如由于 过期,或者如果它们被删除但有 许可证销毁记录 可用。只有当密钥被显式从会话中删除且任何许可证释放消息被确认后,密钥才会变得未知。
例如,如果一个 update() 调用提供了不包含该密钥的新许可证,并包含替换先前包含该密钥的许可证的指令,则该密钥可能变得未知。
密钥系统通常需要包含有关待解密流的信息的初始化数据块,然后才能构建许可证请求消息。此块可以是一个简单的密钥或内容 ID,或者是包含此类信息的更复杂的结构。应用程序通过某种应用程序特定方式或从 媒体数据 中获取初始化数据。
初始化数据是容器特定数据的通用术语,由 CDM 用于生成许可证请求。
初始化数据的格式取决于容器类型,容器 可以 支持多种初始化数据格式。初始化数据类型 是一个指示伴随的初始化数据格式的字符串。初始化数据类型字符串始终区分大小写匹配。建议 初始化数据类型字符串为小写 ASCII 字符串。
加密媒体扩展初始化数据格式注册表 (Encrypted Media Extensions Initialization Data Format Registry) 提供了从 初始化数据类型 字符串到每种格式规范的映射。
当用户代理在 媒体数据 中遇到初始化数据时,它会在 事件的 encryptedinitData 属性中将该数据提供给应用程序。用户代理 严禁 在遇到初始化数据时存储或使用其内容。应用程序通过 generateRequest() 将 初始化数据 提供给 CDM。用户代理 严禁 通过其他方式向 CDM 提供 初始化数据。
对于给定的流集合或 媒体数据,初始化数据 必须 是固定值。它 必须 仅包含与播放给定流集合或 媒体数据 所需密钥相关的信息。它 严禁 包含应用程序数据、客户端特定数据、用户特定数据或可执行代码。
初始化数据 不应 包含密钥系统特定的数据或值。实现 必须 为其支持的每种 初始化数据类型 支持 [EME-INITDATA-REGISTRY] 中定义的通用格式。
不鼓励使用专有格式/内容,强烈不鼓励仅支持或使用专有格式。专有格式仅应与不支持通用格式的现有内容或现有客户端设备一起使用。
如果两个或多个标识符或其他值相同或者可以通过合理的努力和时间来关联它们,则称它们是可关联的。否则,这些值是 不可关联的。
如果引用实体或实体集在没有额外实体参与的情况下,可以通过合理的努力和时间来关联或关联它们,则称两个或多个标识符或其他值是 可被实体关联的。否则,这些值是 不可被实体关联的。
如果两个或多个标识符或其他值是 不可被实体关联的,且该实体集包括应用程序、所有其他应用程序以及它们使用或通信的其他实体(如服务器),则称它们是 不可被应用程序关联的。否则,这些值将被视为 可被应用程序关联的,这是被禁止的。
独特值是指在大量用户或客户端设备之间不共享的值、数据片段、拥有数据片段的含义,或可观察的行为或时序。独特值可能存在于内存中或被持久化。
虽然独特值通常对用户或客户端设备是唯一的,但值不必严格唯一即可成为独特的。例如,在少数用户之间共享的值仍然可以是独特的。
永久标识符是指以某种方式不可磨灭,或用户难以移除、重置或更改的值、数据片段、拥有数据片段的含义或可观察的行为或时序。这包括但不限于:
硬件或基于硬件的标识符。
工厂预置在硬件设备中的值。
与操作系统安装实例相关联或派生的值。
与用户代理安装实例相关联或派生的值。
与 CDM 或其他软件组件相关联或派生的值。
配置文件或类似半永久数据中的值,即使是在客户端生成的。
客户端或其他用户帐户值。
当在客户端外部公开时,独特永久标识符及其衍生值或以其他方式与它们相关的值 必须 被 加密。独特永久标识符 严禁 向应用程序公开,即使是加密形式。
虽然独特永久标识符通常对用户或客户端设备是唯一的,但独特永久标识符不需要严格唯一即可成为独特的。例如,在少数用户之间共享的独特永久标识符仍然可以是独特的。
独特永久标识符不是 独特标识符,因为它不是(在本规范范围内)派生或生成的。
distinctiveIdentifier 控制是否可以使用独特永久标识符。具体而言,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccess 的 distinctiveIdentifier 成员的值为 "required" 时,才可以使用独特永久标识符。
独特标识符是一个值(包括不透明或加密形式),对于该值,任何客户端外部实体都有可能关联或关联超出用户在 Web 平台(例如 Cookie 和其他站点数据)所期望范围的值。例如,那些 可被应用程序以外的实体关联的 值,跨越 a) 来源 (origins),b) 浏览配置文件,或 c) 浏览会话,即使在用户尝试通过清除浏览数据来保护其隐私之后,或用户不容易打破这种关联的值。特别地,如果一个 中心服务器(例如个性化服务器)能够关联 跨来源的值,则该值就是独特标识符;例如,因为个性化请求包含了一个共同值,或者个性化请求中提供的值 可被此类服务器关联,即使在尝试清除浏览数据之后。造成这种情况的可能原因包括在个性化过程中使用了 独特永久标识符。
向应用程序公开的独特标识符(即使是加密形式)受 标识符要求 的约束,包括必须 被加密、针对每个来源和配置文件唯一,以及 可被清除。
虽然独特标识符的实例化或使用是由应用程序使用本规范定义的 API 触发的,但不必将标识符提供给应用程序以触发与独特标识符相关的条件。(独特永久标识符甚至不以不透明或加密形式提供给应用程序。)
distinctiveIdentifier 控制是否可以使用独特标识符。具体而言,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccess 的 distinctiveIdentifier 成员的值为 "required" 时,才可以使用独特标识符。
独特标识符是指满足以下所有条件的值、数据片段、拥有数据片段的含义,或可观察的行为或时序:
它是 独特 的。
虽然独特标识符通常对用户或客户端设备是唯一的,但标识符不需要严格唯一即可成为独特的。例如,在少数用户之间共享的标识符仍然可以是独特的。
它、关于它的信息或从中派生或以其他方式与它相关的值,即使以加密形式,也会在客户端外部公开。这包括但不限于提供给应用程序和/或许可证、个性化 或其他服务器。
它具有以下一个或多个属性:
它派生自一个或多个 独特永久标识符。
产生该值的生成、个性化、配置或其他过程涉及、使用了、提供了、派生自或类似地涉及了一个或多个 独特永久标识符 或另一个独特标识符。
它是 可被清除的,但不是 连同 Cookie 和其他站点数据一起清除的。
例如,通过用户代理外部的某种机制,如操作系统级机制。
规范明确禁止向应用程序公开的其他相关属性包括:
此类规范禁止的值的示例包括但不限于:
所有来源使用单个基于硬件的值。
所有来源使用单个基于随机值的值。
从 个性化 过程中获得的用于所有来源的单个值。
包含上述任何值的全部或部分的值。
用于多个但不一定是所有来源的单个值。
用于域上所有来源的单个值。(标识符必须是针对每个 来源 (origin) 的。)
预配置的源特定值。
通过极易反转的方式生成的值,因此 可被应用程序关联,无论是在客户端生成还是涉及 个性化 过程。例如,XOR 运算或以其他方式将来源(的部分)与固定值集成。
虽然独特标识符通常 可被生成它们的实体关联,但它们 必须 是 不可被应用程序关联的。换句话说,此类相关或关联仅由最初生成独特标识符值的实体(例如个性化服务器)实现。有权访问 独特永久标识符 的实体 严禁 向应用程序公开此功能,因为这会使产生的独特标识符 可被应用程序关联,并 应该 注意避免将此类关联暴露给其他实体或第三方。
独特标识符的示例包括但不限于:
包含在密钥请求中的一系列字节,与其他客户端设备包含的字节序列不同,并且是基于或通过 独特永久标识符 直接或间接获取的。
密钥请求中包含的公钥,与其他客户端设备请求中包含的公钥不同,并且是基于或通过 独特永久标识符 直接或间接获取的。
证明私钥持有(例如,通过对某些数据签名),这是其他客户端设备没有的,且是基于或通过 独特永久标识符 直接或间接获取的。
此类密钥的标识符。
用于派生另一个被公开值的值,即使第一个值没有直接公开。
派生自另一个独特标识符的值。
一个随机值,该值在提供 独特永久标识符 的同时被报告给(例如 个性化)服务器,或在提供 独特永久标识符 后由此类服务器提供。
派生自工厂预置在硬件设备中的唯一值的值。
派生自工厂预置在硬件设备中的唯一硬件值(例如 MAC 地址或序列号)或软件值(例如操作系统安装实例或操作系统用户帐户名)的值。
不是 独特标识符的事物示例:
如果安装基数很大,则在给定 CDM 版本的所有副本之间共享的公钥。
唯一但仅在一次会话中使用的随机数 (nonce) 或临时密钥。
未在客户端外部公开的值,即使通过派生或类似方式,包括通过 个性化 或类似方式。
例如,当 CDM 不让这些证明进一步流向应用程序,而是使用不构成独特标识符的密钥自行进行新证明时,视频管道和 CDM 之间证明中使用的设备唯一密钥。
连同浏览数据(例如 Cookie)一起被完全清除/可清除的值,之后它将被替换为一个 不可关联的(不仅是 不可被应用程序关联的)值,即使是由中央服务器(例如 个性化 服务器)关联,并且满足以下一项或多项:
值的生成过程中没有涉及 独特永久标识符 或独特标识符。
它是不使用来自系统输入而生成的随机值。
它是服务器在不使用或不知晓另一个独特标识符的情况下提供的值。
如果一个实现、配置、实例或对象在其生命周期或相关实体的生命周期内的任何时间,在客户端外部公开(即使是加密形式)了一个或多个 独特标识符、关于它们的信息、或从中派生或以其他方式与它们相关的值,则该实现、配置、实例或对象 使用独特标识符。这包括但不限于向应用程序和/或许可证、个性化 或其他服务器提供此类值。
如果一个实现、配置、实例或对象在其生命周期或相关实体的生命周期内的任何时间,在客户端外部公开(即使是加密形式)了一个或多个 独特永久标识符、关于它们的信息、或从中派生或以其他方式与它们相关的值,则该实现、配置、实例或对象 使用独特永久标识符。这包括但不限于向 个性化 服务器提供此类值。此类值 严禁 提供给应用程序。
如果一个实现、配置、实例或对象 使用独特标识符 和/或 使用独特永久标识符,则该实现、配置、实例或对象 使用独特标识符或独特永久标识符。
distinctiveIdentifier 控制是否可以使用 独特标识符 和 独特永久标识符。具体而言,只有当用于创建 MediaKeys 对象的 MediaKeySystemAccess 的 distinctiveIdentifier 成员的值为 "required" 时,才可以使用此类标识符。
在播放过程中,嵌入的媒体数据会公开给嵌入 来源 (origin) 中的脚本。为了使 API 能够在 事件中提供 初始化数据,媒体数据 必须 与嵌入页面 CORS-同源。如果 媒体数据 与嵌入文档跨来源,作者 应该 在 encryptedHTMLMediaElement 上使用 crossOrigin 属性,并在 媒体数据 响应中使用 CORS 头,使其 CORS-同源。
在播放过程中,嵌入的媒体数据会公开给嵌入 来源 (origin) 中的脚本。为了使 API 能够在 事件中提供 初始化数据,媒体数据 严禁 成为 混合内容 [MIXED-CONTENT]。encrypted
时间 必须 等同于 ECMAScript 语言规范中表示的时间。
如果不存在此类时间或时间不确定,时间将等于 NaN。它绝不应具有 Infinity 的值。
时间通常代表具有毫秒精度的某个瞬间;然而,仅凭这一点并不足以作为定义。定义的“时间值和时间范围”参考增加了其他重要要求。
给定机器上的用户代理可能支持在各种不同的上下文、模式或临时状态下执行,这些上下文、模式或状态预期在应用程序可见的状态和数据方面独立运行。特别是,预期所有存储的数据都是独立的。在本规范中,我们将此类独立的上下文或模式称为“浏览配置文件”。
此类独立上下文的示例包括用户代理在不同的操作系统用户帐户中运行,或者用户代理提供为单个帐户定义多个独立配置文件的功能。
本节定义了获取 密钥系统 访问权限的机制。请求中包含功能特性也实现了功能检测。
算法的步骤在拒绝 promise 时总是会被终止。
requestMediaKeySystemAccess() 是一个由字符串 encrypted-media 标识的 权限策略控制功能。其 默认允许列表 是 'self' [PERMISSIONS-POLICY]。
WebIDLenum MediaKeysRequirement {
"required",
"optional",
"not-allowed"
};
MediaKeysRequirement 枚举定义如下
| 枚举描述 | |
|---|---|
required(必须)
|
|
可选
|
|
not-allowed
|
|
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,默认为 ""MediaKeySystemAccess 的 getConfiguration() 方法返回的 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"(临时)会话的应用程序,应在调用 requestMediaKeySystemAccess() 时将此成员设置为 "required"。
sessionTypes 类型为 sequence<DOMString>MediaKeySessionType 列表。所有值都必须受支持。当该字典被传递给 requestMediaKeySystemAccess() 时,如果该成员 不存在 [Infra],则该字典将被视为如同此成员被设置为 [ " 一样。temporary" ]
实现 应该 不向此字典添加成员。如果添加了成员,它们 必须 是 MediaKeysRequirement 类型,并且 建议 它们具有 "optional" 的默认值,以支持最广泛的应用程序和客户端组合。
用户代理实现无法识别的字典成员将根据 [WEBIDL] 被忽略,并且不会在 requestMediaKeySystemAccess() 算法中被考虑。如果应用程序使用非标准字典成员,则它 必须 不依赖于用户代理实现对包含此类字典成员的配置的拒绝。
此字典 必须 不被用于向 CDM 传递状态或数据。
WebIDLdictionary MediaKeySystemMediaCapability {
DOMString contentType = "";
DOMString? encryptionScheme = null;
DOMString robustness = "";
};
MediaKeySystemMediaCapability 成员contentType 类型为 DOMString,默认为 ""encryptionScheme 类型为 DOMString,默认为 null与内容类型关联的加密方案。值为 null 或不存在,表示应用程序不需要特定的加密方案,因此任何加密方案均可接受。
空字符串不同于 null 或不存在,因此会被视为无法识别的加密方案。
robustness 类型为 DOMString,默认为 ""与内容类型关联的健壮性级别。空字符串表示任何解密和解码该内容类型的能力均可接受。实现 必须 配置 CDM 以支持至少在用于创建 MediaKeys 对象的 MediaKeySystemAccess 对象的配置中指定的健壮性级别。
为了使此对象所代表的能力被视为受支持,contentType 必须 不为空字符串,并且其完整值(包括所有编解码器)必须 在 robustness 下受到支持。
如果一组编解码器中的任何一个都是可接受的,请为每个编解码器使用此字典的单独实例。
MediaKeySystemMediaCapability 传递给 requestMediaKeySystemAccess() 时,应在 MIME 类型中指定编解码器和编解码器约束(例如,参见 [RFC6381]),除非容器规范性地隐含了它们,并应将 encryptionScheme 设置为特定值,而不是依赖 null 的默认值。
不同的加密方案通常是不兼容的。默认的 null 值以及将 null 解析为“任何”,为不知情的应用程序提供了向后兼容性,并为旧版本用户代理提供了 Polyfill(腻子脚本)路径。
MediaKeySystemAccess 对象提供对 密钥系统 的访问。
WebIDL[Exposed=Window, SecureContext] interface MediaKeySystemAccess {
readonly attribute DOMString keySystem;
MediaKeySystemConfiguration getConfiguration ();
Promise<MediaKeys> createMediaKeys ();
};
keySystem 类型为 DOMString,只读getConfiguration()
返回由 requestMediaKeySystemAccess() 算法选择的配置选项的受支持组合。
返回的对象是解析此对象时所调用的 requestMediaKeySystemAccess() 调用中传递的第一个可满足的 MediaKeySystemConfiguration 配置的非严格子集(加上任何隐含的默认值)。它不包含未在该单一配置中指定的任何能力的值(隐含的默认值除外),因此可能无法反映 密钥系统 实现的所有能力。配置中的所有值可以以任何组合方式使用。MediaKeysRequirement 类型的成员反映了该能力是否为任何组合所必需。它们不会具有 "optional" 的值。
当调用此方法时,用户代理 必须 执行以下步骤:
返回此对象的 configuration(配置)值。
这会导致每次调用此方法时都会创建一个新的对象并从 configuration 初始化。
如果应用程序未提供 encryptionScheme,则累积的 configuration 必须 仍包含一个值为 null 的 encryptionScheme 字段,以便 Polyfill 能够在不指定具体值的情况下检测用户代理对此字段的支持。
createMediaKeys()
为 keySystem 创建一个新的 MediaKeys 对象。
当调用此方法时,用户代理 必须 执行以下步骤:
设 promise 为一个新的 Promise。
并行执行以下步骤
令 configuration 为此对象的 configuration 值。
如果 configuration 的 distinctiveIdentifier 成员的值为 "required",则令 use distinctive identifier 为 true,否则为 false。
如果 configuration 的 persistentState 成员的值为 "required",则令 persistent state allowed 为 true,否则为 false。
如有必要,加载并初始化由该对象的 cdm implementation 值所代表的 密钥系统 实现。
令 instance 为该对象 cdm implementation 值所代表的 密钥系统 实现的新实例。
初始化 instance 以使用 configuration 启用、禁用和/或选择 密钥系统 特性。
如果 use distinctive identifier 为 false,则防止 instance 使用特有标识符和特有永久标识符。
如果 persistent state allowed 为 false,则防止 instance 持久化任何与该对象的 Document 的应用程序或 origin(源)相关的状态。
如果上述任何步骤失败,则用一个新的 DOMException 拒绝 promise,其名称是相应的 错误名称。
令 media keys 为一个新的 MediaKeys 对象,并按如下方式初始化
令 use distinctive identifier 值为 use distinctive identifier。
令 persistent state allowed 值为 persistent state allowed。
令 supported session types 值为 configuration 的 sessionTypes 成员的值。
令 cdm implementation 值为该对象的 cdm implementation 值。
令 cdm instance 值为 instance。
用 media keys 解决 promise。
返回 promise。
MediaKeys 对象代表一组相关的 HTMLMediaElement 可用于回放期间解密 媒体数据 的密钥。它还代表一个 CDM 实例。
当 MediaKeys 对象不再可访问时,用户代理可能会销毁它。
例如,当没有脚本引用且没有附加的媒体元素时。
对于返回 promise 的方法,所有错误都会通过拒绝返回的 Promise 来异步报告。这包括 [WEBIDL] 类型映射错误。
算法的步骤在拒绝 promise 时总是会被终止。
WebIDLenum MediaKeySessionType {
"temporary",
"persistent-license"
};
MediaKeySessionType 枚举定义如下
| 枚举描述 | |
|---|---|
temporary
|
一种会话,其许可证、密钥以及会话的相关记录或数据不会被持久化。 应用程序无需担心管理此类存储。对该会话类型的支持是 必需 的。 |
persistent-license
|
一种会话,其许可证(以及可能与会话相关的其他数据)将被持久化。当其包含的许可证和密钥被销毁时,许可证销毁记录 应 被持久化。许可证销毁记录 是一种 密钥系统 特有的证明,表明其包含的许可证和密钥已不再被客户端使用。对该会话类型的支持是 可选 的。 此类会话只有在与创建此对象的 应用程序有责任确保当其不再需要此类会话的数据时将其删除。参见 会话存储与持久化。 |
WebIDL[Exposed=Window, SecureContext] interface MediaKeys {
MediaKeySession createSession (optional MediaKeySessionType sessionType = "temporary");
Promise<MediaKeyStatus> getStatusForPolicy (optional MediaKeysPolicy policy = {});
Promise<boolean> setServerCertificate (BufferSource serverCertificate);
};
createSession()
返回一个新的 MediaKeySession 对象。
sessionType 参数会影响返回对象的行为。
当调用此方法时,用户代理 必须 执行以下步骤:
如果该对象的 supported session types 值不包含 sessionType,则 抛出 [WEBIDL] 。NotSupportedError
如果该对象的 persistent state allowed 值为 false,则 是否为持久会话类型?算法返回 true 的 sessionType 值将失败。
如果实现不支持当前状态下的 MediaKeySession 操作,则 抛出 [WEBIDL] 。InvalidStateError
某些实现无法执行 MediaKeySession 算法,直到使用 setMediaKeys() 将此 MediaKeys 对象与媒体元素关联。此步骤使应用程序能够在尝试执行此类操作之前检测到这种不常见的行为。
令 session 为一个新的 MediaKeySession 对象,并按如下方式初始化
令 sessionId 属性为空字符串。
令 expiration 属性为 NaN。
令 closed 属性为一个新的 promise。
令 key status 为一个新的空 MediaKeyStatusMap 对象,并按如下方式初始化
令 size 属性为 0。
令 session type 值为 sessionType。
令 uninitialized 值为 true。
令 callable 值为 false。
令 closing or closed 值为 false。
令 use distinctive identifier 值为该对象的 use distinctive identifier 值。
令 cdm implementation 值为该对象的 cdm implementation。
令 cdm instance 值为该对象的 cdm instance。
返回 session。
getStatusForPolicy()
返回给定 MediaKeysPolicy 的 MediaKeyStatus。
WebIDLdictionary MediaKeysPolicy {
DOMString minHdcpVersion;
};
MediaKeysPolicy 字典是一个仅包含可选属性的对象。每个属性代表一项策略要求。如果 CDM 基于所有要求允许呈现解密的媒体数据,则称该策略得到满足。
HDCP 策略由 minHdcpVersion 表示。如果系统能够启用指定的 HDCP 版本或更高版本,则该策略将产生 "usable" 的 MediaKeyStatus。[EME-HDCP-VERSION-REGISTRY] 提供了从 minHdcpVersion 值到 HDCP 规范的映射。
HDCP 状态的确定方式应与 CDM 在回放期间强制执行此类限制的方式相同。通过这种方式,应用程序开发人员可以获得合理的提示,以优化他们获取的内容从而开始回放。
当调用此方法时,用户代理 必须 执行以下步骤:
TypeError 拒绝的 promise。设 promise 为一个新的 Promise。
加入队列一个任务以运行以下步骤
对于 policy 的每个 字典成员,运行以下步骤
如果键不是有效的 MediaKeysPolicy 成员或值的类型不正确,则用 拒绝 promise 并中止这些步骤。TypeError
对于 policy 的每个 字典成员,运行以下步骤
如果 CDM 无法确定该 字典成员 的 MediaKeyStatus,则用 拒绝 promise 并中止这些步骤。NotSupportedError
对于 policy 的每个 字典成员,运行以下步骤
如果 CDM 会阻止呈现该 字典成员 的解密媒体数据,则用 "output-restricted" 解决 promise 并中止这些步骤。
用 "usable" 解决 promise。
返回 promise。
setServerCertificate()
提供一个用于加密发送给许可证服务器的消息的服务器证书。
对于使用此类证书的 密钥系统,应用程序 应该 调用 setServerCertificate() 以显式设置服务器证书。否则,许可证交换可能会失败。
某些密钥系统也支持通过 加入队列一个 "message" 事件 算法从服务器请求证书。然而,并非所有使用此类证书的密钥系统都支持这一点。
服务器证书内容是 密钥系统 特有的。它 必须 不包含可执行代码。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象 cdm implementation 值所代表的 Key System 实现不支持服务器证书,则返回一个以 false 解析的期约(promise)。
如果 serverCertificate 为空数组,则返回一个以新创建的 拒绝的期约。TypeError
令 certificate 为 serverCertificate 参数内容的一个副本。
设 promise 为一个新的 Promise。
并行执行以下步骤
令 sanitized certificate 为 certificate 的经过验证和/或净化的版本。
用户代理在将证书传递给 CDM 之前,应彻底验证该证书。这可能包括验证数值是否在合理范围内、剥离无关数据或字段、预解析、净化和/或生成完全净化的版本。用户代理应检查字段的长度和数值是否合理。未知的字段应被拒绝或移除。
使用此对象的 cdm instance 来处理 sanitized certificate。
如果上述步骤失败,则使用一个新的 DOMException 拒绝 promise,该异常的名称应为适当的 错误名称。
以 true 解析 promise。
返回 promise。
运行“是否为持久化会话类型?”算法以确定指定的会话类型是否支持任何形式的持久化。运行此算法的请求需包含一个 MediaKeySessionType 值。
运行以下步骤
令 session type 为指定的 MediaKeySessionType 值。
遵循以下列表中针对 session type 值的步骤
temporary"false。persistent-license"true。本节描述与存储和持久化相关的通用要求。
如果 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)能够访问所存储的数据。参见 用户设备上存储的信息。
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)关闭与该对象关联的 密钥会话。
关闭密钥会话会导致销毁任何未显式存储的许可证和密钥。
对于返回 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 ();
};
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
generateRequest()
基于 initData 生成许可证请求。如果算法成功且期约被解析,则始终会排队一个类型为 "license-request" 或 "individualization-request" 的 。message
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 closing or closed 值(正在关闭或已关闭)为 true,则返回一个以 拒绝的期约。InvalidStateError
如果此对象的 uninitialized(未初始化)值为 false,则返回一个以 拒绝的期约。InvalidStateError
令此对象的 uninitialized 值为 false。
如果 initDataType 为空字符串,则返回一个以新创建的 拒绝的期约。TypeError
如果 initData 为空数组,则返回一个以新创建的 拒绝的期约。TypeError
如果此对象 cdm implementation 值所代表的 Key System 实现不支持将 initDataType 用作 Initialization Data Type(初始化数据类型),则返回一个以 拒绝的期约。字符串比较区分大小写。NotSupportedError
令 init data 为 initData 参数内容的一个副本。
令 session type 为此对象的 session type。
设 promise 为一个新的 Promise。
并行执行以下步骤
如果 init data 对于 initDataType 无效,则以新创建的 拒绝 promise。TypeError
令 sanitized init data 为 init data 的经过验证和净化的版本。
用户代理在将 初始化数据 传递给 CDM 之前,必须(MUST)彻底验证该数据。这包括验证字段的长度和数值是否合理、验证数值是否在合理范围内,以及剥离无关、不受支持或未知的数据或字段。用户代理 建议(RECOMMENDED)预解析、净化和/或生成 初始化数据 的完全净化版本。如果 initDataType 指定的 初始化数据 格式支持多个条目,用户代理 应(SHOULD)移除 CDM 不需要的内容。用户代理 不得(MUST NOT)重新排列 初始化数据 中的条目。
如果上述步骤失败,则以新创建的 拒绝 promise。TypeError
如果 sanitized init data 为空,则以 拒绝 promise。NotSupportedError
令 session id 为空字符串。
令 message 为 null。
令 message type 为 null。
令 cdm 为此对象 cdm instance 值所代表的 CDM 实例。
使用 cdm 执行以下步骤
如果 cdm 不支持 sanitized init data,则以 拒绝 promise。NotSupportedError
遵循以下列表中针对 session type 值的步骤
temporary"令 requested license type 为临时且不可持久化的许可证。
返回的许可证必须不可持久化,且不得要求持久化与之相关的信息。
persistent-license"令 requested license type 为可持久化的许可证。
令 session id 为唯一的 会话 ID 字符串。
如果运行“是否为持久化会话类型?”算法在 session type 上的结果为 true,则此 ID 必须(MUST)在此对象 来源(origin) 文档(Document) 的整个生命周期(包括跨文档和浏览会话)中保持唯一。
令 message 为基于 sanitized init data(按 initDataType 解读)生成的针对 requested license type 的许可证请求。
cdm 不得(MUST NOT)使用任何非通过 sanitized init data 提供的流特定数据,包括 媒体数据。
cdm 不应(SHOULD NOT)在此阶段存储会话数据,包括会话 ID。参见 会话存储与持久化。
令 message type 为 "license-request"。
令 message 为在基于 sanitized init data 生成针对 requested license type 的许可证请求之前需要处理的请求。
在随后的 update() 调用中,CDM 必须(MUST)基于 sanitized init data(按 initDataType 解读)生成针对 requested license type 的许可证请求。
令 message type 反映 message 的类型,即 "license-request" 或 "individualization-request"。
加入队列一个任务以运行以下步骤
如果因资源不足导致上述任何步骤失败,则以 拒绝 promise。QuotaExceededError
如果因任何其他原因导致上述任何步骤失败,则以名称为适当 错误名称 的新 DOMException 拒绝 promise。
将 sessionId 属性设置为 session id。
将此对象的 callable 值设置为 true。
以 undefined 解析 promise。
在 session 上运行 Queue a "message" Event(排队“message”事件)算法,并提供 message type 和 message。
返回 promise。
load()
将指定会话存储的数据加载到此对象中。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以 拒绝的期约。InvalidStateError
如果此对象的 uninitialized 值为 false,则返回一个以 拒绝的期约。InvalidStateError
令此对象的 uninitialized 值为 false。
如果 sessionId 为空字符串,则返回一个以新创建的 拒绝的期约。TypeError
如果在此对象 session type 上运行“是否为持久化会话类型?”算法的结果为 false,则返回一个以新创建的 拒绝的期约。TypeError
令 origin 为此对象 文档(Document) 的 来源(origin)。
设 promise 为一个新的 Promise。
并行执行以下步骤
令 sanitized session ID 为 sessionId 的经过验证和/或净化的版本。
用户代理在将 sessionId 值传递给 CDM 之前,应彻底验证该值。至少应包括检查其长度和数值是否合理(例如,不应超过几十个字符且应为字母数字)。
如果上述步骤失败,或者 sanitized session ID 为空,则以新创建的 拒绝 promise。TypeError
如果在此对象 文档(Document) 中存在一个未 关闭 且 sessionId 属性为 sanitized session ID 的 MediaKeySession 对象,则以 拒绝 promise。QuotaExceededError
换句话说,如果在此浏览上下文中对于此 sanitized session ID 已经存在一个未关闭的会话(无论类型如何),则不要创建会话。
令 expiration time 为 NaN。
令 message 为 null。
令 message type 为 null。
令 cdm 为此对象 cdm instance 值所代表的 CDM 实例。
使用 cdm 执行以下步骤
如果 origin 中没有存储用于 sanitized session ID 的数据,则以 false 解析 promise 并中止此算法的并行步骤。
如果所存储会话的 session type 与当前 MediaKeySession session type 不相同,则以新创建的 拒绝 promise。TypeError
令 session data 为存储在 origin 中用于 sanitized session ID 的数据。此数据 不得(MUST NOT)包含来自其他来源的数据或未与任何来源关联的数据。
如果存在任何 文档(Document) 中有一个未 关闭 且表示该 session data 的 MediaKeySession 对象,则以 拒绝 promise。QuotaExceededError
换句话说,如果在此浏览上下文中对于此 sanitized session ID 已经存在一个未关闭的持久会话,则不要创建会话。
加载 session data。
如果 session data 指示了会话的 过期时间,则令 expiration time 为该过期时间。
如果需要发送消息,执行以下步骤
令 message 为基于 session data 生成的消息。
令 message type 为该消息对应的适当 MediaKeyMessageType。
加入队列一个任务以运行以下步骤
如果上述任何步骤失败,则使用适当的 错误名称 拒绝 promise。
将 sessionId 属性设置为 sanitized session ID。
将此对象的 callable 值设置为 true。
如果已加载的会话包含关于任何密钥的信息(存在 已知密钥),则对该 session 运行 Update Key Statuses(更新密钥状态)算法,并提供每个密钥的 密钥 ID 以及相应的 MediaKeyStatus。
如果需要进行额外处理以确定密钥的状态,请使用 "status-pending"。一旦一个或多个密钥的额外处理完成,请再次运行 更新密钥状态 算法以更新实际状态。
在 session 上运行 Update Expiration(更新过期时间)算法,并提供 expiration time。
以 true 解析 promise。
如果 message 不为 null,则在 session 上运行 排队“message”事件 算法,并提供 message type 和 message。
返回 promise。
update()
向 CDM 提供消息,包括许可证。
response 参数包含要提供给 CDM 的消息。其内容特定于 Key System。它 不得(MUST NOT)包含可执行代码。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以 拒绝的期约。InvalidStateError
如果此对象的 callable 值为 false,则返回一个以 拒绝的期约。InvalidStateError
如果 response 为空数组,则返回一个以新创建的 拒绝的期约。TypeError
令 response copy 为 response 参数内容的一个副本。
设 promise 为一个新的 Promise。
并行执行以下步骤
令 sanitized response 为 response copy 的经过验证和/或净化的版本。
用户代理在将响应传递给 CDM 之前,应彻底验证该响应。这可能包括验证数值是否在合理范围内、剥离无关数据或字段、预解析、净化和/或生成完全净化的版本。用户代理应检查字段的长度和数值是否合理。未知的字段应被拒绝或移除。
如果上述步骤失败,或者 sanitized response 为空,则以新创建的 拒绝 promise。TypeError
令 message 为 null。
令 message type 为 null。
令 session closed 为 false。
令 cdm 为此对象 cdm instance 值所代表的 CDM 实例。
使用 cdm 执行以下步骤
如果 sanitized response 的格式在任何方面无效,则以新创建的 拒绝 promise。TypeError
处理 sanitized response,并遵循以下列表中第一个匹配条件的规定
这包括初始许可证、已更新许可证以及许可证续订消息。
处理 sanitized response,并遵循以下列表中第一个匹配条件的规定
temporary" 且 sanitized response 未指定应存储会话数据(包括其包含的任何许可证、密钥或类似会话数据)persistent-license" 且 sanitized response 包含可持久化的许可证以新创建的 拒绝 promise。TypeError
另参见 会话存储与持久化。
每个会话的状态信息(包括密钥)必须(MUST)以这种方式存储:即关闭一个会话不应影响其他会话中可观察的状态,即使它们包含重叠的密钥 ID。
当 sanitized response 包含密钥和/或相关数据时,cdm 可能会(在内存中)索引由密钥 ID 标识的密钥和相关数据。
会话内的替换算法取决于 Key System。
CDM 实现 建议(RECOMMENDED)支持每个 MediaKeySession 对象具有标准且足够高的最低密钥数(包括标准替换算法),以及标准且足够高的 MediaKeySession 对象数。这使得可以在不同用户代理间实现合理数量的密钥轮换算法,并可能降低在使用不同密钥的同一元素(例如自适应流、各种音频和视频轨道)的场景中发生播放中断的可能性。
persistent-license"运行以下步骤
例如,sanitized response 可能包含将用于生成另一个 事件的信息。在这种情况下,无需根据 sessionType 验证内容。message
如果需要发送消息,执行以下步骤
令 message 为该消息。
令 message type 为该消息对应的适当 MediaKeyMessageType。
加入队列一个任务以运行以下步骤
对该对象运行 Session Closed(会话关闭)算法,原因设为 "release-acknowledged"。
运行以下步骤
如果 CDM 对于此对象 已知 的密钥集合发生变化,或任何密钥的状态发生变化,则在 session 上运行 Update Key Statuses(更新密钥状态)算法,并提供每个已知密钥的 密钥 ID 以及相应的 MediaKeyStatus。
如果需要进行额外处理以确定密钥的状态,请使用 "status-pending"。一旦一个或多个密钥的额外处理完成,请再次运行 更新密钥状态 算法以更新实际状态。
如果会话的 过期时间 发生变化,则在 session 上运行 Update Expiration(更新过期时间)算法,并提供新的过期时间。
如果上述任何步骤失败,则用一个新的 DOMException 拒绝 promise,其名称是相应的 错误名称。
如果 message 不为 null,则在 session 上运行 排队“message”事件 算法,并提供 message type 和 message。
以 undefined 解析 promise。
返回 promise。
close()
指示应用程序不再需要该会话,CDM 应释放与该会话关联的任何资源并将其关闭。不应释放或清除持久化数据。
当请求被处理时,返回的期约被解析;当会话关闭时,closed 属性期约以 "closed-by-application" 被解析。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以 undefined 解析的期约。
如果此对象的 callable 值为 false,则返回一个以 拒绝的期约。InvalidStateError
设 promise 为一个新的 Promise。
将此对象的 closing or closed 值设置为 true。
并行执行以下步骤
令 cdm 为此对象 cdm instance 值所代表的 CDM 实例。
使用 cdm 关闭与此对象关联的 密钥会话。
关闭密钥会话会导致销毁任何未显式存储的许可证和密钥。
加入队列一个任务以运行以下步骤
以 undefined 解析 promise。
对该对象运行 Session Closed(会话关闭)算法,原因设为 "closed-by-application"。
返回 promise。
remove()
移除与会话关联的所有许可证和密钥。对于 持久化会话类型,一旦 update() 处理了释放消息确认,其他会话数据将按照每种会话类型的定义被清除。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以 拒绝的期约。InvalidStateError
如果此对象的 callable 值为 false,则返回一个以 拒绝的期约。InvalidStateError
设 promise 为一个新的 Promise。
并行执行以下步骤
令 cdm 为此对象 cdm instance 值所代表的 CDM 实例。
令 message 为 null。
令 message type 为 null。
使用 cdm 执行以下步骤
如果任何许可证和/或密钥与会话关联
销毁与会话关联的许可证和/或密钥。
这意味着无论许可证和/或密钥是位于内存中、持久存储中还是两者兼有,都将被销毁。
遵循以下列表中针对此对象 session type 值的步骤
temporary"继续执行以下步骤。
persistent-license"令 record of license destruction 为该对象所代表许可证的 许可证销毁记录。
存储该 record of license destruction。
令 message 为包含或反映该 record of license destruction 的消息。
加入队列一个任务以运行以下步骤
在 session 上运行 Update Key Statuses(更新密钥状态)算法,提供会话中所有的 密钥 ID,并为每个 ID 提供 "released" MediaKeyStatus 值。
在 session 上运行 Update Expiration(更新过期时间)算法,并提供 NaN。
如果上述任何步骤失败,则用一个新的 DOMException 拒绝 promise,其名称是相应的 错误名称。
令 message type 为 "license-release"。
以 undefined 解析 promise。
如果 message 不为 null,则在 session 上运行 排队“message”事件 算法,并提供 message type 和 message。
返回 promise。
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);
};
size,类型为 unsigned long,只读已知密钥 的数量。
has()
如果由 keyId 标识的密钥的状态已知,则返回 true。
get()
返回由 keyId 标识的密钥的 MediaKeyStatus,如果未标识该密钥的状态,则返回 undefined。
此接口具有由 可迭代(iterable) [WebIDL] 带来的 entries、keys、values、forEach 和 @@iterator 方法。
要迭代的键值对是所有 已知密钥 的 密钥 ID 和关联的 MediaKeyStatus 值所组成的对集合的一个快照,按 密钥 ID 排序。密钥 ID 的比较方式如下:对于长度为 m 的密钥 ID A 和长度为 n 的密钥 ID B(假设 m <= n),当且仅当 A 的 m 个八位字节在字典序中小于 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;
};
messageType,类型为 MediaKeyMessageType,只读实现 不得(MUST NOT)要求应用程序处理消息类型。实现 必须(MUST)支持不区分消息的应用程序,且 不得(MUST NOT)要求应用程序必须处理消息类型。具体而言,Key Systems 必须(MUST)支持将所有类型的消息传递到单个 URL。
此属性允许应用程序在不解析消息的情况下区分消息。它旨在支持可选的应用程序和/或服务器优化,但应用程序不需要使用它。
message,类型为 ArrayBuffer,只读WebIDLdictionary MediaKeyMessageEventInit : EventInit {
required MediaKeyMessageType messageType;
required ArrayBuffer message;
};
MediaKeyMessageEventInit 成员messageType,类型为 MediaKeyMessageTypemessage,类型为 ArrayBuffer本节是非规范性的。
| 事件名称 | Interface | 调度时间:当……时 |
|---|---|---|
keystatuseschange
|
Event
|
会话中的密钥或其状态发生了变化。 |
message
|
MediaKeyMessageEvent
|
CDM 已为该会话生成了一条消息。 |
“排队‘message’事件”算法将 message 事件排队到 MediaKeySession 对象。运行此算法的请求需包含目标 MediaKeySession 对象、message type 和 message。
message 不得(MUST NOT)包含 区分永久标识符,即使是加密形式。如果 MediaKeySession 对象的 use distinctive identifier 值为 false,则 message 不得(MUST NOT)包含 区分标识符,即使是加密形式。
运行以下步骤
令 session 为指定的 MediaKeySession 对象。
排队一个任务,创建一个名为 的事件,该事件不冒泡且不可取消,使用 messageMediaKeyMessageEvent 接口,其 type 属性设置为 message,且其 isTrusted 属性初始化为 true,并在 session 上分发它。
事件接口 MediaKeyMessageEvent 具有
messageType = 指定的 message typemessage = 指定的 message“更新密钥状态”算法用于更新 MediaKeySession 的 已知 密钥集合或一个或多个密钥的状态。运行此算法的请求需包含目标 MediaKeySession 对象,以及一个 密钥 ID 和关联 MediaKeyStatus 对的序列。
该算法始终在一个任务中运行。
运行以下步骤
令 session 为关联的 MediaKeySession 对象。
令 input statuses 为密钥 ID 和关联 MediaKeyStatus 对的序列。
令 statuses 为 session 的 keyStatuses 属性。
运行以下步骤以替换 statuses 的内容
清空 statuses。
对于 input statuses 中的每一对。
令 pair 为该对。
向 statuses 插入 pair 密钥 ID 的条目,其值为 pair 的 MediaKeyStatus 值。
这些步骤的效果是替换 session 的 keyStatuses 属性的内容,而不使对该属性的现有引用无效。此替换从脚本的角度来看是原子的。也就是说,脚本 绝不能(MUST NOT)看到部分填充的序列。
排队一个任务,在 session 上 触发 名为 的事件。keystatuseschange
排队一个任务,在每个媒体元素上运行 Attempt to Resume Playback If Necessary(必要时尝试恢复播放)算法,其 mediaKeys 属性是创建该 session 的 MediaKeys 对象。
“更新过期时间”算法更新 MediaKeySession 的 过期时间。运行此算法的请求需包含目标 MediaKeySession 对象和新的过期时间(可能是 NaN)。
该算法始终在一个任务中运行。
运行以下步骤
令 session 为关联的 MediaKeySession 对象。
令 expiration time 为 NaN。
如果新的过期时间不是 NaN,令 expiration time 为该过期时间。
将 session 的 expiration 属性设置为以 时间(time) 表示的 expiration time。
“会话关闭”算法在 密钥会话 被 CDM 关闭后更新 MediaKeySession 的状态。运行此算法的请求需包含目标 MediaKeySession 对象和一个 MediaKeySessionClosedReason。
该算法始终在一个任务中运行。
当会话关闭时,与之关联的许可证和密钥将不再可用于解密 媒体数据。在本算法运行后,所有 MediaKeySession 方法都将失败,且不会再为此对象排队任何事件。
CDM 可能在任何时候关闭会话,例如当不再需要会话或丢失系统资源时。在这种情况下,监控 CDM 状态变更 算法会检测到变化并运行此算法。
即使密钥 ID 有重叠,其他会话中的密钥 必须(MUST)不受影响。
在本算法运行后,将执行此算法排队的事件的事件处理程序,但无法再排队进一步的事件。因此,CDM 无法因关闭会话而发送任何消息。
运行以下步骤
令 session 为关联的 MediaKeySession 对象。
令 promise 为 session 的 closed 属性。
如果 promise 已被解析,则中止这些步骤。
将 session 的 closing or closed 值设置为 true。
在 session 上运行 Update Key Statuses(更新密钥状态)算法,提供一个空序列。
在 session 上运行 Update Expiration(更新过期时间)算法,提供 NaN。
以提供的原因解析 promise。
“监控 CDM 状态变更”算法执行当 CDM 状态的各个方面发生变化时所需的步骤。
该算法始终与主事件循环并行运行。
运行以下步骤
令 session 为 MediaKeySession 对象。
令 cdm 为 session 的 cdm instance 值所代表的 CDM 实例。
如果 cdm 有一条尚未发送的外发消息,排队一个任务以执行以下步骤
令 message type 和 message 分别为该消息类型和消息。
运行 Queue a "message" Event(排队“message”事件)算法,并传递 session、message type 和 message。
如果 cdm 改变了 session 已知 的密钥集合,或改变了一个或多个密钥的状态,排队一个任务以执行以下步骤
令 statuses 为一个包含密钥 ID 和 MediaKeyStatus 值对的列表,其中包含 session 已知 的每个密钥的一对。
运行 Update Key Statuses(更新密钥状态)算法,传递 session 和 statuses。
如果 cdm 改变了 session 的 过期时间,排队一个任务以执行以下步骤
令 expiration time 为 session 的新过期时间。
运行 更新过期时间 (Update Expiration) 算法,并传入 session 和 expiration time。
如果 cdm 已关闭 session,则 排队一个任务,以便在 session 上运行 会话关闭 (Session Closed) 算法,并附带适当的 MediaKeySessionClosedReason 值。
如果 cdm 因硬件上下文重置而变得不可用,则 排队一个任务,以运行带有原因 "hardware-context-reset" 的 CDM 不可用 (CDM Unavailable) 算法。
如果 cdm 因任何其他原因变得不可用,则 排队一个任务,以运行带有原因 "internal-error" 的 CDM 不可用 (CDM Unavailable) 算法。
这些方法通过拒绝返回的 Promise 来报告错误,错误类型为 简单异常 (simple exception) [WEBIDL] 或 DOMException。算法中使用了来自 [WEBIDL] 的以下 简单异常 和 DOMException 名称。算法中指定的原因列在每个名称旁边,尽管这些名称 也可能 用于其他原因。
| 名称 | 可能的原因(不详尽) |
|---|---|
TypeError
|
参数为空。 无效的初始化数据。 无效的响应格式。 为 " temporary" 会话提供了持久化许可。 |
NotSupportedError
|
无法移除现有的 MediaKeys 对象。不支持该 密钥系统 (Key System)。 密钥系统不支持该初始化数据类型。 密钥系统不支持该会话类型。 密钥系统不支持该初始化数据。 密钥系统不支持该操作。 |
InvalidStateError
|
此时无法移除现有的 MediaKeys 对象。会话已被使用。 会话尚未初始化。 会话已关闭。 |
QuotaExceededError
|
MediaKeys 对象无法与额外的 HTMLMediaElement 一起使用。此 sessionId 已存在非关闭状态的会话。 资源不足,无法创建新的会话或许可请求。 |
本节概述了对算法起补充作用的会话存储和持久化。
除了 存储和持久化 (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 也可 在适当情况下移除会话,但应用程序 不应 依赖此行为。
本节规定了在支持加密媒体扩展时对 HTMLMediaElement [HTML] 的增加和修改。
以下内部值被添加到 HTMLMediaElement 中:
正在附加媒体密钥 (attaching media keys),其 应 为布尔值。
加密块队列 (encrypted block queue),其 应 为等待解密的加密块队列。
解密因等待密钥而受阻 (decryption blocked waiting for key),其 应 为布尔值。
播放因等待密钥而受阻 (playback blocked waiting for key),其 应 为布尔值。
对 HTMLMediaElement 的行为进行了以下修改:
当创建 HTMLMediaElement 时,其 正在附加媒体密钥 值 应 初始化为 false,其 加密块队列 值 应 为空,其 解密因等待密钥而受阻 值 应 初始化为 false,且其 播放因等待密钥而受阻 值 应 初始化为 false。
当 当前播放位置 的改变不是作为正常播放的一部分沿 播放方向 前进时,加密块队列 值 应 为空,解密因等待密钥而受阻 值 应 初始化为 false,且 播放因等待密钥而受阻 值 应 设置为 false。
换句话说,这些值应在例如 加载媒体资源 或 搜寻 (seeking) 时重置。
除 [HTML] 中指定的标准外,如果 HTMLMediaElement 的 播放因等待密钥而受阻 值为 true,则其 应 被视为 受阻媒体元素 (blocked media element)。
当用户代理准备开始播放,并且在 资源获取算法 期间发现 媒体数据 (media data) 可能包含加密块的迹象时,用户代理 应 运行 媒体数据可能包含加密块 (Media Data May Contain Encrypted Blocks) 算法。
对于某些容器格式,此类迹象与 初始化数据 (Initialization Data) 是分开的。
该算法应在解析相关的容器数据(包括运行 遇到初始化数据 (Initialization Data Encountered) 算法)之后,但在解码开始之前运行。
当用户代理在 资源获取算法 期间在 媒体数据 中遇到 初始化数据 时,用户代理 应 运行 遇到初始化数据 (Initialization Data Encountered) 算法。
某些容器格式可能支持不包含 初始化数据 的加密媒体数据,因此支持不会触发此算法的媒体数据。
对于在 资源获取算法 期间遇到的每个加密 媒体数据 块,用户代理 应 按遇到加密块的顺序运行 遇到加密块 (Encrypted Block Encountered) 算法。
上述步骤为用户代理实现提供了灵活性,使其可以在遇到加密块之后的任何时间、在播放需要它之前执行解密。
当 解密因等待密钥而受阻 值为 true 且发生以下任一情况时,用户代理 应 运行 等待密钥 (Wait for Key) 算法。
如下所述,添加了额外的属性和方法。
对于返回 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);
};
mediaKeys,类型为 MediaKeys,只读,可为空onencrypted,类型为 EventHandler 事件的事件处理程序。所有 encryptedHTMLMediaElement 必须 同时作为内容属性和 IDL 属性支持它。
onwaitingforkey,类型为 EventHandler 事件的事件处理程序。所有 waitingforkeyHTMLMediaElement 必须 同时作为内容属性和 IDL 属性支持它。
setMediaKeys()
提供在播放期间解密媒体数据时要使用的 MediaKeys。
在播放期间清除或替换关联的 MediaKeys 对象的支持是实现质量问题。在许多情况下,它会导致糟糕的用户体验或拒绝的 Promise。
当调用此方法时,用户代理 必须 执行以下步骤:
如果此对象的 正在附加媒体密钥 值为 true,则返回一个被 拒绝的 Promise。InvalidStateError
如果 mediaKeys 和 mediaKeys 属性是同一个对象,则返回一个以 undefined 解析的 Promise。
令此对象的 正在附加媒体密钥 值为 true。
设 promise 为一个新的 Promise。
并行执行以下步骤
如果满足以下所有条件:
mediaKeys 不为 null,
mediaKeys 表示的 CDM 实例已被另一个媒体元素使用
用户代理无法将其与此元素一起使用
则令此对象的 正在附加媒体密钥 值为 false,并使用 拒绝 promise。QuotaExceededError
如果 mediaKeys 属性不为 null,请运行以下步骤:
如果用户代理或 CDM 不支持移除此关联,则令此对象的 正在附加媒体密钥 值为 false,并使用 拒绝 promise。NotSupportedError
如果当前无法移除此关联,则令此对象的 正在附加媒体密钥 值为 false,并使用 拒绝 promise。InvalidStateError
例如,某些实现可能不允许在播放期间移除。
如果上述步骤失败,则令此对象的 正在附加媒体密钥 值为 false,并使用适当的 错误名称 拒绝 promise。
如果 mediaKeys 不为 null,请运行以下步骤:
如果上述步骤失败,请运行以下步骤:
将 mediaKeys 属性设置为 null。
令此对象的 正在附加媒体密钥 值为 false。
使用一个新的 DOMException 拒绝 promise,其名称为适当的 错误名称。
排队一个任务,以便在媒体元素上运行 必要时尝试恢复播放 (Attempt to Resume Playback If Necessary) 算法。
将 mediaKeys 属性设置为 mediaKeys。
令此对象的 正在附加媒体密钥 值为 false。
以 undefined 解析 promise。
返回 promise。
MediaEncryptedEvent 接口MediaEncryptedEvent 对象用于 事件。encrypted
WebIDL[Exposed=Window]
interface MediaEncryptedEvent : Event {
constructor(DOMString type, optional MediaEncryptedEventInit eventInitDict = {});
readonly attribute DOMString initDataType;
readonly attribute ArrayBuffer? initData;
};
initDataType,类型为 DOMString,只读initData 属性中的 初始化数据 (Initialization Data) 的 初始化数据类型 (Initialization Data Type)。initData,类型为 ArrayBuffer,只读,可为空WebIDLdictionary MediaEncryptedEventInit : EventInit {
DOMString initDataType = "";
ArrayBuffer? initData = null;
};
MediaEncryptedEventInit 成员initDataType,类型为 DOMString,默认值为 ""initData,类型为 ArrayBuffer,可为空,默认值为 null本节是非规范性的。
| 事件名称 | Interface | 调度时间:当……时 | 前提条件 |
|---|---|---|---|
encrypted
|
MediaEncryptedEvent
|
用户代理在 媒体数据 中遇到 初始化数据。 | 元素的 readyState 大于或等于 HAVE_METADATA。注 该元素可能正在播放或已经播放过。 |
waitingforkey
|
Event
|
播放因等待密钥而受阻。 | readyState 小于或等于 HAVE_CURRENT_DATA。该元素的 播放因等待密钥而受阻 值刚变为 true。 |
如果用户代理在播放媒体数据之前需要指定 MediaKeys 对象,则“媒体数据可能包含加密块”算法会暂停播放。运行此算法的请求包含一个目标 HTMLMediaElement 对象。
运行以下步骤
令 媒体元素 (media element) 为指定的 HTMLMediaElement 对象。
如果 媒体元素 的 mediaKeys 属性为 null,且实现要求在解码潜在的加密 媒体数据 之前指定一个 MediaKeys 对象,请运行以下步骤:
当应用程序在调用 setMediaKeys() 以提供 MediaKeys 对象之前提供 媒体数据 时,可能会达到这些步骤。选择 CDM 可能会影响所使用的流水线和/或解码器,因此某些实现可能会延迟播放可能包含加密块的媒体数据,直到通过将 MediaKeys 对象传递给 setMediaKeys() 指定了 CDM。
在 媒体元素 上运行 等待密钥 算法。
等待恢复播放的信号。
“遇到初始化数据”算法会为在 媒体数据 中遇到的 初始化数据 排队一个 事件。运行此算法的请求包含一个目标 encryptedHTMLMediaElement 对象。
运行以下步骤
令 媒体元素 为指定的 HTMLMediaElement 对象。
令 initDataType 为空字符串。
令 initData 为 null。
如果 媒体数据 是 CORS-same-origin 且 不是 混合内容 (mixed content),请运行以下步骤:
令 initDataType 为表示该初始化数据的 初始化数据类型 的字符串。
令 initData 为该初始化数据。
虽然媒体元素可能允许加载“可升级内容” [MIXED-CONTENT],但用户代理 不得 向应用程序公开来自此类媒体数据的初始化数据。
排队一个任务,以创建一个名为 的事件,该事件不冒泡且不可取消,使用 encryptedMediaEncryptedEvent 接口,其 type 属性设置为 encrypted,其 isTrusted 属性初始化为 true,并在 媒体元素 上分发它。
事件接口 MediaEncryptedEvent 具有:
initDataType = initDataTypeinitData = initData
readyState 没有 改变,且没有算法被中止。此事件仅提供信息。
如果媒体数据 不是 CORS-same-origin 或属于 混合内容,则 initData 属性将为 null。应用程序可以从其他来源获取初始化数据。
“遇到加密块”算法会为解密排队一个加密媒体数据块,并尽可能尝试解密。运行此算法的请求包含一个目标 HTMLMediaElement 对象。
运行以下步骤
令 媒体元素 为指定的 HTMLMediaElement 对象。
令 block 为该加密媒体数据块。
将 block 添加到 媒体元素 的 加密块队列 末尾。
如果 媒体元素 的 解密因等待密钥而受阻 值为 false,请运行 尝试解密 (Attempt to Decrypt) 算法。
“尝试解密”算法尝试解密已排队等待解密的媒体数据。运行此算法的请求包含一个目标 HTMLMediaElement 对象。
运行以下步骤
令 媒体元素 为指定的 HTMLMediaElement 对象。
如果 媒体元素 的 加密块队列 为空,则中止这些步骤。
如果 媒体元素 的 mediaKeys 属性不为 null,请运行以下步骤:
令 media keys 为该属性引用的 MediaKeys 对象。
令 cdm 为 media keys 的 cdm 实例 (cdm instance) 值所表示的 CDM 实例。
如果 cdm 由于任何原因不再可用,请运行以下步骤:
运行 资源获取算法 的 媒体数据已损坏 (media data is corrupted) 步骤。
对 media keys 运行 CDM 不可用 算法,硬件上下文重置的原因设为 "hardware-context-reset",否则设为 "internal-error"。
中止这些步骤。
如果存在至少一个由 media keys 创建且未 关闭 的 MediaKeySession,请运行以下步骤:
此检查确保 cdm 已完成加载,并且是可用匹配密钥的前提条件。
令 block 为 媒体元素 的 加密块队列 中的第一个条目。
令 block key ID 为 block 的密钥 ID。
密钥 ID 通常由容器指定。
使用 cdm 执行以下步骤
令 available keys 为由 media keys 创建的会话中密钥的并集。
令 block key 为 null。
如果任何 available keys 对应于 block key ID 且 可用于解密,令 session 为包含该密钥的 MediaKeySession 对象,并令 block key 为该密钥。
如果运行上述步骤导致任何 available keys 的状态发生变化,则 排队一个任务,以便在每个受影响的 session 上运行 更新密钥状态 (Update Key Statuses) 算法,并为每个会话提供其中所有的 密钥 ID 以及相应的 MediaKeyStatus 值。
如果 block key 不为 null,请运行以下步骤:
使用 cdm 用 block key 解密 block。
按照下列列表中第一个匹配的条件执行步骤
运行 资源获取算法 的 媒体数据已损坏 (media data is corrupted) 步骤。
如果 cdm 不再可用,对 media keys 运行 CDM 不可用 算法,硬件上下文重置的原因设为 "hardware-context-reset",否则设为 "internal-error"。
中止这些步骤。
从 媒体元素 的 加密块队列 前端移除 block。
正常处理解密后的块。
换句话说,解码该块。
返回此算法的开头。
并非所有的解密问题(例如使用错误的密钥)都会导致解密失败。在这种情况下,这里不会触发错误,但在解码过程中可能会触发错误。
否则,没有任何会话包含用于 block key ID 的密钥,因此继续。
将 媒体元素 的 解密因等待密钥而受阻 值设置为 true。
当不存在 可用于解密 block 的密钥时,将达到此步骤。
一旦用户代理呈现了无法解密的块之前的块(尽其所能,例如所有完整的视频帧),它将运行 等待密钥 算法。
该算法此处不会直接运行,以允许实现在当前播放位置之前解密和解码媒体数据,而不影响可见行为。
对于基于帧的加密,当媒体元素在 资源获取算法 中尝试解码帧时,可以按如下方式实现:
令 encrypted 为 false。
检测帧是否已加密。
解码该帧。
提供该帧以进行渲染。
“等待密钥”算法排队一个 事件并更新 waitingforkeyreadyState。它仅应在 HTMLMediaElement 对象处于 潜在播放 (potentially playing) 状态且其 readyState 大于或等于 HAVE_FUTURE_DATA 时调用。运行此算法的请求包含一个目标 HTMLMediaElement 对象。
运行以下步骤
令 媒体元素 为指定的 HTMLMediaElement 对象。
如果 媒体元素 的 播放因等待密钥而受阻 值为 true,则中止这些步骤。
将 媒体元素 的 播放因等待密钥而受阻 值设置为 true。
上述步骤的结果是,如果媒体元素尚未成为 受阻媒体元素,则它将成为受阻媒体元素。在这种情况下,媒体元素将停止播放。
按照下列列表中第一个匹配的条件执行步骤
将 媒体元素 的 readyState 设置为 HAVE_CURRENT_DATA。
将 媒体元素 的 readyState 设置为 HAVE_METADATA。
换句话说,如果 当前播放位置 的视频帧和音频数据因未加密和/或成功解密而已被解码,则将 readyState 设置为 HAVE_CURRENT_DATA。否则,包括如果之前情况如此但数据不再可用,将 readyState 设置为 HAVE_METADATA。
排队一个任务,以便在 媒体元素 上 触发一个名为 的事件。waitingforkey
暂停播放。
“必要时尝试恢复播放”算法会在媒体元素因等待密钥而受阻且必要密钥当前 可用于解密 时恢复播放。运行此算法的请求包含一个目标 HTMLMediaElement 对象。
运行以下步骤
令 媒体元素 为指定的 HTMLMediaElement 对象。
如果 媒体元素 的 播放因等待密钥而受阻 为 false,则中止这些步骤。
在 媒体元素 上运行 尝试解密 算法。
将 媒体元素 的 解密因等待密钥而受阻 值设置为 false。
将 媒体元素 的 播放因等待密钥而受阻 值设置为 false。
上述步骤的结果是,媒体元素可能不再是 受阻媒体元素,因此可以恢复播放。
根据 适当的情况,将 媒体元素 的 readyState 值设置为 HAVE_CURRENT_DATA、HAVE_FUTURE_DATA 或 HAVE_ENOUGH_DATA。
超出 HAVE_CURRENT_DATA 的状态和 canplaythrough 事件不(或不太可能)考虑超出当前密钥的密钥可用性。
就绪状态的改变也可能导致 HTMLMediaElement 事件被触发,如 此处 所述。
本节是非规范性的。
由 CDM 处理的媒体数据 可能 无法通过 Web 平台 API 以通常的方式(例如使用 CanvasRenderingContext2D drawImage() 方法和 AudioContext MediaElementAudioSourceNode)获取。本规范未定义此类媒体数据不可用的条件,但是,如果媒体数据无法通过此类 API 获取,则它们 可能 的行为就像根本不存在媒体数据一样。
如果媒体渲染不由 UA 执行,例如在基于硬件的媒体流水线的情况下,完整的 HTML 渲染功能(例如 CSS 变换) 可能 不可用。一个可能的限制是视频媒体 可能 被限制为仅出现在与窗口边缘平行且具有正常方向的矩形区域中。
本节定义了针对用户代理和 密钥系统 的实现要求,包括 CDM 和服务器,这些要求可能未在算法中明确解决。此处的以及贯穿本规范的要求适用于所有实现,无论 CDM 是与用户代理分离还是属于用户代理的一部分。
用户代理实现者 必须 确保 CDM 不访问使用本规范功能播放受保护媒体时非合理所需的任何信息、存储或系统能力。具体而言,CDM 不应:
访问网络资源(无论是本地还是远程),除非通过本规范明确允许的用户代理。
访问存储(例如磁盘或内存),除非使用本规范功能播放受保护媒体时合理需要。
访问硬件组件或设备,除非使用本规范功能播放受保护媒体时合理需要。
用户代理实现者可以使用各种技术来满足上述要求。例如,同时也实现自己的 CDM 的用户代理实现者可以将上述要求作为该组件的设计要求。利用第三方 CDM 的用户代理实现者可以确保它在受限环境(例如“沙箱”)中执行,而无法访问被禁止的信息和组件。
往返于 CDM 的所有消息和通信(例如在 CDM 与许可服务器之间) 必须 通过用户代理传递。CDM 不得 进行直接的带外网络请求。除 直接个性化 (Direct Individualization) 中描述的通信外,所有消息和通信 必须 通过本规范定义的 API 经由应用程序传递。具体而言,所有包含应用程序、来源 (origin) 或内容特定信息,或发送到由应用程序指定或基于其来源的 URL 的通信,必须 通过 API 传递。这包括所有许可交换消息。
持久化数据 (Persistent Data) 包括 CDM 或代表 CDM 的用户代理在 MediaKeys 对象销毁后存储的所有数据。具体而言,它包括由 CDM 或代表 CDM 的用户代理存储的任何标识符(包括 独特标识符)、许可、密钥、密钥 ID 或 许可销毁记录。
可能以应用程序或许可服务器可见的方式影响消息或行为的持久化数据 必须 以特定于 来源 和特定于 浏览配置文件 的方式存储,并且 不得 泄漏到私人浏览会话中或从中泄漏。具体而言,但不限于此,会话数据、许可、密钥和每个来源的标识符 必须 按 来源 和 浏览配置文件 进行存储。
请参阅 会话存储和持久化。
使用持久化数据的实现 必须 允许用户清除该数据,使其在外部(例如通过本规范定义的 API)和客户端设备上均无法再检索。
用户代理 应:
像处理其他站点数据(如 Cookie [COOKIES])一样处理持久化数据。具体而言:
允许用户作为用户代理清除浏览历史记录功能的一部分来清除持久化数据。
将持久化数据包括在“删除所有数据”功能中。
在与其他站点数据相同的 UI 位置显示持久化数据。
允许用户按每个 来源 和每个 浏览配置文件 清除持久化数据,特别是作为“忘记此站点”功能的一部分,该功能会忘记与特定站点关联的 Cookie [COOKIES]、数据库等。
确保清除持久化数据的操作足够原子化,以防止通过依赖未同时清除的其他类型本地存储数据,来重新关联新旧标识符的“Cookie 复活”式重新关联。请参阅 数据清除不完整。
以一种帮助用户理解 数据清除不完整 的可能性,并使他们能够删除与所有持久化数据的特性(包括 Cookie [COOKIES] 和 Web 存储)关联的数据的方式呈现这些界面。
以一种帮助用户理解 数据清除不完整 的可能性,并使他们能够同时删除所有此类持久化存储特性中所有此类数据的方式,呈现用于禁用和重新启用 密钥系统 的界面。
允许用户通过 来源 和/或所有来源专门删除持久化数据。
用户代理 应 将持久化数据视为潜在敏感数据;此信息的发布很有可能损害用户隐私。为此,用户代理 应 确保持久化数据被安全存储,并在删除数据时,立即从底层存储中删除。
暴露给应用程序或可由其推断出的值(例如通过 CDM 使用),即使并非设计为标识符,也可能被用于识别客户端或用户。本节定义了避免或至少减轻此类关注的要求。对于 标识符 还有额外的要求。
暴露给应用程序或可由其推断出的所有 独特值 (Distinctive Values) 必须 在每个 来源 和 浏览配置文件 内是唯一的。也就是说,用于一个 来源 的使用本规范定义 API 的值 必须 与用于任何其他使用这些 API 的来源的值不同,并且在一个 浏览配置文件 中使用的值 必须 与用于任何其他配置文件的值不同,无论来源如何。此类值 不得 泄漏到私人浏览会话中或从中泄漏。
跨来源和配置文件的值 必须 是 应用程序不可关联的 (non-associable by applications),这意味着 不得 可能关联来自多个来源或配置文件的值,例如确定它们来自同一个客户端或用户。具体而言,从独立于来源和/或独立于配置文件的值派生每个来源值的实现,必须 以确保上述不可关联性属性的方式进行,例如使用具有适当不可逆特性的派生函数。
作为 允许持久化数据被清除 中要求的后果,暴露给应用程序的所有持久化值 必须 是可清除的,使得这些值在外部(例如通过本规范定义的 API)和客户端设备上均无法再检索、观察或推断。
一旦清除,在后续需要值时 必须 生成新的 应用程序不可关联的 值。
实现使用标识符,特别是 独特标识符或独特永久标识符,带来了隐私问题。本节定义了避免或至少减轻此类问题的要求。暴露给应用程序的值 的要求也适用于暴露给应用程序的标识符。
实现 应 避免 使用独特标识符或独特永久标识符。
例如,使用适用于一组客户端或设备而不是单个客户端的标识符或其他值。
实现 应 仅在执行与特定 CDM 实例和会话相关的策略所必需时 使用独特标识符或独特永久标识符。
例如,"temporary" 和 "persistent-license" 会话可能有不同的要求。
使用独特标识符或独特永久标识符 的实现 应 支持不使用它们的选项。具有此类支持的实现 应 向用户公开选择此选项的能力。
如果支持,应用程序可以选择使用 distinctiveIdentifier = "not-allowed" 来进入此模式。选择此类选项可能会影响 requestMediaKeySystemAccess() 调用的结果和/或随后生成的会话所产生的许可证请求。
允许用户选择或决定此实现能力,可以使用户在访问内容的同时保持更高程度的隐私。
特征性标识符 (Distinctive Identifiers) 和 特征性永久标识符 (Distinctive Permanent Identifiers) 在客户端外部暴露时,必须在消息交换层面进行加密。所有其他标识符在客户端外部暴露时,应当在消息交换层面进行加密。加密必须确保任何两个标识符密文实例只能由拥有解密密钥的实体进行关联。
标识符可能通过以下方式暴露:
通过 事件提供给应用程序。message
在来自服务器的消息中,例如传递给 update() 的消息。
作为 个性化 (Individualization) 的一部分。
除 特征性永久标识符 外,所有标识符必须针对每个 源 (origin) 和 浏览配置文件 保持唯一。请参阅 8.4.1 使用每个源每个配置文件的值。
所有由实现向应用程序暴露的标识符(包括 特征性标识符,即使是加密形式),必须满足在不同 源、浏览配置文件 以及 清除标识符 后应用程序无法关联。
作为 允许清除持久化数据 中各项要求的结果,除 特征性永久标识符 外,所有潜在标识符或 特征性值 必须是可以清除的,以确保在客户端设备外部(如通过本规范定义的 API)和客户端设备内部,这些值均不再可检索、可观察或可推断。
使用特征性标识符 的实现必须允许用户清除 特征性标识符。使用特征性永久标识符 的实现必须允许用户清除与 特征性永久标识符 关联的值。
标识符(尤其是 特征性标识符)有时是通过一个称为“个性化”或“配置”的过程生成或获取的。生成的标识符必须 对应用程序不可关联,并且其 使用 必须仅暴露给 来自单个配置文件的单个源。此过程可以多次执行,例如在标识符被 清除 之后。
此过程必须由用户代理 直接执行,或 通过应用程序 执行。这两种个性化类型的机制、流程和限制是不同的,如下节所述。使用哪种方法取决于 CDM 的实现以及对本规范要求的应用(特别是下述要求)。
distinctiveIdentifier 控制是否可以 使用 特征性标识符 和 特征性永久标识符(包括用于个性化)。具体而言,仅当用于创建 MediaKeys 对象的 MediaKeySystemAccess 的 distinctiveIdentifier 成员值为 "required" 时,才可以 使用 此类标识符。
直接个性化是在 CDM 与一个独立于源和应用程序的服务器之间执行的。尽管该服务器是独立于源的,但个性化的结果使 CDM 能够根据本规范的其他要求提供特定于源的标识符。该过程必须由用户代理执行,且不得使用本规范定义的 API。
例如,此过程可能通过与用户代理或 CDM 供应商托管的预定服务器通信来初始化客户端设备,并/或获取 单个浏览配置文件 的 特定于源 的 可清除 标识符,这可能 使用特征性永久标识符 或来自客户端设备的其他 永久标识符。
对于此类个性化,所有消息交换
必须由用户代理处理,并通过用户代理的网络栈由用户代理执行。
不得由 CDM 直接执行。
不得通过本规范定义的 API 传递给或通过应用程序。
必须发送到独立于任何源和应用程序选择的 URL。
必须使用 TLS。
实现不得向集中式服务器暴露(即使以加密形式) 源、特定于源或应用程序的信息,或与 源相关联 的值,因为这可能会创建用户或设备访问过的所有源的中央记录。
应用辅助个性化是在 CDM 与应用程序(包括应用程序选择的服务器)之间执行的,并产生一个特定于源的标识符。该过程必须通过本规范定义的 API 执行,且不得涉及其他通信方法。与其他 API 的使用方式一样,该过程可以 使用一个或多个特征性标识符,但不得 使用特征性永久标识符 或非特定于源的值,即使是加密形式。如果该过程 使用了特征性标识符,则结果标识符根据定义也是一个 特征性标识符。
对于此类个性化,所有消息交换
必须通过本规范定义的 API 传递给或通过应用程序。
应为所有相关的 事件使用消息类型 "messageindividualization-request"。
不得由用户代理执行。
不得由 CDM 直接执行。
不得包含或以其他方式 使用特征性永久标识符。
不得包含非特定于源的客户端信息。
必须遵守 标识符要求。
这仅包括使用 每个源和配置文件唯一 且 可清除 的值,并按要求对其进行 加密。
不得向 CDM 提供可执行代码。
当在过程中 使用 包括 特征性标识符 在内的 可关联 值时,实现不得向集中式服务器暴露(即使以加密形式) 源、特定于源或应用程序的信息,或与 源相关联 的值,因为这可能会创建用户或设备访问过的所有源的中央记录。
采取适当的预防措施,此类个性化可以提供比 直接个性化 更好的隐私保护,尽管不如不 使用特征性标识符 的模型。为了保持此类设计的优势并避免引入其他隐私问题,此类实现及其支持的应用程序应当避免将个性化消息推迟或转发到中央服务器或不由应用程序作者控制的其他服务器。
实现必须支持每个 MediaKeySession 对象中的多个密钥。
如何支持多个密钥的机制属于实现细节,但必须对应用程序和本规范定义的 API 透明。
实现必须支持播放期间密钥之间的无缝切换。这包括同一 MediaKeySession 中的密钥以及不同 MediaKeySession 对象中的密钥。
实现应当允许将使用其支持的任何 初始化数据类型 生成的许可证与任何内容类型一起使用。
否则,requestMediaKeySystemAccess() 算法可能会因某种 initDataTypes 不支持特定的 videoCapabilities 而拒绝 MediaKeySystemConfiguration。
对于任何可能出现在支持的容器中的支持的 初始化数据类型,用户代理必须支持从每个此类支持的容器中 提取 该类型的 初始化数据。
本节定义了本规范的实现所支持的内容(媒体资源)的属性。
媒体容器不得加密。本规范依赖于用户代理在无需解密任何媒体数据的情况下解析媒体容器的能力。这包括 遇到加密块 和 遇到初始化数据 算法,以及支持标准的 HTMLMediaElement [HTML] 功能,例如 定位 (seeking)。
媒体资源(包括所有轨道)必须按照容器特定的“通用加密”规范进行加密和打包,该规范允许在提供一个或多个密钥时以完全指定且兼容的方式解密内容。
加密媒体扩展流格式注册表 提供了对此类流格式的引用。
带内支持内容(例如字幕、描述性音频和转录稿)不应当加密。
此类轨道的解密——特别是为了能够将其传回给用户代理——通常不受实现支持。因此,对此类轨道进行加密将阻止它们在用户代理实现中广泛用于辅助功能。
为了确保辅助功能信息以可用形式提供,对于选择支持加密带内支持内容的实现:a) CDM 必须将解密后的数据提供给用户代理,且 b) 用户代理必须以与等效的非加密支持内容相同的方式处理它,例如作为 定时文本轨道 [HTML] 公开。
所有用户代理必须支持本节中描述的通用 密钥系统。
这确保了存在一个保证在所有用户代理(包括完全开源的用户代理)中得到支持的通用基准功能水平。因此,只需要基本解密的内容提供商可以构建简单的应用程序,这些应用程序将在所有平台上运行,而无需与任何内容保护提供商合作。
"org.w3.clearkey" 密钥系统 使用纯文本明文(未加密)密钥来解密源。无需额外的客户端内容保护。此 密钥系统 描述如下。
以下描述了 Clear Key 如何支持 密钥系统 特定的功能。
encryptionScheme:实现必须支持 方案,并可以支持其他方案。"cenc"
robustness:仅支持空字符串。
distinctiveIdentifier:不支持 "required"。
persistentState:除非应用程序打算创建非 "temporary" 的会话(如果支持),否则不为 "required"。
"persistent-license" MediaKeySessionType:实现可以支持此类型。
setServerCertificate() 方法:不支持。
getStatusForPolicy() 方法:实现应始终使用 "usable" 来解析 promise。
setMediaKeys() 方法:实现可以支持将 MediaKeys 对象与多个 HTMLMediaElement 相关联。
以下描述了 Clear Key 如何实现 密钥系统 特定的行为。
在 generateRequest() 算法中
生成的 message 是一个 UTF-8 编码的 JSON 对象,如 许可证请求格式 中所述。
该请求是通过从 sanitized init data 中提取密钥 ID 生成的。
"type" 成员的值即为 sessionType 参数的值。
sessionId 属性是一个可由 32 位整数表示的数值。
expiration 属性始终为 NaN。
在 update() 算法中
对于 "persistent-license" 类型的会话,在 remove() 算法中,反映 许可证销毁记录 的 message 是一个 UTF-8 编码的 JSON 对象,如 许可证释放格式 中所述。
keyStatuses 属性方法最初包含通过 update() 提供的所有密钥 ID,状态为 "usable"。当执行 remove() 算法时,keyStatuses 属性将被设置为空列表。
初始化数据:实现可以支持注册的初始化数据类型的任何组合 [EME-INITDATA-REGISTRY]。实现应当支持 "keyids" 类型 [EME-INITDATA-KEYIDS] 以及适用于用户代理所支持内容类型的其他类型。
本节描述了通过 事件的 message 属性提供给应用程序的许可证请求格式。message
该格式是一个包含以下成员的 JSON 对象
MediaKeySessionType。当包含在 MediaKeyMessageEvent 对象的 ArrayBuffer message 属性中时,JSON 字符串按照 Encoding 规范 [ENCODING] 进行 UTF-8 编码。应用程序可以使用 TextDecoder 接口 [ENCODING] 将 ArrayBuffer 的内容解码为 JSON 字符串。
本节是非规范性的。
以下示例是针对两个密钥 ID 的临时许可证请求。(换行符仅为提高可读性。)
{
"kids": [
"LwVHf8JLtPrv2GUXFW2v_A",
"0DdtU9od-Bh5L3xbv0Xf_A"
],
"type": "temporary"
}
本节描述了通过 update() 方法的 response 参数提供的许可证格式。
该格式是一个 JSON Web Key (JWK) Set,包含用于解密的对称密钥的表示,如 JSON Web Key (JWK) 规范 [RFC7517] 中所定义。
对于集合中的每个 JWK,参数值如下
该 JSON 对象可以有一个可选的 "type" 成员值,它必须是 MediaKeySessionType 值之一。如果未指定,则使用默认值 "temporary"。 update() 算法会将此值与 sessionType 进行比较。
当作为 ArrayBuffer response 参数传递给 update() 方法时,JSON 字符串必须按照 Encoding 规范 [ENCODING] 进行 UTF-8 编码。应用程序可以使用 TextEncoder 接口 [ENCODING] 对 JSON 字符串进行编码。
本节是非规范性的。
以下示例是一个包含单个对称密钥的 JWK Set。(换行符仅为提高可读性。)
{
"keys": [{
"kty": "oct",
"k": "tQ0bJVWb6b0KPL6KtZIy_A",
"kid": "LwVHf8JLtPrv2GUXFW2v_A"
}],
"type": "temporary"
}
本节描述了通过 事件的 message 属性提供的许可证释放消息格式。message
该格式是一个 JSON 对象。对于 "persistent-license" 类型的会话,该对象应包含以下成员
当包含在 MediaKeyMessageEvent 对象的 ArrayBuffer message 属性中时,JSON 字符串按照 Encoding 规范 [ENCODING] 进行 UTF-8 编码。应用程序可以使用 TextDecoder 接口 [ENCODING] 将 ArrayBuffer 的内容解码为 JSON 字符串。
本节是非规范性的。
以下示例是针对一个包含两个密钥的 "persistent-license" 会话的许可证释放。(换行符仅为提高可读性。)
{
"kids": [ "LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A" ]
}
本节描述了通过 update() 方法的 response 参数提供的许可证释放确认格式。
该格式是一个包含以下成员的 JSON 对象
当作为 ArrayBuffer response 参数传递给 update() 方法时,JSON 字符串必须按照 Encoding 规范 [ENCODING] 进行 UTF-8 编码。应用程序可以使用 TextEncoder 接口 [ENCODING] 对 JSON 字符串进行编码。
本节是非规范性的。
以下示例是针对两个密钥 ID 的临时许可证请求。(换行符仅为提高可读性。)
{
"kids": [
"LwVHf8JLtPrv2GUXFW2v_A",
"0DdtU9od-Bh5L3xbv0Xf_A"
]
}
本节是非规范性的。
有关 base64url 及其使用方法的更多信息,请参阅 [RFC7515] 中的“Base64url 编码”术语定义以及“关于实现无填充 base64url 编码的说明”。具体而言,没有 '=' 填充,并且必须分别使用字符 '-' 和 '_' 代替 '+' 和 '/'。
用户代理和 密钥系统 实现必须将 媒体数据、初始化数据、传递给 update() 的数据、许可证、密钥数据以及应用程序提供的所有其他数据视为不受信任的内容和潜在攻击向量。它们必须使用适当的防御措施来缓解任何相关的威胁,并小心地安全解析、解密此类数据。用户代理应当在将数据传递给 CDM 之前对其进行验证。
如果 CDM 运行的上下文(例如 DOM)与用户代理不同(沙盒化),则此类验证尤为重要。
实现不得向应用程序返回活动内容或影响程序控制流的被动内容。
例如,暴露可能来自媒体数据的 URL 或其他信息是不安全的,传递给 generateRequest() 的 初始化数据 即是这种情况。应用程序必须确定要使用的 URL。 事件的 messagemessageType 属性可由应用程序用于在适用时从一组 URL 中进行选择。
用户代理有责任为用户提供安全的网络浏览方式。此责任适用于用户代理使用的任何功能,包括来自第三方的功能。用户代理实现者必须从 密钥系统 实现者处获得足够的信息,使他们能够正确评估与 密钥系统 集成的安全影响。用户代理实现者必须确保 CDM 实现提供和/或支持足够的控制,以便用户代理为用户提供安全保障。用户代理实现者必须确保在出现安全漏洞时,CDM 实现能够并且将会被迅速、主动地更新。
利用未完全沙盒化和/或使用平台功能的 CDM 实现,可能允许攻击者访问操作系统或平台功能、提升权限(例如以系统或 root 身份运行),和/或访问驱动程序、内核、固件、硬件等。此类功能、软件和硬件可能并未针对敌意软件或基于网络的攻击进行加固,并且可能不会像用户代理那样及时更新安全修复程序。对 CDM 实现中的安全漏洞修复缺乏、不频繁或缓慢的更新增加了风险。此类 CDM 实现及暴露它们的用户代理在所有安全领域都必须特别小心,包括对 所有数据 的解析。
当使用属于或由客户端操作系统、平台和/或硬件提供的 CDM 或底层机制时,用户代理应当特别勤勉。
如果用户代理选择支持一个无法被充分沙盒化或以其他方式确保安全的 密钥系统 实现,则用户代理应当在加载或调用它之前 确保用户完全知情和/或给予明确同意。
授予未经身份验证的源权限等同于在网络攻击者存在的情况下向任何源授予权限。请参阅 滥用持久化同意。
本节是非规范性的。
潜在的网络攻击及其影响包括
以下技术可以缓解风险
使用 TLS 的应用程序可以确信只有用户、代表用户运行的软件,以及拥有证书标识它们来自同一域的其他 TLS 页面才能与该应用程序交互。此外,与安全源相结合的 特定于源 的权限确保授予应用程序的权限不会被网络攻击者滥用。
本规范定义的 API 仅在安全上下文中暴露。另请参阅 安全源与传输。
用户代理必须正确处理混合内容 [MIXED-CONTENT],包括阻止“可阻止内容” [MIXED-CONTENT] 以避免潜在地暴露于不安全内容。此类暴露可能会损害其他缓解措施,例如 TLS 的使用。
用户代理可以选择阻止所有混合内容,包括“可选可阻止内容” [MIXED-CONTENT],通过防止 不受信任的媒体数据 被传递给 CDM(请参阅 CDM 攻击与漏洞)来进一步提高安全性。
用户代理应当确保在 源 访问可能比其他用户代理功能(例如 DOM 内容)带来更大安全问题的 密钥系统 之前,用户完全知情和/或给予明确同意。
本节是非规范性的。
恶意页面可以在 iframe 中托管合法应用程序,试图隐藏攻击或欺骗用户关于来源的信息,例如使使用看起来像是来自合法的内容提供商。对于那些 告知用户或需要同意(例如出于安全和/或 隐私原因)的实现,这一点尤为相关。除了 网络攻击 外,攻击者还可以通过将本规范定义的 API 托管在 iframe 中来利用其合法用途。通过让合法应用程序执行操作,攻击者可以重用已授予的权限(或白名单)和/或看起来像是合法的请求或使用。
包括出于安全和/或 隐私原因 而 告知用户或需要同意 的用户代理,应当将同意的 UI 和持久性建立在顶级 文档 的 源 与使用本规范定义 API 的 源 的组合之上。这确保用户知晓做出请求的主文档,并确保为一个(合法的)组合持久化权限不会无意中允许恶意使用未被发现。
作者应当防止其他实体将他们的应用程序托管在 iframe 中。因合法应用设计原因必须支持被托管的应用程序不应当允许托管文档提供 任何要传递给 CDM 的数据(无论是通过本规范定义的 API 还是作为媒体数据),并且不应当允许托管框架调用本规范定义的 API。
本节是非规范性的。
共享一个主机名的不同作者,例如在 geocities.com 上托管内容的用户,都共享一个 源。用户代理不提供通过路径名限制 API 访问的功能。
在共享主机上使用本规范定义的 API 会损害用户代理实现的基于源的安全和隐私缓解措施。例如,特定于源的 特征性标识符 由主机上的所有作者共享,且持久化数据可能被主机上的任何作者访问和操纵。如果修改或删除此类数据可能抹除用户对特定内容的权利,那么后者尤为重要。
即使用户代理提供了路径限制功能,通常的 DOM 脚本安全模型也会使得绕过此保护并从任何路径访问数据变得轻而易举。
因此,建议共享主机上的作者避免使用本规范定义的 API,因为这样做会损害用户代理中的基于源的安全和隐私缓解措施。
用户设备上 密钥系统 的存在或使用引发了许多隐私问题,分为两类:(a) 可能由 EME 接口本身或在 密钥系统 消息中披露的用户特定信息,以及 (b) 可能持久存储在用户设备上的用户特定信息。
用户代理必须承担起为用户提供对其隐私的适当控制权的责任。由于用户代理可能与第三方 CDM 实现集成,CDM 实现者必须向用户代理实现者提供足够的信息和控制权,以使他们能够实施适当的技术来确保用户对自己的隐私拥有控制权,包括但不限于下述技术。
关于 EME 和 密钥系统 披露信息的担忧分为两类:(a) 关于可能有助于指纹识别用户代理或设备的非特定信息的担忧,以及 (b) 可能直接用于 用户追踪 的用户特定信息。
恶意应用程序可能能够通过检测或枚举支持的 密钥系统 列表及相关信息来识别用户或用户代理。如果没有提供适当的源保护,这可能包括检测已访问过的站点以及为这些站点存储的信息。特别是,密钥系统 必须不在不同 源 之间共享密钥或其他数据。
本规范中的几个功能公开了可能对指纹识别有微小贡献的能力信息
MediaKeySystemMediaCapability 的 encryptionScheme 成员揭示了 密钥系统 支持哪些加密方案。此信息很大程度上由 密钥系统 本身决定,因此除了应用程序选择 密钥系统 所隐含的内容外,几乎没有增加额外信息。
getStatusForPolicy() 揭示了是否可以强制执行指定的 HDCP 策略。这反映了当前连接的显示链的能力,如果用户重新配置了他们的显示器,它可能会在会话期间发生变化。
本节是非规范性的。
CDM(特别是那些在用户代理外部实现的 CDM)可能不具备与 Web 平台相同的基本隔离。采取步骤避免信息泄露非常重要,特别是在不同源之间。这包括内存中和存储的数据。未能做到这一点可能导致信息泄露给/来自隐私浏览会话、跨越 浏览配置文件(包括跨操作系统用户帐户),甚至跨越不同的浏览器或应用程序。
为避免此类问题,用户代理和 CDM 实现必须确保
会话数据不会与未关联创建该会话的 MediaKeys 对象的媒体元素共享。除其他事项外,这意味着会话的密钥不得用于解密由其 mediaKeys 属性不是该 MediaKeys 对象的媒体元素加载的内容。
持久化会话数据(如果适用)按 源 进行存储。
仅可加载由请求 源 存储的数据。
不可能从 CDM 中提取、导出或推断除本规范中明确描述或在未经用户许可的情况下通过其他 Web 平台 API 提供给页面的信息。这适用于在客户端设备外部或向应用程序暴露的任何信息,例如在 CDM 消息中的信息。
此要求涵盖的信息类型包括但不限于
位置,包括地理位置
除 特征性标识符 外的凭据或标识符
操作系统帐户名和其他潜在的 PII(个人身份信息)
本地目录路径,其中可能包含类似信息。
本地网络详情(例如设备的本地 IP 地址)
本地设备,包括但不限于蓝牙、USB 和用户媒体。
未与本规范定义的 API 相关联或非因其结果而存储的用户状态。
本节是非规范性的。
第三方主机(或任何能够将内容分发到多个站点的实体,例如广告商)可以使用由 CDM 代表其存储的 特征性标识符 或持久化数据(包括许可证、密钥、密钥 ID 或 许可证销毁记录)来跨多个会话(包括跨 源 和 浏览配置文件)追踪用户,从而建立用户活动或兴趣的配置文件。此类追踪将破坏 Web 平台其他部分提供的隐私保护,例如可能启用其他方式无法实现的精准定向广告。结合知晓用户真实身份的站点(例如需要认证凭据的内容提供商或电子商务站点),这可能使压迫性团体能比在完全匿名网络使用的世界中更准确地针对个人。
可能通过本规范中的 API 实现获取的用户或客户端特定信息包括
本规范提出了一个特定的担忧,因为此类信息通常存储在用户代理(及其关联的 浏览配置文件 存储)之外,通常在 CDM 中。
由于许可证和 许可证销毁记录 的内容是 密钥系统 特定的,且由于密钥 ID 可以包含任何值,这些数据项可能被滥用以存储用户识别信息。
密钥系统 可能会访问或创建用于设备或设备用户的持久性或半持久性标识符。在某些情况下,这些标识符可能会以安全方式绑定到特定设备。如果这些标识符存在于 密钥系统 消息中,则设备和/或用户可能会被追踪。如果未应用以下缓解措施,这可能包括随时间追踪用户/设备以及关联给定设备的多个用户。
值得注意的是,此类标识符,特别是那些不可清除、非 源 特定或 永久 的标识符,其追踪影响超过了现有技术,例如 Cookie [COOKIES] 或嵌入在 URL 中的会话标识符。
如果不加缓解,根据 密钥系统 的设计,此类追踪可能采取三种形式
在所有情况下,此类标识符预计都可供完全支持该 密钥系统(并因此可以解释 密钥系统 消息)的站点和/或服务器使用,从而使此类站点能够进行追踪。
如果 密钥系统 消息以一致的方式包含从用户标识符派生的信息,例如使得特定内容项目的初始 密钥系统 消息的一部分随时间保持不变并且依赖于用户标识符,那么任何应用程序都可以使用此信息来随时间追踪该设备或用户。
此外,如果 密钥系统 允许在不同源之间存储和重用密钥或其他数据,则两个源可能串通并通过记录它们访问通用密钥的能力来追踪唯一用户。
最后,如果任何用于用户控制 密钥系统 的用户界面将数据与 HTTP 会话 Cookie [COOKIES] 或持久化存储中的数据分开呈现,则用户很可能只修改其中一个的站点授权或删除数据。这将允许站点将各种功能用作彼此的冗余备份,破坏用户保护其隐私的尝试。
除了站点和其他第三方追踪用户的潜力外,用户代理实现者、CDM 供应商或设备供应商也可以构建用户的活动或兴趣的配置文件,例如用户访问的使用本规范定义 API 的站点。此类追踪将破坏 Web 平台其他部分提供的隐私保护,特别是与源隔离相关的部分。
标识符(例如 特征性标识符)可以从由 CDM 供应商运营或提供的服务器获取,例如通过 个性化 过程。该过程可能包括向服务器提供客户端标识符(包括 特征性永久标识符)。为了生成特定于源的标识符,也可以提供一个表示源的值。
在此类实现中,CDM 供应商可能能够追踪用户的活动,例如已访问的源数量或需要新标识符的次数。如果在标识符请求中提供了源或 与源相关联 的值,CDM 供应商可能会追踪用户或设备用户访问过的站点。
以下部分描述了可以在未经用户同意的情况下缓解追踪风险的技术。
密钥系统 实现应当尽可能避免 使用特征性标识符和特征性永久标识符,仅在它们对实现的健壮性有实质性贡献时使用它们。请参阅 限制或避免使用特征性标识符和永久标识符。
实现不得将 特征性永久标识符 暴露给应用程序或源。
密钥系统 消息中的 特征性标识符 必须与时间戳或随机数一起加密,以确保 密钥系统 消息始终不同。这防止了除了完全支持该 密钥系统 的服务器外,使用 密钥系统 消息进行追踪。请参阅 加密标识符。
用户代理应当以强烈关联 HTTP 会话 Cookie [COOKIES] 的方式,向用户呈现 特征性标识符 和由 密钥系统 存储的数据的存在,将其包含在“删除所有数据”中,并将其呈现在相同的 UI 位置。这可能会鼓励用户以合理的怀疑态度看待此类标识符。用户代理应当帮助用户避免 不完全的数据清除。
不要向个性化服务器或与源无关的其他实体提供 源 或与源 相关联 的值。如果实现使用了此类过程,请遵循 个性化 部分中的要求和建议。
对于暴露给应用程序的所有 特征性值,实现必须为每个 源 和 浏览配置文件 使用不同的 应用程序无法关联 的值。请参阅 8.4.1 使用每个源每个配置文件的值。
这对于 使用特征性标识符 的实现尤为重要。请参阅 8.5.3 使用每个源每个配置文件的标识符。
CDM 所使用的任何可能以应用程序或许可证服务器可见的方式影响消息或行为的数据,必须按 来源 (origin) 和 浏览配置文件 (browsing profile) 进行分区,并且不得泄漏给隐私浏览会话或从中泄漏。这包括内存中数据和持久化数据。具体而言(但不限于),会话数据、许可证、密钥和每个来源的标识符必须按来源和按浏览配置文件进行分区。请参阅 8.3.1 使用特定于来源和浏览配置文件的 密钥系统 (Key System) 存储 以及 8.4.1 使用按来源按配置文件的值。
用户代理必须为用户提供清除由 密钥系统 (Key Systems) 维护的任何持久化数据的能力,包括 特征标识符。请参阅 允许清除持久化数据。
用户代理可以(可能以用户配置的方式)在一段时间后自动删除 特征标识符 和/或其他密钥系统数据。
例如,用户代理可以被配置为将此类数据仅存储为会话存储,一旦用户关闭了所有可以访问该数据的浏览上下文,即删除该数据。
这可以限制站点跟踪用户的能力,因为站点只有在用户登录站点本身(例如通过进行购买或登录服务)时,才能跨多个会话跟踪用户。
然而,如果用户不完全理解此类过期带来的影响,这也可能使用户对内容的访问(特别是购买或租赁的内容)处于风险之中。
用户代理可以限制对 密钥系统 和/或功能的访问,仅限于源自浏览上下文顶层 文档 (Document) 来源 的脚本。例如,requestMediaKeySystemAccess() 可能会拒绝来自在 iframe 中运行的其他来源页面的特定配置请求。
用户代理必须确保用户在使用特征标识符或永久特征标识符之前获得充分知情和/或给予明确同意。
用户代理应该为用户提供一个全局控制,用于决定是否启用 密钥系统,和/或是否启用 密钥系统 对特征标识符或永久特征标识符的使用(如果 密钥系统 支持的话)。用户代理应该帮助用户避免不完全的数据清除。
用户代理可以要求用户在站点使用每个 密钥系统(和/或特定功能)之前明确授权访问。用户代理应该使用户能够临时或永久地撤销此授权。
虽然这些建议防止了为用户跟踪而琐碎地使用本规范中定义的 API,但它们并不能完全阻止这种行为。在单个来源内,站点可以继续在会话期间跟踪用户,然后可以将所有这些信息与站点获得的任何识别信息(姓名、信用卡号、地址)一起传递给第三方。如果第三方与多个站点合作获取此类信息,并且如果标识符不是每个来源和配置文件唯一的,那么仍然可以创建配置文件。
本节是非规范性的。
密钥系统 可能会在用户设备上存储信息,或者用户代理可能会代表密钥系统存储信息。这可能会向同一设备的其他用户泄露有关用户的信息,包括使用过特定 密钥系统 的 来源(即访问过的站点),甚至使用 密钥系统 解密过的内容。
如果由一个来源存储的信息影响了另一个来源的 密钥系统 的操作,那么用户在一个站点上访问的站点或查看的内容可能会泄露给另一个潜在的恶意站点。
如果为客户端设备上的一个 浏览配置文件 存储的信息影响了其他 浏览配置文件 或浏览器的 密钥系统 的操作,那么在一个配置文件中访问的站点或查看的内容可能会通过另一个 浏览配置文件 被揭示或关联,甚至包括针对不同的操作系统用户帐户或浏览器。
缓解这些担忧的要求在 8.3 持久化数据 中定义。
本节是非规范性的。
如果所有此类数据和功能以及 Cookie [COOKIES] 和其他站点数据没有同时被清除和/或禁用,用户为保护其隐私而尝试通过清除 特征标识符 和存储的数据和/或禁用 密钥系统 的尝试可能会失败。例如
如果用户在不清除 特征标识符 和由密钥系统存储的数据的情况下清除 Cookie 或其他持久化存储,站点可以通过使用各种功能作为彼此的冗余备份来挫败这些尝试。
如果用户在不清除由 密钥系统 存储的数据(包括持久化会话)以及 Cookie 和其他持久化存储的情况下清除 特征标识符,站点可以通过使用剩余的数据将旧标识符和新标识符关联起来,从而挫败这些尝试。
如果用户在不清除 Cookie 或其他持久化存储的情况下禁用 密钥系统(特别是针对特定 来源),站点可以通过使用剩余的功能来挫败这些尝试。
如果用户禁用了 密钥系统,然后决定在不同时清除 Cookie 或其他持久化存储、特征标识符 以及由 密钥系统 存储的数据的情况下重新启用 密钥系统,站点可能能够将禁用前的数据与重新启用 密钥系统 后的数据和行为关联起来。
缓解这些担忧的建议在 8.3 持久化数据 中定义。
用户代理可以支持旨在保护用户匿名性和/或确保浏览活动记录不在客户端持久化的操作模式(例如,隐私浏览)。前几节中讨论的隐私问题对于采用此类模式的用户来说可能尤为令人担忧。
支持此类模式的用户代理实现者应该仔细考虑是否应在这些模式中禁用对 密钥系统 的访问。例如,此类模式可以禁止创建支持或使用 persistentState 或 distinctiveIdentifier 的 MediaKeySystemAccess 对象(无论是作为 CDM 实现的一部分,还是因为应用程序指出它们是“required”)。如果实现不禁止此类创建,则它们应该在允许使用之前,告知用户其影响以及对此类模式的预期隐私属性的潜在后果。
本规范中定义的 API 仅在安全来源上受支持,从而保护了前面几节中讨论的信息。标识符按照 加密标识符 中的规定进行了额外加密。
应用程序(包括其使用的服务器)应该对所有涉及或包含来自 CDM 的数据或消息的流量使用安全传输,包括但不限于从 message 事件传递到 update() 的所有数据。
所有用户代理必须正确处理混合内容 [MIXED-CONTENT],以避免在用户代理或应用程序希望强制执行安全来源和传输时暴露于不安全的内容或传输。
除了标记为非规范性的章节外,本规范中的所有创作指南、图表、示例和注释均为非规范性内容。本规范中的其他所有内容均为规范性内容。
本文档中的关键字 MAY、MUST、MUST NOT、OPTIONAL、RECOMMENDED、REQUIRED、SHALL、SHALL NOT、SHOULD 和 SHOULD NOT 仅在全部大写时(如此处所示),根据 BCP 14 [RFC2119] [RFC8174] 进行解释。
本节是非规范性的。
本节包含使用提议的扩展进行各种用例的示例解决方案。这些并不是这些用例的唯一解决方案。视频元素在示例中使用,但同样适用于所有媒体元素。在某些情况下,例如使用同步 XHR,示例被简化以保持对扩展的关注。
在这个简单的示例中,源文件和 明文许可证 在页面中硬编码。只会创建一个会话。
<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>
此示例使用 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>
如果不需要在 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>
这是一个更完整的示例,展示了所有正在使用的事件。
请注意,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>
此示例请求一个用于未来使用的持久化许可证并将其存储。它还提供了稍后检索许可证和销毁许可证的函数。
<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>
如果某些媒体在许可证中有 HDCP 策略,应用程序可以在预取之前检查该版本。
const status = await video.mediaKeys.getStatusForPolicy({
minHdcpVersion: '1.4'
});
if (status === 'usable') {
// Pre-fetch HD content.
} else { // 'output-restricted'
// Pre-fetch SD content.
}
编辑们感谢 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 对本规范的贡献。感谢其他许多为本规范做出贡献的人,包括通过参与邮件列表和问题讨论。
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自
引用自