赋予用户控制权:媒体会话 API

Avatar of Idorenyin Udoh
Idorenyin Udoh

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

假设这样一个场景:您在一个打开的浏览器标签页中播放了一首很棒的 Kendrick Lamar 的歌曲。您很喜欢这首歌,但这时有人走进了您的房间,您需要暂停它。哪个标签页在播放?浏览器试图稍微帮助您解决这个问题。您可能可以静音整个系统音频。但如果能够在不一定要找到该标签页的情况下,直接控制音频播放,岂不是更好?

媒体会话 API 使这成为可能。它允许用户在浏览器标签页之外控制媒体播放。如果实现,它将可以在设备上的多个位置使用,包括

  • 许多移动设备上的通知区域,
  • 其他可穿戴设备,以及
  • 许多桌面设备的媒体中心区域。

此外,媒体会话 API 允许我们使用媒体键和语音助手(如 Siri、Google Assistant、Bixby 或 Alexa)来控制媒体播放。

媒体会话 API

媒体会话 API 主要由以下两个接口组成:

  • MediaMetadata
  • MediaSession

MediaMetadata 接口提供有关正在播放的媒体的数据。它负责告知我们媒体的标题、专辑、封面和艺术家(在本例中为 Kendrick Lamar)。MediaSession 接口负责媒体播放功能。

在我们深入探讨主题之前,我们需要了解特征检测。在实现功能之前检查浏览器是否支持该功能是一个好习惯。要检查浏览器是否支持媒体会话 API,我们需要在 JavaScript 文件中包含以下内容:

if ('mediaSession' in navigator) {
  // Our media session api that lets us seek to the beginning of Kendrick Lamar's "Alright"
}

MediaMetadata 接口

构造函数 MediaMetadata.MediaMetadata() 创建一个新的 MediaMetadata 对象。创建后,我们可以添加以下属性:

  • MediaMetadata.title 设置或获取正在播放的媒体的标题。
  • MediaMetadata.artist 设置或获取正在播放的媒体的艺术家或乐队的名称。
  • MediaMetadata.album 设置或获取包含正在播放的媒体的专辑名称。
  • MediaMetadata.artwork 设置或获取与正在播放的媒体相关的图像数组。

MediaMetadata 对象的 artwork 属性的值是一个 MediaImage 对象的数组。MediaImage 对象包含描述与媒体关联的图像的详细信息。这些对象具有以下三个属性:

  • src:图像的 URL
  • sizes:指示图像的大小,以便无需缩放单个图像。
  • type:图像的 MIME 类型

让我们为 Kendrick Lamar 的专辑 To Pimp a Butterfly 中的歌曲“Alright”创建一个 MediaMetadata 对象。

if ('mediaSession' in navigator) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Alright',
    artist: 'Kendrick Lamar',
    album: 'To Pimp A Butterfly',
    artwork: [
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },
      // More sizes, like 192x192, 256x256, 384x384, and 512x512
    ]
  });
}

MediaSession 接口

如前所述,它允许用户控制媒体的播放。我们可以通过此接口对正在播放的媒体执行以下操作:

  • play:播放媒体
  • pause:暂停媒体
  • previoustrack:切换到上一曲目
  • nexttrack:切换到下一曲目
  • seekbackward:从当前位置后退几秒钟
  • seekforward:从当前位置快进几秒钟
  • seekto:从当前位置跳转到指定时间点
  • stop:停止媒体播放
  • skipad:跳过正在播放的广告(如果有)

MediaSessionAction 枚举类型将这些操作作为字符串类型提供。要支持任何这些操作,我们必须使用 MediaSessionsetActionHandler() 方法为该操作定义一个处理程序。该方法接受操作和一个回调函数,当用户调用该操作时,该回调函数会被调用。让我们稍微深入了解一下,以便更好地理解它。

要为 playpause 操作设置处理程序,我们需要在 JavaScript 文件中包含以下内容:

let alright = new HTMLAudioElement();

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('play', () => {
    alright.play();
  });
  navigator.mediaSession.setActionHandler('pause', () => {
    alright.pause();
  });
}

在这里,当用户通过媒体界面播放时,我们将曲目设置为播放;当用户暂停时,将其设置为暂停

对于 previoustracknexttrack 操作,我们需要包含以下内容:

let u = new HTMLAudioElement();
let forSaleInterlude = new HTMLAudioElement();

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('previoustrack', () => {
    u.play();
  });
  navigator.mediaSession.setActionHandler('nexttrack', () => {
    forSaleInterlude.play();
  });
}

如果您不是 Kendrick Lamar 的粉丝,这可能不太容易理解,但希望您能掌握要点。当用户想要播放上一曲目时,我们将上一曲目设置为播放;当是下一曲目时,则为下一曲目。

要实现 seekbackwardseekforward 操作,我们需要包含以下内容:

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('seekbackward', (details) => {
    alright.currentTime = alright.currentTime - (details.seekOffset || 10);
  });
  navigator.mediaSession.setActionHandler('seekforward', (details) => {
    alright.currentTime = alright.currentTime + (details.seekOffset || 10);
  });
}

