媒体会话

W3C 工作草案,

关于本文档的更多信息
本版本:
https://www.w3.org/TR/2025/WD-mediasession-20250919/
最新发布版本:
https://www.w3.org/TR/mediasession/
编辑草案:
https://w3c.github.io/mediasession/
之前版本:
历史记录:
https://www.w3.org/standards/history/mediasession/
反馈:
GitHub
编辑:
(Google Inc.)
(Apple Inc.)
前任编辑:
(Google Inc.)
(Google Inc.)
(Google Inc.)
(Opera)
版本历史:
https://github.com/w3c/mediasession/commits

摘要

本规范允许网页开发者在平台界面上展示自定义的媒体元数据、定制可用的媒体控制项,并访问平台上的媒体按键,包括键盘、耳机、遥控器上的硬件按键,以及移动设备通知区域和锁屏上的软件按键。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 发布的技术文档和本技术报告的最新修订版可在 W3C 标准与草案索引中查阅。

欢迎对本规范提出反馈和意见。 有关本规范的讨论,优先通过 GitHub Issues 进行。也可发送邮件至媒体工作组邮件列表 public-media-wg@w3.org存档)。 本草案突出了工作组仍需讨论的一些待解决问题。 这些问题的结果尚未决定,包括其是否有效。

本文档由 媒体工作组推荐流程 发布为工作草案。 本文档旨在成为 W3C 推荐标准。

作为工作草案发布并不代表 W3C 及其成员的认可。

本文档为草案,可能随时被更新、替换或废止。 不应将本文档作为除“正在进行中的工作”以外引用。

本文档由遵循 W3C 专利政策 的工作组编制。 W3C 维护了与本组交付物相关的公开专利披露列表;该页面也包括披露专利的说明。任何知晓专利且认为其包含必要权利要求的个人,必须按照W3C 专利政策第6节进行披露。

本文档受 2025年8月18日 W3C 流程文件管辖。

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. 播放状态

为了使 playpause 操作能正常工作,用户代理应能判断 浏览上下文中的活动媒体会话是否正在播放媒体,这称为 猜测的播放状态。推荐的判断 猜测的播放状态的方法是监控其节点文档的 浏览上下文浏览上下文的媒体元素。该 浏览上下文猜测的播放状态 如果其中任何一个 可能正在播放且未 静音,则为 "playing", 否则为 "paused"。还应考虑其他信息,如 WebAudio 和插件等。

playbackState 属性指定了 声明播放状态,来源于 浏览上下文。该状态与 猜测的播放状态结合,以计算 实际播放状态,这是最终状态,将用于 playpause 操作。

实际播放状态的计算方式如下:

当页面希望在媒体暂停时执行某些准备步骤,同时允许这些步骤被 pause 操作中断时,playbackState 属性可能很有用。参见 设置 playbackState 示例。

活动媒体会话实际播放状态发生变化时,用户代理必须运行 媒体会话操作更新算法

4.2. 路由

由于用户代理可能有多个标签页,每个标签页可以包含一个 顶级可遍历项和子 可导航项,每个 可导航项都可以有一个 MediaSession 对象,因此可能同时存在多个 MediaSession 对象。

用户代理必须从这些 MediaSession 对象中最多选择一个展示给用户,称为 活动媒体会话活动媒体会话 可能为 null。选择方式由用户代理决定,且应以用户体验为优先。注意, playbackState 属性不得影响媒体会话路由。它只对 活动媒体会话 有效。

推荐用户代理通过管理 音频焦点来选择 活动媒体会话。如果某个标签页或 浏览上下文当前正在播放音频或用户期望控制其中的媒体,则称其具有 音频焦点。AudioFocus API 即面向此场景,完成后可用于此目的。

每当 活动媒体会话发生变化时,用户代理必须运行 媒体会话操作更新算法元数据更新算法

4.3. 元数据

活动媒体会话的媒体元数据可以根据平台规范在平台界面中显示。每当活动 媒体会话发生变化,或设置 metadata 时,用户代理必须运行 元数据更新算法。步骤如下:

  1. 如果活动媒体会话为 null,则取消向平台展示的媒体元数据,并结束这些步骤。
  2. 如果 metadata空元数据,则取消向平台展示的媒体元数据,并结束这些步骤。
  3. 更新平台展示的媒体元数据,使其与活动媒体会话metadata 一致。
  4. 如果用户代理希望显示封面图片, 用户代理必须运行图片获取算法

