跳转到内容

能力 · 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。

规范

规范状态
Media Session Standard: setActionHandler() methodW3C 草案
Media Session Standard: setPositionState() methodW3C 草案
Media Session Standard: MediaMetadata constructorW3C 草案