鉴于我认为这些内容都不容易理解,我想对 seekbackwardseekforward 操作进行简要说明。这两个操作的处理程序 seekbackwardseekforward 在用户想要后退或快进几秒钟时被触发,顾名思义。MediaSessionActionDetails 字典在 seekOffset 属性中提供“几秒钟”的值。但是,seekOffset 属性并不总是存在,因为并非所有用户代理的行为都相同。当它不存在时,我们应该将曲目设置为后退或快进“几秒钟”,这对我们来说是有意义的。因此,我们使用 10 秒,因为它相当长。简而言之,如果提供了 seekOffset 秒,我们将曲目设置为跳转该时间;如果没有提供,则跳转 10 秒。

要向我们的媒体会话 API 添加 seekto 功能,我们需要包含以下代码片段:

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('seekto', (details) => {
    if (details.fastSeek && 'fastSeek' in alright) {
      alright.fastSeek(details.seekTime);
      return;
    }
    alright.currentTime = details.seekTime;
  });
}

在这里,MediaSessionActionDetails 字典提供 fastSeekseekTime 属性。fastSeek 基本上是快速执行的跳转(如快进或倒带),而 seekTime 是曲目应该跳转到的时间。虽然 fastSeek 是一个可选属性,但对于 seekto 操作处理程序,MediaSessionActionDetails 字典始终提供 seekTime 属性。因此,从根本上讲,当属性可用且用户快速跳转时,我们将曲目 fastSeek 设置为 seekTime;当用户只是跳转到指定时间时,我们将其设置为 seekTime

虽然我不知道为什么有人会想停止 Kendrick 的歌曲,但描述 MediaSession 接口的 stop 操作处理程序也不会造成什么伤害:

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('stop', () => {
    alright.pause();
    alright.currentTime = 0;
  });
} 

当广告正在播放且用户想要跳过它以便继续收听 Kendrick Lamar 的 “Alright 歌曲时,用户会调用 skipad(就像“跳过广告”而不是“跳过垫子”)操作处理程序。老实说,skipad 操作处理程序的完整细节超出了我对“媒体会话 API”的理解范围。因此,如果您真的想实现它,可能应该在阅读完本文后自己查找相关信息。

总结

我们需要注意一件事。每当用户播放曲目、跳转或更改播放速度时,我们都应该更新媒体会话 API 提供的界面上的位置状态。我们用来实现此目的的是 mediaSession 对象的 setPositionState() 方法,如下所示:

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

此外,我想提醒您,并非所有用户的浏览器都支持所有操作。因此,建议在 try...catch 块中设置操作处理程序,如下所示:

const actionsAndHandlers = [
  ['play', () => { /*...*/ }],
  ['pause', () => { /*...*/ }],
  ['previoustrack', () => { /*...*/ }],
  ['nexttrack', () => { /*...*/ }],
  ['seekbackward', (details) => { /*...*/ }],
  ['seekforward', (details) => { /*...*/ }],
  ['seekto', (details) => { /*...*/ }],
  ['stop', () => { /*...*/ }]
]
 
for (const [action, handler] of actionsAndHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action, ${action}, is not supported`);
  }
}

将我们所做的一切整合在一起,我们将得到以下内容:

let alright = new HTMLAudioElement();
let u = new HTMLAudioElement();
let forSaleInterlude = new HTMLAudioElement();

const updatePositionState = () => {
  navigator.mediaSession.setPositionState({
    duration: alright.duration,
    playbackRate: alright.playbackRate,
    position: alright.currentTime
  });
}
 
const actionsAndHandlers = [
  ['play', () => {
    alright.play();
    updatePositionState();
  }],
  ['pause', () => { alright.pause(); }],
  ['previoustrack', () => { u.play(); }],
  ['nexttrack', () => { forSaleInterlude.play(); }],
  ['seekbackward', (details) => {
    alright.currentTime = alright.currentTime - (details.seekOffset || 10);
    updatePositionState();
  }],
  ['seekforward', (details) => {
    alright.currentTime = alright.currentTime + (details.seekOffset || 10);
    updatePositionState();
  }],
  ['seekto', (details) => {
    if (details.fastSeek && 'fastSeek' in alright) {
      alright.fastSeek(details.seekTime);
      updatePositionState();
      return;
    }
    alright.currentTime = details.seekTime;
    updatePositionState();
  }],
  ['stop', () => {
    alright.pause();
    alright.currentTime = 0;
  }],
]
 
if ( 'mediaSession' in navigator ) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Alright',
    artist: 'Kendrick Lamar',
    album: 'To Pimp A Butterfly',
    artwork: [
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },
      { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },
      // More sizes, like 192x192, 256x256, 384x384, and 512x512
    ]
  });
 
  for (const [action, handler] of actionsAndHandlers) {
    try {
      navigator.mediaSession.setActionHandler(action, handler);
    } catch (error) {
      console.log(`The media session action, ${action}, is not supported`);
    }
  }
}

以下是该 API 的演示:

我实现了六个操作。请在您空闲时间尝试其余操作。

如果您在移动设备上查看此 Pen,请注意它如何显示在您的通知区域中。

如果您的智能手表已与您的设备配对,请快速查看一下。

如果您在桌面上使用 Chrome 查看 Pen,请导航到媒体中心并使用那里的媒体按钮。演示甚至有多个曲目,因此您可以尝试前后切换曲目。

如果您看到了这里(或者没有),感谢您的阅读,并且恳请您在您创建的下一个具有媒体功能的应用程序中实现此 API。