图片获取算法如下:

  1. 如果有其他图片获取算法正在运行,取消已有算法的执行实例。
  2. 如果 metadataartwork 为空,则结束这些步骤。
  3. 如果平台支持显示媒体封面图片,则从 metadataartwork 中选择一个 首选封面图片
  4. request 为一个新的 请求,其 URL首选封面图片srcdestination 为 "image",mode 为 "no-cors",credentials mode 为 "include", 并且设置 use-URL-credentials flag
  5. 抓取 request,并在抓取的 processResponse 算法执行以下步骤。
    1. response 为传递给抓取的 processResponse 算法的 响应
    2. 如果 responsetype 不是 "error", 则尝试将 responsebody 解码为图片。
    3. 如果图片格式受支持,则使用该图片作为平台界面的封面图片。否则,图片获取算法 失败并结束。

如果在图片获取算法中未能获取到图片,用户代理可以有回退行为,如显示默认图片作为封面。

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 上以 actionhandler 参数调用 操作处理器更新算法时,用户代理必须执行以下步骤:

  1. 如果 handlernull,则从 MediaSession已支持的媒体会话操作中移除 action,并终止这些步骤。
  2. action 添加到 MediaSession已支持的媒体会话操作中,并关联 handler

已支持的媒体会话操作发生变化时,用户代理应运行 媒体会话操作更新算法。用户代理可以 队列一个任务以运行 媒体会话操作更新算法,以避免在同一事件循环中多次修改操作导致界面闪烁。

当用户代理被名为 source媒体会话操作来源通知某个名为 action媒体会话操作已被触发时,用户代理必须使用 用户交互任务源 队列一个任务,执行以下 处理媒体会话操作步骤:

  1. sessionsource目标
  2. 如果 sessionnull,则将 session 设为 活动媒体会话
  3. 如果 sessionnull,终止这些步骤。
  4. actionssession已支持的媒体会话操作
  5. 如果 actions 不包含键 action,终止这些步骤。
  6. handleractions 中与键 action 相关联的 MediaSessionActionHandler
  7. details 参数(类型为 MediaSessionActionDetails)运行 handler
  8. 在与 session 关联的 浏览上下文中运行 激活通知步骤。

当用户代理收到对 playpause 的联合命令(如耳机按钮点击)时,必须使用 用户交互任务源 队列一个任务,执行以下步骤:

  1. 如果 活动媒体会话null,终止这些步骤。
  2. action媒体会话操作
  3. 如果 活动媒体会话实际播放状态playing,则将 action 设为 pause
  4. 否则,将 action 设为 play
  5. 使用 action 运行 处理媒体会话操作步骤。

建议用户代理为 playpause 媒体会话操作实现默认处理器,如果 活动媒体会话未定义处理器时。

用户代理可以为 togglemicrophonetogglecameratogglescreensharehangup 媒体会话操作实现默认处理器,如果 活动媒体会话未定义处理器时。

用户代理可以通过 MediaStreamTrackmuted 属性向网页暴露麦克风、摄像头和屏幕共享状态,除了 togglemicrophonetogglecameratogglescreenshare 媒体会话操作外。在这种情况下,用户代理必须先运行相应的 MediaSessionActionHandler,再以不同任务运行 设置 track 静音状态 的相关步骤。

voiceactivity 操作来源必须始终有一个目标,其文档必须始终拥有 live 麦克风 MediaStreamTrack。用户代理必须仅在麦克风有一个或多个 live MediaStreamTrack 时,且检测到语音活动时,调用 MediaSessionActionHandler(类型为 voiceactivity)。如果麦克风未静音且所有相关 MediaStreamTrack 均已 enabled,用户代理可以忽略语音活动。建议用户代理按隐私和能效策略设定两次 MediaSessionActionHandler(类型为 voiceactivity)的最小间隔。

voiceactivity 仅表示语音活动的开始。当 MediaStreamTrack 被静音时,应用可以显示用户正在说话的通知,或启动 AudioWorklet 进行音频处理。语音活动结束未定义任何操作。与其他由用户明确触发的操作不同,voiceactivity 还依赖于用户代理或系统的语音活动检测算法。为隐私和能效考虑,如果语音活动结束后很快又重新开始,网页可能不会收到结束和重新开始的通知。

页面只应在能够处理该操作时为 媒体会话操作注册 MediaSessionActionHandler,因为用户代理会将其列为 已支持的媒体会话操作并更新 媒体会话操作来源

