使用 Media Session API 自定义媒体通知和播放控件

如何与硬件媒体键集成、自定义媒体通知等。

François Beaufort
François Beaufort

为了让用户知道当前在浏览器中播放的内容,并在不返回启动页面的情况下对其进行控制,我们引入了 Media Session API。它允许 Web 开发者通过自定义媒体通知中的元数据、媒体事件(如播放、暂停、查找、曲目更改)以及视频会议事件(如麦克风静音/取消静音、打开/关闭摄像头和挂断)来定制此体验。这些自定义功能在多种情况下可用,包括桌面媒体中心、移动设备上的媒体通知,甚至可穿戴设备。我将在本文中介绍这些自定义功能。

Screenshots of Media Session contexts.
桌面上的媒体中心、移动设备上的媒体通知和可穿戴设备。

关于 Media Session API

Media Session API 提供了多项优势和功能

  • 支持硬件媒体键。
  • 可在移动设备、桌面设备和配对的可穿戴设备上自定义媒体通知。
  • 桌面设备上提供了媒体中心
  • 锁定屏幕媒体控件在 ChromeOS 和移动设备上可用。
  • 画中画窗口控件可用于 音频播放视频会议幻灯片演示
  • 移动设备上提供了 Assistant 集成。

浏览器支持

  • Chrome: 73.
  • Edge: 79.
  • Firefox: 82.
  • Safari: 15.

来源

以下几个示例将说明其中一些要点。

示例 1: 如果用户按下键盘的“下一曲目”媒体键,Web 开发者可以处理此用户操作,无论浏览器是在前台还是在后台。

示例 2: 如果用户在设备屏幕锁定时在 Web 上收听播客,他们仍然可以点击锁定屏幕媒体控件中的“后退查找”图标,以便 Web 开发者将播放时间向后移动几秒钟。

示例 3: 如果用户有标签页正在播放音频,他们可以轻松地从桌面上的媒体中心停止播放,以便 Web 开发者有机会清除其状态。

示例 4: 如果用户正在进行视频通话,他们可以按画中画窗口中的“切换麦克风”控件,以阻止网站接收麦克风数据。

这一切都是通过两个不同的接口完成的:MediaSession 接口和 MediaMetadata 接口。第一个接口允许用户控制正在播放的内容。第二个接口告诉 MediaSession 需要控制的内容。

为了说明这一点,下图显示了这些接口如何与特定的媒体控件相关联,在本例中为移动设备上的媒体通知。

Media Session interfaces illustration.
移动设备上媒体通知的结构。

让用户知道正在播放的内容

当网站正在播放音频或视频时,用户会自动在移动设备上的通知托盘或桌面上的媒体中心收到媒体通知。浏览器会尽力通过使用文档的标题和它可以找到的最大图标图像来显示适当的信息。使用 Media Session API,可以自定义媒体通知,使其包含更丰富的媒体元数据,例如标题、艺术家姓名、专辑名称和艺术作品,如下所示。

只有当媒体时长至少为 5 秒时,Chrome 才会请求“完整”音频焦点以显示媒体通知。这确保了叮咚声等偶然的声音不会显示通知。

// After media (video or audio) starts playing
await document.querySelector("video").play();

if ("mediaSession" in navigator) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      { src: 'https://via.placeholder.com/96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/192', sizes: '192x192', type: 'image/png' },
      { src: 'https://via.placeholder.com/256', sizes: '256x256', type: 'image/png' },
      { src: 'https://via.placeholder.com/384', sizes: '384x384', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  });

  // TODO: Update playback state.
}

当播放结束时,无需“释放”媒体会话,因为通知会自动消失。请记住,当下次播放开始时,仍会使用 navigator.mediaSession.metadata。这就是为什么当媒体播放源更改时更新它很重要,以确保在媒体通知中显示相关信息。

关于媒体元数据,有几点需要注意。

  • 通知艺术作品阵列支持 Blob URL 和数据 URL。
  • 如果未定义任何艺术作品,并且存在所需尺寸的图标图像(使用 <link rel=icon> 指定),则媒体通知将使用它。
  • Chrome for Android 中的通知艺术作品目标尺寸为 512x512。对于低端设备,尺寸为 256x256
  • 媒体 HTML 元素的 title 属性用于 macOS “正在播放”小部件。
  • 如果媒体资源是嵌入式的(例如在 iframe 中),则必须从嵌入式上下文中设置 Media Session API 信息。请参阅下面的代码段。
<iframe id="iframe">
  <video>...</video>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

您还可以向媒体元数据添加单独的章节信息,例如章节标题、时间戳和屏幕截图图像。这允许用户浏览媒体内容。

navigator.mediaSession.metadata = new MediaMetadata({
  // title, artist, album, artwork, ...
  chapterInfo: [{
    title: 'Chapter 1',
    startTime: 0,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }, {
    title: 'Chapter 2',
    startTime: 42,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }]
});
Chapter information displayed in a ChromeOS media notification.
ChromeOS 中具有章节功能的媒体通知。

让用户控制正在播放的内容

媒体会话操作是网站可以为用户处理的操作(例如“播放”或“暂停”),当用户与当前媒体播放进行交互时。操作类似于事件,并且工作方式与事件非常相似。与事件一样,操作是通过在适当的对象(在本例中为 MediaSession 的实例)上设置处理程序来实现的。当用户按下耳机、另一个远程设备、键盘上的按钮或与媒体通知交互时,会触发某些操作。

Screenshot of a media notification in Windows 10.
Windows 10 中的自定义媒体通知。

由于某些媒体会话操作可能不受支持,因此建议在设置它们时使用 try…catch 代码块。

const actionHandlers = [
  ['play',          () => { /* ... */ }],
  ['pause',         () => { /* ... */ }],
  ['previoustrack', () => { /* ... */ }],
  ['nexttrack',     () => { /* ... */ }],
  ['stop',          () => { /* ... */ }],
  ['seekbackward',  (details) => { /* ... */ }],
  ['seekforward',   (details) => { /* ... */ }],
  ['seekto',        (details) => { /* ... */ }],
  /* Video conferencing actions */
  ['togglemicrophone', () => { /* ... */ }],
  ['togglecamera',     () => { /* ... */ }],
  ['hangup',           () => { /* ... */ }],
  /* Presenting slides actions */
  ['previousslide', () => { /* ... */ }],
  ['nextslide',     () => { /* ... */ }],
];

