能力 · API
Media Session API
发布于 更新于
navigator.mediaSession 让页面告诉操作系统正在播放什么(标题、艺术家、专辑、封面),并接收用户在锁屏、耳机按键、键盘媒体键和浏览器自带媒体中心里发出的播放、暂停、跳转与切曲命令。没有它,这些控件仍能切换 <audio> 或 <video> 元素的播放状态,但系统显示的是页面 URL 而不是曲目名,也不提供跳过或进度按钮。
MediaSession 与 setActionHandler() 自桌面 Chrome 73、Android 上的 Chrome 57、Firefox 82、macOS 与 iOS 上的 Safari 15 起可用;MediaMetadata 更早(Chrome 57、Safari 14)。Android 上的 Firefox 暴露了 API 但没有媒体控件界面,Android WebView 没有实现(BCD api.MediaSession)。各个动作比接口本身来得晚:seekto 在 Chrome 78,stop 在 Chrome 77,togglemicrophone、togglecamera、hangup 在 Chrome 93(两个 toggle 动作在 Safari 18.4 也有),previousslide 与 nextslide 在 Chrome 111,enterpictureinpicture 在 Chrome 120,skipad 在 Chrome 128。
navigator.mediaSession.metadata = new MediaMetadata(init)navigator.mediaSession.playbackState = "none" | "paused" | "playing"
navigator.mediaSession.setActionHandler(action, handler)navigator.mediaSession.setActionHandler(action, null)navigator.mediaSession.setPositionState()navigator.mediaSession.setPositionState(state)
navigator.mediaSession.setMicrophoneActive(active)navigator.mediaSession.setCameraActive(active)navigator.mediaSession.setScreenshareActive(active)页面赋值 MediaMetadata 之前 metadata 为 null。setActionHandler() 与 setPositionState() 返回 undefined;handler 传 null 会移除该动作及其在系统控件中的按钮,不带参数调用 setPositionState() 会清除位置信息,系统随之不再绘制进度条。三个采集状态 setter 返回 Promise<undefined>,只对希望系统「静音」按钮与自身状态同步的会议类页面有意义。该接口为 [Exposed=Window],不要求安全上下文。
setActionHandler(action, handler) 接受一个 MediaSessionAction 枚举值和一个 MediaSessionActionHandler 回调,回调收到一个 MediaSessionActionDetails 参数。枚举共十七个成员:play、pause、seekbackward、seekforward、previoustrack、nexttrack、skipad、stop、seekto、togglemicrophone、togglecamera、togglescreenshare、hangup、previousslide、nextslide、enterpictureinpicture、voiceactivity。
MediaSessionActionDetails 成员 |
类型 | 出现时机 | 说明 |
|---|---|---|---|
action |
MediaSessionAction |
每次调用 | 正在投递的动作;required。 |
seekOffset |
double |
seekbackward、seekforward(可选) |
要移动的秒数;缺省时由页面自定步长。 |
seekTime |
double |
seekto(必有) |
目标时间点,单位秒。 |
fastSeek |
boolean |
seekto(可选) |
用户仍在拖动进度条时为 true;序列中最后一次调用为 false。 |
isActivating |
boolean |
togglemicrophone、togglecamera、togglescreenshare |
浏览器即将暂停该类全部输入源时为 false。 |
enterPictureInPictureReason |
"other"、"useraction"、"contentoccluded" |
enterpictureinpicture(必有) |
浏览器要求页面进入画中画的原因。 |
new MediaMetadata(init) 接受一个 MediaMetadataInit 字典,所有成员均可选。
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title |
DOMString |
"" |
曲目或单集标题。 |
artist |
DOMString |
"" |
表演者或作者。 |
album |
DOMString |
"" |
专辑、节目或播放列表名。 |
artwork |
sequence<MediaImage> |
[] |
供系统挑选的图片。每个 MediaImage 有必填的 src(USVString,按文档 base URL 解析)、可选的 sizes(与 <link sizes> 语法相同,如 "512x512")和可选的 type(MIME 类型)。 |
chapterInfo |
sequence<ChapterInformationInit> |
[] |
章节标题、起始时间与封面;仅 Chrome 127(BCD api.MediaMetadata.chapterInfo)。 |
setPositionState(state) 接受一个 MediaPositionState 字典。
| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
duration |
unrestricted double |
是(字典为空时除外) | 时长,单位秒;直播流用 Infinity。 |
position |
double |
否(默认 0) |
最近一次报告的播放位置,单位秒,范围 0 到 duration。 |
playbackRate |
double |
否(默认 1.0) |
正数表示正向播放,负数表示倒放;不得为零。 |
规范定义的每个异常都是 TypeError。
| 方法 | 条件 |
|---|---|
setActionHandler() |
action 不是十七个枚举值之一(WebIDL 枚举转换)。 |
setPositionState() |
非空字典缺少 duration;duration 为负数或 NaN;position 为负数或大于 duration;playbackRate 为 0。 |
new MediaMetadata() |
某个 artwork 条目的 src 无法解析为 URL。 |
setMicrophoneActive()、setCameraActive()、setScreenshareActive() 在文档不是 fully active 时以 InvalidStateError 拒绝;active 为 true 而文档不可见时也可能以 InvalidStateError 拒绝。playbackState 不抛任何异常:无效字符串被忽略,保留上一个有效值。Chromium 多抛一个 TypeError:运行时特性关闭时的 skipad(见实测框)。
每个示例都先检查 'mediaSession' in navigator。回退方案是页面自己的播放控件:<audio> 元素本身不受影响,所以在 Android WebView 和任何没有该 API 的浏览器中它们照常工作。
为播客播放器注册元数据与动作处理函数
Section titled “为播客播放器注册元数据与动作处理函数”把每个 setActionHandler() 调用包进各自的 try 块,浏览器不认识的动作(Chrome 73 到 77 的 seekto、Chrome 之外的 previousslide)就会被跳过,而不是中断循环、让剩下的按钮没有注册。根据元素自身事件设置 playbackState,用户在页面内而不是在系统控件里暂停时,系统的播放/暂停图标也能保持正确。
const audio = document.querySelector('audio');
function registerMediaSession(episode) { if (!('mediaSession' in navigator)) { audio.controls = true; // 只保留页面内控件 return; }
navigator.mediaSession.metadata = new MediaMetadata({ title: episode.title, artist: episode.show, album: `第 ${episode.season} 季`, artwork: [ { src: episode.art512, sizes: '512x512', type: 'image/png' }, { src: episode.art96, sizes: '96x96', type: 'image/png' }, ], });
const handlers = { play: () => audio.play(), pause: () => audio.pause(), seekbackward: ({ seekOffset }) => { audio.currentTime -= seekOffset ?? 15; }, seekforward: ({ seekOffset }) => { audio.currentTime += seekOffset ?? 30; }, nexttrack: () => loadEpisode(episode.next), };
for (const [action, handler] of Object.entries(handlers)) { try { navigator.mediaSession.setActionHandler(action, handler); } catch (err) { console.info(`Media Session 动作 "${action}" 不可用:${err.message}`); } }
audio.addEventListener('play', () => { navigator.mediaSession.playbackState = 'playing'; }); audio.addEventListener('pause', () => { navigator.mediaSession.playbackState = 'paused'; });}播客有意不注册 previoustrack:没有处理函数时系统会隐藏该按钮,比注册一个什么都不做的处理函数更好。
报告播放位置,让锁屏显示进度条
Section titled “报告播放位置,让锁屏显示进度条”系统会根据最近一次报告外推位置,所以页面只需在播放开始、跳转或变速时调用 setPositionState(),不必在每个 timeupdate 上调用。seekto 处理函数尊重 fastSeek:拖动过程中的中间事件用 fastSeek(),最后一次用精确赋值。对 NaN 时长的防护在 loadedmetadata 之前很重要,因为规范对 NaN 以 TypeError 拒绝。
function reportPosition() { if (!('mediaSession' in navigator) || !('setPositionState' in navigator.mediaSession)) return; if (!Number.isFinite(audio.duration) && audio.duration !== Infinity) return; // 元数据尚未加载
navigator.mediaSession.setPositionState({ duration: audio.duration, playbackRate: audio.playbackRate, position: Math.min(audio.currentTime, audio.duration), });}
for (const event of ['loadedmetadata', 'play', 'seeked', 'ratechange']) { audio.addEventListener(event, reportPosition);}
if ('mediaSession' in navigator) { try { navigator.mediaSession.setActionHandler('seekto', ({ seekTime, fastSeek }) => { if (fastSeek && 'fastSeek' in audio) { audio.fastSeek(seekTime); return; } audio.currentTime = seekTime; reportPosition(); }); } catch { // 不支持 seekto:系统不显示进度条,页面内跳转仍可用 }}把 position 钳制到 duration 以内,可以避免 ended 事件时 currentTime 多出几毫秒而触发的 TypeError。
- Document Picture-in-Picture API,与
enterpictureinpicture动作配套 - Screen Wake Lock API,需要保持屏幕常亮的视频场景
- 通知的 actions 与 badge 选项
- Media Session Standard: setActionHandler() method(w3.org)
- Media Session Standard: setPositionState() method(w3.org)
- Customize media notifications and playback controls with the Media Session API(web.dev)
- MediaSession browser compatibility(developer.mozilla.org)
规范
| 规范 | 状态 |
|---|---|
| Media Session Standard: setActionHandler() method | W3C 草案 |
| Media Session Standard: setPositionState() method | W3C 草案 |
| Media Session Standard: MediaMetadata constructor | W3C 草案 |