当调用 媒体会话操作更新算法时,用户代理必须执行以下步骤:

  1. available actions媒体会话操作数组。
  2. 如果 活动媒体会话为 null,则将 available actions 设为空数组。
  3. 否则,将 available actions 设为 活动媒体会话已支持的媒体会话操作的键列表。
  4. 对每个 媒体会话操作来源 source,执行以下子步骤:
    1. 可选地,如果 活动媒体会话不为 null:
      1. 如果 活动媒体会话实际播放状态playing,则从 available actions 中移除 play
      2. 否则,从 available actions 中移除 pause
    2. 如果 source 是用户代理创建的界面元素,若可用空间有限,则可以从 available actions 中移除部分元素。
    3. 用更新后的 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 属性 反映 MediaSessionmetadata。 读取时,必须返回 MediaSessionmetadata。 设置时,需按照以下步骤(value为新值)执行:

  1. 如果 MediaSessionmetadata 不为 null,则将其 media session 设为 null
  2. MediaSessionmetadata 设为 value
  3. 如果 MediaSessionmetadata 不为 null,则将其 media session 设为当前 MediaSession
  4. 并行运行 元数据更新算法

playbackState 属性表示 媒体会话声明播放状态,用于声明会话的 浏览上下文 是否正在播放媒体。初始值为 none。设置时,如果为有效的 MediaSessionPlaybackState 值,则用户代理必须设置此 IDL 属性为新值。读取时,用户代理必须返回最近设置的有效值。playbackState 属性用于提示用户代理判断 浏览上下文 当前是播放还是暂停状态。

设置 playbackState 可能导致 实际播放状态发生变化,并运行 媒体会话操作更新算法

MediaSessionPlaybackState 枚举用于指示 浏览上下文是否正在播放媒体,值描述如下:

setActionHandler(action, handler) 方法被调用时,必须在 MediaSession 上以 actionhandler 运行 操作处理器更新算法

setPositionState(state) 方法被调用时,必须执行以下步骤:

setMicrophoneActive(active) 方法用于向用户代理指示页面期望的麦克风采集状态(例如页面不再通过通话发送音频时视为“非激活”,可调用 setMicrophoneActive(false))。被调用时,必须执行以下步骤:

  1. documentthis相关全局对象关联文档
  2. captureKind 为 "microphone"。
  3. 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。

同理,setCameraActive(active) 方法用于向用户代理指示页面期望的摄像头采集状态。调用时,必须执行以下步骤:

  1. documentthis相关全局对象关联文档
  2. captureKind 为 "camera"。
  3. 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。

同理,setScreenshareActive(active) 方法用于向用户代理指示页面期望的屏幕共享采集状态。调用时,必须执行以下步骤:

  1. documentthis相关全局对象关联文档
  2. captureKind 为 "screenshare"。
  3. 返回运行 采集状态更新算法(参数:document, active, captureKind)的结果。

采集状态更新算法在以 documentactivecaptureKind 调用时,必须执行以下步骤:

  1. 如果 document 不是 完全激活,返回 一个被拒绝的 promise,错误为 InvalidStateError
  2. 如果 activetruedocument可见性状态不是 "visible",用户代理可返回 一个被拒绝的 promise,错误为 InvalidStateError
  3. p 为新建的 promise。
  4. 并行运行以下步骤:
    1. applyPausePolicytrue,如果用户代理实现了针对 UI 的 暂停所有输入源(类型为 captureKind)的策略,否则为 false
    2. 如果 applyPausePolicytrue,运行以下子步骤:
      1. currentlyActivefalse,如果用户代理当前 暂停所有输入源(类型为 captureKind),否则为 true
      2. 如果 active 等于 currentlyActive,使用 队列任务,任务源为 用户交互任务源,将 p resolve 为 undefined 并终止这些步骤。
      3. 如果 activetrue,用户代理可以等待继续,比如提示用户。
      4. 如果用户代理拒绝更新采集状态请求,使用 队列任务,任务源为 用户交互任务源,将 p reject 为 NotAllowedError 并终止这些步骤。
    3. 按照 captureKindactive 更新用户代理采集状态界面。
    4. 使用 队列任务,任务源为 用户交互任务源,将 p resolve 为 undefined
    5. 如果 applyPausePolicytrue,运行以下子步骤:
      1. newMutedStatetrue,如果 activefalse,否则为 false
      2. 对每个来源为 captureKindMediaStreamTrack, 使用 队列任务,任务源为 用户交互任务源设置 track 静音状态newMutedState
  5. 返回 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 关联有一个 封面图片序列,类型为 MediaImageMediaMetadata 还关联有一个 已转换封面图片,初始值为 undefined

MediaMetadata 关联有一个 章节信息列表。

MediaMetadata 等于 null 或同时满足以下所有条件时,称其为 空元数据

MediaMetadata(init) 构造函数被调用时,必须执行以下步骤:

  1. metadata 为一个新的 MediaMetadata 对象。
  2. metadatatitle 设为 inittitle
  3. metadataartist 设为 initartist
  4. metadataalbum 设为 initalbum
  5. initartwork 作为 input,运行 封面转换算法,如果成功则将 metadata封面图片设为结果。
  6. chapters 为类型为 ChapterInformation 的空列表。
  7. initchapterInfo 中的每个 entry创建 ChapterInformation 并添加到 chapters
  8. metadata章节信息设为 创建冻结数组后的结果。
  9. 返回 metadata

当以 input 参数调用 封面转换算法时,其中 input 为类型 MediaImage 的序列,用户代理必须执行以下步骤:

  1. output 为类型 MediaImage 的空列表。
  2. inputMediaImage 列表)中的每个 entry,执行以下步骤:
    1. image 为新的 MediaImage
    2. baseURL入口设置对象指定的 API 基础 URL。
    3. 解析 entrysrc,使用 baseURL。如果未失败,则将 imagesrc 设为返回值,否则抛出 TypeError 并终止这些步骤。
    4. imagesizes 设为 entrysizes
    5. imagetype 设为 entrytype
    6. image 添加到 output
  3. 返回 output 作为结果。

title 属性反映 MediaMetadata标题。读取时,必须返回 MediaMetadata标题。设置时,必须将 MediaMetadata标题 设为给定值。

artist 属性反映 MediaMetadata艺术家。读取时,必须返回 MediaMetadata艺术家。设置时,必须将 MediaMetadata艺术家 设为给定值。

album 属性反映 MediaMetadata专辑。读取时,必须返回 MediaMetadata专辑。设置时,必须将 MediaMetadata专辑 设为给定值。

artwork 属性反映 MediaMetadata封面图片。读取时,必须执行以下步骤:

  1. 如果 MediaMetadata已转换封面图片undefined,则执行以下步骤:
    1. frozenArtwork 为一个 JavaScript 数组值。
    2. MediaMetadata封面图片中的每个 entry,执行以下步骤:
      1. imageIDL 转为 JS 对象后的 entry
      2. 执行 SetIntegrityLevel(image, "frozen"),防止脚本意外变更。
      3. image 推入 frozenArtwork
    3. 执行 SetIntegrityLevel(frozenArtwork, "frozen")。
    4. MediaMetadata已转换封面图片 设为 frozenArtwork
  2. 返回 MediaMetadata已转换封面图片

设置时,必须按如下步骤,以 value 为新值:

  1. convertedArtwork将 ECMAScript 转为 IDL 值value,类型为 MediaImage 的序列。
  2. convertedArtwork 运行 封面转换算法,成功则将 MediaMetadata封面图片 设为结果。
  3. MediaMetadata已转换封面图片设为 undefined

MediaMetadata标题艺术家专辑封面图片被修改时,用户代理必须执行以下步骤:

  1. 如果该实例没有关联的 媒体会话,则终止这些步骤。
  2. 否则,队列一个任务以执行以下子步骤:
    1. 如果该实例不再有关联的 媒体会话,则终止这些步骤。
    2. 否则,并行运行 元数据更新算法

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 startTime = 0;
  sequence<MediaImage> artwork = [];
};

ChapterInformation 对象用于表示单独章节的元数据,例如章节标题、时间戳和该章节的截图图片数据,用户代理可用其提供自定义用户界面。

ChapterInformation 关联有 标题,类型为 DOMString。

ChapterInformation 关联有 startTime,类型为 double。

ChapterInformation 关联有 封面图片列表。

创建一个 ChapterInformation,传入 init,按如下步骤执行:

  1. chapterInfo 为一个新的 ChapterInformation 对象。
  2. chapterInfotitle 设为 inittitle
  3. chapterInfostartTime 设为 initstartTime。 如果 startTime 为负或大于 duration,抛出 TypeError
  4. artwork 为用 initartwork 作为 input 运行 封面转换算法 的结果。
  5. chapterInfo封面图片设为 创建冻结数组后的 artwork
  6. 返回 chapterInfo

title 属性反映 ChapterInformation标题。读取时,必须返回 ChapterInformation标题

startTime 属性反映 ChapterInformationstartTime(秒)。读取时,必须返回 ChapterInformationstartTime

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 字典成员可在 媒体会话操作seekbackwardseekforward 时提供。它表示需要调整的播放时间(秒),如果有应为正值,否则网站应自行选择合理时间(如几秒)。

媒体会话操作seekto 时:

