Copyright © 2026 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范扩展了 HTMLMediaElement [HTML]
,提供用于控制加密内容播放的 API。
该 API 支持从简单的明文密钥解密到高价值视频的各种用例 (前提是用户代理具有适当的实现)。许可证/密钥交换由 应用控制,从而有助于开发支持一系列内容解密和保护技术的 健壮播放应用。
本规范并不定义内容保护或数字版权管理 系统。相反,它定义了一个通用 API,可用于发现、选择此类系统并与之交互, 也可用于发现、选择更简单的内容加密系统并与之交互。实现 数字版权管理并非符合本规范的要求:只要求实现 Clear Key 系统,作为共同基线。
该通用 API 支持一组简单的内容加密能力,将 身份验证和授权等应用功能留给页面作者处理。这是通过要求 特定于内容保护系统的消息由页面中介来实现的,而不是假定 加密系统与许可证服务器或其他服务器之间存在带外通信。
本节描述此 文档在其发布时的状态。当前 W3C 出版物列表以及此技术报告的最新修订版本可在 W3C 标准与草案 索引中找到。
自 2017年9月作为 W3C 推荐标准发布以来,新增了两个特性:
encryptionScheme
属性检测加密方案能力。
getStatusForPolicy()
方法,查询与 HDCP 策略关联的密钥状态的能力。
纳入其他特性超出了媒体工作组的范围。除 编辑性更新外,对本规范所做的其他实质性变更用于解决 针对本规范的维护议题:
encrypted-media 标识符与 Permissions Policy 集成。
MediaKeyStatus 添加“usable-in-future”,用于尚
不能用于解密的密钥。
QuotaExceededError。
MediaKeySessionClosedReason,
以描述会话关闭的可能原因,
并相应调整算法。
有关自上一版本以来所做变更的完整列表,请参见 提交记录。
本文档由 媒体工作组作为 工作草案发布,并使用 推荐标准 轨道。
作为 工作草案发布并不意味着 W3C 及其成员认可本文档。
这是一个草案文档,可能随时被其他 文档更新、替换或废弃。不应将本文档作为 进行中工作以外的内容来引用。
本文档由一个 按照 W3C 专利 政策运行的小组制作。 W3C 维护了一份 与该小组交付物有关的任何专利披露的公开列表; 该页面还包括 披露专利的说明。实际 知晓某项专利并认为该专利包含 必要权利要求的个人, 必须按照 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 在一段时间内必须在源内唯一,
包括跨浏览会话。
底层内容保护协议不一定需要支持会话 ID。
除非另有说明,密钥指可用于解密
媒体数据中块的解密密钥。
每个此类密钥由一个密钥 ID唯一标识。密钥与用于将其
提供给 CDM 的会话相关联。(同一密钥可存在于多个会话中。)此类密钥
必须只能
通过 update() 调用提供给
CDM。(之后它们可以作为已存储会话数据的一部分
由 load()
加载。)
例如,当两个视频分辨率之间的策略不同时,每组 流都会使用不同的密钥进行加密,以便可以 独立强制执行这些策略;类似地,当音频和视频流的 策略不同时,它们会使用不同的密钥。跨策略组共享密钥会阻止 独立强制执行,因此每个策略组使用不同的密钥是确保 在各客户端之间实现强制执行和兼容性的唯一方式。
如果 CDM 确定某个密钥 当前可用于解密一个或多个媒体数据块, 则该密钥被视为可用于解密。
例如,如果密钥的许可证已过期,则该密钥不可用于解密。即使其 许可证尚未过期,如果使用该密钥的其他条件(例如, 输出保护)当前未得到满足,则该密钥也不可用于解密。
一个密钥关联着一个密钥 ID,该 ID 是一个八位字节序列,并且 唯一 标识该密钥。容器指定能够解密媒体数据中某个块 或一组块的密钥的 ID。初始化数据 可以包含密钥 ID,以标识解密 媒体数据所需的密钥。 但是,并不要求初始化数据包含媒体数据或媒体 资源中使用的任何或所有密钥 ID。提供给 CDM 的许可证将每个密钥与一个 密钥 ID 关联起来,从而使 CDM 在解密一个加密的媒体 数据块时能够选择适当的密钥。
如果会话的 CDM 实现包含关于某个密钥的任何信息
——特别是密钥
ID——则该密钥被视为该会话已知,
无论实际的密钥是否可用或其值是否已知。已知密钥通过
keyStatuses 属性暴露。
即使密钥变得不可用,例如由于 过期,或者密钥已被移除但存在许可证销毁记录, 这些密钥仍被视为已知。只有当密钥从会话中被显式移除,并且 任何许可证释放消息得到确认后,密钥才会变为未知。
例如,如果一次 update() 调用
提供了一个不包含该密钥的新许可证,并包含替换
先前包含该密钥的许可证的指令,则该密钥可能会变为未知。
许可证是特定于密钥系统的状态信息,其中包含一个或多个密钥 ——每个密钥都关联一个密钥 ID——以及可能的其他 关于密钥 使用的信息。
密钥系统通常需要一个初始化数据块, 其中包含关于要解密的流的信息,然后才能构造许可证请求消息。 该块可以是简单的密钥或内容 ID,也可以是包含 此类信息的更复杂结构。应用以某种 特定于应用的方式或从媒体数据中获取初始化数据。
初始化数据是一个通用术语,指容器特定的数据,该数据由 CDM 用于生成许可证请求。
初始化数据的格式取决于容器的类型,并且 容器可以支持一种以上的初始化数据格式。 初始化数据类型是一个字符串, 表示随附的初始化数据的格式。初始化数据类型字符串始终 以区分大小写的方式匹配。建议初始化数据类型字符串 为 小写 ASCII 字符串。
Encrypted Media Extensions Initialization Data Format Registry 提供从初始化数据类型 字符串到每种格式规范的映射。
当用户代理在媒体数据中遇到初始化数据时,它会在
initData 属性中将
该初始化数据提供给应用,该属性属于
事件。用户代理
不得存储初始化数据,也不得在遇到它时使用其内容。
应用通过
encryptedgenerateRequest()
将初始化数据提供给 CDM。
用户代理不得通过其他方式向 CDM 提供初始化数据。
对于给定的一组流或媒体 数据,初始化数据必须是固定值。它必须只包含与播放给定的一组流或媒体数据所需密钥 相关的信息。它不得包含应用数据、客户端特定数据、用户特定 数据或可执行 代码。
初始化数据不应包含特定于密钥系统的数据或值。 实现必须支持 [EME-INITDATA-REGISTRY] 中为它们所支持的每个初始化数据类型 定义的通用格式。
不鼓励使用专有格式/内容,即 [EME-INITDATA-REGISTRY] 中未定义的格式,并且强烈不鼓励支持或仅 使用专有格式。专有格式只应与既有内容一起使用,或在不支持 通用格式的既有客户端设备上使用。
如果两个或更多标识符或其他值相同,或者可以在合理的时间和努力范围内 将它们相关联或建立关联,则称它们是可关联的。 否则,这些值是不可关联的。
如果引用的实体或实体集合可以在合理的时间和努力范围内 在没有其他实体参与的情况下将两个或更多标识符或其他值 相关联或建立关联,则称这些值可由某个 实体关联。否则,这些值是不可由某个实体 关联的。
如果两个或更多标识符或其他值 不可由应用关联, 则它们是 不可由某个实体关联的,其中该实体 是包含该 应用、所有其他应用以及它们使用 或与之通信的服务器等其他实体的集合。否则,这些值会被视为 可由应用关联,这是被禁止的。
显著值是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,它不为大量 用户或客户端设备所共享。显著值可以位于内存中,也可以 被持久化。
虽然显著值通常对用户或客户端设备是唯一的,但一个值 不需要严格唯一即可具有显著性。例如,由 少数用户共享的值仍可能具有显著性。
永久标识符是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,它在某种意义上不可磨灭, 或者用户不易移除、重置或更改。这包括但 不限于:
硬件或基于硬件的标识符
在工厂中配置到硬件设备中的值
与操作系统安装实例相关联或从其派生的值
与用户代理安装实例相关联或从其派生的值
与 CDM 或其他软件 组件相关联或从其派生的值
配置文件或类似半永久数据中的值,即使该值是在客户端上生成的
客户端或其他用户账户值
当暴露到客户端之外时,显著永久标识符以及从其派生 或以其他方式与其相关的值必须被加密。 显著永久标识符绝不能暴露给 应用,即使以 加密形式也不可以。
虽然显著永久标识符通常对用户或客户端 设备是唯一的,但显著永久标识符不需要严格唯一即可 具有显著性。例如,由少数 用户共享的显著永久标识符仍可能具有显著性。
显著永久标识符不是显著标识符,因为 它不是(在本规范范围内)派生或生成的。
distinctiveIdentifier
控制是否可以使用显著
永久标识符。具体来说,只有当用于创建
MediaKeys 对象的
MediaKeySystemAccess 的
distinctiveIdentifier
成员的值为
"required" 时,
才可以使用显著永久标识符。
显著标识符是一个值,包括不透明或加密形式的值,对于该值, 客户端外部的任何实体都可能将这些值相关联或建立关联, 超出用户在 Web 平台上可能预期的范围(例如,cookie 和其他站点 数据)。例如,某些值对于应用以外的实体而言是可关联的, 跨越 a) 来源、 b) 浏览配置文件,或 c) 浏览会话,即使用户已经尝试通过清除浏览数据来保护其隐私,或者对于用户来说 不容易打破这种关联的值。特别是,如果 中央服务器(例如个性化 服务器)能够跨来源关联值,例如因为个性化请求 包含了公共值,或者因为在个性化请求中提供的值 即使在尝试 清除浏览数据后,仍可由此类服务器关联,则该值就是显著标识符。 造成这种情况的可能原因包括在个性化过程中使用显著 永久标识符。
暴露给应用的显著标识符,即使以加密形式暴露,也 受标识符 要求约束,包括 被加密、 按来源和配置文件 唯一,以及 可清除。
虽然显著标识符的实例化或使用是由 应用对本规范中定义的 API 的使用触发的,但该标识符不需要 被提供给应用,也可以触发与显著 标识符相关的条件。(显著永久 标识符不会提供给 应用,即使以不透明或加密形式也不会。)
distinctiveIdentifier
控制是否可以使用显著
标识符。具体来说,只有当用于创建
MediaKeys 对象的
MediaKeySystemAccess 的
distinctiveIdentifier
成员的值为
"required" 时,
才可以使用显著标识符。
显著标识符是一个值、一段数据、拥有一段 数据所暗示的信息,或一种可观察的行为或时序,并且满足以下所有 条件:
它是显著的。
虽然显著标识符通常对用户或客户端设备是唯一的,但 标识符不需要严格唯一即可具有显著性。例如,由 少数用户共享的标识符仍可能具有显著性。
它、关于它的信息,或从其派生或以其他方式与其相关的值, 即使以加密形式,也暴露到了客户端之外。这包括但不限于 将其提供给应用和/或许可证、个性化 或其他服务器。
它具有以下一项或多项属性:
它派生自一个或多个显著永久 标识符。
生成、个性化、 配置或其他产生该值的过程, 涉及、使用、提供、派生自或类似地 涉及一个或多个显著永久 标识符或另一个 显著标识符。
它是可清除的,但不能与 cookie 和其他站点 数据一同清除。
例如,通过某种用户代理外部的机制,例如操作系统级 机制。
对于暴露给应用的值,其他受关注且被规范性禁止的属性包括:
此类被规范性禁止的值的示例包括但不限于:
虽然显著标识符通常可由生成它们的实体关联, 但它们必须是不可由应用关联的。换 句话说,这种相关或关联只能由最初生成 显著标识符值的实体完成,例如 个性化服务器。 有权访问显著永久 标识符的实体不得 将这种能力暴露给应用,因为这会使生成的显著 标识符可由 应用关联,并且应谨慎避免向 其他实体或第三方暴露 这种关联。
显著标识符的示例包括但不限于:
包含在密钥请求中的一系列字节,不同于其他客户端设备包含的 字节序列,并且基于或直接或 间接使用显著永久 标识符获得。
包含在密钥请求中的公钥,该公钥不同于其他客户端设备请求中 包含的公钥,并且基于或直接 或间接使用显著永久 标识符获得。
证明拥有其他客户端设备不具备的私钥(例如,通过对某些数据签名), 并且该私钥基于或直接或 间接使用显著永久 标识符获得。
此类密钥的标识符。
使用此类值来派生另一个被暴露的值,即使第一个 值未被直接暴露。
从另一个显著标识符派生的值。
从工厂中配置到硬件设备中的唯一值派生的值。
从工厂中硬件设备里的唯一硬件值(例如 MAC 地址或序列号) 或软件值(例如操作系统安装实例或操作 系统用户账户名)派生的值。
不属于显著标识符的事物示例:
如果安装基数很大,则给定 CDM 版本的所有副本 共享的公钥。
一个随机数或临时密钥,它是唯一的但只在一个会话中使用。
未以任何派生或类似方式暴露到客户端之外的值, 包括通过个性化 或类似方式。
用于例如视频管线与 CDM 之间证明的设备唯一密钥, 当前提是 CDM 不让 这些证明继续流向 应用,而是使用不构成显著标识符的密钥自行生成新的证明。
一个随浏览数据(例如 cookie)完全清除/可清除的值,清除后它将被一个不可关联的值替换] (不仅仅是不可由 应用关联),即使对于中央服务器(例如 个性化 服务器)也是如此,并且满足以下一项或多项:
没有显著 永久标识符或显著标识符 参与该值的生成。
它是一个在没有系统输入的情况下生成的随机值。
它是由服务器在未使用或不知道另一个 显著标识符的情况下提供的值。
如果一个实现、配置、实例或对象在其生命周期内或相关此类 实体的生命周期内的任何时候,将一个或多个显著标识符、关于 它们的信息,或从它们派生或以其他方式与 它们相关的值暴露到客户端之外,即使以加密形式暴露, 则该实现、配置、实例或对象使用显著 标识符。这包括但不限于将此类值提供给 应用和/或许可证、个性化或其他 服务器。
如果一个实现、配置、实例或对象在其生命周期内或相关此类实体的 生命周期内的任何时候,将一个或多个 显著永久 标识符、关于它们的信息,或从它们派生 或以其他方式与它们相关的值暴露到客户端之外,即使以加密形式暴露, 则该实现、配置、实例或对象使用 显著永久标识符。这包括但不限于 将此类值提供给个性化服务器。此类 值不得 提供给应用。
如果一个实现、配置、实例或对象 使用显著标识符和/或使用显著 永久标识符,则该实现、配置、实例或对象 使用显著标识符或显著永久标识符。
distinctiveIdentifier
控制是否可以使用显著标识符
和显著
永久标识符。具体来说,只有当用于创建
MediaKeys 对象的
MediaKeySystemAccess 的
distinctiveIdentifier
成员的值为
"required" 时,才可以使用此类
标识符。
在播放期间,嵌入的媒体数据会暴露给嵌入来源中的脚本。
为了让 API 能够在 事件中提供初始化数据,
媒体
数据必须与嵌入页面
CORS
同源。
如果媒体
数据与嵌入文档是跨源的,作者
应在 encryptedHTMLMediaElement
上使用 crossOrigin
属性,并在媒体数据响应上使用 CORS 标头,以使其
CORS
同源。
在播放期间,嵌入的媒体数据会暴露给嵌入来源中的脚本。
为了让 API 能够在 事件中提供初始化数据,
媒体
数据不得是混合内容 [MIXED-CONTENT]。
encrypted
时间必须等价于 ECMAScript Language Specification 中表示的时间。
如果不存在这样的时间,或该时间不确定,则时间将等于 NaN。
它不应具有 Infinity 值。
Time 通常表示一个具有毫秒精度的时间瞬点;然而,仅这一点 并不足以构成充分的定义。 ECMAScript 语言 规范中的“时间值与时间范围”一节添加了其他重要要求。
给定机器上的用户代理可以支持在各种不同的 上下文、模式或临时状态中执行,这些上下文、模式或临时状态在 应用可见状态和数据方面预期彼此独立。特别是,所有存储的数据都 预期是独立的。在本规范中,我们将此类独立的上下文 或模式称为“浏览配置文件”。
此类独立上下文的示例包括:用户代理在不同 操作系统用户账户中运行,或者用户代理提供为单个账户定义 多个独立配置文件的能力。
本节定义了获取对密钥系统访问的机制。请求中包含 能力也可以实现特性检测。
拒绝 promise 时,算法的步骤总是会被中止。
requestMediaKeySystemAccess()
是一个由字符串
encrypted-media
标识的策略控制
特性。
它的默认
允许列表是 'self' [PERMISSIONS-POLICY]。
WebIDLenum MediaKeysRequirement {
"required",
"optional",
"not-allowed"
};
MediaKeysRequirement 枚举定义如下:
| 枚举描述 | |
|---|---|
required
|
|
optional
|
|
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,默认值为
""
MediaKeySystemConfiguration
中,
该对象由
MediaKeySystemAccess 的
getConfiguration()
方法返回。
initDataTypes
类型为 sequence<DOMString>,
默认值为 []
audioCapabilities
类型为 sequence<MediaKeySystemMediaCapability>,
默认值为
[]
videoCapabilities
元素不得为空。
videoCapabilities
类型为 sequence<MediaKeySystemMediaCapability>,
默认值为
[]
audioCapabilities
元素不得为空。
distinctiveIdentifier
类型为 MediaKeysRequirement,
默认值为 "optional"
当此成员为 "not-allowed" 时,
实现绝不能在与从此配置创建的任何对象相关联的任何
操作中使用
可区别标识符或可区别永久标识符。
persistentState
类型为 MediaKeysRequirement,默认值为
"optional"
当此成员为
"not-allowed" 时,
CDM 绝不能持久化任何与
应用或此对象的 Document 的
源相关的状态。
就此成员而言,持久化状态不包括由密钥系统
实现控制的持久化唯一
标识符(可区别
标识符)。distinctiveIdentifier
独立地
反映此要求。
当不支持持久化
状态时,只能创建 "temporary" 会话。
打算创建非 "temporary" 会话的应用,
在调用
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()
算法选择的受支持配置选项组合。
返回的对象是第一个可满足的 MediaKeySystemConfiguration
配置的非严格子集(加上任何隐含默认值),该配置传递给
requestMediaKeySystemAccess()
调用,而该调用返回的 promise
以此对象 resolve。它不包含未在该单一配置中指定的能力值
(隐含默认值除外),因此可能不会反映
密钥系统实现的所有能力。配置中的所有值
可以以任意组合使用。类型为
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 值所表示的密钥系统 实现的新实例。
使用 configuration 初始化 instance,以启用、禁用和/或选择 密钥系统特性。
如果 use distinctive identifier 为 false,则阻止
instance 使用
可区别标识符和可区别永久标识符。
如果 persistent state allowed 为 false,则阻止
instance 持久化任何与应用或此对象的
Document 的
源
相关的状态。
如果前面的任何步骤失败,则以一个新的
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 resolve 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 算法,
直到此 MediaKeys
对象使用
setMediaKeys()
与媒体元素关联。
此步骤使应用能够在尝试执行此类操作之前检测到
这种不常见行为。
令 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"
resolve promise 并中止这些步骤。
用 "usable"
resolve promise。
返回 promise。
setServerCertificate()
提供一个服务器证书,用于加密发送给许可证服务器的消息。
对于使用此类证书的密钥系统,应用应当调用
setServerCertificate()
来显式设置服务器证书。
否则,许可证交换可能失败。
某些密钥系统也支持通过 排队一个 "message" 事件算法 从服务器请求证书。然而, 并非所有使用此类证书的密钥系统都支持这一点。
服务器证书内容特定于密钥系统。它绝不能包含 可执行代码。
调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 cdm
implementation 值所表示的密钥系统实现不支持服务器证书,则返回一个以
false 兑现的 promise。
如果 serverCertificate 为空,则返回一个以新创建的
拒绝的 promise。
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不得由于
对此对象或其创建的任何会话进行操作而持久化状态或
访问先前持久化的状态。
如果 MediaKeys 对象的 persistent state
allowed 值为
true,则该对象的 cdm instance可以由于
对此对象或其创建的任何会话进行操作而持久化状态或访问
先前持久化的状态。
持久化数据必须始终以只有此对象的 Document 的 源可以访问它的方式存储。 此外,该数据必须只能由当前 浏览配置文件访问;其他浏览配置文件、用户代理和 应用绝不能 访问所存储的数据。见存储在 用户设备上的信息。
MediaKeySession 对象表示一个密钥会话。
当且仅当 MediaKeySession 对象的
closed 属性已被 resolve 时,
该对象才是已关闭的。
对于每个未关闭的
MediaKeySession 对象,用户代理应持续执行监视 CDM 状态变化算法。
监视 CDM 状态变化算法必须与主事件循环并行运行,但不得与本规范中定义为并行运行的
其他过程并行运行。
如果 MediaKeySession 对象未关闭,且创建它的 MediaKeys
对象仍然可访问,则该
MediaKeySession 对象不得
被销毁,并且应继续接收事件。否则,不再可访问的
MediaKeySession 对象不得
再接收事件,并且可以被销毁。
上述规则意味着,在与 CDM 实例关联的所有
MediaKeys 对象和所有
MediaKeySession 对象被销毁之前,
CDM
实例不得被销毁。
如果 MediaKeySession 对象在变为页面不可访问时
尚未关闭,则 CDM 应关闭与该
对象关联的密钥会话。
关闭密钥会话会导致销毁任何尚未 显式存储的许可证和密钥。
对于返回 promise 的方法,所有错误都会通过拒绝返回的 Promise 异步报告。这包括 [WEBIDL] 类型映射错误。
拒绝 promise 时,算法的以下步骤总是会被中止。
WebIDLenum MediaKeySessionClosedReason {
"internal-error",
"closed-by-application",
"release-acknowledged",
"hardware-context-reset",
"resource-evicted"
};
MediaKeySessionClosedReason 枚举定义如下:
| 枚举描述 | |
|---|---|
internal-error
|
会话因 CDM 中发生不可恢复的错误而关闭。当这种情况
发生时,应用绝不能在此 MediaKeys 实例上创建新会话。
|
closed-by-application
|
会话由应用显式调用该会话的 close()
方法而关闭。
|
release-acknowledged
|
会话因 CDM 收到许可证 销毁记录确认而关闭。 |
hardware-context-reset
|
会话因 CDM 的原始硬件上下文被重置而关闭。
当这种情况发生时,用户代理必须允许应用在
此 MediaKeys 实例上创建
新会话。
注
这可能由于许多原因发生,包括设备休眠、监视器 配置更改等。硬件上下文重置的确切原因取决于 实现。 |
resource-evicted
|
会话因系统需要回收资源以允许创建其他会话而关闭。
注
这意味着应用遇到了特定于设备的会话资源限制。 如果已关闭的会话由于某种原因仍然需要,应用开发者应考虑某种策略来减少 所需会话数量,或主动关闭不需要的会话。 |
WebIDL[Exposed=Window, SecureContext] interface MediaKeySession : EventTarget {
readonly attribute DOMString sessionId;
readonly attribute unrestricted double expiration;
readonly attribute Promise<MediaKeySessionClosedReason> closed;
readonly attribute MediaKeyStatusMap keyStatuses;
attribute EventHandler onkeystatuseschange;
attribute EventHandler onmessage;
Promise<undefined> generateRequest (DOMString initDataType, BufferSource initData);
Promise<boolean> load (DOMString sessionId);
Promise<undefined> update (BufferSource response);
Promise<undefined> close ();
Promise<undefined> remove ();
};
sessionId 类型为 DOMString,只读
此对象及其关联密钥或许可证的会话 ID。
expiration 类型为 unrestricted double,
只读
会话中所有密钥的过期时间,或者如果不存在这样的
时间,或许可证明确永不过期,则为 NaN,具体由 CDM 确定。
此值可能在会话生命周期内发生变化,例如当某个操作触发
窗口的开始时。
closed 类型为 Promise<MediaKeySessionClosedReason>,
只读
keyStatuses 类型为 MediaKeyStatusMap,
只读
指向一个只读映射的引用,该映射把会话已知的密钥 ID映射到关联密钥的当前 状态。每个条目必须具有唯一的密钥 ID。
每当事件循环运转时,映射条目及其值都可能被更新。该
映射永远不会处于不一致或部分更新的状态,但如果两次访问之间
事件循环发生运转,则它可能在访问之间发生变化。密钥 ID 可能会作为
load() 或 update()
调用的结果而被添加。
密钥 ID
可能会作为移除
现有密钥知识(或用一组新密钥替换现有密钥集)的 update() 调用
的结果而被移除。如果密钥变得不可用,例如由于过期,则不会移除密钥
ID。相反,此类
密钥必须被赋予适当的状态,例如“expired”。
某些较旧的平台可能包含不暴露
密钥 ID 的密钥系统实现,
从而无法提供符合要求的用户代理实现。在
这种情况下,为了最大化互操作性,当非空列表
合适时(例如,当此对象表示的密钥会话可以包含
密钥时),该映射会填充为一个单独的键值对,
其中包含单字节密钥 ID 0,以及最适合此对象聚合状态的
MediaKeyStatus。
onkeystatuseschange 类型为 EventHandler
事件的
事件处理器。
keystatuseschange
onmessage 类型为 EventHandler
事件的
事件处理器。
message
generateRequest()
基于 initData 生成许可证请求。如果算法成功且 promise 被兑现,
则始终会排队一个类型为
"license-request" 或
"individualization-request"
的 。
message
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 closing or closed 值为 true,则返回一个
以
拒绝的 promise。
InvalidStateError
如果此对象的 uninitialized 值为 false,则返回一个
以
拒绝的 promise。
InvalidStateError
令此对象的 uninitialized 值为 false。
如果 initDataType 是空字符串,则返回一个以新创建的
拒绝的 promise。
TypeError
如果 initData 为空,则返回一个以新创建的
拒绝的 promise。
TypeError
如果由此对象的 cdm
implementation 值表示的密钥系统实现不支持将
initDataType 作为
初始化数据
类型,则返回一个以
拒绝的 promise。
字符串比较区分大小写。
NotSupportedError
令 session type 为此对象的 session type。
令 promise 为一个新的 promise。
并行运行以下步骤:
如果 init data 对 initDataType 无效,则用新创建的 拒绝
promise。
TypeError
令 sanitized init data 为经过验证和清理的 init data 版本。
用户代理必须在将初始化 数据传递给 CDM 之前对其进行彻底验证。这包括验证 字段的长度和值是否合理,验证值是否在合理限制内, 并剥离无关、不受支持或未知的数据或字段。 建议用户代理预解析、清理 和/或生成初始化数据的完全清理版本。如果由 initDataType 指定的初始化 数据格式支持多个条目, 用户代理应该移除 CDM 不需要的条目。用户代理不得重新排序初始化 数据中的条目。
如果前一步失败,则用新创建的
拒绝
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 必须在此对象的 Document
的源中随时间保持唯一,包括
跨 Document 和浏览会话。
令 message 为基于 sanitized init data 生成并按 initDataType 解释的、针对 requested license type 的许可证请求。
令 message type 为
"license-request"。
令 message 为在能够基于 sanitized init data 为 requested license type 生成许可证请求之前需要处理的请求。
在随后对 update()
的调用中,CDM
必须基于 sanitized init
data 为 requested license
type 生成许可证请求,该数据按 initDataType 解释。
令 message type 反映
message 的类型,即
"license-request"
或
"individualization-request"。
排队 一个任务以运行以下步骤:
如果前述任一步骤因资源不足而失败,则用
拒绝
promise。
QuotaExceededError
如果前述任一步骤因任何其他原因失败,则用一个新的 DOMException
拒绝 promise,其名称为适当的错误名称。
将 sessionId
属性设置为 session id。
将此对象的 callable 值设置为 true。
用 undefined 兑现 promise。
在 session 上运行排队一个 "message" 事件 算法,并提供 message type 和 message。
返回 promise。
load()
将为指定会话存储的数据加载到此对象中。
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 closing or closed 值为 true,则返回一个
以
拒绝的 promise。
InvalidStateError
如果此对象的 uninitialized 值为 false,则返回一个
以
拒绝的 promise。
InvalidStateError
令此对象的 uninitialized 值为 false。
如果 sessionId 为空字符串,则返回一个以新创建的
拒绝的 promise。
TypeError
如果在此对象的 session type 上运行是否为持久会话类型?
算法的结果为 false,则返回一个以新创建的 拒绝的 promise。
TypeError
令 promise 为一个新的 promise。
并行运行以下步骤:
令 sanitized session ID 为经过验证和/或清理的 sessionId 版本。
用户代理在将 sessionId 值传递给 CDM 之前应彻底验证它。至少,这 应包括检查其长度和值是否合理 (例如,不长于数十个字符且为字母数字)。
如果前一步失败,或者 sanitized session ID 为空,
则用新创建的 拒绝
promise。
TypeError
如果此对象的 Document 中存在一个尚未关闭的 MediaKeySession 对象,
且其 sessionId
属性为 sanitized session ID,
则用
拒绝 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 存储的数据。此数据不得包含 来自其他源的数据或不与任何源关联的数据。
如果任何 Document
中存在一个尚未关闭的 MediaKeySession
对象,并且该对象表示 session
data,则用
拒绝 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 上运行更新
密钥状态算法,并提供每个密钥的密钥 ID
以及适当的 MediaKeyStatus。
如果需要额外处理才能确定密钥状态,
则使用 "status-pending"。
一旦一个或多个密钥的额外处理完成,
再次运行更新
密钥状态算法,并使用实际状态。
在 session 上运行更新过期时间算法, 并提供 expiration time。
用 true 兑现 promise。
如果 message 不为 null,则在 session 上运行排队一个 "message" 事件 算法,并提供 message type 和 message。
返回 promise。
update()
向 CDM 提供消息,包括许可证。
response 参数包含要提供给 CDM 的消息。 其内容是密钥系统特定的。它不得 包含可执行代码。
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 closing or closed 值为 true,则返回一个
以
拒绝的 promise。
InvalidStateError
如果此对象的 callable 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
如果 response 为空,则返回一个以新创建的
拒绝的 promise。
TypeError
令 promise 为一个新的 promise。
并行运行以下步骤:
令 sanitized response 为经过验证和/或清理的 response copy 版本。
用户代理在将 response 传递给 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
另见会话存储和 持久化。
每个会话的状态信息(包括密钥)必须以这样的方式存储: 关闭一个会话不会影响其他会话中可观察的 状态,即使它们包含重叠的密钥 ID。
当 sanitized response 包含密钥和/或 相关数据时,cdm 很可能会(在内存中)存储以密钥 ID 索引的密钥 及相关数据。
会话内的替换算法依赖于密钥 系统。
建议 CDM
实现支持每个 MediaKeySession
对象的标准且合理较高的最小密钥数量,包括标准替换算法,以及标准
且合理较高的 MediaKeySession
对象最小数量。
这使得可跨用户代理实现合理数量的密钥轮换算法,
并且在涉及同一元素中各种流
(例如自适应流、各种音频和视频轨道)
使用不同密钥的用例中,可能减少播放中断的可能性。
persistent-license"
运行以下步骤:
例如,sanitized response 可能包含
将用于生成另一个
事件的信息。在这种
情况下,无需针对 sessionType 验证内容。
message
如果需要发送消息,则执行以下步骤:
令 message 为该消息。
令 message type 为该消息对应的适当
MediaKeyMessageType。
排队 一个任务以运行以下步骤:
在此对象上运行会话已关闭
算法,原因为
"release-acknowledged"。
运行以下步骤:
如果此对象的 CDM
已知密钥集合发生变化,或任何密钥的状态发生变化,则在
session 上运行更新密钥
状态算法,
并提供每个已知密钥的密钥 ID
以及适当的
MediaKeyStatus。
如果需要额外处理才能
确定密钥状态,则使用
"status-pending"。
一旦一个或多个密钥的额外
处理完成,则再次运行
更新密钥
状态算法,并使用实际
状态。
如果前述任一步骤失败,则用一个新的 DOMException
拒绝 promise,
其名称为适当的
错误名称。
如果 message 不为 null,则在 session 上运行排队一个 "message" 事件算法,并提供 message type 和 message。
用 undefined 兑现 promise。
返回 promise。
close()
表示应用不再需要该会话,并且 CDM 应 释放与该会话关联的任何资源并关闭它。持久化数据 不应被释放或清除。
当请求已被处理时,返回的 promise 会被兑现;当会话关闭时,
closed 属性 promise 会以
"closed-by-application"
兑现。
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 closing or closed 值为 true,则返回一个
以 undefined 兑现的 promise。
如果此对象的 callable 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
令 promise 为一个新的 promise。
将此对象的 closing or closed 值设置为 true。
并行运行以下步骤:
令 cdm 为此对象的 cdm instance 值表示的 CDM 实例。
使用 cdm 关闭与此对象关联的密钥会话。
关闭密钥会话会导致销毁任何尚未被显式存储的许可证和 密钥。
排队 一个任务以运行以下步骤:
用 undefined 兑现 promise。
在此对象上运行会话
已关闭算法,原因为
"closed-by-application"。
返回 promise。
remove()
移除与会话关联的所有许可证和密钥。对于持久会话类型,一旦释放消息确认由
update() 处理,
其他会话数据将按每种会话类型的定义被清除。
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 closing or closed 值为 true,则返回一个
以
拒绝的 promise。
InvalidStateError
如果此对象的 callable 值为 false,则返回一个以
拒绝的 promise。
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 上运行更新密钥状态
算法,
并提供会话中所有密钥 ID
以及每个密钥对应的
"released"
MediaKeyStatus
值。
在 session 上运行更新过期时间算法,
并提供 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;
如果由 keyId 标识的密钥状态
未知,则返回 undefined。
此接口具有由 iterable
[WebIDL] 带来的
entries、keys、values、
forEach 和 @@iterator 方法。
要迭代的值对是由所有已知密钥的
密钥 ID及关联的
MediaKeyStatus 值组成的对集合的快照,
并按密钥 ID排序。密钥 ID 按如下方式比较:对于长度为
m 的密钥 ID A
和长度为 n 的 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 确定该密钥当前可用于解密。 当前可能不可用于解密的密钥不得具有 此状态。 |
expired
|
该密钥因其过期时间已
过去而不再可用于解密。expiration 属性表示的时间必须
早于当前时间。会话中的所有其他密钥必须
具有此状态。
|
released
|
密钥本身不再可供 CDM 使用,但关于 该密钥的信息,例如许可证销毁记录, 是可用的。 |
output-restricted
|
存在与该密钥关联且当前无法满足的输出限制。 使用此密钥解密的媒体数据在必要时可根据输出限制被阻止呈现。 应用应避免使用会触发与该密钥关联的输出限制的流。 |
output-downscaled
|
存在与该密钥关联且当前无法满足的输出限制。
使用此密钥解密的媒体数据在必要时可根据输出限制以较低质量(例如
分辨率)呈现。应用
应避免使用会触发与该密钥关联的输出限制的流。 支持降级缩放是可选的。应用不应该依赖降级缩放来 确保在无法满足输出要求时不中断播放。 |
usable-in-future
|
该密钥尚未可用于解密, 因为开始时间在 未来。当达到其开始时间时,该密钥将变得可用。 |
status-pending
|
该密钥的状态尚未知,且正在确定中。一旦确定, 该状态将用实际状态更新。 |
internal-error
|
该密钥因 CDM 中与其他值无关的错误而当前不可用于解密。此值 不能由应用采取行动处理。 |
MediaKeyMessageEvent 对象用于 事件。
message
WebIDLenum MediaKeyMessageType {
"license-request",
"license-renewal",
"license-release",
"individualization-request"
};
MediaKeyMessageType 定义如下:
| 枚举描述 | |
|---|---|
license-request
|
消息包含对新许可证的请求。 |
license-renewal
|
消息包含对现有许可证续订的请求。 |
license-release
|
消息包含许可证销毁记录。 |
individualization-request
|
消息包含对应用辅助个体化(或
重新个体化)的请求。 与所有其他消息一样,消息中的任何标识符必须 每源且每配置文件可区别,并且 绝不能是可区别永久 标识符。 |
WebIDL[Exposed=Window, SecureContext]
interface MediaKeyMessageEvent : Event {
constructor(DOMString type, MediaKeyMessageEventInit eventInitDict);
readonly attribute MediaKeyMessageType messageType;
readonly attribute ArrayBuffer message;
};
messageType 类型为 MediaKeyMessageType,只读
实现绝不能要求应用处理消息 类型。 实现必须支持不区分消息的应用,并且 绝不能要求应用处理消息类型。 具体而言,密钥系统必须支持 将所有类型的消息传递给单个 URL。
此属性允许应用在不解析消息的情况下区分消息。 它旨在启用可选的应用和/或服务器优化, 但应用不需要使用它。
message 类型为 ArrayBuffer,
只读
WebIDLdictionary MediaKeyMessageEventInit : EventInit {
required MediaKeyMessageType messageType;
required ArrayBuffer message;
};
MediaKeyMessageEventInit
成员
messageType
类型为 MediaKeyMessageType
message
类型为 ArrayBuffer
本节为非规范性内容。
| 事件名称 | 接口 | 分派时机... |
|---|---|---|
keystatuseschange
|
Event
|
会话中的密钥或其状态已发生变化。 |
message
|
MediaKeyMessageEvent
|
CDM 已为该会话生成一条消息。 |
“排队一个 "message" 事件”算法会将一个 message 事件排队到
MediaKeySession
对象。运行此算法的请求包括目标 MediaKeySession 对象、
一个 message type 和一个 message。
message绝不能包含可区别永久标识符,
即使是以加密形式。若 MediaKeySession 对象的
use distinctive
identifier 值为 false,则 message绝不能包含可区别标识符,即使
是以加密形式。
运行以下步骤:
令 session 为指定的 MediaKeySession 对象。
排队一个
任务,以使用 MediaKeyMessageEvent 接口创建一个名为
的事件;
该事件不冒泡且不可取消,其
type 属性设置为 messagemessage,其 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
属性内容会被替换,而不会使对该属性的
现有引用失效。从脚本的角度看,此替换是原子的。
也就是说,脚本永远不会看到部分填充的序列。
排队一个
任务,以在
session 上触发一个事件,其名称为
。
keystatuseschange
排队一个
任务,以在每个媒体元素上运行
必要时尝试恢复播放算法;
这些媒体元素的 mediaKeys 属性是
创建该 session 的
MediaKeys 对象。
“更新到期时间”算法会更新
MediaKeySession 的
到期时间。
运行此算法的请求包括目标
MediaKeySession 对象和新的
到期时间,该时间可以是 NaN。
该算法总是在任务中运行。
运行以下步骤:
令 session 为关联的 MediaKeySession 对象。
令 expiration time 为 NaN。
如果新的到期时间不是 NaN,则令 expiration time
为该到期时间。
将 session 的 expiration 属性设置为
以时间表示的
expiration time。
会话已关闭算法会在密钥会话已被 CDM 关闭后,更新 MediaKeySession 状态。运行此算法的请求包括一个
目标 MediaKeySession 对象
和一个 MediaKeySessionClosedReason。
该算法始终在一个任务中运行。
当会话关闭时,与其关联的许可证和密钥不再
可用于解密媒体数据。在此算法
运行后,所有 MediaKeySession 方法
都将失败,并且不会再为此对象排队任何事件。
CDM 可以在任何时候关闭会话,例如当该 会话不再 需要时,或当系统资源丢失时。在这种情况下,监视 CDM 状态变化算法会检测该变化并运行此算法。
其他会话中的密钥必须不受影响,即使它们具有 重叠的密钥 ID。
在此算法运行后,由此算法排队的事件的事件处理器 将会被执行,但不能再排队更多事件。因此,不会有任何消息 因关闭会话而由 CDM 发送。
运行以下步骤:
令 session 为关联的 MediaKeySession 对象。
令 promise 为 session 的 closed
属性。
如果 promise 已兑现,则中止这些步骤。
将 session 的 closing or closed 值设置为 true。
在 session 上运行更新密钥状态 算法,并提供一个 空序列。
在 session 上运行更新过期时间算法,
并提供
NaN。
用所提供的原因兑现 promise。
监视 CDM 状态变化算法会在 CDM 状态的各个 方面发生变化时执行所需步骤。
该算法始终与主事件循环并行运行。
运行以下步骤:
令 session 为 MediaKeySession 对象。
令 cdm 为由 session 的 cdm instance 值表示的 CDM 实例。
如果 cdm 有尚未发送的外发消息,则排队一个 任务以执行以下步骤:
令 message type 和 message 分别为消息类型和 消息。
运行排队一个 "message" 事件算法,传入 session、 message type 和 message。
如果 cdm 已更改 session 已知的密钥集合,或 一个或多个密钥的状态,则排队一个 任务以执行以下 步骤:
令 statuses 为密钥 ID 和 MediaKeyStatus 值对的列表,
其中包含 session
已知的每个密钥
对应的一个值对。
运行更新密钥 状态算法,传入 session 和 statuses。
如果 cdm 已更改 session 的过期时间, 则排队一个 任务以执行以下步骤:
令 expiration time 为 session 的新过期时间。
运行更新过期时间 算法,传入 session 和 expiration time。
如果 cdm 已关闭 session,则排队一个
任务以在 session 上运行
会话已关闭算法,
并提供适当的
MediaKeySessionClosedReason
值。
如果 cdm 因硬件上下文重置而变得不可用,则排队一个
任务以运行CDM 不可用算法,
原因为
"hardware-context-reset"。
如果 cdm 因任何其他原因而变得不可用,则排队一个
任务以
运行CDM 不可用算法,原因为
"internal-error"。
这些方法通过使用简单异常
[WEBIDL] 或 DOMException 拒绝返回的
promise 来报告错误。算法中使用了以下来自 [WEBIDL] 的
简单
异常和
DOMException 名称。
算法中指定的原因列在每个名称旁边,尽管这些名称也可以
因其他原因使用。
| 名称 | 可能原因(非详尽) |
|---|---|
TypeError
|
参数为空。 初始化数据无效。 响应格式无效。 为 " temporary"
会话提供了持久许可证。
|
NotSupportedError
|
无法移除现有 MediaKeys 对象。不支持该密钥系统。 密钥 系统不支持该初始化数据类型。 密钥 系统不支持该会话类型。 密钥 系统不支持该初始化数据。 密钥 系统不支持该操作。 |
InvalidStateError
|
现有 MediaKeys 对象
此时无法移除。会话已被使用。 会话尚未初始化。 会话已关闭。 |
QuotaExceededError
|
MediaKeys 对象不能与更多
HTMLMediaElement 一起使用。已存在针对此 sessionId 的未关闭会话。 资源不足,无法创建新会话或许可证请求。 |
本节提供会话存储和持久化的概述,以补充这些 算法。
除了存储和持久化中的要求外,还适用以下要求。
如果在此对象的
session type 上运行是否为持久会话类型?
算法的结果为 false,则用户代理和 CDM 不得
在任何时候持久化该会话的记录或与该会话相关的数据。这包括
许可证、密钥、许可证销毁
记录,以及会话 ID。
本节其余部分适用于是否为持久会话类型?算法返回
true 的会话类型。
CDM
不应该存储会话数据,包括
会话 ID,直到
首次调用 update()。具体而言,CDM
不应该在 generateRequest()
算法期间存储会话数据。这
确保应用知道该会话,并知道它最终需要
移除该会话。
当会话被清除时,与该会话关联的所有数据必须被清除,
例如在 update() 中处理
许可证销毁记录
确认时。参见持久化
数据。
CDM
必须确保给定会话的数据
只存在于一个尚未在任何 Document
中关闭的
MediaKeySession 对象中。
换言之,当已经有一个
MediaKeySession 表示由 sessionId
参数指定的会话时,load() 必须失败,
这可能是因为通过
generateRequest()
创建它的对象仍处于活动状态,或者它已通过 load()
加载到另一个
对象中。只有曾表示该会话的所有对象都已关闭时,会话才可以再次加载。
使用是否为持久会话类型?算法返回
true 的类型创建会话的应用,应该随后移除已存储的数据,方法是先
使用 remove() 发起移除过程,然后确保
该移除过程成功完成,该过程可能涉及消息交换。CDM
也可以在适当时移除会话,但应用不应该依赖
这一点。
本节规定在支持 Encrypted Media Extensions 时,对 HTMLMediaElement [HTML]
的新增和修改。
向 HTMLMediaElement 添加以下内部值:
attaching media keys,其应具有布尔值。
encrypted block queue,其应是一个等待 解密的加密块队列。
decryption blocked waiting for key,其应具有布尔 值。
playback blocked waiting for key,其应具有布尔 值。
对 HTMLMediaElement 的行为作出以下修改:
当创建 HTMLMediaElement 时,
其 attaching media keys 值应
初始化为 false,其 encrypted block queue 值应
为空,其 decryption blocked waiting for key 值应
初始化为 false,并且其 playback blocked waiting for key 值应
初始化为 false。
当当前
播放位置发生变化,而这种变化不是正常播放过程中沿播放
方向前进时,encrypted block queue 值
应为空,decryption blocked waiting for key 值应
初始化为 false,并且 playback blocked waiting for key
值应设置为 false。
除 [HTML] 中指定的条件外,
如果 HTMLMediaElement 的
playback blocked waiting for key 值为 true,则该元素应被视为被阻塞的媒体
元素。
当用户代理已准备好开始播放,并且在资源 获取算法期间遇到媒体数据可能包含 加密块的指示时,用户代理应运行 媒体数据可能包含加密块 算法。
对于某些容器格式,这种指示与初始化数据是分开的。
该算法将在解析相关容器数据之后运行,包括运行 遇到初始化数据算法, 但应在解码开始之前运行。
当用户代理在资源 获取算法期间,在媒体 数据中遇到初始化数据时,用户代理 应运行 遇到初始化数据 算法。
某些容器格式可能支持不包含 初始化数据的加密媒体数据,从而支持不会触发此 算法的媒体数据。
对于在 资源 获取算法期间遇到的每个加密媒体数据块, 用户代理应按照遇到加密块的顺序运行 遇到加密块算法。
上述步骤为用户代理实现提供了灵活性,使其可以在遇到加密块之后、 在播放需要之前的任何时间执行解密。
当 decryption blocked waiting for
key 值为 true 时发生以下任一情况,用户代理应
运行等待密钥
算法。
添加如下指定的其他属性和一个方法。
对于返回 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。
当调用此方法时,用户代理必须运行以下 步骤:
如果此对象的 attaching media keys 值为 true,则返回一个
以
拒绝的 promise。
InvalidStateError
如果 mediaKeys 和 mediaKeys 属性
是同一个对象,则返回一个以 undefined 兑现的 promise。
令此对象的 attaching media keys 值为 true。
令 promise 为一个新的 promise。
并行运行以下步骤:
如果以下所有条件均成立:
mediaKeys 不为 null,
由 mediaKeys 表示的 CDM 实例已被 另一个媒体元素使用
用户代理无法将其与此元素一起使用
则运行以下步骤:
排队 一个任务以运行以下步骤:
令此对象的 attaching media keys 值为 false。
用
拒绝 promise。
QuotaExceededError
中止这些步骤。
如果 mediaKeys
属性不为 null,则运行
以下步骤:
如果用户代理或 CDM 不支持 移除关联,则运行 以下步骤:
排队 一个任务以运行以下步骤:
令此对象的 attaching media keys 值 为 false。
用
拒绝 promise。
NotSupportedError
中止这些步骤。
如果当前无法移除该关联,则运行以下步骤:
例如,某些实现可能不允许在 播放期间移除。
排队 一个任务以运行以下步骤:
令此对象的 attaching media keys 值 为 false。
用
拒绝 promise。
InvalidStateError
中止这些步骤。
如果前一步失败,则运行以下步骤:
如果 mediaKeys 不为 null,则运行以下步骤:
排队 一个任务以运行以下步骤:
将 mediaKeys
属性设置为 mediaKeys。
令此对象的 attaching media keys 值为 false。
用 undefined 兑现 promise。
如果 mediaKeys 不为 null,则在媒体元素上运行必要时尝试 恢复播放算法。
返回 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 属性中包含的
初始化数据的
初始化数据类型。
initData 类型为 ArrayBuffer,
只读,可空
WebIDLdictionary MediaEncryptedEventInit : EventInit {
DOMString initDataType = "";
ArrayBuffer? initData = null;
};
MediaEncryptedEventInit
成员
initDataType
类型为 DOMString,
默认值为
""
initData
类型为 ArrayBuffer,
可空,默认值为
null
本节为非规范性内容。
| 事件名称 | 接口 | 分派时机... | 前提条件 |
|---|---|---|---|
encrypted
|
MediaEncryptedEvent
|
用户代理在初始化数据中遇到媒体数据。 |
该元素的 readyState
等于或大于
HAVE_METADATA。
注
该元素可能正在播放或已经播放过。 |
waitingforkey
|
Event
|
播放因等待密钥而被阻塞。 |
readyState
等于或小于
HAVE_CURRENT_DATA。
该元素的 playback blocked waiting
for key 值刚变为 true。
|
“媒体数据可能包含加密块”算法会在用户代理要求在播放媒体数据之前指定
MediaKeys 对象时暂停播放。
运行此算法的请求包括目标 HTMLMediaElement 对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
如果 media element 的 mediaKeys 属性为
null,且该实现要求在解码可能加密的媒体数据之前指定
MediaKeys 对象,则运行
以下步骤:
当应用在调用 setMediaKeys()
提供
MediaKeys 对象之前提供媒体数据
时,可能会到达这些步骤。选择一个 CDM 可能会影响所使用的管线和/或解码器,
因此某些实现可能会延迟播放可能包含
加密块的媒体数据,直到通过将 MediaKeys 对象传给
setMediaKeys()
来指定 CDM。
在 media element 上运行等待密钥算法。
等待恢复播放的信号。
“遇到初始化数据”算法会为在媒体
数据中遇到的
初始化数据排队一个
事件。运行此算法的请求包括一个目标
encryptedHTMLMediaElement 对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
令 initDataType 为空字符串。
令 initData 为 null。
如果媒体数据是 CORS 同源 且不是 混合内容,则运行以下步骤:
令 initDataType 为表示初始化数据的初始化 数据类型的字符串。
令 initData 为初始化数据。
虽然媒体元素可以允许加载“可升级内容” [MIXED-CONTENT],但用户代理不得将此类 媒体数据中的初始化数据暴露给应用。
排队一个
任务,以创建一个名为
的事件,该事件不
冒泡且
不可取消,使用 encryptedMediaEncryptedEvent 接口,
将其
type 属性设置为 encrypted,并将其
isTrusted 属性初始化为 true,然后将其分派到
media element。
事件接口 MediaEncryptedEvent 具有:
initDataType =
initDataTypeinitData =
initData
readyState
不会改变,也没有算法
被中止。此事件仅提供信息。
“遇到加密块”算法会将一个加密媒体数据块排队等待
解密,并在可能时尝试解密。运行此算法的请求包括
目标 HTMLMediaElement
对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
令 block 为加密媒体数据块。
将 block 添加到 media element 的 encrypted block queue 末尾。
如果 media element 的 decryption blocked waiting for key
值为 false,则运行尝试解密算法。
“尝试解密”算法会尝试解密已排队等待
解密的媒体数据。运行此算法的请求包括一个目标 HTMLMediaElement
对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
如果 media element 的 encrypted block queue 为空,则中止 这些步骤。
如果 media element 的 mediaKeys 属性不为
null,则运行以下步骤:
令 media keys 为该
属性所引用的 MediaKeys 对象。
令 cdm 为 media keys 的 cdm instance 值所表示的 CDM 实例。
如果 cdm 因任何原因不再可用,则运行以下步骤:
在 media keys 上运行CDM
不可用算法;若为硬件上下文重置,则原因为
"hardware-context-reset",
否则为 "internal-error"。
中止这些步骤。
如果至少存在一个由 media
keys 创建且未关闭的
MediaKeySession,则运行以下
步骤:
此检查确保 cdm 已完成加载,并且是匹配密钥可用的 前提条件。
令 block 为 media element 的 encrypted block queue 中的第一个条目。
令 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 上运行
更新密钥状态
算法,并提供该会话中的所有密钥 ID以及每个密钥对应的
适当
MediaKeyStatus
值。
如果 block key 不为 null,则运行以下步骤:
使用 cdm,用 block key 解密 block。
按照以下列表中第一个匹配条件的步骤执行:
如果 cdm 不再可用,则在
media
keys 上运行CDM
不可用算法;对于硬件上下文重置,原因为
"hardware-context-reset",
否则为
"internal-error"。
中止这些步骤。
从 media element 的 encrypted block queue 前端移除 block。
按正常方式处理解密后的块。
换言之,解码该块。
返回此算法的开头。
并非所有解密问题(例如使用了错误的密钥)都会导致 解密失败。在这种情况下,此处不会触发错误,但 可能会在解码期间触发。
否则,任何会话中都没有用于 block key ID 的密钥, 因此继续。
将 media element 的 decryption blocked waiting for key
值设置为 true。
当没有可用于解密 block 的密钥时,会到达此步骤。
一旦用户代理已渲染无法解密的块之前的块(尽其所能,例如所有完整的视频帧), 它将运行等待密钥算法。
该算法不会在此处直接运行,以允许实现提前于当前播放位置 解密和解码媒体数据,而不影响可见行为。
对于基于帧的加密,当媒体元素作为资源 获取算法的一部分尝试解码帧时,可以按如下方式实现:
令 encrypted 为 false。
检测该帧是否已加密。
解码该帧。
提供该帧用于渲染。
“等待密钥”算法会排队一个
事件并更新
waitingforkeyreadyState。
只有当 HTMLMediaElement
对象可能正在
播放,且其 readyState
等于或大于 HAVE_FUTURE_DATA
时,才应调用它。运行此
算法的请求包括目标 HTMLMediaElement 对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
如果 media element 的 playback blocked waiting for key 值
为 true,则中止这些步骤。
将 media element 的 playback blocked waiting for key
值设置为 true。
由于上述步骤,如果该媒体元素尚未成为被阻塞的媒体 元素,它将成为被阻塞的媒体元素。在这种情况下, 媒体元素将停止播放。
按照以下列表中第一个匹配条件的步骤执行:
将 media element 的 readyState
设置为
HAVE_CURRENT_DATA。
将 media element 的 readyState
设置为
HAVE_METADATA。
换言之,如果当前
播放位置的视频帧和音频数据因为未加密和/或成功解密而已被解码,则将
readyState
设置为 HAVE_CURRENT_DATA。
否则,包括之前曾是这种情况但数据已不再
可用时,将 readyState
设置为
HAVE_METADATA。
排队一个
任务,以在
media
element 上触发一个事件,其名称为
。
waitingforkey
暂停播放。
“必要时尝试恢复播放”算法会在媒体元素因等待密钥而被阻塞且所需密钥当前
可用于解密时恢复播放。运行此
算法的请求包括目标 HTMLMediaElement
对象。
运行以下步骤:
令 media element 为指定的 HTMLMediaElement 对象。
如果 media element 的 playback blocked waiting for key 为
false,则中止这些步骤。
在 media element 上运行尝试解密 算法。
将 media element 的 decryption blocked waiting for
key 值设置为 false。
将 media element 的 playback blocked waiting for key
值设置为 false。
由于上述步骤,媒体元素可能不再是 被阻塞的 媒体元素,因此 播放可以恢复。
视适当情况,
将 media element 的 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 Transforms。一种可能的限制是,视频媒体 可以被 限制为只出现在边与窗口边缘平行且方向正常的矩形区域中。
本节定义实现要求——适用于用户代理和密钥系统,包括 CDM 和服务器——这些要求可能未在 算法中明确说明。这里以及整个规范中的要求适用于所有实现, 无论 CDM 是独立于用户代理,还是用户代理的一部分。
用户代理实现者必须确保 CDM 不访问任何并非使用本规范特性播放受保护媒体 合理所需的信息、 存储 或系统能力。具体而言,CDM 不得:
访问网络资源,无论是本地还是远程,除非通过用户代理并且 本规范明确允许。
访问存储(例如磁盘或内存),除非这是使用本规范特性播放 受保护媒体的合理所需。
访问硬件组件或设备,除非这是使用本规范特性播放 受保护媒体的合理所需。
用户代理实现者可以使用各种技术来满足上述要求。例如, 同时实现其自己的 CDM 的用户代理实现者可以将 上述内容作为该组件的设计要求。使用 第三方 CDM 的用户代理实现者可以确保其在受限环境(例如 “沙盒”)中执行,且无法访问被禁止的信息和组件。
所有发往和来自 CDM 的消息和通信,例如 CDM 与 许可证服务器之间的通信,必须经由用户代理传递。CDM 不得发起直接的 带外网络请求。除直接个性化中描述的那些以外的所有消息和通信必须 通过本规范中定义的 API 经由应用传递。具体而言,所有 包含应用、源或 内容特定信息的通信,或者 发送到由应用指定或基于其源的 URL 的通信,必须通过 这些 API。这包括所有许可证交换消息。
持久化数据包括由 CDM 存储的所有数据,或由用户代理代表
CDM
存储的所有数据,这些数据在 MediaKeys 对象销毁后仍然存在。
具体而言,它包括由
CDM
或
由用户代理代表 CDM 存储的任何标识符(包括显著标识符)、
许可证、密钥、密钥 ID,或许可证销毁记录。
可能以应用或许可证服务器可见的方式影响消息或行为的持久化数据,必须以特定于源 和特定于浏览配置文件的方式存储,并且绝不能泄漏到私密浏览会话或从私密浏览会话泄漏。 具体而言但不限于此,会话数据、许可证、密钥和每源标识符必须 按源以及按浏览配置文件存储。
见会话存储与持久性。
使用持久化数据的实现必须允许用户清除该数据, 使其无论是在外部(例如通过本规范定义的 API)还是在客户端设备上 都不再可检索。
用户代理应当:
将持久化数据视为其他站点数据,例如 cookie [COOKIES]。 具体而言:
允许用户作为清除浏览历史的用户代理功能的一部分来清除持久化数据。
在“移除所有数据”功能中包含持久化数据。
在与其他站点数据相同的 UI 位置呈现持久化数据。
允许用户按每个源 和每个浏览配置文件清除持久化数据,尤其是作为 “忘记此站点”功能的一部分,该功能会忘记与特定站点相关联的 cookie [COOKIES]、数据库等。
确保清除持久化数据的操作具有足够的原子性,以防止出现 “cookie 复活”类型的情形,即依赖另一种未同时清除的本地存储数据, 将新的标识符与旧标识符重新关联。见数据清除不完整。
以帮助用户理解数据清除不完整可能性的方式呈现这些接口, 并使他们能够同时删除与所有持久化数据功能相关联的数据,包括 cookie [COOKIES] 和 web storage。
以帮助用户理解数据清除不完整可能性的方式呈现 禁用和重新启用密钥系统 的接口,并使他们能够同时删除所有持久化存储功能中的 所有此类数据。
允许用户按源 和/或针对所有 源专门删除持久化数据。
用户代理应当将持久化数据视为潜在敏感数据;释放这些信息 很可能会损害用户隐私。为此, 用户代理应当确保安全存储持久化数据,并且在删除 数据时,将其及时从底层存储中删除。
暴露给应用或可由应用推断的值(例如通过 CDM 的使用), 可能被用于标识客户端或用户,无论它们是否设计为 标识符。本节定义了避免或至少缓解此类问题的要求。对于标识符还有其他要求。
暴露给应用或可由应用推断的所有可区别值必须按每个 源和 浏览配置文件保持唯一。也就是说,一个源 使用本规范定义的 API 时所使用的值,必须不同于任何其他 源使用这些 API 时所使用的值;并且一个浏览 配置文件中使用的值,必须不同于任何其他配置文件中使用的值, 无论源如何。此类值绝不能泄漏到私密浏览会话或从私密浏览会话泄漏。
跨源和跨配置文件的值必须不能被应用关联,这意味着 绝不能能够关联来自多个源或多个配置文件的值,例如 用来确定它们来自同一客户端或用户。具体而言,从与源无关和/或与配置文件无关的值 派生每源值的实现,必须以确保上述不可关联属性的方式执行, 例如使用具有适当不可逆属性的派生函数。
作为允许清除持久化数据中要求的结果, 暴露给应用的所有持久化值必须可清除, 使这些值无论是在外部(例如通过本规范定义的 API)还是在客户端 设备上都不再可检索、可观察或可推断。
一旦被清除,当随后需要这些值时,必须生成新的不能被应用关联的值。
实现使用标识符,尤其是可区别 标识符或可区别永久标识符,会带来隐私 问题。本节定义了避免或至少缓解此类问题的要求。 暴露给应用的值的要求也适用于 暴露给应用的标识符。
实现应当避免使用 可区别标识符或可区别永久标识符。
例如,使用适用于一组客户端或设备的标识符或其他值,而不是适用于单个客户端的值。
实现应当仅在为强制执行与特定 CDM 实例和会话相关的策略所必需时,才使用 可区别标识符或可区别永久标识符。
例如,"temporary" 和
"persistent-license"
会话可能有不同的要求。
使用 可区别标识符或可区别永久标识符的实现应当支持不使用它们的选项。具有 此类支持的实现应当向用户暴露选择此 选项的能力。
在受支持时,应用可以使用
distinctiveIdentifier
=
"not-allowed"
来选择此模式。选择这样的选项可能影响
requestMediaKeySystemAccess()
调用的结果,和/或从随后生成的会话生成的
许可证请求。
向用户提供访问权限来选择此实现能力,可能允许用户在保持更高隐私程度的同时访问内容。
显著标识符和显著永久 标识符在暴露到客户端外部时,必须在消息交换层面加密。 所有其他标识符在暴露到客户端外部时,应在消息交换层面加密。 该 加密必须确保标识符密文的任何两个实例 只有拥有解密密钥的实体才能 关联。
除可区别永久标识符外, 所有标识符必须按每个 源和 浏览配置文件保持唯一。见 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]
功能,例如跳转。
媒体资源,包括所有 轨道,必须按容器特定的“common encryption”规范 加密和打包,该规范允许在提供一个或多个密钥时,以完全指定且兼容的方式 解密内容。
Encrypted Media Extensions 流 格式注册表 提供了对此类流格式的引用。
带内支持内容,例如字幕、描述性音频和文字记录,不应 被加密。
此类轨道的解密——尤其是使其能被提供回用户代理的解密——通常不受实现支持。 因此,加密此类轨道会阻止它们在用户代理实现中被广泛用于无障碍功能。
为确保无障碍信息以可用形式提供,对于选择支持加密带内支持内容的实现: a) CDM 必须将 解密后的数据提供给用户代理,并且 b) 用户代理必须以与等效未加密支持内容 相同的方式处理它,例如将其暴露为 定时文本轨道 [HTML]。
所有用户代理必须支持本节所述的公共密钥系统。
这确保所有用户代理(包括完全开源的用户代理)都保证支持一个公共的基本功能级别。 因此,只需要基本解密的内容提供者可以构建简单应用, 它们无需与任何内容保护提供者合作即可在所有平台上运行。
"org.w3.clearkey" 密钥系统使用明文
(未加密)
密钥解密源内容。不需要额外的客户端侧内容保护。
此密钥系统如下所述。
以下内容描述 Clear Key 如何支持密钥系统特定能力:
encryptionScheme:
实现必须
支持
方案,并且可以支持其他方案。
"cenc"
robustness:
仅支持空字符串。
distinctiveIdentifier:
不支持
"required"。
persistentState:
不为
"required",
除非应用打算创建
非 "temporary"
会话(如果受支持)。
"persistent-license"
MediaKeySessionType:
实现可以支持此类型。
setServerCertificate()
方法:不支持。
getStatusForPolicy()
方法:实现应始终以
"usable"
resolve 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()
算法中,反映
record of license destruction 的 message 是一个按 UTF-8 编码的
JSON 对象,如许可证释放格式中所述。
keyStatuses 属性
方法最初包含所有通过 update()
提供的密钥 ID,状态为
"usable"。当执行
remove()
算法时,
keyStatuses 属性
将被设置为空列表。
初始化数据:实现可以支持已注册
初始化数据类型 [EME-INITDATA-REGISTRY]
的任意组合。
实现应当支持
"keyids" 类型 [EME-INITDATA-KEYIDS]
以及适用于用户代理所支持内容类型的其他类型。
本节描述通过 事件的
message 属性提供给应用的许可证请求格式。
message
该格式是一个包含以下成员的 JSON 对象:
MediaKeySessionType。
当包含在
message
属性的 ArrayBuffer 中,并属于一个
MediaKeyMessageEvent 对象时,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"
的会话,该对象应包含以下
成员:
当包含在
message
属性的 ArrayBuffer 中,并属于一个
MediaKeyMessageEvent 对象时,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 Encoding” 术语定义和 “Notes on implementing base64url encoding without padding”。 具体而言,不存在 '=' 填充,并且字符 '-' 和 '_' 必须分别用于替代 '+' 和 '/'。
用户代理和密钥系统实现必须将
媒体
数据、初始化数据、传递给 update() 的数据、许可证、
密钥数据以及应用提供的所有其他数据视为不可信内容和
潜在攻击向量。它们必须使用适当的防护措施来缓解任何
相关威胁,并注意安全地解析、解密等此类数据。用户代理应当
在将数据传递给 CDM 之前对其进行验证。
如果 CDM 未在与例如 DOM 相同的 (沙箱化)上下文中运行,则此类验证尤其重要。
实现绝不能向应用返回会影响程序控制流的主动内容或被动内容。
例如,暴露可能来自媒体数据的 URL 或其他信息并不安全,比如传递给
generateRequest()
的初始化数据就是这种情况。
应用必须确定要使用的 URL。
messageType 属性
可由应用在适用时用于从一组 URL 中进行选择;该属性属于
事件。
message
用户代理负责为用户提供安全的 Web 浏览方式。此 责任适用于用户代理使用的任何功能,包括 来自第三方的功能。用户代理实现者必须从密钥系统实现者处获取 足够的 信息,使其能够恰当地评估与密钥系统集成的安全影响。用户代理实现者 必须确保 CDM 实现提供和/或支持 足够的控制能力,以便 用户代理为用户提供安全性。用户代理实现者必须 确保 CDM 实现在出现安全漏洞时能够且将会被快速、主动地更新。
利用未完全沙盒化和/或使用平台 特性的 CDM 实现,可能允许攻击者访问 OS 或平台功能、提升权限 (例如,以 system 或 root 身份运行),和/或访问驱动程序、内核、固件、硬件等。 此类功能、软件和硬件可能并未被编写为能够抵御恶意 软件或基于 Web 的攻击,并且可能不会通过安全修复进行更新,尤其是 与用户代理相比。CDM 实现中安全 漏洞修复更新缺失、不频繁或缓慢会增加风险。此类 CDM 实现以及暴露它们的 UA 必须在 安全性的所有方面格外谨慎,包括解析所有数据。
当使用属于客户端 OS、平台和/或硬件的一部分,或由其提供的 CDM 或底层机制时, 用户代理应格外谨慎。
如果用户代理选择支持一个无法被充分沙盒化或以其他方式保护的密钥系统实现, 则用户代理应该在加载或调用它之前确保用户已被充分告知和/或给出明确同意。
向未经认证的源授予权限,在存在网络攻击者的情况下等同于 向任何源授予这些权限。参见对 持久化同意的滥用。
本节为非规范性内容。
潜在网络攻击及其影响包括:
以下技术可以缓解这些风险:
使用 TLS 的应用可以确信,只有用户、代表用户工作的软件, 以及其他使用 TLS 且具有标识其来自同一域证书的页面, 能够与该应用交互。此外, 源特定 权限与安全源结合,确保授予应用的 权限不能被网络攻击者滥用。
本规范中定义的 API 仅在安全上下文中暴露。另见 安全源与传输。
用户代理必须正确处理混合内容 [MIXED-CONTENT],包括 阻止 “Blockable Content” [MIXED-CONTENT],以避免 潜在暴露于 不安全内容。此类暴露可能破坏其他缓解措施,例如使用 TLS。
用户代理可以选择阻止所有混合内容,包括 “Optionally-blockable Content” [MIXED-CONTENT],以通过防止不可信媒体数据 被传递给 CDM 来进一步提高安全性(见CDM 攻击与漏洞)。
用户代理应当确保在具有比其他用户代理功能 (例如 DOM 内容)更大安全顾虑的 密钥系统可以被某个 源 访问之前,用户已被充分告知和/或给出明确同意。
本节为非规范性内容。
恶意页面可能在 iframe 中托管合法应用,试图隐藏
攻击或欺骗用户对来源的判断,例如让使用看起来像是
来自合法内容提供者。这对于那些告知用户或要求同意的实现
尤其相关,例如出于安全和/或隐私原因。
除网络攻击外,攻击者还可能试图通过在
iframe 中托管合法用途来利用本规范中定义的 API。通过
让合法应用执行这些操作,攻击者可以重用
现有已授予的权限(或白名单),和/或显得像是合法请求
或使用。
告知用户或要求同意的用户代理, 包括出于安全和/或隐私原因的情况,应当 将 UI 和同意的持久化基于顶层 Document 的 源与使用本规范中定义 API 的 源的组合。 这可确保用户被告知发出请求的主文档,并确保 为一个(合法)组合持久化权限不会无意中允许 恶意使用不被检测到。
作者应当阻止其他实体在
iframe 中托管其应用。出于合法应用设计原因必须支持被托管的
应用不应允许托管文档提供任何要传递给 CDM 的数据——无论是通过本规范中定义的
API
还是作为媒体数据——并且不应允许
托管框架调用本规范中定义的 API。
本节为非规范性内容。
不同作者共享一个主机名,例如在
geocities.com 上托管内容的用户,都共享同一个源。用户代理
不提供按路径名限制 API 访问的功能。
在共享主机上使用本规范中定义的 API 会破坏用户代理实现的基于源的 安全和隐私缓解措施。例如,每源的 可区别标识符会被同一主机名上的所有作者 共享,并且持久化数据可能会被该主机上的任何作者访问和操作。 如果例如修改或删除此类数据可能抹除用户对特定内容的 权利,则后者尤其重要。
即使用户代理提供了路径限制功能,通常的 DOM 脚本安全模型也会使绕过这种保护并从任何路径访问 数据变得轻而易举。
因此,建议共享主机上的作者避免使用本规范 中定义的 API,因为这样做会破坏用户代理中基于源的安全和隐私缓解措施。
用户设备上存在或使用密钥系统会引发若干 隐私问题,分为两类:(a) 可能由 EME 接口本身或在密钥系统消息中 披露的用户特定信息,以及 (b) 可能持久存储在用户设备上的用户特定信息。
用户代理必须负责为用户提供对其自身隐私的充分控制。 由于用户代理可能会与第三方 CDM 实现集成, CDM 实现者必须向用户代理实现者提供足够的信息和控制,以使他们能够实现 适当技术,确保用户能控制自己的隐私,包括但不限于 下文所述技术。
关于 EME 和密钥系统披露信息的顾虑分为 两类:(a) 关于非特定信息的顾虑,尽管这些信息仍可能有助于 对用户代理或设备进行指纹识别;以及 (b) 可直接用于用户跟踪的 用户特定信息。
恶意应用可能能够通过检测或枚举所支持的 密钥系统列表及相关信息,对用户或用户代理进行指纹识别。 如果未提供适当的源保护,这可能包括检测已访问的站点以及为这些站点存储的信息。 特别是,密钥系统 必须不在不同源之间共享密钥或其他数据。
本规范中的若干特性会暴露可能对指纹识别略有贡献的能力信息:
encryptionScheme
成员(属于
MediaKeySystemMediaCapability)
会揭示某个密钥系统
支持哪些加密方案。此信息主要由该密钥系统本身决定,
因此除了应用选择的密钥系统已隐含的信息之外,增加的信息很少。
getStatusForPolicy()
会揭示是否可以强制执行指定的 HDCP 策略。
这反映当前连接的显示链能力,并且如果用户重新配置显示器,
可能会在会话期间发生变化。
本节为非规范性内容。
CDM,尤其是在用户代理外部实现的 CDM,可能不具有与 Web 平台相同的 基本隔离。重要的是应采取措施避免信息泄漏,尤其是跨源泄漏。 这包括内存中数据和 已存储数据。如果不这样做,可能导致信息泄漏到私密 浏览会话或从其中泄漏、跨浏览配置文件(包括跨 操作系统用户 账户)泄漏,甚至跨不同浏览器或应用泄漏。
为避免此类问题,用户代理和 CDM 实现必须确保:
会话数据不会与未关联到创建该会话的
MediaKeys 对象的媒体元素共享。
除其他事项外,这意味着会话的密钥不得用于解密由
mediaKeys 属性
不是该 MediaKeys 对象的
媒体元素加载的内容。
如果适用,持久化的会话数据按每个源 存储。
只能加载由请求源 存储的数据。
无法从 CDM 中提取、派生或推断出 本规范中未明确描述,且未经用户许可也无法通过其他 Web 平台 API 提供给页面的信息。这适用于暴露到客户端设备外部或暴露给应用的任何 信息,包括例如 CDM 消息中的信息。
此要求涵盖的信息类型包括但不限于:
位置,包括地理位置
除显著标识符以外的凭据或标识符
OS 账户名和其他潜在 PII
本地目录路径,其中可能包含类似信息。
本地网络详细信息(例如,设备的本地 IP 地址)
本地设备,包括但不限于 Bluetooth、USB 和用户媒体。
不与本规范中定义的 API 相关联,也不是因其而存储的用户状态。
本节为非规范性内容。
第三方宿主(或任何能够将内容 分发到多个站点的实体,例如广告商)可以使用由 CDM 存储或代表其存储的显著标识符或持久化 数据(包括许可证、密钥、密钥 ID,或许可证销毁记录), 跨多个会话(包括跨源和浏览配置文件)跟踪用户,构建用户 活动或兴趣的画像。此类跟踪会削弱 Web 平台其余部分提供的隐私保护, 并且例如可能实现原本不可能的高度定向 广告。如果结合一个知道 用户真实身份的站点(例如需要 已认证凭据的内容提供商或电子商务站点),这可能使压迫性团体能够 比在纯匿名 Web 使用的世界中更准确地锁定个人。
可通过本规范中 API 的实现获得的用户或客户端特定信息包括:
本规范提出了一个特定的关注点,因为此类信息通常 存储在用户代理(以及关联的浏览 配置文件存储)之外,通常在 CDM 中。
由于许可证和许可证销毁记录的内容是密钥系统特定的,并且由于密钥 ID 可以包含任何值, 这些数据项可能被 滥用来存储可识别用户的信息。
密钥系统可以访问或创建针对 设备或设备用户的持久化或半持久化标识符。在某些情况下,这些标识符可能以安全方式绑定到特定 设备。如果这些标识符出现在密钥系统 消息中, 则设备和/或用户可能被跟踪。如果未应用下述缓解措施, 这可能包括随时间跟踪用户/设备,以及关联给定设备的多个 用户。
需要注意的是,此类标识符,尤其是那些不可清除、 非源特定的, 或永久标识符,其跟踪 影响超过现有技术,例如 cookies [COOKIES] 或嵌入 URL 中的会话 标识符。
如果未加以缓解,此类跟踪可能会根据 密钥系统的设计采取三种形式:
在所有情况下,预计此类标识符可供完全支持 密钥系统的站点和/或服务器使用(因此能够 解释密钥系统 消息),从而使此类站点能够跟踪。
如果密钥系统消息以 一致的方式包含从用户标识符派生的信息, 例如特定内容项的初始密钥系统 消息中的某一部分不会随时间变化且依赖于 用户标识符,则任何应用都可以使用该信息来 随时间跟踪设备或用户。
此外,如果密钥系统允许密钥或其他数据被存储并在 源之间重用,则两个源可能能够串通,通过记录它们访问同一密钥的能力来跟踪 唯一用户。
最后,如果任何用于用户控制密钥系统的用户界面 将数据与 HTTP 会话 cookies [COOKIES] 或 持久化存储中的数据分开呈现,则 用户很可能会修改其中一种的站点授权或删除其中一种的数据,而不修改其他数据。 这会允许站点将这些不同功能用作彼此的冗余备份, 从而挫败用户保护其隐私的尝试。
除了站点和其他第三方可能跟踪用户之外,用户 代理实现者、CDM 供应商或设备供应商也可以构建用户 活动或兴趣的画像,例如用户访问的使用本规范中定义的 API 的站点。 此类跟踪会削弱 Web 平台其余部分提供的隐私保护, 尤其是与源隔离相关的保护。
标识符,例如显著标识符, 可以从由 CDM 供应商运营或提供的服务器获得,例如通过个性化 过程。该过程可能包括向服务器提供客户端标识符,包括 显著永久 标识符。为了生成 每源标识符,也可能提供一个表示该源的值。
在这样的实现中,CDM 供应商可能能够跟踪 用户的活动,例如访问过的源数量或需要新标识符的次数。 如果在标识符请求中提供了源,或提供了与源可关联的值, 则 CDM 供应商可以跟踪用户或设备用户访问过的站点。
以下章节描述了可在未经用户同意的跟踪风险方面进行缓解的技术。
密钥系统实现应尽可能避免使用 显著标识符和显著永久标识符,并且只有在它们能够有意义地 提高实现的健壮性时才使用它们。参见限制或 避免使用显著标识符和永久标识符。
实现不得将显著永久 标识符暴露给 应用或来源。
显著标识符在密钥系统消息中必须被 加密,并附带时间戳或随机数,使密钥系统 消息始终 不同。这会防止使用密钥系统 消息进行跟踪,除非由 完全支持该密钥系统的服务器进行。参见加密标识符。
用户代理应以一种将显著 标识符和 密钥系统存储的数据与 HTTP 会话 cookie [COOKIES] 强关联的方式呈现给用户, 包括将其纳入“移除 所有数据”,并在相同的 UI 位置呈现 它。这可能会鼓励用户以合理的怀疑态度看待此类标识符。 用户代理应帮助用户避免数据清除不完整。
不要向个性化服务器或其他与该来源无关的实体提供来源 或与来源可关联的值。如果实现使用了这样的 过程,请遵循个性化一节中的要求和建议。
对于暴露给应用的所有显著值, 实现必须为每个来源 和 浏览配置文件使用 不同的不可由 应用关联的值。参见8.4.1 使用按来源、按配置文件的值。
这对于使用显著标识符的实现尤其重要。参见 8.5.3 使用按来源、按配置文件的标识符。
CDM 使用的任何数据,如果可能以应用或许可证服务器可见的方式影响 消息或行为,则必须按 来源 和 浏览配置文件进行分区,并且不得 泄漏到私密浏览会话或从私密浏览会话泄漏。这 包括内存中数据和持久化数据。具体但非穷尽地说, 会话数据、许可证、密钥和按来源的标识符必须 按来源 和按浏览配置文件进行分区。参见8.3.1 使用特定于来源和特定于浏览配置文件的密钥系统存储 和8.4.1 使用按来源、按配置文件的值。
用户代理可以在一段时间后自动删除 显著标识符和/或其他 密钥系统数据,并且这种方式可以由用户配置。
例如,用户代理可以被配置为将此类数据作为仅会话 存储,在用户关闭所有可以访问它的浏览上下文后删除这些数据。
这可以限制站点跟踪用户的能力,因为此时该站点 只有在用户向该站点本身进行身份验证时(例如购买或登录 服务),才能跨多个会话跟踪该用户。
然而,如果用户并未充分理解此类过期的影响, 这也可能使用户对内容的访问,尤其是已购买或 租赁内容的访问,面临风险。
用户代理可以将对密钥系统和/或特性的访问限制为
源自浏览上下文的顶级Document的来源的脚本。
例如,requestMediaKeySystemAccess()
可以拒绝
来自在 iframe 中运行的其他来源页面对某些配置的请求。
用户代理必须确保用户在使用 显著标识符或显著永久标识符之前充分知情和/或给出 明确同意。
用户代理应为用户提供一个全局控制项,用于控制某个 密钥系统 是否启用,和/或密钥系统对显著 标识符或显著永久标识符的使用是否启用(如果 密钥系统支持)。用户代理应帮助用户避免数据清除不完整。
用户代理可以要求用户在站点能够使用每个密钥系统和/或某些特性之前,明确授权访问它。用户代理应 允许用户临时或永久撤销此授权。
虽然这些建议阻止了本规范中定义的 API 被轻易用于 用户跟踪,但它们并不能完全阻止这种情况。在单个来源内,站点可以 在会话期间继续跟踪用户,然后可以将所有这些信息连同 该站点获得的任何识别信息(姓名、信用卡号、 地址)一起传递给第三方。如果第三方与多个站点合作以 获取此类信息,并且标识符不是 按来源和配置文件唯一,则仍然可以创建画像。
本节为非规范性内容。
密钥系统可能会在用户设备上存储信息,或者用户 代理可能会代表密钥系统存储信息。这可能向同一设备的另一个用户揭示 某个用户的信息,包括可能曾使用特定源的 密钥系统(即访问过的站点),甚至是 使用某个密钥系统解密过的内容。
如果一个源存储的信息影响另一个源的密钥系统运行, 则一个用户在某个站点上访问过的站点或观看过的内容,可能会被泄露给另一个 可能恶意的站点。
如果客户端设备上为一个浏览配置文件存储的信息影响 其他浏览 配置文件或浏览器的密钥系统运行, 则一个配置文件中访问过的站点或观看过的内容,可能被另一个浏览配置文件泄露或与其相关联, 甚至包括不同操作 系统用户账户或浏览器。
缓解这些顾虑的要求定义在8.3 持久化数据中。
本节为非规范性内容。
如果在清除显著标识符 和已存储数据和/或禁用密钥系统时,未同时清除和/或 禁用所有此类数据和 功能,以及 cookie [COOKIES] 和其他 站点数据,则用户保护其隐私的尝试可能会失败。例如:
如果用户清除 cookie 或其他持久存储,而没有同时清除 显著标识符和密钥系统存储的数据, 站点就可以通过将各种特性彼此作为冗余备份来挫败这些 尝试。
如果用户清除显著 标识符,而没有同时清除 密钥系统存储的数据(包括持久会话),以及 cookie 和其他 持久存储,站点就可以通过使用剩余数据将旧标识符和新标识符 关联起来,从而挫败这些尝试。
如果用户禁用密钥系统,尤其是针对某个特定来源, 而没有 同时清除 cookie 或其他持久存储,站点就可以通过使用剩余特性来挫败这些 尝试。
如果用户禁用密钥系统,之后又决定 启用该密钥系统,而没有同时清除 cookie 或其他 持久存储、显著 标识符,以及密钥 系统存储的数据,则站点可能能够将 禁用之前的数据与该密钥系统 重新启用之后的数据和行为关联起来。
缓解这些顾虑的建议定义在8.3 持久化数据中。
用户代理可以支持一种旨在保护用户匿名性和/或确保浏览活动记录不会 持久化到客户端的操作模式(例如私密浏览)。前几节讨论的隐私顾虑 对使用此类模式的用户而言可能尤其令人担忧。
支持此类模式的用户代理实现者应当仔细考虑
是否应在这些模式中禁用对密钥系统的访问。例如,此类模式
可以禁止创建支持或使用
MediaKeySystemAccess
对象,
这些对象支持或使用
persistentState 或
distinctiveIdentifier
(无论是作为 CDM
实现的一部分,还是因为应用指明它们为
"required")。如果实现不禁止此类创建,
则它们应当在允许使用之前,告知用户此类模式的预期隐私属性所面临的影响和潜在后果。
本规范中定义的 API 仅在安全源上受支持,从而保护前几节讨论的信息。 标识符还会按加密标识符中的规定进行加密。
应用,包括它们使用的服务器,应当对所有涉及或包含来自
CDM
的数据或消息的流量使用安全传输,
包括但不限于从 事件传递的所有数据以及传递给
messageupdate() 的所有数据。
所有用户代理必须正确处理混合内容 [MIXED-CONTENT],以在用户代理或应用希望强制安全 源和传输时避免暴露于 不安全内容或传输。
除标记为非规范性的节外,本规范中的所有编写指南、图表、示例和注均为非规范性内容。 本规范中的其他所有内容均为规范性内容。
本文档中的关键词 可以、必须、绝不能、可选、建议、必需、应、不得、应当 和 不应 应按 BCP 14 [RFC2119] [RFC8174] 中所述进行解释,当且仅当它们像这里所示那样以全 大写形式出现时。
本节为非规范性内容。
本节包含使用所提出扩展来解决各种用例的示例方案。 这些并不是这些用例的唯一解决方案。示例中使用了 video 元素, 但同样适用于所有媒体元素。在某些情况下,例如使用同步 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 初始化期间不需要处理 encrypted 事件,
初始化会简单得多。这可以通过以其他方式提供
初始化数据,或在
MediaKeys
对象创建后再设置源来实现。本示例采用后一种方式。
<script>
var licenseUrl;
var serverCertificate;
var mediaKeys;
// See the previous example for implementations of these functions.
function createSupportedKeySystem() { ... }
function makeNewRequest(mediaKeys, initDataType, initData) { ... }
function licenseRequestReady(event) { ... }
function handleInitData(event) {
makeNewRequest(mediaKeys, event.initDataType, event.initData);
}
createSupportedKeySystem().then(
function(createdMediaKeys) {
mediaKeys = createdMediaKeys;
var video = document.getElementById("v");
video.src = 'foo.webm';
if (serverCertificate)
mediaKeys.setServerCertificate(serverCertificate);
return video.setMediaKeys(mediaKeys);
}
).catch(
console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
);
</script>
<video id="v" autoplay onencrypted='handleInitData(event)'></video>
这是一个更完整的示例,展示了所有事件的使用。
注意,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 对本规范的贡献。也感谢通过参与 邮件列表和 issue 等方式为本规范作出贡献的许多 其他人。
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: