1. 简介
本节为非规范性内容。
媒体在当今被广泛使用,而 Web 是消费媒体内容的主要渠道之一。许多平台可以在各种用户界面元素中显示媒体元数据,例如标题、艺术家、专辑和专辑封面,这些元素包括通知、媒体控制中心、设备锁屏以及可穿戴设备。本规范旨在使网页能够指定在平台界面中显示的媒体元数据,并响应可能来自平台界面或媒体按键的媒体控制,从而提升用户体验。
2. 隐私注意事项
本节为非规范性内容。
本规范中引入的 API 在隐私方面的影响非常小。部分 API 允许网站通过按钮或其他控制形式接收来自用户的指令,有时可能会在用户与网站之间引入新的输入层。
2.1. 隐身模式
出于隐私考虑,在隐身模式下,用户代理在与系统共享 MediaMetadata
信息时应谨慎,并确保不会以损害用户的方式使用这些信息。如果以过于显眼的方式展示这些信息,会违背用户在隐身模式下浏览的意图。当有相关功能时,界面元素应在平台上被标记为私密。
2.2. 媒体会话操作
媒体会话操作为 Web 平台引入了新的输入层。用户代理应确保用户知晓其操作可能会被路由到拥有活动媒体会话的网站,尤其是当这些操作来自远程设备如耳机或其他遥控设备时。建议用户代理在监听这些输入时遵循平台惯例,以帮助用户理解。
3. 安全注意事项
本节为非规范性内容。
本规范中引入的 API 在安全方面的影响非常小。部分 API 允许网站暴露可供用户代理使用的元数据。用户代理显然需要谨慎使用这些数据。
3.1. 用户界面指引
本规范中引入的 MediaMetadata
允许网站提供更多关于播放内容的信息。用户代理应在与媒体播放相关的任何界面中使用这些信息,无论是在用户代理内部还是平台上。
MediaMetadata
预期用于媒体播放场景,这使得伪造变得更难,但由于 MediaMetadata
包含文本字段和图片字段,恶意网站可能尝试伪造其他网站的身份。建议用户代理提供一种方式来查找来源或明确展示元数据来源网站的来源信息。
如果用户代理提供了从基于 MediaMetadata
创建的界面元素返回网站的机制,建议该操作不应被网站察觉,从而降低被伪造的风险。
总体来说,网站通知显示相关的所有安全注意事项在此同样适用。值得注意的是,MediaMetadata
可自定义程度比常规 Web 通知更低,因此更难被伪造。
4. 模型
4.1. 播放状态
为了使 play
和
pause
操作能正常工作,用户代理应能判断 浏览上下文中的活动媒体会话是否正在播放媒体,这称为 猜测的播放状态。推荐的判断 猜测的播放状态的方法是监控其节点文档的
浏览上下文为 浏览上下文的媒体元素。该
浏览上下文的 猜测的播放状态
如果其中任何一个 可能正在播放且未 静音,则为
"playing"
,
否则为
"paused"
。还应考虑其他信息,如
WebAudio 和插件等。
playbackState
属性指定了 声明播放状态,来源于 浏览上下文。该状态与 猜测的播放状态结合,以计算
实际播放状态,这是最终状态,将用于 play
和 pause
操作。
实际播放状态的计算方式如下:
当页面希望在媒体暂停时执行某些准备步骤,同时允许这些步骤被 pause
操作中断时,playbackState
属性可能很有用。参见 设置 playbackState 示例。
当 活动媒体会话 的 实际播放状态发生变化时,用户代理必须运行 媒体会话操作更新算法。
4.2. 路由
由于用户代理可能有多个标签页,每个标签页可以包含一个 顶级可遍历项和子 可导航项,每个 可导航项都可以有一个 MediaSession
对象,因此可能同时存在多个 MediaSession
对象。
用户代理必须从这些 MediaSession
对象中最多选择一个展示给用户,称为 活动媒体会话。
活动媒体会话
可能为 null。选择方式由用户代理决定,且应以用户体验为优先。注意,
playbackState
属性不得影响媒体会话路由。它只对 活动媒体会话 有效。
推荐用户代理通过管理 音频焦点来选择 活动媒体会话。如果某个标签页或 浏览上下文当前正在播放音频或用户期望控制其中的媒体,则称其具有 音频焦点。AudioFocus API 即面向此场景,完成后可用于此目的。
每当 活动媒体会话发生变化时,用户代理必须运行 媒体会话操作更新算法 和 元数据更新算法。
4.3. 元数据
活动媒体会话的媒体元数据可以根据平台规范在平台界面中显示。每当活动
媒体会话发生变化,或设置
metadata
时,用户代理必须运行 元数据更新算法。步骤如下:
- 如果活动媒体会话为 null,则取消向平台展示的媒体元数据,并结束这些步骤。
-
如果
metadata
为 空元数据,则取消向平台展示的媒体元数据,并结束这些步骤。 -
更新平台展示的媒体元数据,使其与活动媒体会话的
metadata
一致。 - 如果用户代理希望显示封面图片, 用户代理必须运行图片获取算法。
图片获取算法如下:
- 如果有其他图片获取算法正在运行,取消已有算法的执行实例。
-
如果 metadata 的
artwork
为空,则结束这些步骤。 -
如果平台支持显示媒体封面图片,则从 metadata 的
artwork
中选择一个 首选封面图片。 - 令 request 为一个新的 请求,其 URL 为
首选封面图片 的
src
, destination 为 "image",mode 为 "no-cors",credentials mode 为 "include", 并且设置 use-URL-credentials flag。 - 抓取 request,并在抓取的 processResponse 算法执行以下步骤。
如果在图片获取算法中未能获取到图片,用户代理可以有回退行为,如显示默认图片作为封面。
4.4. 操作
媒体会话操作是页面可处理的操作,用户可通过这些操作与
MediaSession
进行交互。例如,页面可以处理某些操作,当用户按下耳机或其他远程设备上的按键时会被触发。
媒体会话操作来源是可能产生 媒体会话操作的来源。该来源可以是平台或用户代理创建的界面。
媒体会话操作来源有一个可选的
目标,用于接收由该
媒体会话操作来源产生的
媒体会话操作。如果媒体会话操作来源的
目标为 null
,则所有操作的接收者为
活动媒体会话。
媒体会话操作由
MediaSessionAction
枚举表示,可取如下值之一:
-
play
:表示操作意图为恢复播放。 -
pause
:表示操作意图为暂停当前活动播放。 -
seekbackward
:表示操作意图为将播放时间向后移动一段时间(如几秒)。 -
seekforward
:表示操作意图为将播放时间向前移动一段时间(如几秒)。 -
previoustrack
:表示操作意图为从头开始当前播放(如果有起点),或切换到播放列表中的上一项(如果有播放列表)。 -
nexttrack
:表示操作意图为切换到播放列表中的下一项(如果有播放列表)。 -
skipad
:表示操作意图为跳过当前播放的广告。 -
stop
:表示操作意图为停止播放并清除状态(如果适用)。 -
seekto
:表示操作意图为将播放时间移动到指定时刻。 -
togglemicrophone
:表示操作意图为静音或取消静音用户麦克风。 -
togglecamera
:表示操作意图为开启或关闭用户摄像头。 -
togglescreenshare
:表示操作意图为开启或关闭屏幕共享。 -
hangup
:表示操作意图为结束通话。 -
previousslide
:表示操作意图为演示时返回上一张幻灯片。 -
nextslide
:表示操作意图为演示时切换到下一张幻灯片。 -
enterpictureinpicture
: 表示操作意图为在画中画窗口中打开媒体会话。 -
voiceactivity
:表示操作意图为通知网页麦克风检测到语音活动。
所有 MediaSession
都有一个 已支持的媒体会话操作 映射表,键为 媒体会话操作,值为 MediaSessionActionHandler
。
当在指定 MediaSession
上以 action 和 handler 参数调用 操作处理器更新算法时,用户代理必须执行以下步骤:
-
如果 handler 为
null
,则从MediaSession
的 已支持的媒体会话操作中移除 action,并终止这些步骤。 -
将 action 添加到
MediaSession
的 已支持的媒体会话操作中,并关联 handler。
当 已支持的媒体会话操作发生变化时,用户代理应运行 媒体会话操作更新算法。用户代理可以 队列一个任务以运行 媒体会话操作更新算法,以避免在同一事件循环中多次修改操作导致界面闪烁。
当用户代理被名为 source 的 媒体会话操作来源通知某个名为 action 的 媒体会话操作已被触发时,用户代理必须使用 用户交互任务源 队列一个任务,执行以下 处理媒体会话操作步骤:
- 令 session 为 source 的 目标。
-
如果 session 为
null
,则将 session 设为 活动媒体会话。 -
如果 session 为
null
,终止这些步骤。 - 令 actions 为 session 的 已支持的媒体会话操作。
- 如果 actions 不包含键 action,终止这些步骤。
-
令 handler 为 actions 中与键 action 相关联的
MediaSessionActionHandler
。 -
以 details 参数(类型为
MediaSessionActionDetails
)运行 handler。 - 在与 session 关联的 浏览上下文中运行 激活通知步骤。
当用户代理收到对 play 和 pause 的联合命令(如耳机按钮点击)时,必须使用 用户交互任务源 队列一个任务,执行以下步骤:
-
如果 活动媒体会话为
null
,终止这些步骤。 - 令 action 为 媒体会话操作。
- 如果 活动媒体会话的 实际播放状态为 playing,则将 action 设为 pause。
- 否则,将 action 设为 play。
- 使用 action 运行 处理媒体会话操作步骤。
建议用户代理为 play 和 pause 媒体会话操作实现默认处理器,如果 活动媒体会话未定义处理器时。
用户代理可以为 togglemicrophone、togglecamera、togglescreenshare 或 hangup 媒体会话操作实现默认处理器,如果 活动媒体会话未定义处理器时。
用户代理可以通过 MediaStreamTrack
的 muted
属性向网页暴露麦克风、摄像头和屏幕共享状态,除了 togglemicrophone
、togglecamera
或 togglescreenshare
媒体会话操作外。在这种情况下,用户代理必须先运行相应的 MediaSessionActionHandler
,再以不同任务运行
设置 track 静音状态 的相关步骤。
voiceactivity
操作来源必须始终有一个目标,其文档必须始终拥有 live
麦克风 MediaStreamTrack
。用户代理必须仅在麦克风有一个或多个
live
MediaStreamTrack
时,且检测到语音活动时,调用 MediaSessionActionHandler
(类型为
voiceactivity
)。如果麦克风未静音且所有相关
MediaStreamTrack
均已 enabled
,用户代理可以忽略语音活动。建议用户代理按隐私和能效策略设定两次
MediaSessionActionHandler
(类型为
voiceactivity
)的最小间隔。
voiceactivity
仅表示语音活动的开始。当 MediaStreamTrack
被静音时,应用可以显示用户正在说话的通知,或启动 AudioWorklet
进行音频处理。语音活动结束未定义任何操作。与其他由用户明确触发的操作不同,voiceactivity
还依赖于用户代理或系统的语音活动检测算法。为隐私和能效考虑,如果语音活动结束后很快又重新开始,网页可能不会收到结束和重新开始的通知。
页面只应在能够处理该操作时为 媒体会话操作注册 MediaSessionActionHandler
,因为用户代理会将其列为
已支持的媒体会话操作并更新 媒体会话操作来源。
当调用 媒体会话操作更新算法时,用户代理必须执行以下步骤:
- 令 available actions 为 媒体会话操作数组。
- 如果 活动媒体会话为 null,则将 available actions 设为空数组。
- 否则,将 available actions 设为 活动媒体会话的 已支持的媒体会话操作的键列表。
- 对每个 媒体会话操作来源 source,执行以下子步骤:
4.5. 位置状态
用户代理可以根据平台规范在平台界面上显示当前播放位置和 媒体时长。 位置状态 是以下内容的组合:
- 媒体的 时长,单位为秒。
- 媒体的 播放速率,为系数。
- 媒体的 最后报告的播放位置,即创建位置状态时媒体的播放位置(秒)。
位置状态
由 MediaPositionState
表示,
必须始终与 上次位置更新时间 一起存储,
即位置状态最近一次更新的时间(秒)。
推荐的获取 位置状态 的方式是监控其节点文档的浏览上下文为 浏览上下文的媒体元素。
实际播放速率是按如下方式计算的系数:
当前播放位置(秒)按如下方式计算:
5. MediaSession
接口
[Exposed =Window ]partial interface Navigator { [SameObject ]readonly attribute MediaSession mediaSession ; };enum {
MediaSessionPlaybackState "none" ,"paused" ,"playing" };enum {
MediaSessionAction "play" ,"pause" ,"seekbackward" ,"seekforward" ,"previoustrack" ,"nexttrack" ,"skipad" ,"stop" ,"seekto" ,"togglemicrophone" ,"togglecamera" ,"togglescreenshare" ,"hangup" ,"previousslide" ,"nextslide" ,"enterpictureinpicture" ,"voiceactivity" };enum {
MediaSessionEnterPictureInPictureReason "other" ,"useraction" ,"contentoccluded" };callback =
MediaSessionActionHandler undefined (MediaSessionActionDetails ); [
details Exposed =Window ]interface {
MediaSession attribute MediaMetadata ?metadata ;attribute MediaSessionPlaybackState playbackState ;undefined setActionHandler (MediaSessionAction ,
action MediaSessionActionHandler ?);
handler undefined setPositionState (optional MediaPositionState = {});
state Promise <undefined >setMicrophoneActive (boolean );
active Promise <undefined >setCameraActive (boolean );
active Promise <undefined >setScreenshareActive (boolean ); };
active
MediaSession
对象表示给定文档的媒体会话,并允许文档向用户代理传递有关播放及其处理方式的信息。
MediaSession
关联有一个 metadata
对象,由 MediaMetadata
表示。初始值为 null
。
mediaSession
属性
必须返回与该 MediaSession
实例关联的 Navigator
对象。
metadata
属性
反映 MediaSession
的
metadata
。
读取时,必须返回 MediaSession
的
metadata
。
设置时,需按照以下步骤(value为新值)执行:
-
如果
MediaSession
的metadata
不为null
,则将其 media session 设为null
。 -
将
MediaSession
的metadata
设为 value。 -
如果
MediaSession
的metadata
不为null
,则将其 media session 设为当前MediaSession
。 - 并行运行 元数据更新算法。
playbackState
属性表示 媒体会话 的
声明播放状态,用于声明会话的
浏览上下文 是否正在播放媒体。初始值为 none。设置时,如果为有效的 MediaSessionPlaybackState
值,则用户代理必须设置此 IDL 属性为新值。读取时,用户代理必须返回最近设置的有效值。playbackState
属性用于提示用户代理判断 浏览上下文 当前是播放还是暂停状态。
设置 playbackState
可能导致 实际播放状态发生变化,并运行 媒体会话操作更新算法。
MediaSessionPlaybackState
枚举用于指示 浏览上下文是否正在播放媒体,值描述如下:
-
none
表示 浏览上下文 未声明播放或暂停状态,仅可用于playbackState
属性。 -
playing
表示 浏览上下文 当前正在播放媒体,可被暂停。 -
paused
表示 浏览上下文已暂停媒体,可被恢复。
setActionHandler(action, handler)
方法被调用时,必须在 MediaSession
上以 action 和 handler 运行 操作处理器更新算法。
setPositionState(state)
方法被调用时,必须执行以下步骤:
- 如果 state 是空字典,则清除 位置状态并终止这些步骤。
- 如果 state 的 duration 未定义,则抛出 TypeError。
-
如果 state 的
duration
为负或NaN
,则抛出 TypeError。 -
如果 state 的
position
未定义,则设为零。 - 如果 state 的 position 为负或大于 duration,则抛出 TypeError。
- 如果 state 的 playbackRate 未定义,则设为 1.0。
-
如果 state 的
playbackRate
为零,则抛出 TypeError。 - 更新 位置状态 和 上次位置更新时间。
setMicrophoneActive(active)
方法用于向用户代理指示页面期望的麦克风采集状态(例如页面不再通过通话发送音频时视为“非激活”,可调用 setMicrophoneActive(false)
)。被调用时,必须执行以下步骤:
- 令 document 为 this 的 相关全局对象 的 关联文档。
- 令 captureKind 为 "microphone"。
- 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。
同理,setCameraActive(active)
方法用于向用户代理指示页面期望的摄像头采集状态。调用时,必须执行以下步骤:
- 令 document 为 this 的 相关全局对象 的 关联文档。
- 令 captureKind 为 "camera"。
- 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。
同理,setScreenshareActive(active)
方法用于向用户代理指示页面期望的屏幕共享采集状态。调用时,必须执行以下步骤:
- 令 document 为 this 的 相关全局对象 的 关联文档。
- 令 captureKind 为 "screenshare"。
- 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。
采集状态更新算法在以 document、active 和 captureKind 调用时,必须执行以下步骤:
- 如果 document 不是 完全激活,返回 一个被拒绝的 promise,错误为 InvalidStateError。
-
如果 active 为
true
且 document 的 可见性状态不是 "visible",用户代理可返回 一个被拒绝的 promise,错误为 InvalidStateError。 - 令 p 为新建的 promise。
-
并行运行以下步骤:
-
令 applyPausePolicy 为
true
,如果用户代理实现了针对 UI 的 暂停所有输入源(类型为 captureKind)的策略,否则为false
。 -
如果 applyPausePolicy 为
true
,运行以下子步骤: - 按照 captureKind 和 active 更新用户代理采集状态界面。
-
使用 队列任务,任务源为 用户交互任务源,将 p resolve 为
undefined
。 -
如果 applyPausePolicy 为
true
,运行以下子步骤:-
令 newMutedState 为
true
,如果 active 为false
,否则为false
。 -
对每个来源为 captureKind 的
MediaStreamTrack
, 使用 队列任务,任务源为 用户交互任务源, 设置 track 静音状态为 newMutedState。
-
令 newMutedState 为
-
令 applyPausePolicy 为
- 返回 p。
setMicrophoneActive(active)、setCameraActive(active) 和 setScreenshareActive(active) 方法可能会根据用户代理的特定启发式拒绝调用。这尤其可能发生在网页请求激活(取消静音)麦克风、摄像头或屏幕共享时。用户代理可要求 临时激活,也可能通过提示要求用户输入以做出最终决定。
用户代理可以显示 UI,以触发 媒体会话操作的处理器。
6. MediaMetadata
接口
[Exposed =Window ]interface {
MediaMetadata constructor (optional MediaMetadataInit = {});
init attribute DOMString title ;attribute DOMString artist ;attribute DOMString album ;attribute FrozenArray <object >artwork ; [SameObject ]readonly attribute FrozenArray <ChapterInformation >; };
chapterInfo dictionary {
MediaMetadataInit DOMString = "";
title DOMString = "";
artist DOMString = "";
album sequence <MediaImage >= [];
artwork sequence <ChapterInformationInit >= []; };
chapterInfo
MediaMetadata
对象用于表示与 MediaSession
相关联的元数据,用户代理可以用它来提供自定义的用户界面。
MediaMetadata
可以关联一个 媒体会话 对象。
MediaMetadata
关联有 标题、艺术家 和 专辑,类型为
DOMString。
MediaMetadata
关联有一个 封面图片序列,类型为 MediaImage
。MediaMetadata
还关联有一个 已转换封面图片,初始值为 undefined
。
MediaMetadata
关联有一个 章节信息列表。
当 MediaMetadata
等于 null
或同时满足以下所有条件时,称其为 空元数据:
MediaMetadata(init)
构造函数被调用时,必须执行以下步骤:
- 令 metadata 为一个新的
MediaMetadata
对象。 - 将 metadata 的
title
设为 init 的title
。 - 将 metadata 的
artist
设为 init 的artist
。 - 将 metadata 的
album
设为 init 的album
。 - 用 init 的
artwork
作为 input,运行 封面转换算法,如果成功则将 metadata 的 封面图片设为结果。 - 令 chapters 为类型为
ChapterInformation
的空列表。 - 对 init 的
chapterInfo
中的每个 entry,创建 ChapterInformation 并添加到 chapters。 - 将 metadata 的 章节信息设为 创建冻结数组后的结果。
- 返回 metadata。
当以 input 参数调用 封面转换算法时,其中 input 为类型 MediaImage
的序列,用户代理必须执行以下步骤:
- 令 output 为类型
MediaImage
的空列表。 - 对 input(
MediaImage
列表)中的每个 entry,执行以下步骤: - 返回 output 作为结果。
title
属性反映 MediaMetadata
的 标题。读取时,必须返回
MediaMetadata
的 标题。设置时,必须将 MediaMetadata
的 标题 设为给定值。
artist
属性反映 MediaMetadata
的 艺术家。读取时,必须返回
MediaMetadata
的 艺术家。设置时,必须将
MediaMetadata
的 艺术家 设为给定值。
album
属性反映 MediaMetadata
的 专辑。读取时,必须返回
MediaMetadata
的 专辑。设置时,必须将 MediaMetadata
的 专辑 设为给定值。
artwork
属性反映 MediaMetadata
的 封面图片。读取时,必须执行以下步骤:
-
如果
MediaMetadata
的 已转换封面图片为undefined
,则执行以下步骤:- 令 frozenArtwork 为一个 JavaScript 数组值。
- 对
MediaMetadata
的 封面图片中的每个 entry,执行以下步骤:- 令 image 为 IDL 转为 JS 对象后的 entry。
- 执行 SetIntegrityLevel(image,
"
frozen
"),防止脚本意外变更。 - 将 image 推入 frozenArtwork。
- 执行 SetIntegrityLevel(frozenArtwork,
"
frozen
")。 - 将
MediaMetadata
的 已转换封面图片 设为 frozenArtwork。
-
返回
MediaMetadata
的 已转换封面图片。
设置时,必须按如下步骤,以 value 为新值:
- 令 convertedArtwork 为 将 ECMAScript 转为 IDL 值的 value,类型为
MediaImage
的序列。 - 用 convertedArtwork 运行 封面转换算法,成功则将
MediaMetadata
的 封面图片 设为结果。 - 将
MediaMetadata
的 已转换封面图片设为undefined
。
当 MediaMetadata
的 标题、艺术家、专辑或 封面图片被修改时,用户代理必须执行以下步骤:
7.
ChapterInformation
接口
[Exposed =Window ]interface {
ChapterInformation readonly attribute DOMString title ;readonly attribute double startTime ; [SameObject ]readonly attribute FrozenArray <MediaImage >artwork ; };dictionary {
ChapterInformationInit DOMString = "";
title double = 0;
startTime sequence <MediaImage >= []; };
artwork
ChapterInformation
对象用于表示单独章节的元数据,例如章节标题、时间戳和该章节的截图图片数据,用户代理可用其提供自定义用户界面。
ChapterInformation
关联有 标题,类型为 DOMString。
ChapterInformation
关联有 startTime,类型为 double。
ChapterInformation
关联有 封面图片列表。
要创建一个 ChapterInformation
,传入
init,按如下步骤执行:
-
令 chapterInfo 为一个新的
ChapterInformation
对象。 -
将 chapterInfo 的
title
设为 init 的title
。 -
将 chapterInfo 的
startTime
设为 init 的startTime
。 如果 startTime 为负或大于 duration,抛出 TypeError。 -
令
artwork
为用 init 的artwork
作为 input 运行 封面转换算法 的结果。 -
将 chapterInfo 的 封面图片设为 创建冻结数组后的
artwork
。 - 返回 chapterInfo。
title
属性反映 ChapterInformation
的 标题。读取时,必须返回 ChapterInformation
的 标题。
startTime
属性反映 ChapterInformation
的 startTime(秒)。读取时,必须返回 ChapterInformation
的 startTime。
artwork
属性反映 ChapterInformation
的 封面图片。读取时,必须返回 ChapterInformation
的 封面图片。
8. MediaImage
字典
dictionary {
MediaImage required USVString src ;DOMString sizes = "";DOMString type = ""; };
MediaImage
字典成员借鉴了 ImageResource
,见
[IMAGE-RESOURCE]。
src
字典成员用于指定 MediaImage
对象的 source
。它是用户代理可用于获取图片数据的 URL。
sizes
字典成员用于指定 MediaImage
对象的 sizes
。其规范遵循
HTML
sizes
属性,为字符串,由一组唯一的、以空格分隔的 tokens 组成(无序),这些 tokens 是 ASCII
不区分大小写,用于表示图片的尺寸。每个关键字要么是字符串 "any" 的 ASCII
不区分大小写匹配,要么是由两个有效的非负整数构成(不能有前导 0),用一个小写字母 x 或大写字母 X 分隔。关键字表示原始像素尺寸(非 CSS
像素)。当有多个图片对象时,用户代理可以用该值决定最适合某显示环境的图标(并忽略不适合的)。sizes
属性的解析步骤必须遵循 HTML link
元素 sizes
属性的解析步骤。
type
字典成员用于指定 MediaImage
对象的 MIME
类型。它作为图片的媒体类型的提示,便于用户代理忽略不支持的媒体类型图片。
9.
MediaPositionState
字典
dictionary {
MediaPositionState unrestricted double duration ;double playbackRate ;double position ; };
MediaPositionState
字典用于表示与 MediaSession
相关联的当前播放位置,用户代理可用它提供显示当前播放位置和时长的界面。
duration
字典成员用于指定 时长(秒)。应始终为正数,正无穷可用于表示无定义结束点的媒体(如直播)。
playbackRate
字典成员用于指定 播放速率。可为正表示正向播放,负表示倒放,不应为零。
position
字典成员用于指定 最后报告的播放位置(秒),应始终为正数。
10. MediaSessionActionDetails
字典
dictionary {
MediaSessionActionDetails required MediaSessionAction action ;double seekOffset ;double seekTime ;boolean fastSeek ;boolean ;
isActivating MediaSessionEnterPictureInPictureReason enterPictureInPictureReason ; };
MediaSessionActionHandler
必须以 details 参数运行,其字典类型为 MediaSessionActionDetails
。
action
字典成员用于指定 媒体会话操作,即 MediaSessionActionHandler
关联的操作类型。
seekOffset
字典成员可在 媒体会话操作为 seekbackward
或 seekforward
时提供。它表示需要调整的播放时间(秒),如果有应为正值,否则网站应自行选择合理时间(如几秒)。
isActivating
字典成员:如果用户代理即将 暂停所有相关输入源,则为
false
,否则为 true
。如果用户代理实现了暂停所有输入源策略,并且 媒体会话操作为 togglecamera
、togglemicrophone
或 togglescreenshare
,该成员必须存在。
当 媒体会话操作为 enterpictureinpicture
时,enterPictureInPictureReason
字典成员必须提供,表示用户代理触发此操作的原因。
enterPictureInPictureReason
可取如下值:
-
other
:进入画中画的原因不是现有枚举值之一 -
useraction
:用户主动执行了进入画中画操作(如点击用户代理界面的画中画按钮) -
contentoccluded
:由于页面内容被遮挡,用户代理请求进入画中画。可能是切换标签页、最小化等场景。
11. 权限策略集成
本规范定义了一个由字符串 "mediasession" 标识的 策略控制特性。其 默认允许列表为 *。
文档的 权限策略决定该文档中的内容是否允许使用 MediaSession API。如果在文档中被禁用,用户代理必须不将该文档的媒体会话选为 活动媒体会话。
12. 示例
本节为非规范性内容。
navigator. mediaSession. metadata= new MediaMetadata({ title: "Episode Title" , artist: "Podcast Host" , album: "Podcast Title" , artwork: [{ src: "podcast.jpg" }], chapterInfo: [ { title: "Chapter 1" , startTime: 0 , artwork: [{ src: "chapter1.jpg" }]}, { title: "Chapter 2" , startTime: 120 , artwork: [{ src: "chapter2.jpg" }]} ] });
或者,在 metadata 中提供多个插图图片,可以让用户代理能够为不同显示用途选择不同的插图图片,更好地适配不同屏幕(在
chapterInfo
中的插图同理):
navigator. mediaSession. metadata= new MediaMetadata({ title: "Episode Title" , artist: "Podcast Host" , album: "Podcast Title" , artwork: [ { src: "podcast.jpg" , sizes: "128x128" , type: "image/jpeg" }, { src: "podcast_hd.jpg" , sizes: "256x256" }, { src: "podcast_xhd.jpg" , sizes: "1024x1024" , type: "image/jpeg" }, { src: "podcast.png" , sizes: "128x128" , type: "image/png" }, { src: "podcast_hd.png" , sizes: "256x256" , type: "image/png" }, { src: "podcast.ico" , sizes: "128x128 256x256" , type: "image/x-icon" } ], chapterInfo: [ { title: "Chapter 1" , startTime: 0 , artwork: [ { src: "chapter1_a.jpg" , sizes: "128x128" , type: "image/jpeg" }, { src: "chapter1_b.png" , sizes: "256x256" , type: "image/png" } ]}, { title: "Chapter 2" , startTime: 120 , artwork: [ { src: "chapter2_a.jpg" , sizes: "128x128" , type: "image/jpeg" }, { src: "chapter2_b.png" , sizes: "256x256" , type: "image/png" } ]} ] });
比如,如果用户代理需要将图片作为图标使用,则可以为低像素密度屏幕选择"podcast.jpg"
或"podcast.png"
,为高像素密度屏幕选择"podcast_hd.jpg"
或"podcast_hd.png"
。如果用户代理需要将图片用作锁屏背景,则更倾向于选择"podcast_xhd.jpg"
。
对于播放列表或有声书的章节,多个媒体元素可以共享一个media session。
var audio1= document. createElement( "audio" ); audio1. src= "chapter1.mp3" ; var audio2= document. createElement( "audio" ); audio2. src= "chapter2.mp3" ; audio1. play(); audio1. addEventListener( "ended" , function () { audio2. play(); });
由于会话是共享的,metadata 必须更新以反映当前正在播放的内容。
function updateMetadata( event) { navigator. mediaSession. metadata= new MediaMetadata({ title: event. target== audio1? "Chapter 1" : "Chapter 2" , artist: "An Author" , album: "A Book" , artwork: [{ src: "cover.jpg" }] }); } audio1. addEventListener( "play" , updateMetadata); audio2. addEventListener( "play" , updateMetadata);
var tracks= [ "chapter1.mp3" , "chapter2.mp3" , "chapter3.mp3" ]; var trackId= 0 ; var audio= document. createElement( "audio" ); audio. src= tracks[ trackId]; function updatePlayingMedia() { audio. src= tracks[ trackId]; // 更新 metadata(已省略) } navigator. mediaSession. setActionHandler( "previoustrack" , function () { trackId= ( trackId+ tracks. length- 1 ) % tracks. length; updatePlayingMedia(); }); navigator. mediaSession. setActionHandler( "nexttrack" , function () { trackId= ( trackId+ 1 ) % tracks. length; updatePlayingMedia(); }); navigator. mediaSession. setActionHandler( "seekto" , function ( details) { audio. currentTime= details. seekTime; });
playbackState
:
当页面暂停其媒体并在 iframe 中播放第三方广告时,UA 可能认为会话“不在播放”,但页面希望允许用户暂停广告播放并在广告结束后取消待播放内容。
var adFrame; var audio= document. createElement( "audio" ); audio. src= "foo.mp3" ; function resetActionHandlers() { navigator. mediaSession. setActionHandler( "play" , _=> audio. play()); navigator. mediaSession. setActionHandler( "pause" , _=> audio. pause()); } resetActionHandlers(); // 当页面希望播放广告时会调用此方法。 function pauseAudioAndPlayAd() { audio. pause(); navigator. mediaSession. playbackState= "playing" ; setUpAdFrame(); adFrame. contentWindow. postMessage( "play_ad" ); navigator. mediaSession. setActionHandler( "pause" , pauseAd); } function pauseAd() { adFrame. contentWindow. postMessage( "pause_ad" ); navigator. mediaSession. playbackState= "paused" ; navigator. mediaSession. setActionHandler( "play" , resumeAd); } function resumeAd() { adFrame. contentWindow. postMessage( "resume_ad" ); navigator. mediaSession. playbackState= "playing" ; navigator. mediaSession. setActionHandler( "pause" , pauseAd); } window. onmessage= function ( e) { if ( e. data=== "ad finished" ) { removeAdFrame(); navigator. mediaSession. playbackState= "none" ; resetActionHandlers(); } } function setUpAdFrame() { adFrame= document. createElement( "iframe" ); adFrame. src= "https://example.com/ad-iframe.html" ; document. body. appendChild( adFrame); } function removeAdFrame() { adFrame. remove(); }
// 媒体已加载,设置时长。 navigator. mediaSession. setPositionState({ duration: 60 }); // 媒体从开头开始播放。 navigator. mediaSession. playbackState= "playing" ; // 媒体以 2 倍速从第 10 秒开始播放。 navigator. mediaSession. setPositionState({ duration: 60 , playbackRate: 2 , position: 10 }); // 媒体已暂停。 navigator. mediaSession. playbackState= "paused" ; // 媒体已重置。 navigator. mediaSession. setPositionState( null );
var isMicrophoneActive= false ; var isCameraActive= false ; navigator. mediaSession. setMicrophoneActive( isMicrophoneActive); navigator. mediaSession. setCameraActive( isCameraActive); navigator. mediaSession. setActionHandler( "togglemicrophone" , function () { if ( isMicrophoneActive) { // 静音麦克风。实现已省略。 } else { // 取消麦克风静音。实现已省略。 } isMicrophoneActive= ! isMicrophoneActive; navigator. mediaSession. setMicrophoneActive( isMicrophoneActive); }); navigator. mediaSession. setActionHandler( "togglecamera" , function () { if ( isCameraActive) { // 关闭摄像头。实现已省略。 } else { // 开启摄像头。实现已省略。 } isCameraActive= ! isCameraActive; navigator. mediaSession. setCameraActive( isCameraActive); }); navigator. mediaSession. setActionHandler( "hangup" , function () { // 结束通话。实现已省略。 });
var currentSlideIndex= 0 ; navigator. mediaSession. setActionHandler( "previousslide" , function () { currentSlideIndex-- ; // 设置当前幻灯片。实现已省略。 }); navigator. mediaSession. setActionHandler( "nextslide" , function () { currentSlideIndex++ ; // 设置当前幻灯片。实现已省略。 });
navigator. mediaSession. setActionHandler( "enterpictureinpicture" , function () { remoteVideo. requestPictureInPicture(); });
// 创建一个启用音频的 MediaStream。 const stream= await navigator. mediaDevices. getUserMedia({ audio: true }); const track= stream. getAudioTracks()[ 0 ]; navigator. mediaSession. setActionHandler( "voiceactivity" , function () { if ( track. muted) { // 显示取消静音通知。如用户允许取消静音,则调用 setMicrophoneActive(true) 进行取消静音。 } });
致谢
编辑们感谢 Paul Adenot、Jake Archibald、Tab Atkins、Jonathan Bailey、François Beaufort、Marcos Caceres、Domenic Denicola、Ralph Giles、Anne van Kesteren、Tobie Langel、Michael Mahemoff、Jer Noble、Elliott Sprehn、Chris Wilson 和 Jörn Zaefferer 参与技术讨论,使本规范成为可能。
特别感谢 Philip Jägenstedt 和 David Vest 在设计媒体会话的每个方面时提供的帮助,以及他们在初始设计问题上展现的近乎无限的耐心;感谢 Jer Noble 协助建立适用于 iOS 音频焦点模型的方案;感谢 Mounir Lamouri 和 Anton Vayvod 在早期参与、反馈和支持本规范的制定。