isActivating 字典成员:如果用户代理即将 暂停所有相关输入源,则为 false,否则为 true。如果用户代理实现了暂停所有输入源策略,并且 媒体会话操作togglecameratogglemicrophonetogglescreenshare,该成员必须存在。

媒体会话操作enterpictureinpicture 时,enterPictureInPictureReason 字典成员必须提供,表示用户代理触发此操作的原因。

enterPictureInPictureReason 可取如下值:

11. 权限策略集成

本规范定义了一个由字符串 "mediasession" 标识的 策略控制特性。其 默认允许列表*

文档的 权限策略决定该文档中的内容是否允许使用 MediaSession API。如果在文档中被禁用,用户代理必须不将该文档的媒体会话选为 活动媒体会话

12. 示例

本节为非规范性内容。

设置 metadata
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"

修改 metadata

对于播放列表或有声书的章节,多个媒体元素可以共享一个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);
处理 media session actions
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();
}
设置 position state
// 媒体已加载,设置时长。
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 在早期参与、反馈和支持本规范的制定。

一致性

文档约定

一致性要求通过描述性断言与 RFC 2119 术语相结合来表达。 在本规范的规范性部分中, 关键词 “MUST”(必须)、“MUST NOT”(禁止)、“REQUIRED”(要求)、“SHALL”(应)、“SHALL NOT”(不应)、“SHOULD”(应该)、“SHOULD NOT”(不应该)、“RECOMMENDED”(推荐)、“MAY”(可以)、和 “OPTIONAL”(可选) 应按照 RFC 2119 的说明进行解释。 但为了便于阅读, 本规范未将这些词全部大写。

除明确标记为非规范性、示例和注释的章节外, 本规范的所有文本均为规范性内容。[RFC2119]

本规范中的示例会以 “例如” 开头, 或通过 class="example" 与规范性内容区分开来, 如下所示:

这是一个说明性示例。

说明性注释以 “注意” 开头, 并通过 class="note" 与规范性内容区分开来, 示例:

注意,这是一个说明性注释。

一致性算法

作为算法一部分的命令式要求 (例如 “去除所有前导空格字符” 或 “返回 false 并终止这些步骤”), 应根据引入算法时所用关键词 (如 “must”、“should”、“may” 等) 的含义进行解释。

以算法或具体步骤表达的一致性要求 可以以任何方式实现, 只要最终结果是等效的即可。 特别是,本规范中定义的算法 旨在易于理解, 并非旨在追求高性能。 鼓励实现者进行优化。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[FETCH]
Anne van Kesteren. Fetch 标准. 活标准. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. 活标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 活标准. URL: https://infra.spec.whatwg.org/
[MEDIACAPTURE-STREAMS]
Cullen Jennings; 等. Media Capture and Streams 媒体采集与流. 2025年9月11日. CRD. URL: https://www.w3.org/TR/mediacapture-streams/
[MIMESNIFF]
Gordon P. Hemsley. MIME 嗅探标准. 活标准. URL: https://mimesniff.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. 权限策略. 2025年8月6日. WD. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
S. Bradner. 用于 RFC 中指示需求级别的关键词. 1997年3月. 最佳当前实践. URL: https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren. URL 标准. 活标准. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 活标准. URL: https://webidl.spec.whatwg.org/

参考性引用

[IMAGE-RESOURCE]
Aaron Gustafson; Rayan Kanso; Marcos Caceres. Image Resource 图像资源. 2021年6月4日. WD. URL: https://www.w3.org/TR/image-resource/
[WEBAUDIO-1.1]
Paul Adenot; Hongchan Choi. Web Audio API 1.1. 2024年11月5日. FPWD. URL: https://www.w3.org/TR/webaudio-1.1/

IDL 索引

[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);
};

[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 = [];
};

[Exposed=Window]
interface ChapterInformation {
  readonly attribute DOMString title;
  readonly attribute double startTime;
  [SameObject] readonly attribute FrozenArray<MediaImage> artwork;
};

dictionary ChapterInformationInit {
  DOMString title = "";
  double startTime = 0;
  sequence<MediaImage> artwork = [];
};


dictionary MediaImage {
  required USVString src;
  DOMString sizes = "";
  DOMString type = "";
};

dictionary MediaPositionState {
  unrestricted double duration;
  double playbackRate;
  double position;
};

dictionary MediaSessionActionDetails {
  required MediaSessionAction action;
  double seekOffset;
  double seekTime;
  boolean fastSeek;
  boolean isActivating;
  MediaSessionEnterPictureInPictureReason enterPictureInPictureReason;
};