for (const [action, handler] of actionHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action "${action}" is not supported yet.`);
  }
}

取消设置媒体会话操作处理程序就像将其设置为 null 一样简单。

try {
  // Unset the "nexttrack" action handler at the end of a playlist.
  navigator.mediaSession.setActionHandler('nexttrack', null);
} catch (error) {
  console.log(`The media session action "nexttrack" is not supported yet.`);
}

设置后,媒体会话操作处理程序将在媒体播放期间持续存在。这类似于事件侦听器模式,不同之处在于处理事件意味着浏览器停止执行任何默认行为,并将此用作网站支持媒体操作的信号。因此,除非设置了适当的操作处理程序,否则不会显示媒体操作控件。

Screenshot of the Now Playing widget in macOS Big Sur.
macOS Big Sur 中的“正在播放”小部件。

播放/暂停

"play" 操作表示用户想要恢复媒体播放,而 "pause" 表示想要暂时停止播放。

“播放/暂停”图标始终显示在媒体通知中,并且浏览器会自动处理相关的媒体事件。要覆盖其默认行为,请处理“播放”和“暂停”媒体操作,如下所示。

例如,当查找或加载时,浏览器可能会认为网站未播放媒体。在这种情况下,通过将 navigator.mediaSession.playbackState 设置为 "playing""paused" 来覆盖此行为,以确保网站 UI 与媒体通知控件保持同步。

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('play', async () => {
  // Resume playback
  await video.play();
});

navigator.mediaSession.setActionHandler('pause', () => {
  // Pause active playback
  video.pause();
});

video.addEventListener('play', () => {
  navigator.mediaSession.playbackState = 'playing';
});

video.addEventListener('pause', () => {
  navigator.mediaSession.playbackState = 'paused';
});

上一曲目

"previoustrack" 操作表示用户想要从头开始当前媒体播放(如果媒体播放具有开始的概念),或者移动到播放列表中的上一项(如果媒体播放具有播放列表的概念)。

navigator.mediaSession.setActionHandler('previoustrack', () => {
  // Play previous track.
});

下一曲目

"nexttrack" 操作表示用户想要将媒体播放移动到播放列表中的下一项(如果媒体播放具有播放列表的概念)。

navigator.mediaSession.setActionHandler('nexttrack', () => {
  // Play next track.
});

停止

"stop" 操作表示用户想要停止媒体播放并在适当的情况下清除状态。

navigator.mediaSession.setActionHandler('stop', () => {
  // Stop playback and clear state if appropriate.
});

后退/前进查找

"seekbackward" 操作表示用户想要将媒体播放时间向后移动一小段时间,而 "seekforward" 表示想要将媒体播放时间向前移动一小段时间。在两种情况下,一小段时间都表示几秒钟。

操作处理程序中提供的 seekOffset 值是要将媒体播放时间移动的时间(以秒为单位)。如果未提供(例如 undefined),则应使用合理的时间(例如 10-30 秒)。

const video = document.querySelector('video');
const defaultSkipTime = 10; /* Time to skip in seconds by default */

navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
  // TODO: Update playback state.
});

navigator.mediaSession.setActionHandler('seekforward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  // TODO: Update playback state.
});

查找至特定时间

"seekto" 操作表示用户想要将媒体播放时间移动到特定时间。

操作处理程序中提供的 seekTime 值是要将媒体播放时间移动到的时间(以秒为单位)。

如果操作作为序列的一部分被多次调用,并且这不是该序列中的最后一次调用,则操作处理程序中提供的 fastSeek 布尔值为 true。

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('seekto', (details) => {
  if (details.fastSeek && 'fastSeek' in video) {
    // Only use fast seek if supported.
    video.fastSeek(details.seekTime);
    return;
  }
  video.currentTime = details.seekTime;
  // TODO: Update playback state.
});

设置播放位置

在通知中准确显示媒体播放位置非常简单,只需在适当的时间设置位置状态即可,如下所示。位置状态是媒体播放速率、时长和当前时间的组合。

Screenshot of lock screen media controls in ChromeOS.
ChromeOS 中的锁定屏幕媒体控件。

必须提供时长且为正数。位置必须为正数且小于时长。播放速率必须大于 0。

const video = document.querySelector('video');

function updatePositionState() {
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: video.duration,
      playbackRate: video.playbackRate,
      position: video.currentTime,
    });
  }
}

// When video starts playing, update duration.
await video.play();
updatePositionState();

// When user wants to seek backward, update position.
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek forward, update position.
navigator.mediaSession.setActionHandler('seekforward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek to a specific time, update position.
navigator.mediaSession.setActionHandler('seekto', (details) => {
  /* ... */
  updatePositionState();
});

// When video playback rate changes, update position state.
video.addEventListener('ratechange', (event) => {
  updatePositionState();
});

重置位置状态就像将其设置为 null 一样简单。

// Reset position state when media is reset.
navigator.mediaSession.setPositionState(null);

视频会议操作

当用户将其视频通话放入画中画窗口时,浏览器可能会显示麦克风和摄像头以及挂断的控件。当用户单击这些控件时,网站会通过下面的视频会议操作来处理它们。有关示例,请参阅视频会议示例

Screenshot of video conferencing controls in a Picture-in-Picture window.
画中画窗口中的视频会议控件。

切换麦克风

"togglemicrophone" 操作表示用户想要使麦克风静音或取消静音。setMicrophoneActive(isActive) 方法告诉浏览器网站当前是否认为麦克风处于活动状态。

let isMicrophoneActive = false;

navigator.mediaSession.setActionHandler('togglemicrophone', () => {
  if (isMicrophoneActive) {
    // Mute the microphone.
  } else {
    // Unmute the microphone.
  }
  isMicrophoneActive = !isMicrophoneActive;
  navigator.mediaSession.setMicrophoneActive(isMicrophoneActive);
});

切换摄像头

"togglecamera" 操作表示用户想要打开或关闭活动摄像头。setCameraActive(isActive) 方法指示浏览器是否认为网站处于活动状态。

let isCameraActive = false;

navigator.mediaSession.setActionHandler('togglecamera', () => {
  if (isCameraActive) {
    // Disable the camera.
  } else {
    // Enable the camera.
  }
  isCameraActive = !isCameraActive;
  navigator.mediaSession.setCameraActive(isCameraActive);
});

挂断

"hangup" 操作表示用户想要结束通话。

navigator.mediaSession.setActionHandler('hangup', () => {
  // End the call.
});

幻灯片演示操作

当用户将其幻灯片演示文稿放入画中画窗口时,浏览器可能会显示用于浏览幻灯片的控件。当用户单击这些控件时,网站会通过 Media Session API 来处理它们。有关示例,请参阅幻灯片演示示例

上一张幻灯片

"previousslide" 操作表示用户想要在演示幻灯片时返回到上一张幻灯片。

navigator.mediaSession.setActionHandler('previousslide', () => {
  // Show previous slide.
});

浏览器支持

  • Chrome: 111.
  • Edge: 111.
  • Firefox:不支持。
  • Safari:不支持。

下一张幻灯片

"nextslide" 操作表示用户想要在演示幻灯片时转到下一张幻灯片。

navigator.mediaSession.setActionHandler('nextslide', () => {
  // Show next slide.
});

浏览器支持

  • Chrome: 111.
  • Edge: 111.
  • Firefox:不支持。
  • Safari:不支持。

示例

查看一些 Media Session 示例,其中包含 Blender FoundationJan Morgenstern 的作品

一个演示 Media Session API 的屏幕录像。

资源