Media Session API 让音频视频 PWA 拥有原生级播放体验
面向 PWA 媒体应用的 Media Session 实战模式,让播放控制与系统控件在锁屏与通知场景下保持同步。
为什么媒体 PWA 需要集成 Media Session
当用户在 PWA 中播放音频或视频时,他们期望得到与原生应用相同的控制体验:锁屏播放、通知媒体控件,以及耳机按键处理。Media Session API 让这种集成成为可能,但前提是 PWA 在播放过程中主动设置并更新媒体元数据。
如果没有 Media Session,音频会继续播放,但在 OS 控制层却没有任何痕迹。当音频停止,或者用户切换到其他页面时,系统提供的常规暂停/前进控制会消失。实际效果是:应用在理应"原生化"的场景下缺乏原生感。
在播放开始时初始化 Media Session
媒体开始播放后立即初始化,而不是在页面加载时就做:
const audio = new Audio('track.mp3');
audio.addEventListener('play', () => {
navigator.mediaSession.metadata = new MediaMetadata({
title: '曲目名称',
artist: '演唱者',
album: '专辑名称',
artwork: [
{ src: '/album-art-96.png', sizes: '96x96', type: 'image/png' },
{ src: '/album-art-256.png', sizes: '256x256', type: 'image/png' },
{ src: '/album-art-512.png', sizes: '512x512', type: 'image/png' }
]
});
navigator.mediaSession.setActionHandler('play', () => {
audio.play();
});
navigator.mediaSession.setActionHandler('pause', () => {
audio.pause();
});
navigator.mediaSession.setActionHandler('previoustrack', () => {
// 切到上一首
});
navigator.mediaSession.setActionHandler('nexttrack', () => {
// 切到下一首
});
audio.play();
});等待媒体元素实际开始播放后再设置元数据。设置得太早可能导致浏览器延迟处理或忽略会话。
持续更新可定位位置
锁屏控件与进度条只有在 PWA 主动更新 Media Session 播放状态时才能显示可定位范围:
function updateMediaSession() {
if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState({
duration: audio.duration,
playbackRate: audio.playbackRate,
position: audio.currentTime
});
}
}
audio.addEventListener('timeupdate', () => {
updateMediaSession();
});
// 也要在用户手动拖动时更新
audio.addEventListener('seeked', () => {
updateMediaSession();
});如果发现性能问题,可以对 timeupdate 事件做节流一秒钟通常足以满足 UI 更新需求。
告知浏览器哪些操作可用
Media Session 处理函数声明用户可以使用哪些功能。只为应用实际支持的操作设置处理函数:
function setAvailableActions(canSkip, canSeek) {
navigator.mediaSession.setActionHandler('play', () => audio.play());
navigator.mediaSession.setActionHandler('pause', () => audio.pause());
if (canSkip) {
navigator.mediaSession.setActionHandler('previoustrack', () => playPrevious());
navigator.mediaSession.setActionHandler('nexttrack', () => playNext());
} else {
navigator.mediaSession.setActionHandler('previoustrack', null);
navigator.mediaSession.setActionHandler('nexttrack', null);
}
if (canSeek) {
navigator.mediaSession.setActionHandler('seekto', (details) => {
audio.currentTime = details.seekTime;
});
} else {
navigator.mediaSession.setActionHandler('seekto', null);
}
}每次曲目信息或应用状态变化时调用 setAvailableActions()——比如播放列表结束,或者当前曲目没有相邻曲目时。
链接前台与后台状态
PWA 处于后台时,service worker 或页面可能无法立即收到所有事件。处理可见性变化,保持媒体状态一致:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 更新媒体会话,与当前实际播放时间同步
updateMediaSession();
}
});
// 在 service worker 中,监听发送给客户端的媒体会话事件
self.addEventListener('message', (event) => {
if (event.data.type === 'MEDIA_ACTION') {
switch (event.data.action) {
case 'play':
// 在客户端安排播放
event.ports[0].postMessage({ action: 'play' });
break;
case 'pause':
event.ports[0].postMessage({ action: 'pause' });
break;
}
}
});某些浏览器将 Media Session 操作直接发送给活跃 worker,其他浏览器则发送给页面。请在锁屏控制最关键的手机端进行测试。
清单:可安装媒体 PWA 的 Media Session 就绪检查
- [ ] Media Session 元数据在
play()触发后设置,而不是更早。 - [ ] 播放位置随
timeupdate与seeked事件持续更新。 - [ ] 根据可用操作(跳过/拖动/停止)动态更新处理函数。
- [ ] 页面重新可见时重新同步媒体状态。
- [ ] 在目标浏览器上,service worker 或消息通道负责后台操作的派发。
- [ ] 封面图 URL 无需认证并使用 HTTPS。
- [ ] 仅在支持时使用
setPositionState,对旧浏览器做降级。 - [ ] 不可用时将操作设为 null,避免 OS 控件出现困惑。
这对可安装的媒体 PWA 意味着什么
安装后,用户自然期望在应用切换时播放可以可靠持续。Media Session 正是这种期望的一部分:锁屏控件、耳机按钮、通知播放控件都应保持一致。当用户安装一个媒体 PWA,他们默认会拥有"原生般"的播放集成——这个 API 是实现体验的关键。
把 Media Session 当作播放核心的一部分,而不是可选增强。当播放状态是单一事实源,而 OS 控件只是反映该状态的 UI 层时,集成效果最佳。