Copyright © 2025 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范扩展了 HTMLMediaElement
[HTML]
,提供用于控制加密内容播放的 API。
该 API 支持从简单的 Clear Key 解密到高价值视频(取决于用户代理的实现)等用例。许可证/密钥交换由应用程序控制,便于开发支持多种内容解密和保护技术的健壮播放应用。
本规范不定义内容保护或数字版权管理(Digital Rights Management)系统。相反,它定义了一个通用的 API,可用于发现、选择并与这些系统以及更简单的内容加密系统交互。为了符合本规范,不要求实现数字版权管理:仅需将 Clear Key 系统作为通用基线来实现。
该通用 API 支持一组简单的内容加密能力,将诸如身份验证和授权之类的应用功能留给页面作者。通过要求由页面来中介内容保护系统特定的消息传递,而不是假定加密系统与许可证或其它服务器之间存在带外通信,从而实现这一点。
本节描述了本文档在其发布时的状态。当前的 W3C 出版物列表以及本技术报告的最新修订可在 W3C 标准与草案索引 中找到。
自 2017 年 9 月作为 W3C 推荐标准发布以来,新增了两个功能:
encryptionScheme
属性,增加了对加密方案能力的检测。
getStatusForPolicy
()
方法,增加了查询与 HDCP 策略相关的密钥状态的能力。
将其他功能纳入范围之外是媒体工作组的决定。除编辑更新外,对本规范所做的其他实质性更改主要为解决与本规范相关的维护问题:
encrypted-media
标识符与权限策略(Permissions Policy)集成。
usable-in-future
添加到 MediaKeyStatus
,用于尚不可用于解密的密钥。
QuotaExceededError
。
MediaKeySessionClosedReason
来描述会话关闭的可能原因,并相应调整算法。
有关自上一个版本以来所做更改的完整列表,请参见 提交。
本文档由 媒体工作组(Media Working Group) 以 Recommendation track 的流程作为工作草案发布。
作为工作草案的发布并不意味着被 W3C 及其成员认可。
本文档为草案文件,可能随时被更新、替换或被其他文档废弃。不适宜将此文档作为除工作进展以外的引用来源。
本文档由一个根据 W3C 专利政策 运作的小组制作。 W3C 维护着一份与该小组成果相关的 任何专利披露的公开列表;该页面还包含披露专利的说明。如果个人确实知道某项专利并认为其中含有 必要权利要求(Essential Claim(s)),则该个人必须按照 W3C 专利政策第 6 节 的规定披露该信息。
本文档受 2025年8月18日 W3C 流程文件 管辖。
本节为非规范性内容。
本规范允许脚本选择内容保护机制、控制许可证/密钥交换,并执行自定义许可证管理算法。它支持广泛的用例,而不要求每个用户代理针对每个用例进行客户端修改。这使内容提供者能够为所有设备开发单一的应用解决方案。
支持的内容按照容器特定的“通用加密(common encryption)”规范进行加密,从而在不同密钥系统之间可重用。支持的内容具有未加密的容器,便于将元数据提供给应用程序,并保持与其它 HTMLMediaElement
功能的兼容性。
实现者应关注本规范中描述的安全性和隐私威胁与关注点的缓解措施。特别地,安全与隐私的规范性要求在没有对 Key System 及其实现的安全与隐私属性的了解时无法满足。8. 实现要求 包含与底层 Key System 实现的集成和使用相关的安全与隐私条款。10. 安全性 侧重于外部威胁,例如输入数据或网络攻击。11. 隐私 侧重于用户特定信息的处理并为用户提供对其隐私的充分控制。
尽管本规范与媒体数据来源无关,但作者应意识到许多实现仅支持解密通过媒体源扩展(Media Source Extensions)提供的媒体数据 [MEDIA-SOURCE]。
使用该 API 实现的通用栈示例如下所示。该图显示了一个示例流程;也可以有其他组合的 API 调用和事件。
内容解密模块(CDM)是客户端组件,提供一个或多个 Key Systems 的功能,包括解密。
实现可能会将 CDM 的实现分离开来,也可能将其视为与用户代理不可分。对此 API 和应用程序这是透明的。
Key System 是对解密机制和/或内容保护提供方的通用称呼。Key System 字符串为 Key System 提供唯一标识。用户代理使用它们来选择 CDM 并识别密钥相关事件的来源。用户代理 MUST 支持 常见密钥系统。用户代理 MAY 还可提供额外的具有对应 Key System 字符串的 CDMs。
Key System 字符串始终为反向域名。Key System 字符串使用区分大小写的匹配进行比较。建议 CDMs 使用简单的小写 ASCII key system 字符串。
例如,“com.example.somesystem”。
在给定系统内(示例中的 “somesystem”),子系统由密钥系统提供方确定并定义。例如,“com.example.somesystem.1”和 “com.example.somesystem.1_5”。密钥系统提供方应记住这些字符串将用于比较和发现,因此应便于比较且结构保持相对简单。
Key Session,或简称 Session,提供与 CDM 的消息交换上下文,结果是密钥被提供给 CDM。会话以 MediaKeySession
对象体现。每个 Key Session 与在 generateRequest
()
调用中提供的单个 Initialization Data 相关联。
每个 Key Session 与单个 MediaKeys
对象相关联,且只有与该 MediaKeys
对象关联的媒体元素才可访问与该会话关联的密钥。其它 MediaKeys
对象、CDM 实例以及媒体元素 MUST NOT 访问该密钥会话或使用其密钥。会话及其包含的密钥在会话关闭后(包括当 MediaKeySession
对象被销毁时)不再 可用于解密。
所有与 Key Session 相关且未被显式存储的许可证和密钥在 Key Session 关闭时 MUST 被销毁。
Key IDs MUST 在会话内唯一。
Session ID 是由 CDM 生成的唯一字符串标识符,应用可用它来识别 MediaKeySession
对象。
每次当用户代理与 CDM 成功创建新会话时,都会生成一个新的 Session ID。
每个 Session ID 在创建它的浏览上下文中 SHALL 是唯一的。对于 Is persistent session type? 算法返回
true
的会话类型,Session ID 在时间上(包括跨浏览会话)在该 origin 内 MUST 保持唯一。
底层内容保护协议不一定需要支持 Session ID。
除非另有说明,key 指可用于解密 媒体数据 中区块的解密密钥。每个此类密钥由 key ID 唯一标识。密钥与用于将其提供给 CDM 的 session
相关联。(相同密钥可存在于多个会话中。)这些密钥 MUST 仅通过 update
()
调用提供给 CDM。(它们稍后可以作为存储会话数据的一部分通过 load
()
加载。)
作者 SHOULD 对每组需要执行显著不同策略的流使用不同的密钥(和 key ID)。例如,如果两个视频分辨率之间的策略可能不同,则包含一种分辨率的流不应使用用于另一种分辨率的密钥进行加密。对于加密的情况,音频流 SHOULD NOT 与任何视频流使用相同的密钥。这是确保在客户端之间执行和兼容的唯一方法。
如果 CDM 确定某密钥当前可用于解密一个或多个 媒体数据 的区块,则该密钥被视为可用于解密。
例如,如果密钥的许可证已过期,则该密钥不可用于解密。即使许可证未过期,如果其它使用条件(例如输出保护)当前未满足,该密钥也不可用于解密。
key 关联一个 key ID,该 key ID 是一系列八位字节并能唯一标识该密钥。容器指定可解密媒体数据中某区块或一组区块的密钥的 ID。Initialization Data MAY 包含用于识别解密媒体数据所需密钥的 key ID(s)。然而,并不要求 Initialization Data 包含媒体数据或媒体资源中使用的任何或所有 key ID。提供给 CDM 的 许可证 将每个密钥与一个 key ID 关联,以便 CDM 在解密加密的媒体数据区块时选择适当的密钥。
如果 CDM 的会话实现包含关于某密钥的任何信息——特别是 key
ID ——则该密钥被视为为会话所知,无论该密钥是否可用或其值是否已知。已知密钥通过 keyStatuses
属性暴露。
即使密钥变得不可用,例如由于 过期,或如果它们被移除但存在 许可证销毁记录,这些密钥仍被视为已知。只有当它们被显式从会话中移除并且任何许可证释放消息被确认后,密钥才变为未知。
例如,如果一次 update
()
调用提供了不包含该密钥的新许可证并包含替换先前包含该密钥的许可证的指示,则该密钥可能变为未知。
许可证是密钥系统特定的状态信息,包含一个或多个 密钥,每个密钥都与一个 key ID 关联,并可能包含关于密钥使用的其它信息。
Key Systems 通常在构造许可证请求消息之前需要一段包含待解密流信息的初始化数据块。该块可以是一个简单的密钥或内容 ID,或包含此类信息的更复杂结构。它 SHOULD 始终允许唯一标识解密内容所需的 key(s)。该初始化信息 MAY 通过某些应用特定方式获取或随媒体数据提供。
Initialization Data 是一个通用术语,表示容器特定的数据,CDM 用它来生成许可证请求。
初始化数据的格式取决于容器类型,且容器 MAY 支持多种初始化数据格式。Initialization Data Type 是一个字符串,用于指示随附 Initialization Data 的格式。Initialization Data Type 字符串始终区分大小写匹配。建议 Initialization Data Type 字符串使用小写 ASCII 字符串。
Encrypted Media Extensions Initialization Data Format Registry [EME-INITDATA-REGISTRY] 提供了从 Initialization Data Type 字符串到每种格式规范的映射。
当用户代理在 媒体数据 中遇到 Initialization
Data 时,它将该 Initialization Data 提供给应用,置于 initData
属性中,作为
事件的一部分。用户代理 MUST NOT 在遇到时存储 Initialization Data 或使用其 内容。应用通过在 encrypted
generateRequest
()
调用中将 Initialization Data 提供给 CDM。用户代理 MUST NOT 通过其它方式将 Initialization Data 提供给 CDM。
Initialization Data 对于给定的一组流或媒体数据 MUST 是固定值。它 MUST 仅包含与播放给定一组流或媒体数据所需密钥相关的信息。它 MUST NOT 包含应用数据、客户端特定数据、用户特定数据或可执行代码。
Initialization Data SHOULD NOT 包含 Key System 特定的数据或值。实现 MUST 支持 [EME-INITDATA-REGISTRY] 中为其支持的每种 Initialization Data Type 定义的通用格式。
不鼓励使用专有格式/内容,且强烈不建议仅支持或仅使用专有格式。专有格式应仅在预先存在的内容或不支持通用格式的预先存在的客户端设备上使用。
如果两个或多个标识符或其它值是相同的,或可以在合理的时间和努力范围内将它们相关联或关联起来,则称它们为可关联的。否则,这些值为 不可关联。
如果在合理时间和努力范围内,所述实体或实体集合能够在不依赖其它实体参与的情况下将它们相关联或关联起来,则称两个或多个标识符或其它值为 可被某实体关联。否则,这些值为 不可被某实体关联。
如果它们为 不可被某实体关联,且该实体为包含应用、所有其它应用以及它们使用或通信的服务器在内的集合,则称两个或多个标识符或其它值为 应用不可关联。否则,这些值将被视为 可被应用关联,这是被禁止的。
Distinctive Value 是一种值、数据片段、持有某数据的含义或一种可观察的行为或时序,不在大量用户或客户端设备间共享。Distinctive Value 可以存在于内存中或被持久化。
Distinctive Value 的示例包括但不限于:
虽然 Distinctive Value 通常对用户或客户端设备是唯一的,但值不必严格唯一才算区别性。例如,在少量用户之间共享的值仍然可能是区别性的。
永久标识符是一种以某种方式不可磨灭或对用户而言非平凡以移除、重置或更改的值、数据片段、持有某数据的含义或可观察的行为或时序。这包括但不限于:
硬件或基于硬件的标识符
在出厂时在硬件中预置的值
与操作系统安装实例相关或从其派生的值
与用户代理安装实例相关或从其派生的值
与 CDM 或其它软件组件相关的值
配置文件或类似半永久数据中的值,即使在客户端生成
客户端或其它用户账户值
当在客户端之外暴露时,区别性永久标识符及其派生或相关值 MUST 被 加密。区别性永久标识符 MUST NOT 以任何形式(即使是加密形式)暴露给应用。
虽然区别性永久标识符通常对用户或客户端设备是唯一的,但它不必严格唯一才算区别性。例如,在少量用户之间共享的区别性永久标识符仍可能是区别性的。
区别性永久标识符并不是 区别性标识符,因为它不是在本规范范围内派生或生成的。
distinctiveIdentifier
控制是否允许使用区别性永久标识符。具体地,只有当用于创建 MediaKeySystemAccess
的 distinctiveIdentifier
成员的值为 "required
" 时,才允许使用区别性永久标识符。
区别性标识符是一个值(包括不透明或加密形式),外部实体可以将该值与其它值相关联或匹配,从而超出用户在 Web 平台上可能预期的关联范围(例如 cookie 和其它站点数据)。例如,在以下情形下的值即为区别性标识符:可被应用以外的实体跨 a) origins、b) 浏览配置文件 或 c) 浏览会话 关联,即使用户尝试通过清除浏览数据来保护其隐私,或该值不易被用户打破这种关联。特别地,如果某中心服务器(例如个性化服务器)能够将值在各 origin 间关联,比如因为个性化请求包含了共同的值,或者因为个性化请求中提供的值即使在尝试清除浏览数据后仍可被该服务器关联,那么该值即为区别性标识符。可能导致此类情况的原因包括在个性化过程中使用了 区别性永久标识符。
暴露给应用的区别性标识符,即使以加密形式,MUST 遵守 标识符要求,包括被 加密、在每个 origin 与配置文件中唯一,并且可 被清除。
虽然区别性标识符的实例化或使用由应用对本规范中定义的 API 的使用触发,但触发与区别性标识符相关的条件并不要求该标识符必须被提供给应用。(区别性永久标识符 MUST NOT 以任何形式提供给应用,包括不透明或加密形式。)
distinctiveIdentifier
控制是否允许使用区别性标识符。具体地,仅当用于创建 MediaKeySystemAccess
的 distinctiveIdentifier
成员的值为 "required
" 时,才允许使用区别性标识符。
区别性标识符是满足以下所有条件的值、数据片段、持有某数据的含义或可观察的行为或时序:
它是 区别性 的。
虽然区别性标识符通常对单个用户或客户端设备是唯一的,但标识符不必严格唯一才算区别性。例如,少量用户共享的标识符仍可能是区别性的。
它、关于它的信息或从它派生或与它相关的值被暴露,即使是加密形式,也会在客户端之外被暴露。这包括但不限于将其提供给应用和/或许可证、个性化 或其它服务器。
它具有下列一种或多种属性:
公开给应用的值在规范性上被禁止的其它属性包括但不限于:
它是 区别性永久标识符。
它 不是 可清除 的。
清除标识符后创建的值可能会被应用 与之前的值关联。
对不同 origin 的值可能 不是每 origin/每配置文件唯一。
不同 origin 的值可能被应用 关联。
此类在规范上被禁止的值的示例包括但不限于:
虽然区别性标识符通常可被生成它们的实体所 关联,但它们 MUST 对应用而言 不可关联。换句话说,此类关联或匹配仅可由最初生成区别性标识符值的实体(例如 个性化 服务器)完成。拥有访问 区别性永久标识符 的实体 MUST NOT 将此能力暴露给应用,否则会使生成的区别性标识符对应用变得 可关联,并且 SHOULD 注意避免向其它实体或第三方暴露此类关联能力。
区别性标识符的示例包括但不限于:
在密钥请求中包含的一系列字节,该序列与其它客户端设备包含的不同,并基于或直接/间接使用了 区别性永久标识符。
在密钥请求中包含的公钥,该公钥与其它客户端设备请求中包含的公钥不同,并基于或直接/间接使用了 区别性永久标识符。
证明拥有私钥(例如,通过签署某些数据),其它客户端设备没有该私钥,并基于或直接/间接使用了 区别性永久标识符。
该私钥的标识符。
用于派生另一个即使第一个值不直接暴露仍会被暴露的值。
从另一个区别性标识符派生的值。
从出厂时预置在硬件设备中的唯一值派生的值。
从硬件设备中的唯一硬件值(例如 MAC 地址或序列号)或软件值(例如操作系统安装实例或操作系统用户帐户名)派生的值。
从包含在 CDM 二进制文件或 CDM 使用的其它文件中的唯一值派生的值。
并非 Distinctive Identifier 的示例包括:
在给定 CDM 版本的所有副本中共享的公钥(如果已安装基数很大)。
唯一但仅在一个会话中使用的 nonce 或短期密钥。
未被暴露(即使是派生或类似方式)到客户端之外的值,包括通过 个性化 或类似方式。
在视频管道与 CDM 之间用于证明的设备唯一密钥,当 CDM 不允许这些证明继续流向应用,而是使用不会构成区分性标识符的新密钥自行做新的证明时,该值不构成区别性标识符。
与浏览数据(例如 cookies)一并完全清除/可清除的值,之后它将被一个 不可关联 的值替换(不仅仅是对应用 不可关联),并且满足下列一项或多项条件:
在生成该值时未涉及任何 区别性永久标识符 或区别性标识符。
它是一个在没有系统输入的情况下生成的随机值。
它是由服务器提供的值,且在生成时未使用或未知任何其它区别性标识符。
如果某实现、配置、实例或对象在其生命周期内或相关实体的生命周期内在客户端之外暴露(即使以加密形式)一个或多个 区别性标识符、有关它们的信息或从它们派生或与它们相关的值,则称其 使用了区别性标识符。这包括但不限于将此类值提供给应用和/或许可证、个性化 或其它服务器。
如果某实现、配置、实例或对象在其生命周期内或相关实体的生命周期内在客户端之外暴露(即使以加密形式)一个或多个 区别性永久标识符、有关它们的信息或从它们派生或与它们相关的值,则称其 使用了区别性永久标识符。此类值 MUST NOT 提供给应用。
如果某实现、配置、实例或对象 使用了区别性标识符 和/或 使用了区别性永久标识符,则称其 使用了区别性标识符或区别性永久标识符。
distinctiveIdentifier
控制是否允许使用 区别性标识符 和 区别性永久标识符。具体地,只有当用于创建 MediaKeySystemAccess
的 distinctiveIdentifier
成员的值为 "required
" 时,才允许使用这些标识符。
在播放期间,嵌入的媒体数据会在嵌入页面的 origin
下暴露给脚本。为了能在 Initialization Data 在
事件中被提供,媒体数据 MUST 与嵌入页面保持 CORS-same-origin。如果媒体数据与嵌入文档为跨源,作者
SHOULD 在 encrypted
HTMLMediaElement
上使用 crossOrigin
属性,并在媒体数据响应上使用 CORS 头,使其为 CORS-same-origin。
在播放期间,嵌入的媒体数据会在嵌入页面的 origin
下暴露给脚本。为了能在 Initialization Data 在
事件中被提供,媒体数据 MUST NOT 为 混合内容 [MIXED-CONTENT]。
encrypted
时间 MUST 等价于 ECMAScript 中所表示的时间值(参见 ECMAScript Time Values and Time Range)[ECMA-262]。
如果不存在这样的时间或时间不确定,时间将等于 NaN
。它不应具有 Infinity
的值。
时间通常表示一个具有毫秒精度的时刻;然而这并不是充分的定义。已定义的时间值与时间范围引用添加了其它重要要求。
在给定机器上的用户代理可能支持在多种不同上下文、模式或临时状态下执行,这些上下文或模式在对应用可见的状态和数据方面应当独立。特别地,所有存储数据应当是独立的。在本规范中,我们将此类独立上下文或模式称为“浏览配置文件”。
此类独立上下文的示例包括用户代理在不同操作系统用户帐户中运行,或用户代理提供在单个帐户下定义多个独立配置文件的能力。
本节定义获取 Key System 访问的机制。在请求中包含能力也允许进行功能检测。
当 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
通过
getConfiguration
()
方法由
MediaKeySystemAccess
返回时保留。
initDataTypes
类型为 sequence<DOMString
>,
默认值为 []
audioCapabilities
类型为 sequence<MediaKeySystemMediaCapability
>,
默认值为
[]
videoCapabilities
元素不能为空。
videoCapabilities
类型为 sequence<MediaKeySystemMediaCapability
>,
默认值为
[]
audioCapabilities
元素不能为空。
distinctiveIdentifier
类型为 MediaKeysRequirement
,
默认值为 "optional"
当该成员为 "not-allowed
" 时,实现 MUST NOT 在从此配置创建的任何对象的任何操作中 使用区别性标识符或区别性永久标识符。
persistentState
类型为 MediaKeysRequirement
,默认值为
"optional"
当该成员为 "not-allowed
" 时,CDM MUST NOT 持久化与应用或该对象的 origin
相关的任何状态。
就此成员而言,持久化状态不包括由 Key System 实现控制的持久化唯一标识符(区别性标识符)。distinctiveIdentifier
独立反映该要求。
当不支持持久化状态时,仅允许创建 "temporary
" 会话。
对于 "temporary
"
会话,是否需要及能否存储状态取决于 Key System 的具体实现,并可能随所使用的功能而异。
如果应用打算创建非 "temporary
" 会话,应在调用 requestMediaKeySystemAccess
()
时,将该成员设置为 "required
"。
sessionTypes
类型为 sequence<DOMString
>
MediaKeySessionType
列表。所有值都必须被支持。
如果此成员在字典传递给 requestMediaKeySystemAccess
()
时 不存在 [Infra],则该字典会被视为已设置为
[ "
。
temporary
"
]
实现 SHOULD NOT 向该字典添加成员。如确需添加,MUST 类型为 MediaKeysRequirement
,并且强烈建议(RECOMMENDED)默认值为 "optional
",以支持最广泛的应用与客户端组合。
用户代理实现未识别的字典成员会被 [WEBIDL] 忽略,不会在 requestMediaKeySystemAccess
()
算法中考虑。应用如使用非标准字典成员,MUST NOT 依赖用户代理实现会因包含此类成员而拒绝配置。
此字典 MUST NOT 用于向 CDM 传递状态或数据。
WebIDLdictionary MediaKeySystemMediaCapability
{
DOMString contentType
= "";
DOMString? encryptionScheme
= null;
DOMString robustness
= "";
};
MediaKeySystemMediaCapability
成员
contentType
类型为 DOMString
,默认值为
""
应用 SHOULD 确保 MIME 类型显式指定 codec 及 codec 约束(例如,参见 [RFC6381]),除非这些已被容器规范性蕴含。
encryptionScheme
类型为 DOMString
,默认值为
null
与 contentType 关联的加密方案。值为 null 或未提供表示应用未指定特定加密方案,因此任何加密方案均可接受。
了解此字段的应用 SHOULD 指定所需的加密方案,因为不同加密方案通常彼此不兼容。应用实际接受“任何”加密方案并不现实,但默认值 null 及将 null 解释为“任何”可为未适配的应用提供向后兼容性,并为旧版用户代理提供 polyfill 路径。
空字符串与 null 或未提供不同,因此会被视为未识别的加密方案。
robustness
类型为 DOMString
,默认值为
""
与 contentType 关联的健壮性级别。空字符串表示可接受任何解密和解码 contentType 的能力。
实现 MUST 配置 CDM 至少支持用于创建 MediaKeys
的 MediaKeySystemAccess
对象配置中的健壮性级别。具体如何配置 CDM 取决于实现,且实现 MAY 可选择在配置中使用最高健壮性级别,即使有更高健壮性可用。如果仅指定空字符串,实现 MAY 可配置为使用最低健壮性级别。
应用 SHOULD 指定所需的健壮性级别,以避免意外的客户端不兼容。
为使该对象表示的能力被视为受支持,contentType
MUST NOT 为空字符串,且其全部值(包括所有 codec)MUST 与 robustness
一起被支持。
如果可接受任何一组 codec,应为每个 codec 使用该字典的独立实例。
MediaKeySystemAccess
对象用于访问 Key System。
WebIDL[Exposed=Window, SecureContext] interface MediaKeySystemAccess
{
readonly attribute DOMString keySystem
;
MediaKeySystemConfiguration
getConfiguration
();
Promise<MediaKeys
> createMediaKeys
();
};
keySystem
类型为 DOMString
,只读
getConfiguration()
返回由 requestMediaKeySystemAccess
()
算法选择的受支持配置选项组合。
返回的对象是第一个可满足的 MediaKeySystemConfiguration
配置(加上任何隐含默认值)的非严格子集,来自于传递给 requestMediaKeySystemAccess
()
的参数,该参数返回了被解析为本对象的 promise。它不包含未在该单一配置中指定的能力(除隐含默认值外),因此可能不反映 Key System 实现的所有能力。配置中的所有值都可以任意组合使用。类型为 MediaKeysRequirement
的成员反映该能力是否在任何组合中是必需的。它们不会有值 "optional
"。
调用此方法时,用户代理 MUST 运行以下步骤:
返回此对象的 configuration 值。
每次调用该方法都会创建并从 configuration 初始化一个新对象。
如果应用未指定 encryptionScheme
,累计的
configuration MUST 仍包含 encryptionScheme
字段且值为 null
,这样 polyfill 可以检测用户代理对该字段的支持,无需指定具体值。
createMediaKeys()
为 keySystem 创建一个新的 MediaKeys
对象。
调用此方法时,用户代理 MUST 运行以下步骤:
令 promise 为一个新的 promise。
并行运行下列步骤:
令 configuration 为此对象的 configuration 值。
若 configuration 的 distinctiveIdentifier
成员值为 "required
",则
use distinctive identifier 为 true
,否则为
false
。
若 configuration 的 persistentState
成员值为 "required
",则
persistent state allowed 为 true
,否则为 false
。
如有必要,加载并初始化由此对象的 cdm implementation 值表示的 Key System 实现。
令 instance 为此对象 cdm implementation 值表示的 Key System 实现的新实例。
用 configuration 初始化 instance,以启用、禁用和/或选择 Key System 功能。
如果 use distinctive identifier 为 false
,则禁止
instance 使用区别性标识符或区别性永久标识符。
如果 persistent state allowed 为 false
,则禁止
instance 持久化与应用或本对象 origin
相关的任何状态。
如果上述任何步骤失败,则以合适的 error name 拒绝
promise,并创建新的 DOMException
。
令 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
|
一种会话,其许可证、密钥及与会话相关的记录或数据不被持久化。 应用无需管理此类存储。对该会话类型的支持为 必需(REQUIRED)。 |
persistent-license
|
一种会话,其许可证(以及可能的其它与会话相关的数据)将被持久化。当许可证及其包含的密钥被销毁时,必须持久保存一份 许可证销毁记录。该“许可证销毁记录”是一个由 Key System 指定的、表明许可证及其密钥在客户端不再可用的证明。对该会话类型的支持为 可选(OPTIONAL)。
只有当创建此对象的 应用负责确保当不再需要这些会话的持久化数据时将其移除。参见 会话存储与持久化。 |
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 参数会影响返回对象的行为。
调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 supported session types 值不包含 sessionType,则 抛出
[WEBIDL] 一个
。
NotSupportedError
对于那些 Is persistent
session type? 算法返回 true
的 sessionType,如果此对象的
persistent state allowed 值为 false
,则会失败。
如果实现不支持在当前状态下执行 MediaKeySession
操作,则 抛出
[WEBIDL] 一个
。
InvalidStateError
有些实现直到该 MediaKeys
对象通过 setMediaKeys()
与媒体元素关联后,才可执行 MediaKeySession
算法。此步骤使应用在尝试执行此类操作之前能够检测到这种不常见的行为。
令 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()
返回给定 MediaKeyStatus
对应于指定的
MediaKeysPolicy
。
WebIDLdictionary MediaKeysPolicy
{
DOMString minHdcpVersion
;
};
MediaKeysPolicy
字典仅由可选属性组成。每个属性表示一项策略要求。如果 CDM 会基于所有这些要求允许呈现解密后的媒体数据,则该策略被认为已满足。
HDCP 策略由 minHdcpVersion
表示。如果系统能够启用所指定或更高版本的 HDCP,则该策略将导致返回的 MediaKeyStatus
为 "usable
"。[EME-HDCP-VERSION-REGISTRY]
提供了从 minHdcpVersion
值到
HDCP 规范的映射。
HDCP 状态的判定应以 CDM 在播放期间执行此类限制时的方式一致。通过这种方式,应用开发者可以获得合理的提示,以便优化他们为开始播放而获取的内容。
调用此方法时,用户代理 MUST 运行以下步骤:
TypeError
被拒绝的 promise。
令 promise 为一个新的 promise。
排队一个任务 来运行以下步骤:
对 policy 的每个 字典成员 运行下列步骤:
如果键不是有效的 MediaKeysPolicy
成员或值的类型不正确,则以
拒绝 promise 并中止这些步骤。
TypeError
对 policy 的每个 字典成员 运行下列步骤:
如果 CDM 无法确定该字典成员对应的 MediaKeyStatus
,则以
拒绝 promise 并中止这些步骤。
NotSupportedError
对 policy 的每个 字典成员 运行下列步骤:
如果 CDM
会阻止就该字典成员呈现解密后的媒体数据,则用 "output-restricted
"
解析 promise。
用 "usable
" 解析
promise。
返回 promise。
setServerCertificate()
提供一个服务器证书,用于加密发送到许可证服务器的消息。
使用此类证书的 Key Systems 必须也支持通过 排队 "message" 事件 算法从服务器请求该证书。
此方法允许应用主动向支持该功能的实现提供服务器证书,以避免当 CDM 请求证书时额外的往返。该方法用于优化,应用不必一定使用它。
服务器证书内容为 Key System 特定内容。它 MUST NOT 包含可执行代码。
调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 cdm implementation 值所表示的 Key
System 实现不支持服务器证书,则返回一个解析为 false
的 promise。
如果 serverCertificate 是空数组,则返回一个以新建
被拒绝的 promise。
TypeError
令 certificate 为 serverCertificate 参数内容的副本。
令 promise 为一个新的 promise。
并行运行下列步骤:
令 sanitized certificate 为对 certificate 的验证和/或清理后的版本。
用户代理应在将证书传递给 CDM 之前对其进行彻底验证。此过程可能包括验证字段值在合理范围内、剥离无关数据或字段、预解析、对其进行清理,和/或生成完全清理后的版本。用户代理应检查字段的长度和值是否合理。未知字段应被拒绝或移除。
使用此对象的 cdm instance 来处理 sanitized certificate。
如果前一步失败,则以合适的 DOMException
(其
name 为相应的 错误名)拒绝 promise。
用 true
解析 promise。
返回 promise。
Is persistent session type? 算法用于确定指定的会话类型是否支持任何形式的持久化。请求运行此算法时包含一个 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 的文档访问的方式存储。此外,该数据 必须(MUST) 仅能被当前的 浏览配置文件 访问;其它浏览配置文件、用户代理和应用不得访问存储的数据。参见 用户设备上存储的信息。
MediaKeySession
对象表示一个 key session。
当且仅当对象的 closed
属性已被解析时,MediaKeySession
对象才被视为 closed。
用户代理 SHALL 持续为每个未被 closed 的 MediaKeySession
对象执行 Monitor for CDM State Changes 算法。Monitor for CDM State Changes 算法 MUST 与主事件循环并行运行,但不应与本规范中也被定义为并行运行的其它过程并行执行。
一个 MediaKeySession
对象 SHALL
NOT 被销毁,并且在它未被 closed 且创建它的 MediaKeys
对象仍然可访问时 SHALL 继续接收事件。否则,一个已不再可访问的 MediaKeySession
对象
SHALL NOT 再接收进一步的事件并且 MAY 被销毁。
上述规则意味着在所有与该 CDM 实例相关的 MediaKeys
对象和 MediaKeySession
对象都被销毁之前,不得销毁该 CDM
实例。
如果在 MediaKeySession
对象在变得对页面不可访问时仍未被 closed,则相应的 CDM 必须关闭与该对象关联的 key session。
关闭 key session 会导致销毁任何未被显式存储的许可证和密钥。
何时精确关闭 key session 属于实现细节,应用不应依赖具体的时序。
想要在采取其它操作之前确保会话已关闭的应用 SHOULD 调用 close
()
并等待返回的 promise 被解析。
对于返回 promise 的方法,所有错误都通过拒绝返回的 Promise 异步报告。这也包括 [WEBIDL] 的类型映射错误。
当拒绝 promise 时,算法的下列步骤将始终中止。
WebIDLenum MediaKeySessionClosedReason
{
"internal-error
",
"closed-by-application
",
"release-acknowledged
",
"hardware-context-reset
",
"resource-evicted
"
};
MediaKeySessionClosedReason
枚举定义如下:
枚举说明 | |
---|---|
internal-error
|
该会话因 CDM 中发生不可恢复错误而被关闭。发生此情况时,应用 MUST NOT 在该
MediaKeys 实例上创建新的会话。
|
closed-by-application
|
该会话是因为应用显式调用会话的 close () 方法而被关闭。
|
release-acknowledged
|
该会话因为 CDM 收到许可证销毁记录的确认而被关闭。 |
hardware-context-reset
|
该会话因为 CDM 的原始硬件上下文被重置而被关闭。
发生此情况时,用户代理 MUST 允许应用在该 MediaKeys 实例上创建新会话。
Note
这可能由多种原因引起,包括设备休眠、显示器配置更改等。硬件上下文重置的具体原因取决于实现。 |
resource-evicted
|
该会话因系统需要回收资源以允许创建其它会话而被关闭。
Note
这表示应用已达到设备特定的会话资源限制。如果被关闭的会话仍然需要,应用开发者应考虑减少所需会话数或主动关闭不需要的会话等策略。 |
WebIDL[Exposed=Window, SecureContext] interface MediaKeySession
: EventTarget {
readonly attribute DOMString sessionId
;
readonly attribute unrestricted double expiration
;
readonly attribute Promise<MediaKeySessionClosedReason
> closed
;
readonly attribute MediaKeyStatusMap
keyStatuses
;
attribute EventHandler onkeystatuseschange
;
attribute EventHandler onmessage
;
Promise<undefined> generateRequest
(DOMString initDataType, BufferSource initData);
Promise<boolean> load
(DOMString sessionId);
Promise<undefined> update
(BufferSource response);
Promise<undefined> close
();
Promise<undefined> remove
();
};
sessionId
类型为 DOMString
, 只读
此对象及其关联的 key(s) 或 license(s) 的 Session ID。
expiration
类型为 unrestricted double
,
只读
会话中所有 key(s) 的 到期时间,如果不存在此类时间或 CDM 明确指示许可证永不过期,则为 NaN
,由 CDM 决定。
此值在会话生命周期内 可能(MAY) 发生变化,例如某个操作触发了窗口开始时间。
closed
类型为 Promise<MediaKeySessionClosedReason
>,
只读
当因运行 Session Closed 算法而导致该对象被视为 closed 时发出信号。此 promise 只能被 fulfilled,不会被 rejected。
keyStatuses
类型为 MediaKeyStatusMap
,
只读
对只读映射的引用,该映射将 key IDs(对会话已知)映射到关联密钥的当前状态。每个条目 必须(MUST) 具有唯一的 key ID。
映射条目及其值可能在事件循环运行时被更新。映射 不得(MUST NOT)
出现不一致或部分更新,但在两次访问之间如果事件循环运行过,映射可能发生变化。Key IDs 可能因调用 load()
或 update()
而被添加。Key IDs 可能因调用 update()
并移除对现有密钥的知识(或用新的一组密钥替换现有集合)而被移除。Key IDs 不得(MUST NOT)
因变得不可用(例如到期)而被移除。相反,此类密钥 必须(MUST) 被赋予适当的状态,例如 "expired
"。
某些旧平台上的 Key System 实现可能不暴露 key
IDs,从而无法提供完全符合规范的用户代理实现。为了最大化互操作性,暴露此类 CDMs 的用户代理实现 应(SHOULD) 如下实现此成员:每当应有非空列表(例如该对象表示的 key session 可能包含 key(s)
时),用一对条目填充映射,条目使用单字节 key ID 0
和最适合该对象聚合状态的 MediaKeyStatus
。
onkeystatuseschange
类型为 EventHandler
用于处理
事件的事件处理器。
keystatuseschange
onmessage
类型为 EventHandler
用于处理
事件的事件处理器。
message
generateRequest()
基于 initData 生成许可证请求。如果算法成功并且 promise 被解析,则类型为
"license-request
" 或
"individualization-request
"
的
将始终被排入队列。
message
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以
拒绝的 promise。
InvalidStateError
如果此对象的 uninitialized 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
将此对象的 uninitialized 值设为 false。
如果 initDataType 是空字符串,则返回一个以新建的
拒绝的 promise。
TypeError
如果 initData 是空数组,则返回一个以新建的
拒绝的 promise。
TypeError
如果此对象的 cdm implementation 值所表示的 Key
System 实现不支持将 initDataType 作为一种 Initialization Data Type,则返回一个以
拒绝的 promise。字符串比较区分大小写。
NotSupportedError
令 init data 为 initData 参数内容的副本。
令 session type 为此对象的 session type。
令 promise 为一个新的 promise。
并行运行下列步骤:
如果 init data 对于 initDataType 无效,则以新建的
拒绝
promise。
TypeError
令 sanitized init data 为经验证和清理后的 init data 版本。
用户代理 MUST 在将 Initialization Data 传递给 CDM 之前对其进行彻底验证。这包括验证字段的长度和值是否合理、验证值是否在合理范围内,以及剥离无关、不支持或未知的数据或字段。建议用户代理对 Initialization Data 进行预解析、清理和/或生成完全清理后的版本。如果 initDataType 指定的 Initialization Data 格式支持多条目,用户代理 SHOULD 移除 CDM 不需要的条目。用户代理 MUST NOT 重新排序 Initialization Data 中的条目。
如果前一步失败,则以新建的
拒绝
promise。
TypeError
如果 sanitized init data 为空,则以
拒绝 promise。
NotSupportedError
令 session id 为空字符串。
令 message 为 null。
令 message type 为 null。
令 cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。
使用 cdm 执行下列步骤:
如果 sanitized init data 不被 cdm 支持,则以
拒绝 promise。
NotSupportedError
按下列列表中与 session type 值相对应的条目执行步骤:
temporary
"
将 requested license type 设为一个临时的不可持久化的许可证。
返回的许可证不得为可持久化的,也不得要求持久化与其相关的信息。
persistent-license
"
将 requested license type 设为可持久化的许可证。
令 session id 为一个唯一的 Session ID 字符串。
如果对 session type 运行 Is persistent session
type? 算法的结果为 true
,则该 ID MUST 在此对象的 origin
范围内随时间保持唯一,包括跨越不同的 Documents 和浏览会话。
令 message 为基于按照 initDataType 解释的 sanitized init data 所生成的、用于 requested license type 的许可证请求。
cdm MUST NOT 使用任何流特定数据,包括未通过 sanitized init data 提供的 media data。
cdm SHOULD NOT 在此时存储会话数据,包括会话 ID。参见 会话存储与持久化。
令 message type 为 "license-request
"。
令 message 为在生成可基于 sanitized init data 的 requested license type 许可证请求之前需要被处理的请求。
在随后对 update()
的调用中,CDM
MUST 基于按照
initDataType 解释的 sanitized init
data 生成针对 requested license type
的许可证请求。
令 message type 表示 message 的类型,为
"license-request
"
或 "individualization-request
"。
排队一个任务(Queue a task) 来运行下列步骤:
如果前述任何步骤因资源短缺而失败,则以
拒绝 promise。
QuotaExceededError
如果前述任何步骤因其它原因失败,则以一个新的 DOMException
(其
name 为相应的 error name) 拒绝 promise。
将 sessionId
属性设为 session id。
将此对象的 callable 值设为 true。
用 undefined
解析 promise。
在 session 上运行 Queue a "message" Event 算法,提供 message type 和 message。
返回 promise。
load()
将为指定会话存储的数据加载到此对象中。
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以
拒绝的 promise。
InvalidStateError
如果此对象的 uninitialized 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
将此对象的 uninitialized 值设为 false。
如果 sessionId 是空字符串,则返回一个以新建的
拒绝的 promise。
TypeError
如果对本对象的 session type 运行 Is persistent session type? 算法的结果为
false
,则返回一个以新建的
拒绝的 promise。
TypeError
令 origin 为此对象的 origin。
令 promise 为一个新的 promise。
并行运行下列步骤:
令 sanitized session ID 为 sessionId 的经验证和/或清理后的版本。
用户代理在将 sessionId 值传递给 CDM 之前应对其进行彻底验证。至少应检查长度和值是否合理(例如,不超过几十个字符且为字母数字)。
如果前一步失败,或 sanitized session ID 为空,则以新建的
拒绝
promise。
TypeError
如果在本对象的 Document 中存在一个尚未被
closed 的 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 存储的数据。此数据 MUST NOT 包含来自其它 origin 的数据或与 origin 无关的数据。
如果存在一个在任一 Document
中尚未被 closed 且表示该
session data 的 MediaKeySession
对象,则以
拒绝 promise。
QuotaExceededError
换言之,如果在任何浏览上下文中已存在针对该 sanitized session ID 的未关闭持久会话,则不要创建新会话。
加载 session data。
如果 session data 指示了该会话的某个 expiration time,则将 expiration time 设为该到期时间。
如果需要发送消息,则执行下列步骤:
令 message 为基于 session data 生成的消息。
令 message type 为该消息的适当 MediaKeyMessageType
。
排队一个任务(Queue a task) 来运行下列步骤:
如果前述任何步骤失败,则以适当的 error name 拒绝 promise。
将 sessionId
属性设为 sanitized session ID。
将此对象的 callable 值设为 true。
如果已加载的会话包含有关任何密钥的信息(存在 known
keys),则对 session 运行 Update
Key Statuses 算法,提供每个密钥的 key ID 及相应的 MediaKeyStatus
。
如果确定某密钥的状态需要额外处理,可使用 "status-pending
"。一旦对一个或多个密钥的额外处理完成,再次运行
Update
Key Statuses 算法并提供实际状态。
在 session 上运行 Update Expiration 算法,提供 expiration time。
用 true
解析 promise。
如果 message 非 null,则在 session 上运行 Queue a "message" Event 算法,提供 message type 和 message。
返回 promise。
update()
向 CDM 提供消息(包括许可证)。
response 参数包含要提供给 CDM 的消息。其内容依赖于 Key System,且 MUST NOT 包含可执行代码。
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个以
拒绝的 promise。
InvalidStateError
如果此对象的 callable 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
如果 response 是空数组,则返回一个以新建的
拒绝的 promise。
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 以这样一种方式存储:关闭一个会话不会影响其它会话中可观察到的状态,即使这些会话包含重叠的 key ID。
当 sanitized response 包含密钥和/或相关数据时,cdm 很可能在内存中按 key ID 存储该密钥和相关数据。
会话内部的替换算法依赖于 Key System。
建议 CDM
实现支持每个 MediaKeySession
对象的标准且合理的最小密钥数目(包含标准替换算法),以及标准且合理的最小 MediaKeySession
对象数目。这使得在用户代理之间能够实现合理数量的密钥轮换算法,并可能在涉及同一元素中多路流(例如自适应流、不同音视频轨道)使用不同密钥的用例中减少播放中断的可能性。
persistent-license
"
运行下列步骤:
关闭 key session
并清除与此对象关联的 所有 存储会话数据,包括 sessionId
和 许可证销毁记录。
将 session closed 设为 true。
例如,sanitized response 可能包含将用于生成另一个
事件的信息。在这种情况下,无需验证其内容是否与 sessionType 匹配。
message
如果需要发送消息,执行下列步骤:
令 message 为该消息。
令 message type 为该消息的适当 MediaKeyMessageType
。
排队一个任务(Queue a task) 来运行下列步骤:
在此对象上以原因 "release-acknowledged
"
运行 Session Closed 算法。
运行下列步骤:
如果此对象的 CDM
对该对象已知的密钥集合变化或任何密钥的状态发生变化,则对 session 运行 Update Key
Statuses 算法,提供每个已知密钥的 key ID 及相应的
MediaKeyStatus
。
如果确定某密钥的状态需要额外处理,可使用 "status-pending
"。一旦额外处理完成,再次运行
Update Key
Statuses 算法并提供实际状态。
如果会话的 expiration time 发生变化,则在 session 上运行 Update Expiration 算法,提供新的到期时间。
如果前述任一步骤失败,则以新的 DOMException
(其
name 为相应的 error name) 拒绝
promise。
如果 message 非 null,则在 session 上运行 Queue a "message" Event 算法,提供 message type 和 message。
用 undefined
解析 promise。
返回 promise。
close()
表示应用不再需要该会话,且 CDM 应释放与会话关联的任何资源并关闭该会话。已持久化的数据不应被释放或清除。
当请求被处理完毕时,返回的 promise 将被解析;当会话关闭时,closed
属性的 promise 将被解析为 "closed-by-application
"。
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 closing or closed 值为 true,则返回一个解析为 undefined
的 promise。
如果此对象的 callable 值为 false,则返回一个以
拒绝的 promise。
InvalidStateError
令 promise 为一个新的 promise。
将此对象的 closing or closed 值设为 true。
并行运行下列步骤:
令 cdm 为由此对象的 cdm instance 值所表示的 CDM 实例。
使用 cdm 关闭与此对象关联的 key session。
关闭 key session 会导致销毁任何未被显式存储的许可证和密钥。
排队一个任务(Queue a task) 来运行下列步骤:
用 undefined
解析 promise。
在此对象上以原因 "closed-by-application
"
运行 Session
Closed 算法。
返回 promise。
remove()
移除与该会话关联的所有许可证和密钥。对于 持久会话类型,其它会话数据将在释放消息确认被 update()
处理后按每种会话类型的定义被清除。
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 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 的消息。
排队一个任务(Queue a task) 来运行下列步骤:
在 session 上运行 Update Key Statuses
算法,为会话中的所有 key ID 提供 "released
"
的 MediaKeyStatus
值。
在 session 上运行 Update Expiration 算法,提供
NaN
。
如果前述任一步骤失败,则以新的 DOMException
(其
name 为相应的 error name) 拒绝 promise。
令 message type 为 "license-release
"。
用 undefined
解析 promise。
如果 message 非 null
,则在 session 上运行 Queue a
"message" Event 算法,提供 message type 和
message。
返回 promise。
MediaKeyStatusMap
对象是一个只读映射,
将 key IDs 映射到关联密钥的当前状态。
密钥的状态与该密钥当前是否正在被使用以及与媒体数据无关。
例如,如果某个密钥具有当前无法满足的输出要求,则无论该密钥是否已被或当前被用来解密媒体数据,其状态都应相应地为 "output-downscaled
" 或
"output-restricted
"。
WebIDL[Exposed=Window, SecureContext] interface MediaKeyStatusMap
{
iterable<BufferSource,MediaKeyStatus
>;
readonly attribute unsigned long size
;
boolean has
(BufferSource keyId);
(MediaKeyStatus
or undefined) get
(BufferSource keyId);
};
size
类型为 unsigned long
,
只读
已知的 known keys 的数量。
has()
如果已知由 keyId 标识的密钥的状态,则返回 true
。
get()
返回由 keyId 标识的密钥的 MediaKeyStatus
,
如果由 keyId 标识的密钥的状态未知,则返回 undefined
。
此接口具有由 iterable
[WebIDL] 提供的
entries
、keys
、values
、
forEach
和 @@iterator
方法。
要迭代的键值对是针对所有 known keys 所形成的 key
ID 与其关联的 MediaKeyStatus
值的快照,按 key ID 排序。Key ID 的比较方式如下:对于长度为 m 的 key 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 确定该密钥当前可用于 解密。 可能当前不可用于 解密 的密钥 MUST NOT 被标示为此状态。 |
expired
|
该密钥因其 到期时间 已过而不再可用于 解密。expiration 属性表示的时间 MUST 早于当前时间。会话中的所有其他密钥 MUST 具有此状态。
|
released
|
该密钥本身不再可供 CDM 使用, 但有关该密钥的信息(例如 许可证销毁记录)仍然可用。 |
output-restricted
|
与该密钥相关的输出限制当前无法满足。使用此密钥解密的媒体数据可能会在必要时被阻止呈现以遵守输出限制。应用应避免使用会触发该密钥相关输出限制的流。 |
output-downscaled
|
与该密钥相关的输出限制当前无法满足。根据输出限制的需要,使用此密钥解密的媒体数据可能以较低质量(例如分辨率)呈现。应用应避免使用会触发该密钥相关输出限制的流。 对下采样(downscaling)的支持为 可选(OPTIONAL)。应用 SHOULD NOT 依赖下采样以在输出要求无法满足时确保播放不中断。 |
usable-in-future
|
该密钥尚未可用于 解密,因为其开始时间在将来。该密钥将在其开始时间到达时变为可用。 |
status-pending
|
该密钥的状态尚不确定,正在确定中。一旦确定,将使用实际状态进行更新。 |
internal-error
|
由于 CDM 中发生了与其它值无关的错误,该密钥当前不可用于 解密。此值对应用不可行动(non-actionable)。 |
MediaKeyMessageEvent
对象用于
事件。
message
WebIDLenum MediaKeyMessageType
{
"license-request
",
"license-renewal
",
"license-release
",
"individualization-request
"
};
MediaKeyMessageType
定义如下:
枚举说明 | |
---|---|
license-request
|
消息包含对新许可证的请求。 |
license-renewal
|
消息包含对现有许可证的续订请求。 |
license-release
|
消息包含一份 许可证销毁记录。 |
individualization-request
|
消息包含对 App-Assisted
Individualization(或重新个性化)的请求。 与其它消息一样,消息中的任何标识符 MUST 在 origin 与 profile 范围内保持区别性,且 MUST NOT 为 Distinctive Permanent Identifiers。 |
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
类型为 MediaKeyMessageType
message
类型为 ArrayBuffer
本节为非规范性内容。
事件名称 | 接口 | 触发时... |
---|---|---|
keystatuseschange
|
Event
|
会话中的密钥或其状态发生了变化。 |
message
|
MediaKeyMessageEvent
|
CDM 已为该会话生成了一条消息。 |
Queue a "message" Event 算法将一个 message 事件排入指定的 MediaKeySession
对象的事件队列。请求运行此算法时包括一个目标 MediaKeySession
对象、一个 message
type 和一个 message。
message MUST NOT 包含 Distinctive Permanent
Identifier(s),即使是以加密形式也不得包含。若 MediaKeySession
对象的 use distinctive
identifier 值为 false,则 message MUST NOT 包含 Distinctive Identifier(s),即使是以加密形式也不得包含。
运行下列步骤:
令 session 为指定的 MediaKeySession
对象。
排队一个任务(Queue
a task) 来创建一个名为
的事件,该事件不冒泡且不可取消,使用 message
MediaKeyMessageEvent
接口,\
将其 type 属性设为 message
,并将其 isTrusted 属性初始化为
true
,然后在 session 上派发该事件。
该事件接口 MediaKeyMessageEvent
具有:
messageType
=
指定的 message typemessage
= 指定的
message
Update Key Statuses 算法用于更新某个 known 密钥集合,或更新一个或多个密钥的状态,该会话为 MediaKeySession
。请求运行此算法时包括一个目标 MediaKeySession
对象以及一系列 key ID 与其对应的 MediaKeyStatus
对。
该算法总是在一个 task 中运行。
运行下列步骤:
令 session 为相关联的 MediaKeySession
对象。
令 input statuses 为由 key ID 与对应 MediaKeyStatus
对组成的序列。
令 statuses 为 session 的 keyStatuses
属性。
运行下列步骤以替换 statuses 的内容:
清空 statuses。
对 input statuses 中的每一对进行处理。
令 pair 为该对。
在 statuses 中插入一项,其键为 pair 的 key ID,值为 pair
的 MediaKeyStatus
值。
该步骤的效果是以原子方式替换 session 的 keyStatuses
属性内容,而不会使对该属性的现有引用失效。从脚本角度看,这种替换是原子的;脚本 MUST NOT 看到部分填充的序列。
排队一个任务(Queue
a task) 来 触发一个事件(fire an event),事件名为
,在
session 上触发。
keystatuseschange
排队一个任务(Queue
a task) 来在每个其 mediaKeys
属性为创建该
session 的 MediaKeys
对象的媒体元素上运行
Attempt
to Resume Playback If Necessary 算法。
Update Expiration 算法用于更新某个 expiration time,目标为某个 MediaKeySession
。请求运行此算法时包括一个目标 MediaKeySession
对象和新的到期时间,该时间可以为
NaN
。
该算法总是在一个 task 中运行。
运行下列步骤:
令 session 为相关联的 MediaKeySession
对象。
令 expiration time 为 NaN
。
如果新的到期时间不是 NaN
,则令 expiration time 为该到期时间。
将 session 的 expiration
属性设置为以 time 表示的 expiration time。
Session Closed 算法在 key session 被相应的 CDM 关闭后,更新对应的 MediaKeySession
状态。请求运行此算法时包括一个目标 MediaKeySession
对象和一个 MediaKeySessionClosedReason
。
该算法总是在一个 task 中运行。
当会话被关闭时,与之关联的许可证和密钥不再可用于解密 媒体数据。在运行此算法之后,所有 MediaKeySession
的方法将失败,且不会再为该对象排入新的事件。
CDM 可能在任何时刻关闭会话,例如当会话不再需要或系统资源丢失时。在这种情况下,Monitor for CDM State Changes 算法会检测到该变化并运行此算法。
其它会话中的密钥 MUST 不受影响,即使它们具有重叠的 key ID。
在此算法运行后,由该算法排入队列的事件的事件处理器将被执行,但不能再排入更多事件。因此,关闭会话不会导致 CDM 发送更多消息。
运行下列步骤:
令 session 为相关联的 MediaKeySession
对象。
令 promise 为 session 的 closed
属性。
如果 promise 已被解析,则中止这些步骤。
将 session 的 closing or closed 值设为 true。
在 session 上运行 Update Key Statuses 算法,传入一个空序列。
在 session 上运行 Update Expiration 算法,传入
NaN
。
用提供的原因解析 promise。
Monitor for CDM State Changes 算法执行在 CDM 状态发生各种变化时所需的步骤。
该算法总是与主事件循环并行运行。
运行下列步骤:
令 session 为 MediaKeySession
对象。
令 cdm 为由 session 的 cdm instance 值表示的 CDM 实例。
如果 cdm 有一个尚未发送的外发消息(outgoing message),则 排队一个任务(Queue a task) 来执行下列步骤:
令 message type 和 message 分别为该消息的类型与消息内容。
运行 Queue a "message" Event 算法,传入 session、message type 与 message。
如果 cdm 更改了对 session 已知的密钥集合(known keys)或更改了一个或多个密钥的状态,则 排队一个任务(Queue a task) 来执行下列步骤:
令 statuses 为一个包含键值对的列表,每项为对 session 已知的一个 key ID 与其对应的 MediaKeyStatus
值。
运行 Update Key Statuses 算法,传入 session 与 statuses。
如果 cdm 更改了 session 的 expiration time,则 排队一个任务(Queue a task) 来执行下列步骤:
令 expiration time 为 session 的新到期时间。
运行 Update Expiration 算法,传入 session 与 expiration time。
如果 cdm 已关闭 session,则 排队一个任务(Queue
a task) 来在 session 上运行 Session
Closed 算法,并提供一个适当的 MediaKeySessionClosedReason
值。
如果 cdm 因硬件上下文重置而变为不可用,则 排队一个任务(Queue
a task) 来运行 CDM Unavailable 算法,原因设为 "hardware-context-reset
"。
如果 cdm 因其它原因变为不可用,则 排队一个任务(Queue
a task) 来运行 CDM Unavailable 算法,原因设为 "internal-error
"。
方法通过拒绝返回的 promise,并抛出 简单异常
[WEBIDL] 或 DOMException
来报告错误。下列
[WEBIDL] 中定义的 简单异常 和 DOMException 名称
会在算法中使用。算法中指定的原因会列在每个名称旁边,但这些名称 MAY 也可用于其他原因。
名称 | 可能原因(非详尽) |
---|---|
TypeError
|
参数为空。 初始化数据无效。 响应格式无效。 为 " temporary "
会话提供了持久许可证。
|
NotSupportedError
|
现有 MediaKeys 对象无法移除。Key System 不被支持。 初始化数据类型不被 Key System 支持。 会话类型不被 Key System 支持。 初始化数据不被 Key System 支持。 操作不被 Key System 支持。 |
InvalidStateError
|
现有 MediaKeys 对象目前无法移除。会话已经被使用。 会话尚未初始化。 会话已关闭。 |
QuotaExceededError
|
MediaKeys 对象无法与更多 HTMLMediaElement 一起使用。针对该 sessionId 已存在一个未关闭的会话。 资源不足,无法创建新会话或许可证请求。 |
本节提供了与算法互补的会话存储与持久化概述。
除 存储与持久化 的要求外,下列要求也适用。
如果对本对象的 session type 运行 Is persistent session type? 算法结果为
false
,则用户代理和 CDM MUST NOT
在任何时候持久化与该会话相关的记录或数据。这包括许可证、密钥、许可证销毁记录,以及 Session ID。
本节剩余内容适用于 Is persistent session type? 算法返回 true
的会话类型。
CDM
SHOULD NOT 在首次调用 update()
前存储会话数据(包括 Session ID)。具体来说,CDM
SHOULD NOT 在 generateRequest()
算法期间存储会话数据。这可确保应用知晓会话的存在,并知道需要最终移除它。
与会话相关的 所有 数据在会话被清除时 MUST 被清除,例如在处理 update()
时收到 许可证销毁记录确认。参见 持久数据。
CDM
MUST 确保某个会话的数据只存在于一个未被 closed 的 MediaKeySession
对象中,无论在哪个 Document。换言之,当已经存在一个由
generateRequest()
创建且仍活动的对象,或已通过
load()
加载到另一个对象中时,针对该 sessionId 的 MediaKeySession
的 load()
MUST
失败。只有所有曾经表示该会话的对象都已 closed 时,会话才 MAY 被再次加载。
应用在使用 Is persistent session type? 算法返回 true
的类型创建会话后,SHOULD 之后通过先用 remove()
启动移除过程,然后确保移除过程(可能涉及消息交换)成功完成来删除存储的数据。CDM MAY 也会酌情移除会话,但应用 SHOULD NOT 依赖此行为。
本节规定了当支持加密媒体扩展(Encrypted Media Extensions)时,HTMLMediaElement
[HTML]
所增加和修改的内容。
以下内部值被添加到 HTMLMediaElement
:
attaching media keys,其值 SHALL 是布尔类型。
encrypted block queue,其值 SHALL 是一个等待解密的加密块队列。
decryption blocked waiting for key,其值 SHALL 是布尔类型。
playback blocked waiting for key,其值 SHALL 是布尔类型。
对 HTMLMediaElement
的行为做如下修改:
当创建一个 HTMLMediaElement
时,其
attaching media keys 值 SHALL
初始化为 false
,其 encrypted block queue 值 SHALL
为空,其 decryption blocked waiting for key 值 SHALL 初始化
为 false
,其 playback blocked waiting for key 值 SHALL
初始化为 false
。
当 当前播放位置
被更改,但不是作为正常播放的一部分沿 播放方向
前进时,
encrypted block queue 值 SHALL 为空,
decryption blocked waiting for key 值 SHALL 初始化为
false
,
playback blocked waiting for key 值 SHALL 被设为 false
。
换句话说,这些值应在加载媒体资源或进行 seek 时被重置。
除 [HTML] 中规定的标准外,
如果 playback blocked waiting for key 的值为 true
,
HTMLMediaElement
也应被视为 受阻塞的媒体元素。
当用户代理准备开始播放,并在 媒体数据中检测到可能包含加密块的迹象, 且该检测发生在 资源获取算法期间时, 用户代理 SHALL 运行 媒体数据可能包含加密块算法。
对于某些容器格式,该指示与 Initialization Data 是分开的。
该算法应在解析相关容器数据之后运行,包括运行 Initialization Data Encountered 算法,但在解码开始之前。
当用户代理在 媒体数据 中发现 Initialization Data, 且该发现发生在 资源获取算法期间, 用户代理 SHALL 运行 Initialization Data Encountered 算法。
某些容器格式可能支持加密媒体数据,但该数据不包含 Initialization Data,因此不会触发该算法。
对于在 资源获取算法期间遇到的每个加密 媒体数据块, 用户代理 SHALL 按遇到加密块的顺序运行 Encrypted Block Encountered 算法。
上述步骤为用户代理实现提供了灵活性,可以在遇到加密块后,在需要播放之前的任意时间进行解密。
当下列任一情况发生且 decryption blocked waiting for key 值为 true
时,
用户代理 SHALL 运行 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
,只读,可为 null
onencrypted
类型为 EventHandler
用于
事件的事件处理器。所有 encrypted
HTMLMediaElement
都
MUST 同时支持作为内容属性和 IDL 属性。
onwaitingforkey
类型为 EventHandler
用于
事件的事件处理器。所有 waitingforkey
HTMLMediaElement
都
MUST 同时支持作为内容属性和 IDL 属性。
setMediaKeys()
提供用于播放时解密媒体数据的 MediaKeys
。
在播放过程中清除或替换关联的 MediaKeys
对象的支持属于实现质量问题。在许多情况下会导致不良的用户体验或 promise 被拒绝。
当调用此方法时,用户代理 MUST 运行以下步骤:
如果此对象的 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
例如,某些实现可能不允许在播放期间解除关联。
如果前述步骤失败,则将 attaching media keys 设为 false,并以合适的 错误名 拒绝 promise。
如果 mediaKeys 不为 null,则运行以下步骤:
如果前述步骤失败,运行以下步骤:
将 mediaKeys
属性设为 null。
将 attaching media keys 设为 false。
以新的 DOMException
(其
name 为合适的 错误名)拒绝 promise。
排队一个任务,在该媒体元素上运行 Attempt to Resume Playback If Necessary 算法。
将 mediaKeys
属性设为 mediaKeys。
将 attaching media keys 设为 false。
用 undefined
解析 promise。
返回 promise。
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
,只读,可为
null
WebIDLdictionary MediaEncryptedEventInit
: EventInit {
DOMString initDataType
= "";
ArrayBuffer? initData
= null;
};
MediaEncryptedEventInit
成员
initDataType
类型为 DOMString
,默认值为
""
initData
类型为 ArrayBuffer
,可为
null,默认值为
null
本节为非规范性内容。
事件名称 | 接口 | 触发时... | 前置条件 |
---|---|---|---|
encrypted
|
MediaEncryptedEvent
|
用户代理在 Initialization Data 在 媒体数据 中被发现时触发。 |
元素的 readyState
等于或大于
HAVE_METADATA 。
注
元素可能正在播放或已经播放过。 |
waitingforkey
|
Event
|
播放因等待密钥而被阻塞。 |
readyState
等于或小于
HAVE_CURRENT_DATA 。
元素的 playback blocked waiting for key 值刚刚变为 true 。
|
Media Data May Contain Encrypted Blocks 算法在用户代理要求在播放媒体数据之前指定 MediaKeys
对象时暂停播放。运行此算法的请求包含一个目标 HTMLMediaElement
对象。
运行下列步骤:
令 media element 为指定的 HTMLMediaElement
对象。
如果 media element 的 mediaKeys
属性为 null,
且实现要求在解码可能被加密的 媒体数据 之前指定 MediaKeys
对象,则运行下列步骤:
当应用在调用 setMediaKeys
()
来提供 MediaKeys
对象之前就提供媒体数据时,可能会到达这些步骤。选择 CDM
可能会影响使用的管线和/或解码器,因此一些实现可能会延迟播放可能包含加密块的媒体数据,直到通过向 setMediaKeys
()
传入一个 MediaKeys
对象来指定
CDM 为止。
在 media element 上运行 Wait for Key 算法。
等待恢复播放的信号。
Initialization Data Encountered 算法为在 Initialization Data 中遇到的项排入一个
事件。运行此算法的请求包含一个目标 encrypted
HTMLMediaElement
对象。
运行下列步骤:
令 media element 为指定的 HTMLMediaElement
对象。
令 initDataType 为空字符串。
令 initData 为 null。
如果媒体数据是 CORS-same-origin 且 不是 混合内容受限,则运行下列步骤:
令 initDataType 为表示该 Initialization Data 的 Initialization Data Type 的字符串。
令 initData 为该 Initialization Data。
虽然媒体元素可能允许加载“可升级内容”(Upgradeable Content),但用户代理 MUST NOT 将来自此类媒体数据的 Initialization Data 暴露给应用程序。
排队一个任务
来创建一个名为
的事件,该事件不冒泡且不可取消,使用 encrypted
MediaEncryptedEvent
接口,将其
type 属性设为 encrypted
并将 isTrusted 初始化为
true
,然后在 media element 上派发该事件。
该事件接口 MediaEncryptedEvent
包含:
initDataType
=
initDataTypeinitData
=
initData
readyState
不会被改变,且不会中止任何算法。此事件仅提供信息。
如果媒体数据不是 CORS-same-origin
或为 混合内容,则 initData
属性将为
null。应用可以从其它来源检索 Initialization Data。
Encrypted Block Encountered 算法将一块加密的媒体数据排入解密队列,并在可能的情况下尝试解密。运行此算法的请求包含一个目标 HTMLMediaElement
对象。
运行下列步骤:
令 media element 为指定的 HTMLMediaElement
对象。
令 block 为该块加密的媒体数据。
将 block 添加到 media element 的 encrypted block queue 的末端。
如果 media element 的 decryption blocked waiting for key 值为
false
,则运行 Attempt to Decrypt 算法。
Attempt to Decrypt 算法尝试解密已排队等待解密的媒体数据。运行此算法的请求包含一个目标 HTMLMediaElement
对象。
运行下列步骤:
令 media element 为指定的 HTMLMediaElement
对象。
如果 media element 的 encrypted block queue 为空,则中止这些步骤。
如果 media element 的 mediaKeys
属性不为
null,则运行下列步骤:
令 media keys 为该属性所引用的 MediaKeys
对象。
令 cdm 为 media keys 的 cdm instance 值所表示的 CDM 实例。
如果 cdm 因任何原因不再可用,运行下列步骤:
运行 资源获取算法 的 “media data is corrupted” 步骤。
在 media keys 上以原因为硬件上下文重置或其他适当原因运行 CDM Unavailable 算法。
中止这些步骤。
如果由 media keys 创建且尚未被 closed
的至少一个 MediaKeySession
存在,则运行下列步骤:
该检查确保 cdm 已完成加载,并且是匹配密钥可用的先决条件。
令 block 为 media element 的 encrypted block queue 中的第一项。
令 block key ID 为 block 的 key ID。
key ID 通常由容器指定。
使用 cdm 执行下列步骤:
令 available keys 为由 media keys 创建的 session 中所有密钥的并集。
令 block key 为 null。
如果任何 available keys 对应于 block key ID 且可 用于解密,则令
session 为包含该密钥的 MediaKeySession
对象,并令 block key 为该密钥。
如果多个会话包含对于 block key ID 可 用于解密 的密钥,选择使用哪个会话和密钥取决于 Key System。
如果在运行前述步骤后任何 available keys 的状态发生变化,则为每个受影响的
session 排队一个任务来运行 Update Key Statuses
算法,提供该会话中所有的 key ID 及其对应的 MediaKeyStatus
值。
如果 block key 不为 null,则运行下列步骤:
使用 cdm 用 block key 解密 block。
按下列列表中首个匹配的情况执行步骤:
运行资源获取算法的 “media data is corrupted” 步骤。
如果 cdm 不再可用,则在 media
keys 上以相应原因运行 CDM
Unavailable 算法(硬件上下文重置时使用
hardware-context-reset
,否则使用
internal-error
)。
中止这些步骤。
从 media element 的 encrypted block queue 前端移除 block。
正常处理解密后的块。
换言之,对该块进行解码。
返回到本算法的开头。
并非所有解密问题(例如使用了错误的密钥)都会导致解密失败。在此类情况下,此处不会触发错误,但在解码期间可能会触发错误。
否则,表示在任何会话中都不存在该 block key ID 的密钥,则继续后续流程。
将 media element 的 decryption blocked waiting for key 值设为
true
。
当对于 block 不存在可 用于解密 的密钥时,将到达此步骤。
一旦用户代理已呈现位于该无法解密块之前的块(尽可能多,例如所有完整的视频帧),它将运行 Wait for Key 算法。
在此处不直接运行该算法是为了允许实现提前对当前播放位置之前的媒体数据进行解密和解码,而不影响可见行为。
对于基于帧的加密,当媒体元素作为资源获取算法的一部分尝试解码一个帧时,可按如下方式实现:
令 encrypted 为 false。
检测该帧是否被加密。
解码该帧。
提供该帧用于渲染。
Wait for Key 算法排入一个
事件并更新 waitingforkey
readyState
。该算法仅应在
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
挂起播放。
Attempt to Resume Playback If Necessary 算法在媒体元素因等待密钥而被阻塞且所需密钥当前 可用于解密 时恢复播放。运行此算法的请求包含一个目标 HTMLMediaElement
对象。
运行下列步骤:
令 media element 为指定的 HTMLMediaElement
对象。
如果 media element 的 playback blocked waiting for key 为
false
,则中止这些步骤。
在 media element 上运行 Attempt to Decrypt 算法。
如果用户代理能够沿播放方向推进当前播放位置:
将 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
事件通常不会(或不太可能)考虑当前密钥之外的密钥可用性。
readyState 的变化也可能导致如规范中所述触发相应的 HTMLMediaElement
事件。
本节为非规范性内容。
由 CDM 处理的媒体数据在常规方式下(例如通过 CanvasRenderingContext2D
的
drawImage
()
方法或通过 AudioContext
的
MediaElementAudioSourceNode
)可能无法通过
Web 平台 API 获取。该规范并不定义媒体数据不可用的具体条件,但如果媒体数据对这些 API 不可用,则这些 API MAY
表现得像根本不存在媒体数据一样。
在媒体呈现并非由用户代理执行的情况下(例如使用硬件媒体管线),完整的 HTML 呈现能力(例如 CSS 变换)MAY 无法使用。一个可能的限制是视频媒体MAY 被限制为仅在与窗口边缘平行且方向正常的矩形区域中显示。
本节定义实现要求——针对用户代理和 Key Systems(包括 CDM 与服务器)——这些要求可能未在算法中明确涉及。此处及整份规范中的要求适用于所有实现,无论 CDM 是独立于用户代理还是作为其一部分。
用户代理实现者 MUST 确保 CDMs 不得访问任何对使用本规范功能播放受保护媒体并非合理必要的信息、存储或系统能力。具体而言,CDM SHALL NOT:
访问网络资源(本地或远程),除非通过用户代理并且本规范明确允许。
访问存储(例如磁盘或内存),除非为使用本规范功能播放受保护媒体的合理需要。
访问硬件组件或设备,除非为使用本规范功能播放受保护媒体的合理需要。
用户代理实现者可使用各种技术来满足上述要求。例如,自行实现 CDM 的用户代理实现者可以将上述要求作为该组件的设计要求。使用第三方 CDM 的用户代理实现者可以确保其在受限环境(例如“沙箱”)中执行,从而无法访问被禁止的信息和组件。
与 CDM 之间的所有消息与通信(例如 CDM 与许可证服务器之间)MUST 通过用户代理传递。CDM MUST NOT 发起直接的带外网络请求。除 Direct Individualization 所述外,所有消息与通信 MUST 通过应用程序并使用本规范定义的 API 传递。具体而言,所有包含应用、origin 或内容特定信息,或发送到由应用指定或基于其 origin 的 URL 的通信 MUST 通过这些 API。包括所有许可证交换消息在内。
持久化数据包括由 CDM 或代表 CDM 由用户代理存储的、在 MediaKeys
对象销毁后仍然存在的所有数据。具体包括任何标识符(包括 Distinctive
Identifier(s))、许可证、密钥、key ID 或 许可证销毁记录,这些由 CDM
或代表 CDM 的用户代理存储。
可能影响应用或许可证服务器可见消息或行为的持久化数据 MUST 以 origin-特定且 浏览配置文件-特定的方式存储,且MUST NOT 泄漏到私人浏览会话或从私人浏览会话泄漏。具体但不限于,会话数据、许可证、密钥和按 origin 的标识符 MUST 按 origin 和按浏览配置文件存储。
使用持久化数据的实现 MUST 允许用户清除这些数据,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索。
用户代理 SHOULD:
将持久化数据如同其他站点数据(例如 cookies)处理。具体包括:
允许用户作为清除浏览历史功能的一部分清除持久化数据。
将持久化数据包含在“删除所有数据”的功能中。
在与其它站点数据相同的 UI 位置展示持久化数据。
允许用户按 origin 和按 浏览配置文件 清除持久化数据,尤其是在“忘记此站点”功能中一并忘记与该站点关联的 cookies、数据库等。
确保清除持久化数据的操作具有足够的原子性,以防止通过未同时清除的其他本地存储数据重新关联新旧标识符(“cookie resurrection”)。参见 incomplete clearing of data。
在界面中以有助于用户理解 incomplete clearing of data 可能性的方式呈现这些接口,并使他们能够同时删除与所有持久化功能关联的数据(包括 cookies 和 Web 存储)。
以便于用户理解 incomplete clearing of data 可能性的方式呈现用于禁用和重新启用 Key System 的界面,并使他们能够同时删除所有此类持久化存储中的数据。
允许用户针对特定 origin 或所有 origin 专门删除持久化数据。
用户代理 SHOULD 将持久化数据视为潜在敏感数据;这些信息的泄露可能严重危及用户隐私。因此,用户代理 SHOULD 确保持久化数据被安全存储,并在删除数据时及时从底层存储中清除。
暴露给应用或可被应用推断出的值(例如通过其被 CDM 使用)可能被用来识别客户端或用户,无论这些值是否被设计为标识符。本节定义了避免或至少缓解此类问题的要求。关于 Identifiers 有额外要求。
所有暴露给或可被应用推断出的 Distinctive Values MUST 在每个 origin 和每个 浏览配置文件 范围内唯一。也就是说,使用本规范定义的 API 为一个 origin 生成的值 MUST 与为任何其他 origin 生成的值不同,且在一个浏览配置文件中使用的值 MUST 与其它配置文件的值不同。此类值 MUST NOT 泄漏到私人浏览会话或从私人浏览会话泄漏。
跨 origin 与配置文件的值 MUST 是 应用不可关联的,即应用不应能将来自多个 origin 或配置文件的值关联起来以判断它们是否来自同一客户端或用户。具体而言,那些从与 origin 无关或配置文件无关的值派生 per-origin 值的实现 MUST 采用确保上述不可关联性的方法,例如使用具备非可逆性质的派生函数。
根据 Allow Persistent Data to Be Cleared 的要求,所有持久化并暴露给应用的值 MUST 可被清除,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索、观察或推断。
一旦被清除,当再次需要值时 MUST 生成新的、对应用不可关联的值。
实现中使用标识符(尤其是 Distinctive Identifier(s) 或 Distinctive Permanent Identifier(s))会带来隐私问题。本节定义了避免或至少缓解这些问题的要求。Values Exposed to the Application 中的要求同样适用于暴露给应用的标识符。
概要如下:
除 Permanent Identifiers 外,所有标识符 MUST 在每个 origin 与配置文件范围内唯一、对应用不可关联且可被清除。
所有标识符 SHOULD 在暴露到客户端外部时被加密。
Distinctive Identifiers MUST 在暴露到客户端外部时被加密、对于每个 origin 与配置文件唯一,并且可被清除。
Distinctive Permanent Identifiers MUST 在暴露到客户端外部时被加密,并且 MUST NOT 暴露给应用。
由本规范 API 使用生成的所有潜在标识符或 Distinctive Values(未在上文覆盖者)MUST 在每个 origin 与配置文件范围内唯一且可被清除,包括随机标识符、会话数据和其他 CDM 数据。
实现 SHOULD 避免使用 Distinctive Identifier(s) 或 Distinctive Permanent Identifier(s)。
例如,使用适用于一组客户端或设备的标识符或其他值,而非针对单个客户端。
实现 SHOULD 仅在必要时使用 Distinctive Identifier(s) 或 Distinctive Permanent Identifier(s),以执行与特定 CDM 实例和会话相关的策略。
例如,"temporary
" 与
"persistent-license
"
会话可能有不同需求。
支持使用 Distinctive Identifiers 或 Distinctive Permanent Identifiers 的实现 SHOULD 提供不使用它们的选项,并且应向用户暴露该选项以供选择。
当支持时,应用可通过将 distinctiveIdentifier
设为
"not-allowed
"
来选择该模式。选择此选项可能影响 requestMediaKeySystemAccess
()
的结果以及随后生成的会话所产生的许可证请求。
向用户提供选择此实现能力的访问可能使用户在保持更高隐私的同时仍能访问内容。
Distinctive Identifiers 与 Distinctive Permanent Identifiers MUST 在暴露到客户端外部时在消息交换层被加密。所有其他标识符 SHOULD 在暴露到客户端外部时在消息交换层被加密。所使用的加密 MUST 确保任何两次同一标识符密文实例仅能被持有相应解密密钥的实体 关联。
标识符可能通过以下方式暴露:
通过
事件暴露给应用。
message
在来自服务器的消息中,例如作为传递给 update
()
的消息的一部分。
作为 individualization 的一部分。
CDM MUST 验证用于加密的密钥属于其 Key System 的有效服务器。对于暴露给应用的标识符,这可通过 服务器证书 实现。
服务器 MUST NOT 将 Distinctive Identifier 暴露给除发送该标识符的 CDM 之外的任何实体。
这意味着:
使用设备特定或用户特定密钥进行的每个签名 MUST 即使对相同明文也应不同。
与设备或用户特定密钥相关的标识符、密钥或证书 MUST 为许可证或 individualization 服务器加密。
来自许可证服务器发送到 CDM 的消息 MUST NOT 在加密信封外部暴露接收者唯一的标识符(例如预期解密密钥的 ID)。
除 Distinctive Permanent Identifiers 外,所有标识符 MUST 在每个 origin 与每个 浏览配置文件 范围内唯一。参见 8.4.1 Use Per-Origin Per-Profile Values。
这包括但不限于 Distinctive Identifiers。
Distinctive Permanent Identifiers MUST NOT 暴露给应用或 origin。
所有暴露给应用的标识符,包括 Distinctive Identifiers,即使为加密形式,MUST 对应用保持 不可关联,跨 origins、浏览配置文件 和清除标识符的情形均应如此。
对于所有此类标识符,一个或多个应用(包括相关的许可证或其它服务器)MUST NOT 能够实现对此类值的关联或匹配。
根据 Allow Persistent Data to Be Cleared 的要求,除 Distinctive Permanent Identifiers 外,所有潜在标识符或 Distinctive Values MUST 可被清除,使其在外部(例如通过本规范定义的 API)和客户端设备上均不可再检索、观察或推断。
使用 Distinctive Identifiers 的实现 MUST 允许用户清除这些 Distinctive Identifier(s)。使用 Distinctive Permanent Identifier(s) 的实现 MUST 允许用户清除与这些 Distinctive Permanent Identifier(s) 相关联的值。
一旦清除,当再次需要诸如 Distinctive Identifiers 等值时,必须生成新的不可被应用关联的值。
标识符,尤其是 Distinctive Identifiers,有时通过称为 individualization 或 provisioning 的流程生成或获取。由此产生的标识符 MUST 满足 non-associable by applications 要求,并且对它们的 使用 MUST 仅在来自单一配置文件的单一 origin的范围内暴露。该过程 MAY 被重复执行,例如在标识符被 清除 之后。
该过程 MUST 由 用户代理直接执行 或 通过应用执行。两种 individualization 类型的机制、流程和限制不同,下面的各节对其作了说明。采用哪种方法取决于 CDM 的实现以及对本规范要求的应用,尤其是下文所述的要求。
distinctiveIdentifier
控制是否可以对 Distinctive Identifiers 以及 Distinctive Permanent
Identifiers 进行包括 individualization 在内的使用。具体地,只有当用于创建 MediaKeys
对象的 distinctiveIdentifier
成员的值为 "required
" 时,才允许使用这些标识符。
Direct Individualization 在 CDM 与一个与 origin 和应用无关的服务器之间进行。尽管该服务器与 origin 无关,individualization 的结果仍允许 CDM 根据本规范的其他要求提供 origin 特定的标识符。该过程 MUST 由用户代理执行,且 MUST NOT 使用本规范中定义的 API。
例如,该过程可能通过与用户代理或 CDM 厂商托管的预定服务器通信来初始化客户端设备和/或为 单个浏览配置文件获取一个 可清除的 per-origin 标识符,可能使用客户端设备的 Distinctive Permanent Identifier(s) 或其他 Permanent Identifier(s)。
对于此类 individualization,所有消息交换:
MUST 由用户代理处理并通过用户代理的网络栈执行。
MUST NOT 由 CDM 直接执行。
MUST NOT 通过本规范定义的 API 传递给或经由应用程序。
MUST 发送到独立于任何 origin 和应用的 URL。
MUST 对所有 Distinctive Identifiers 和 Distinctive Permanent Identifiers 进行加密。
MUST 使用 TLS。
实现 MUST NOT 将 origin(s)、origin 或应用特定信息,或可与 origin(s) associable 的值暴露给集中式服务器(即便是以加密形式),因为这可能会创建用户或设备访问过的所有 origin 的集中记录。
App-Assisted Individualization 在 CDM 与应用之间进行(可包括应用选择的服务器),并产生一个 per-origin 标识符。该过程 MUST 通过本规范定义的 API 执行,且 MUST NOT 涉及其它通信方式。与所有通过这些 API 的用法一样,该过程 MAY 使用一个或多个 Distinctive Identifier(s),但 MUST NOT 使用 Distinctive Permanent Identifier(s) 或 非 origin 特定的值,即便是以加密形式。若该过程 使用了一个或多个 Distinctive Identifier(s),则生成的标识符也将按定义成为 Distinctive Identifier。
对于此类 individualization,所有消息交换:
MUST 通过本规范定义的 API 传递给或经由应用程序。
SHALL 对所有相关的
事件 使用消息类型 "message
individualization-request
"。
MUST NOT 由用户代理执行。
MUST NOT 由 CDM 直接执行。
MUST NOT 包含或以其它方式 使用 Distinctive Permanent Identifier(s)。
MUST NOT 包含非 origin 特定的每客户端信息。
MUST 遵守 identifier requirements。
这包括仅使用 在 origin 与配置文件范围内唯一、可清除 的值,并按需对其 进行加密。
MUST NOT 向 CDM 提供可执行代码。
当在流程中使用可 associable 的值,包括 Distinctive Identifier(s) 时,实施者 MUST NOT 将 origin(s)、origin 或应用特定信息,或可与 origin(s) associable 的值暴露给集中服务器(即便是以加密形式),因为这同样可能创建用户或设备访问过的所有 origin 的集中记录。
经过适当的防护,此类 individualization 可以比 Direct Individualization 提供更好的隐私,尽管仍不及不使用 Distinctive Identifier(s) 的模型。为保持此设计的隐私优势并避免引入其他隐私问题,此类实现及支持它们的应用 SHOULD 避免将 individualization 消息延后或转发到由应用作者不控制的集中服务器或其他服务器。
实现 MUST 在每个 MediaKeySession
对象中支持多个密钥。
多个密钥如何被支持属于实现细节,但这一点 MUST 对应用和本规范定义的 API 透明。
实现 MUST 支持在播放期间在密钥之间无缝切换。这包括同一 MediaKeySession
中的密钥以及位于不同 MediaKeySession
对象中的密钥。
实现 SHOULD 允许用其支持的任何 Initialization Data Type 生成的许可证被用于任何内容类型。
否则,requestMediaKeySystemAccess
()
算法可能例如会因为某个 MediaKeySystemConfiguration
中的某个 initDataTypes
与某个 videoCapabilities
不兼容而拒绝该配置。
对于任何可能出现在受支持容器中的受支持的 Initialization Data Type,用户代理 MUST 支持从每种此类受支持的容器中 提取 该类型的 Initialization Data。
换言之,表明支持某个 Initialization Data Type 意味着既支持 CDM 生成许可证请求,也(对于容器特定类型)支持用户代理从容器中提取该类型的数据。这并不意味着实现必须能够从任意受支持的内容类型中解析任意受支持的 Initialization Data。
本节定义了本规范实现所支持的内容(media resource)的属性。
媒体容器 MUST NOT 被加密。本规范依赖于用户代理在无需解密任何媒体数据的情况下解析媒体容器的能力。这包括 Encrypted Block Encountered 和 Initialization Data Encountered
算法,以及支持标准的 HTMLMediaElement
功能,例如
seeking。
Media resources(包括所有轨道)MUST 按照允许在提供密钥时以完全规范且兼容的方式解密内容的容器特定 “common encryption” 规范进行加密与打包。
Encrypted Media Extensions Stream Format Registry [EME-STREAM-REGISTRY] 提供了此类流格式的参考。
带内支持内容(例如字幕、描述音轨和转录)SHOULD NOT 被加密。
对这些轨道进行解密——尤其是要将其提供回用户代理——通常不被实现支持。因此,对这些轨道加密将阻止它们在用户代理实现中广泛用于无障碍功能。
为确保可访问性信息以可用的形式提供,对于选择支持加密的带内支持内容的实现:a) CDM MUST 将解密后的数据提供给用户代理,且 b) 用户代理 MUST 以与等效未加密支持内容相同的方式对其进行处理,例如将其作为 timed text tracks 暴露 [HTML]。
所有用户代理 MUST 支持本节中描述的通用 密钥系统。
这确保了在所有用户代理中都有一个通用的基线功能级别,包括那些完全开源的用户代理。因此,仅需要基本解密的内容提供商可以构建简单的应用程序,在所有平台上工作,而无需与任何内容保护提供商合作。
"org.w3.clearkey"
密钥系统 使用明文(未加密)的密钥来解密源内容。不需要额外的客户端侧内容保护。该 密钥系统 在下文描述。
下文描述了 Clear Key 如何支持与 密钥系统 相关的特定功能:
encryptionScheme
:
实现 MUST 支持
方案,并 MAY 支持其他方案。
"cenc"
robustness
:
仅支持空字符串。
distinctiveIdentifier
:
不支持 "required
"。
persistentState
:
不应为 "required
"
,除非应用打算创建非 "temporary
"
会话(如果支持)。
"persistent-license
"
类型的 MediaKeySessionType
:
实现 MAY 支持该类型。
setServerCertificate
()
方法:不支持。
getStatusForPolicy
()
方法:实现应始终将 promise 解析为 "usable
"。
setMediaKeys
()
方法:实现 MAY 支持将 MediaKeys
对象关联到多个 HTMLMediaElement
。
下文描述了 Clear Key 如何实现与 密钥系统 相关的特定行为:
在 generateRequest
()
算法中:
生成的 message 是一个 JSON 对象,按 许可请求格式 所述以 UTF-8 编码。
请求通过从 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
属性将被设置为空列表。
初始化数据:实现 MAY
支持注册的初始化数据类型的任意组合 [EME-INITDATA-REGISTRY]。实现
SHOULD 支持
"keyids"
类型 [EME-INITDATA-KEYIDS]
及其他适合用户代理支持的内容类型的类型。
本节描述通过
事件的 message
属性提供给应用的许可请求的格式。
message
该格式是一个 JSON 对象,包含以下成员:
MediaKeySessionType
。
当包含在 message
属性的 ArrayBuffer 中,作为 MediaKeyMessageEvent
对象的一部分时,JSON 字符串按
Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextDecoder
接口将
ArrayBuffer 的内容解码为 JSON 字符串。
本节为非规范性内容。
下例是针对两个密钥 ID 的临时许可的许可请求。(换行仅为可读性)
{
"kids": [
"LwVHf8JLtPrv2GUXFW2v_A",
"0DdtU9od-Bh5L3xbv0Xf_A"
],
"type": "temporary"
}
本节描述作为 update
()
方法的
response 参数提供的许可的格式。
该格式是一个包含对称密钥表示的 JSON Web Key (JWK) 集,用于解密,定义见 JSON Web Key (JWK) 规范 [RFC7517]。
对于集合中的每个 JWK,其参数值如下:
JSON 对象 MAY 有一个可选的 "type" 成员值,该值 MUST 是 MediaKeySessionType
值之一。
如果未指定,则使用默认值 "temporary
"。update
()
算法将此值与
sessionType 进行比较。
当作为 ArrayBuffer response 参数传递给 update
()
方法时,JSON 字符串
MUST 按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextEncoder
接口对 JSON
字符串进行编码。
本节为非规范性内容。
下例是一个包含单个对称密钥的 JWK 集。(换行仅为可读性)
{
"keys": [{
"kty": "oct",
"k": "tQ0bJVWb6b0KPL6KtZIy_A",
"kid": "LwVHf8JLtPrv2GUXFW2v_A"
}],
"type": "temporary"
}
本节描述通过
事件的 message
属性提供的许可释放消息的格式。
message
该格式是一个 JSON 对象。对于类型为
"persistent-license
"
的会话,该对象应包含以下成员:
当包含在 message
属性的 ArrayBuffer 中,作为 MediaKeyMessageEvent
对象的一部分时,JSON 字符串按
Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextDecoder
接口将
ArrayBuffer 的内容解码为 JSON 字符串。
本节为非规范性内容。
下例是包含两个密钥的 "persistent-license
"
会话的许可释放示例。(换行仅为可读性)
{
"kids": [ "LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A" ]
}
本节描述作为 update
()
方法的
response 参数提供的许可释放确认的格式。
该格式是一个 JSON 对象,包含以下成员:
当作为 ArrayBuffer response 参数传递给 update
()
方法时,JSON 字符串
MUST 按 Encoding 规范 [ENCODING] 中规定的方式以 UTF-8 编码。应用 MAY 使用 TextEncoder
接口对 JSON
字符串进行编码。
本节为非规范性内容。
下例是针对两个密钥 ID 的临时许可的许可请求。(换行仅为可读性)
{
"kids": [
"LwVHf8JLtPrv2GUXFW2v_A",
"0DdtU9od-Bh5L3xbv0Xf_A"
]
}
本节为非规范性内容。
有关 base64url 及其使用的更多信息,请参阅 RFC7515 中的 "Base64url Encoding" 术语定义和 "Notes on implementing base64url encoding without padding"。具体来说,不使用 '=' 填充,并且字符 '-' 和 '_' MUST 分别替代 '+' 和 '/'。
User Agent 和 Key System 实现必须将
media
data、Initialization Data、传入 update
()
的数据、许可证、
密钥数据,以及应用提供的所有其他数据视为不受信任的内容和潜在攻击向量。它们应当使用适当的防护措施来减轻任何相关的威胁,并在解析、解密等处理此类数据时谨慎。User Agents 应该
在将数据传递给 CDM 之前验证数据。
如果 CDM 并未在与例如 DOM 相同的(沙箱化的)上下文中运行,则这种验证尤为重要。
实现不得向应用返回会影响程序控制流的活动内容或被动内容。
例如,暴露可能来自媒体数据的信息(例如传入 Initialization Data 到
generateRequest
()
的情况)中的 URL 或其他信息并不安全。应用必须确定要使用的 URL。messageType
属性可被应用用于在适用时从一组
URL 中进行选择。
User Agents 负有为用户提供安全浏览网络方式的责任。该责任适用于 User Agents 使用的任何功能,包括第三方功能。User agent 实现者必须从 Key System 实现者处获取足够的信息,以使其能够正确评估与集成该 Key System 的安全影响。User agent 实现者 必须确保 CDM 实现提供和/或支持足够的控制,以便 user agent 能够为用户提供安全保障。User agent 实现者 必须 确保 CDM 实现能够并且会在发现安全脆弱性时快速且积极地进行更新。
利用未完全沙箱化和/或使用平台特性的 CDM 实现,攻击者可能访问操作系统或平台特性、提升权限(例如以 system 或 root 身份运行),以及/或访问驱动、内核、固件、硬件等。这类特性、软件和硬件可能没有针对敌对软件或基于 Web 的攻击进行健壮性编写,并且可能不会像 user agent 那样频繁获得安全修复。CDM 实现中缺乏、稀少或缓慢的安全修复更新会增加风险。此类 CDM 实现及暴露它们的 UA 在所有安全领域都必须特别谨慎,包括解析 所有数据。
当使用作为客户端操作系统、平台和/或硬件的一部分或由其提供的 CDM 或底层机制时,user agents 应格外谨慎。
如果 user agent 选择支持无法被充分沙箱化或无法以其他方式加固的 Key System 实现,则 user agent 应该在加载或调用该实现之前确保用户已充分获知并/或给予明确同意。
向未验证的来源授予权限等同于在存在网络攻击者的情况下向任何来源授予该权限。参见 持久同意的滥用。
本节为非规范性。
潜在的网络攻击及其影响包括:
DNS 欺骗攻击:无法保证声称属于某一域名(origin)的主机确实来自该域名。
被动网络攻击:无法保证客户端与服务器之间传输的数据(包括 Distinctive Identifiers 和 Distinctive Permanent Identifiers)不会被其他实体查看。参见 用户追踪。
主动网络攻击:无法保证页面不会被注入额外脚本或 iframe(无论页面是否使用本规范中定义的 API)。其后果包括:
可以将对本规范中定义的 API 的调用注入到任何页面。
来自本规范中为合法目的使用这些 API 的页面的调用可能被操纵,包括修改请求的功能、修改或添加调用,以及修改或注入数据。另见 输入数据攻击和脆弱性。
在客户端与服务器之间传输的数据(包括 Distinctive Identifiers 和 Distinctive Permanent Identifiers)可能会被其他实体查看和/或修改。参见 用户追踪。
持久同意的滥用:无法保证请求使用本规范中定义的 API 的主机就是用户先前提供同意的主机。其后果是,在存在网络攻击者的情况下,向未验证来源授予权限等同于向任何来源授予权限。
以下技术可以缓解这些风险:
使用 TLS 的应用可以确保只有用户、代表用户工作的软件,以及使用 TLS 并具有识别为同一域的证书的其他页面,能够与该应用交互。此外,结合安全源(origin)的基于 origin 的权限,可确保授予给应用的权限不能被网络攻击者滥用。
本规范中定义的 API 仅在安全上下文中暴露。另见 安全源和传输。
User agents 必须正确处理混合内容 [MIXED-CONTENT],包括阻止“可阻止内容”[MIXED-CONTENT],以避免暴露于不安全内容。这种暴露可能会破坏其他缓解措施,例如 TLS 的使用。
User agents 可以选择阻止所有混合内容,包括“可选择阻止内容”[MIXED-CONTENT],以通过防止将不受信任的媒体数据传递给 CDM 来进一步提高安全性(参见 CDM 攻击和脆弱性)。
User agents 应该在允许可能带来比其他 user agent 功能(例如 DOM 内容)更大安全风险的 Key System 被某个 origin 访问之前,确保用户已充分获知并/或给予明确同意。
这些机制必须按 origin 进行,以避免合法用途导致随后被恶意访问滥用,并且必须按 browsing profile 进行。
本节为非规范性。
恶意页面可能将合法应用托管在 iframe 中,以尝试隐藏攻击或欺骗用户关于来源的信息,例如使其看起来来自合法内容提供者。这对那些会告知用户或要求同意的实现尤为相关,例如出于安全和/或隐私原因。除了网络攻击之外,攻击者还可能通过在 iframe
中托管合法使用来尝试利用本规范中定义的
API。通过让合法应用执行这些操作,攻击者可以重用已授予的权限(或白名单),并/或看起来像是合法的请求或使用。
对于那些告知用户或要求同意的 user agents,包括出于安全和/或隐私原因的情况,应该 将 UI 和同意的持久化基于顶层文档的 origin 与使用本规范中定义 API 的 origin 的组合。 这可确保用户被告知发起请求的主文档,并且为一种(合法)组合持久化权限不会无意中允许恶意使用不被发现。
作者应当防止其他实体在 iframe
中托管其应用。必须出于合法应用设计原因需要支持被托管的应用,不应允许托管文档向 CDM
提供任何数据 —— 无论是通过本规范中定义的 API 还是作为媒体数据 —— 并且不应允许托管的框架调用本规范中定义的 API。
本节为非规范性。
例如,不同作者在同一主机名上托管内容(如用户在 geocities.com
上托管内容)时共享同一 origin。User agents
不提供按路径名限制 API 访问的功能。
在共享主机上使用本规范中定义的 API 会破坏 user agents 实现的基于 origin 的安全与隐私缓解措施。例如,按 origin 的 Distinctive Identifiers 会被同一主机名下的所有作者共享,且持久化的数据可能被主机上的任何作者访问和操纵。如果例如修改或删除此类数据可能会抹去用户对特定内容的访问权,则后者尤为重要。
即使 user agents 提供了路径限制功能,通常的 DOM 脚本安全模型也会使绕过该保护并从任何路径访问数据变得非常容易。
因此,建议共享主机上的作者避免使用本规范中定义的 API,因为这样做会破坏 user agents 中基于 origin 的安全与隐私缓解措施。
在用户设备上存在或使用 Key System(s) 会引发许多隐私问题,主要分为两类: (a) 可能由 EME 接口本身或在 Key System 消息中披露的与用户相关的信息;以及 (b) 可能在用户设备上持久存储的与用户相关的信息。
User Agents 必须 承担为用户提供对其隐私的充分控制的责任。由于 User Agents 可能与第三方的 CDM 实现集成,CDM 实现者 必须 向 user agent 实现者提供足够的信息和控制,以便他们能够实施适当的技术,确保用户能控制其隐私,包括但不限于下面描述的技术。
关于 EME 和 Key Systems 披露的信息的关切分为两类: (a) 关于非特指的信息的担忧,尽管这些信息并不具体但仍可能促成对用户代理或设备的指纹识别;以及 (b) 可能被直接用于 用户追踪 的与用户相关的信息。
恶意应用可能通过检测或枚举所支持的 Key Systems 列表及相关信息来为用户或用户代理创建指纹。如果没有提供适当的 origin 保护,这可能包括检测已访问过的站点以及为这些站点存储的信息。尤其是,Key Systems 必须 不在 origins 之间共享密钥或其他数据。
本节为非规范性。
CDMs,尤其是那些在 user agent 之外实现的,可能没有 Web 平台的相同基本隔离措施。采取措施以避免信息泄露尤为重要,特别是跨 origin 的泄露。这包括内存中的数据和持久存储的数据。如果不这样做,可能会导致与私密浏览会话之间的信息泄露、跨 browsing profiles(包括跨操作系统用户帐户)之间的泄露,甚至在不同浏览器或应用之间发生泄露。
为避免此类问题,user agent 和 CDM 实现 必须 确保:
会话数据不得与未与创建该会话的 MediaKeys
对象关联的媒体元素共享。此要求意味着,例如,某个会话的密钥 必须 不得用于解密由 mediaKeys
属性不是该 MediaKeys
对象加载的内容。
如果适用,持久化的会话数据应按 origin 存储。
仅可加载由请求的 origin 存储的数据。
不得从 CDM 中提取、推导或推断出本规范未明确描述的,或在未经用户许可情况下通过其他 Web 平台 API 无法获得的信息。此要求适用于任何暴露给客户端设备外部或应用的信息,包括例如在 CDM 消息中所携带的信息。
该要求涵盖的信息类型包括但不限于:
位置,包括地理位置
凭据或标识符(Distinctive Identifiers 除外)
操作系统帐户名及其他可能的 PII
本地目录路径,可能包含类似信息
本地网络详情(例如设备的本地 IP 地址)
本地设备,包括但不限于蓝牙、USB 和用户媒体
与本规范定义的 API 无关或未由其存储的用户状态
本节为非规范性。
第三方主机(或任何能够将内容分发到多个站点的实体,例如广告商)可能使用 Distinctive Identifier 或持久化数据(包括许可证、密钥、key ID,或由或代表 CDM 存储的记录)来在多个会话间跟踪用户(包括跨 origins 和 browsing profiles),从而构建用户活动或兴趣的档案。这样的追踪会破坏 Web 平台提供的隐私保护,例如可能使高度定向广告成为可能。结合能够识别用户真实身份的网站(例如需要身份验证凭证的内容提供者或电子商务站点),这可能使压迫性组织比在纯匿名 Web 环境中更精确地定位个人。
通过本规范的实现可获得的用户或客户端特定信息包括:
已访问的 origins(通过存储或内存中的数据、权限等)
已查看的内容(通过存储或内存中的许可证、密钥、key ID、records of license destruction 等)
本规范带来特别关注的原因在于,此类信息通常存储在 user agent 之外(且不与 browsing profile 存储)——通常在 CDM 中。
由于许可证的内容和 records of license destruction 是 Key System-特定的,并且 key ID 可能包含任意值,这些数据项可能被滥用来存储可识别用户的信息。
Key Systems 可能为设备或设备用户访问或创建持久或半持久的标识符。在某些情况下,这些标识符可能以安全方式绑定到特定设备。如果这些标识符出现在 Key System 消息中,则设备和/或用户可能会被跟踪。如果不采取下述缓解措施,这可能包括随时间跟踪用户/设备以及关联同一设备的多个用户。
需要注意的是,此类标识符,尤其是那些不可清除、非 origin 特定或被视为永久的(permanent),其跟踪影响超过现有技术(例如 cookies [COOKIES] 或嵌入 URL 的会话标识符)。
若未缓解,此类跟踪可能有三种形式,取决于 Key System 的设计:
在所有情况下,预期这些标识符可被完全支持该 Key System 的站点和/或服务器获得(因此能够解释 Key System 消息),使得这些站点能够进行追踪。
如果 Key Systems 所暴露的标识符并非 origin 特定,则两个完全支持该 Key System 的站点和/或服务器可能串通以跟踪用户。
如果 Key System 消息包含从用户标识符派生出的信息并以一致的方式呈现,例如对特定内容项的初始 Key System 消息的一部分随时间不变且依赖于用户标识符,则任何应用都可利用该信息随时间跟踪设备或用户。
此外,如果某个 Key System 允许跨 origins 存储并重用密钥或其他数据,那么两个 origin 可能通过记录它们访问某个公共密钥的能力而串通追踪唯一用户。
最后,如果任何用于用户控制 Key Systems 的用户界面将数据与 HTTP 会话 cookie [COOKIES] 或持久存储中的数据分开显示,则用户很可能在修改站点授权或删除数据时只在其中一处进行操作而不是同时在所有位置进行。这将使站点能够将各种功能互为备份,从而破坏用户保护其隐私的尝试。
除了站点和其他第三方可能跟踪用户的潜在风险外,user agent 实现者、CDM 供应商或设备供应商也可能构建用户活动或兴趣的档案,例如用户访问的使用本规范定义 API 的站点。这种跟踪会破坏 Web 平台其余部分所提供的隐私保护,尤其是与 origin 隔离相关的保护。
例如 Distinctive Identifiers 等标识符,可能从由 CDM 供应商运营或提供的服务器获取,例如通过 individualization 过程。该过程可能包括向服务器提供客户端标识符(包括 Distinctive Permanent Identifier(s))。为生成 per-origin 标识符,可能还会提供表示 origin 的值。
在这种实现中,CDM 供应商可能能够跟踪用户的活动,例如访问过的 origin 数量或需要新标识符的次数。如果在标识符请求中提供了 origin 或与 origin associable 的值,则 CDM 供应商可以跟踪用户访问的网站或设备用户。
下节描述了可以在不征得用户同意的情况下缓解跟踪风险的技术。
Key System 的实现 应该 尽可能避免 using Distinctive Identifiers and Distinctive Permanent Identifiers,仅在它们能显著增强实现的健壮性时才使用。参见 Limit or Avoid use of Distinctive Identifiers and Permanent Identifiers。
实现 不得 向应用或 origin 暴露 Distinctive Permanent Identifiers。
Distinctive Identifiers 出现在 Key System 消息中时,必须 将其与时间戳或随机数一起加密,从而使得 Key System 消息始终不同。这样可以防止使用 Key System 消息进行跟踪,除非由完全支持该 Key System 的服务器协作。参见 Encrypt Identifiers。
User agents 应该 以与 HTTP 会话 cookie [COOKIES] 强相关的方式向用户展示 Distinctive Identifiers 及 Key Systems 存储的数据的存在情况,包括将其纳入“删除所有数据”操作,并在相同的 UI 位置展示。这可能促使用户对这类标识符持更谨慎的态度。User agents 应该 帮助用户避免 Incomplete Clearing of Data。
不要向个别化(individualization)服务器或与该 origin 无关的其他实体提供 origin 或与 origin associable 的值。如果实现使用了个别化流程,请遵循 Individualization 部分中的要求和建议。
对于向应用暴露的所有 Distinctive Values,实现 必须 为每个 origin 和每个 browsing profile 使用不同的 non-associable by applications 值。参见 8.4.1 Use Per-Origin Per-Profile Values。
对于那些 使用 Distinctive Identifier(s) 的实现,这一点尤为重要。参见 8.5.3 Use Per-Origin Per-Profile Identifiers。
任何可能以应用或许可证服务器可见的方式影响消息或行为的由 CDM 使用的数据 必须 按 origin 和 browsing profile 分区,并且 不得 泄露到私人浏览会话或从私人浏览会话泄露。这包括内存中的数据和持久化的数据。具体但不限于,会话数据、许可证、密钥和按 origin 的标识符 必须 按 origin 和按 browsing profile 分区。参见 8.3.1 Use origin-specific and browsing profile-specific Key System storage 和 8.4.1 Use Per-Origin Per-Profile Values。
User agents 必须 提供用户清除任何由 Key Systems 维护的持久数据(包括 Distinctive Identifiers)的能力。参见 Allow Persistent Data to Be Cleared。
User agents 可以(例如以用户可配置的方式)在一段时间后自动删除 Distinctive Identifiers 和/或其他 Key System 数据。
例如,用户代理可以配置为将此类数据视为仅限会话的存储,在用户关闭所有可访问该数据的浏览上下文后删除该数据。
这可以限制站点跟踪用户的能力,因为站点则仅能在用户对站点进行身份验证(例如购买或登录服务)时跨多个会话跟踪用户。
但是,如果用户不了解此类过期的含义,这也可能危及用户对内容(尤其是已购买或租赁内容)的访问权。
User agents 可以 限制对 Key
Systems 和/或功能的访问,仅允许来自顶层文档 origin
的脚本访问。例如,requestMediaKeySystemAccess
()
可能会拒绝来自在 iframe
中运行的其他 origin 页面对某些配置的请求。
User agents 必须 确保在 using Distinctive Identifier(s) or Distinctive Permanent Identifier(s) 之前,用户已被充分告知并/或已给予明确同意。
此类机制 必须 针对每个 origin 进行,以避免合法用途使后续的恶意访问成为可能,并且 必须 针对每个 browsing profile 进行。
将本规范中定义的 API 限制在安全上下文中,可以确保 网络攻击者 无法利用授予给未验证 origin 的权限。参见 abuse of persisted consent。
User Agents 应该 为用户提供一个全局控制,允许用户决定是否启用某个 Key System,和/或是否启用 Key System 的 use of Distinctive Identifier(s) or Distinctive Permanent Identifier(s)(如果该 Key System 支持)。User agents 应该 帮助用户避免 Incomplete Clearing of Data。
User agents 可以 要求用户在站点使用某个 Key System(和/或其某些功能)之前明确授权访问。User agents 应该 允许用户临时或永久撤销该授权。
User agents 可以 允许用户共享 origins 和/或 Key Systems 的黑名单。这样社区可以共同保护其隐私。
虽然这些建议可以防止本规范中定义的 API 被用于明显的用户跟踪,但并不能完全阻止跟踪。在单一 origin 内,站点仍可以在会话期间跟踪用户,然后将所有这些信息与站点获得的任何标识信息(姓名、信用卡号、地址)一并传递给第三方。如果第三方与多个站点合作获取此类信息,并且标识符不是 按 origin 和 profile 唯一,那么仍然可以创建用户档案。
This section is non-normative.
Key Systems 可能在用户设备上存储信息,或者 user agents 可能代 Key Systems 存储信息。这样可能会向同一设备的其他用户泄露有关某用户的信息,包括可能使用过特定 origins 的站点(即访问过的站点)或甚至用 Key System 解密过的内容。
如果一个 origin 存储的信息会影响另一个 origin 的 Key System 的操作,那么用户在某个站点访问过的站点或查看过的内容可能会被另一个(可能是恶意的)站点透露出来。
如果为客户端设备上的某个 browsing profile 存储的信息会影响其他 browsing profiles 或浏览器上 Key System 的行为,那么一个配置文件中访问的站点或查看的内容可能会被另一个 browsing profile 揭示或与之相关联,甚至可能涉及不同的操作系统用户账户或不同的浏览器。
缓解这些关切的要求在 8.3 Persistent Data 中有定义。
This section is non-normative.
用户通过清除 Distinctive Identifiers 和已存储的数据和/或禁用某个 Key System 来保护其隐私的尝试,可能会在未同时清除和/或禁用所有相关数据、功能、cookies [COOKIES] 及其他站点数据时被破坏。例如:
如果用户清除 cookies 或其他持久存储,但没有同时清除 Distinctive Identifiers 和 Key Systems 存储的数据,站点可以通过将这些功能互为冗余备份来挫败用户的清除行为。
如果用户清除 Distinctive Identifiers,但没有同时清除由 Key Systems 存储的数据(包括持久会话),以及 cookies 和其他持久存储,站点可以利用剩余的数据将旧标识与新标识关联,从而挫败清除。
如果用户禁用某个 Key System(尤其是针对特定 origin),但没有同时清除 cookies 或其他持久存储,站点可以利用剩余的功能来挫败这些尝试。
如果用户禁用某个 Key System,之后又决定重新启用该 Key System,但没有同时清除 cookies、其他持久存储、Distinctive Identifiers 以及 Key Systems 存储的数据,则站点可能能够将禁用前的数据与重新启用后的数据和行为相关联。
缓解这些关切的建议在 8.3 Persistent Data 中有定义。
User agents 可能支持一种旨在保持用户匿名和/或确保不在客户端保留浏览活动记录的操作模式(例如私密浏览)。对于使用这些模式的用户,上述各节讨论的隐私问题可能尤为令人关注。
支持此类模式的 user agent 实现者 应该 仔细考虑在这些模式下是否应禁用对 Key Systems
的访问。例如,这些模式 可以 禁止创建支持或使用 MediaKeySystemAccess
对象,这些对象支持或使用 persistentState
或
distinctiveIdentifier
(无论是作为
CDM
实现的一部分,还是因为应用将其标记为 "required
")。如果实现不禁止此类创建,则在允许使用之前,应该 向用户告知其对这些模式预期隐私属性的影响和潜在后果。
本规范中定义的 API 仅在安全的 origins 上支持,从而保护前述各节中讨论的信息。标识符也按 Encrypt Identifiers 中规定的方式进行加密。
应用(包括其使用的服务器)应该 对所有包含或涉及 CDM 的数据或消息的流量使用安全传输,这包括但不限于来自
事件传出的所有数据,以及传递给 message
update
()
的数据。
所有 user agents 必须 正确处理混合内容 [MIXED-CONTENT],以避免在用户代理或应用希望强制执行安全 origin 和传输时暴露于不安全的内容或传输。
除标记为非规范性的各节之外,本规范中的所有撰写指南、图表、示例和注释均为非规范性。规范性要求适用于本规范中的其他所有内容。
文档中以全大写出现的关键字 MAY、MUST、MUST NOT、OPTIONAL、RECOMMENDED、REQUIRED、SHALL、SHALL NOT、SHOULD 和 SHOULD NOT 的解释,应当按照 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>
本示例使用 Key System 的 requestMediaKeySystemAccess
()
方法选择一个受支持的 Key System,然后使用来自 Initialization Data
(来自media
data)来生成许可证请求并将其发送到相应的许可证服务器。所支持的某个 key system 使用了 serverCertificate,该证书已被预先提供。
<script>
var licenseUrl;
var serverCertificate;
// Returns a Promise<MediaKeys>.
function createSupportedKeySystem() {
someSystemOptions = [
{ initDataTypes: ['keyids', 'webm'],
audioCapabilities: [
{ contentType: 'audio/webm; codecs="opus"' },
{ contentType: 'audio/webm; codecs="vorbis"' }
],
videoCapabilities: [
{ contentType: 'video/webm; codecs="vp9"' },
{ contentType: 'video/webm; codecs="vp8"' }
]
}
];
clearKeyOptions = [
{ initDataTypes: ['keyids', 'webm'],
audioCapabilities: [
{ contentType: 'audio/webm; codecs="opus"' },
{ contentType: 'audio/webm; codecs="vorbis"' }
],
videoCapabilities: [
{ contentType: 'video/webm; codecs="vp9"',
robustness: 'foo' },
{ contentType: 'video/webm; codecs="vp9"',
robustness: 'bar' },
{ contentType: 'video/webm; codecs="vp8"',
robustness: 'bar' },
]
}
];
return navigator.requestMediaKeySystemAccess('com.example.somesystem', someSystemOptions).then(
function(keySystemAccess) {
// Not shown:
// 1. Use both attributes of keySystemAccess.getConfiguration().audioCapabilities[0]
// and both attributes of keySystemAccess.getConfiguration().videoCapabilities[0]
// to retrieve appropriate stream(s).
// 2. Set video.src.
licenseUrl = 'https://license.example.com/getkey';
serverCertificate = new Uint8Array([ ... ]);
return keySystemAccess.createMediaKeys();
}
).catch(
function(error) {
// Try the next key system.
navigator.requestMediaKeySystemAccess('org.w3.clearkey', clearKeyOptions).then(
function(keySystemAccess) {
// Not shown:
// 1. Use keySystemAccess.getConfiguration().audioCapabilities[0].contentType
// and keySystemAccess.getConfiguration().videoCapabilities[0].contentType
// to retrieve appropriate stream(s).
// 2. Set video.src.
licenseUrl = 'https://license.example.com/clearkey/request';
return keySystemAccess.createMediaKeys();
}
);
}
).catch(
console.error.bind(console, 'Unable to instantiate a key system supporting the required combinations')
);
}
function handleInitData(event) {
var video = event.target;
if (video.mediaKeysObject === undefined) {
video.mediaKeysObject = null; // Prevent entering this path again.
video.pendingSessionData = []; // Will store all initData until the MediaKeys is ready.
createSupportedKeySystem().then(
function(createdMediaKeys) {
video.mediaKeysObject = createdMediaKeys;
if (serverCertificate)
createdMediaKeys.setServerCertificate(serverCertificate);
for (var i = 0; i < video.pendingSessionData.length; i++) {
var data = video.pendingSessionData[i];
makeNewRequest(video.mediaKeysObject, data.initDataType, data.initData);
}
video.pendingSessionData = [];
return video.setMediaKeys(createdMediaKeys);
}
).catch(
console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
);
}
addSession(video, event.initDataType, event.initData);
}
function addSession(video, initDataType, initData) {
if (video.mediaKeysObject) {
makeNewRequest(video.mediaKeysObject, initDataType, initData);
} else {
video.pendingSessionData.push({initDataType: initDataType, initData: initData});
}
}
function makeNewRequest(mediaKeys, initDataType, initData) {
var keySession = mediaKeys.createSession();
keySession.addEventListener("message", licenseRequestReady, false);
keySession.generateRequest(initDataType, initData).catch(
console.error.bind(console, 'Unable to create or initialize key session')
);
}
function licenseRequestReady(event) {
var request = event.message;
var xmlhttp = new XMLHttpRequest();
xmlhttp.keySession = event.target;
xmlhttp.open("POST", licenseUrl);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
var license = new Uint8Array(xmlhttp.response);
xmlhttp.keySession.update(license).catch(
console.error.bind(console, 'update() failed')
);
}
}
xmlhttp.send(request);
}
</script>
<video autoplay onencrypted='handleInitData(event)'></video>
如果在 MediaKeys
初始化期间不需要处理 encrypted
事件,则初始化要简单得多。这可以通过以其他方式提供 Initialization Data 或在创建 MediaKeys
对象之后再设置源来实现。本示例采用后者。
<script>
var licenseUrl;
var serverCertificate;
var mediaKeys;
// See the previous example for implementations of these functions.
function createSupportedKeySystem() { ... }
function makeNewRequest(mediaKeys, initDataType, initData) { ... }
function licenseRequestReady(event) { ... }
function handleInitData(event) {
makeNewRequest(mediaKeys, event.initDataType, event.initData);
}
createSupportedKeySystem().then(
function(createdMediaKeys) {
mediaKeys = createdMediaKeys;
var video = document.getElementById("v");
video.src = 'foo.webm';
if (serverCertificate)
mediaKeys.setServerCertificate(serverCertificate);
return video.setMediaKeys(mediaKeys);
}
).catch(
console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
);
</script>
<video id="v" autoplay onencrypted='handleInitData(event)'></video>
这是一个更完整的示例,演示了如何使用所有事件。
注意 handleMessage()
可能会被多次调用,包括在需要多次往返的情况下对 update
()
调用的响应中,或出于 Key
System 可能需要发送消息的任何其他原因。
<script>
var licenseUrl;
var serverCertificate;
var mediaKeys;
// See previous examples for implementations of these functions.
// createSupportedKeySystem() additionally sets renewalUrl.
function createSupportedKeySystem() { ... }
function handleInitData(event) { ... }
// This replaces the implementation in the previous example.
function makeNewRequest(mediaKeys, initDataType, initData) {
var keySession = mediaKeys.createSession();
keySession.addEventListener('message', handleMessage, false);
keySession.addEventListener('keystatuseschange', handlekeyStatusesChange, false);
keySession.closed.then(
function(reason) {
console.log('Session', this.sessionId, 'closed, reason', reason);
}.bind(keySession)
);
keySession.generateRequest(initDataType, initData).catch(
console.error.bind(console, 'Unable to create or initialize key session')
);
}
function handleMessageResponse(keySession, response) {
var license = new Uint8Array(response);
keySession.update(license).catch(
function(err) {
console.error('update() failed: ' + err);
}
);
}
function sendMessage(type, message, keySession) {
var url = licenseUrl;
if (type == "license-renewal")
url = renewalUrl;
xmlhttp = new XMLHttpRequest();
xmlhttp.keySession = keySession;
xmlhttp.open('POST', url);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4)
handleMessageResponse(xmlhttp.keySession, xmlhttp.response);
}
xmlhttp.send(message);
}
function handleMessage(event) {
sendMessage(event.messageType, event.message, event.target);
}
function handlekeyStatusesChange(event) {
// Evaluate the current state using one of the map-like methods exposed by
// event.target.keyStatuses.
// For example:
event.target.keyStatuses.forEach(function(status, keyId) {
switch (status) {
case "usable":
break;
case "expired":
// Report an expired key.
break;
case "status-pending":
// The status is not yet known. Consider the key unusable until the status is updated.
break;
default:
// Do something with |keyId| and |status|.
}
})
}
createSupportedKeySystem().then(
function(createdMediaKeys) {
mediaKeys = createdMediaKeys;
var video = document.getElementById("v");
video.src = 'foo.webm';
if (serverCertificate)
mediaKeys.setServerCertificate(serverCertificate);
return video.setMediaKeys(mediaKeys);
}
).catch(
console.error.bind(console, 'Failed to create and initialize a MediaKeys object')
);
</script>
<video id='v' src='foo.webm' autoplay></video>
此示例请求一个用于将来使用的持久许可证并将其存储。它还提供了用于之后检索该许可证和销毁它的函数。
<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 对本规范的贡献。也感谢通过参与邮件列表和问题等方式为本规范做出贡献的许多其他人员。
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:
Referenced in: