解析B站Chrome浏览器上的媒体控件实现原理

相信大家在使用chrome浏览B站的时候,会注意到浏览器的右上角会多出一个媒体控件按钮。

点一下会弹出这样一个框。

通过这个框我们可以实现对视频的后退、暂停、播放、快进等功能,并且即使在不同的标签页,也可以操作。

它是如何实现的呢?

Media Session API

Media Session API 是一个 Web API,它允许开发者在网页上控制媒体会话。通过此 API,网页可以控制用户当前正在使用的音频或视频的播放、暂停和跳转操作,并显示相关的媒体信息,如标题、艺术家和封面图像。

我们可以在MDN上看到它相关的介绍。

如何使用

首先我们需要一个可交互的媒体,我们这里用视频来举例。这里需要页面渲染出一个video标签。

html 复制代码
<video src="./test.mp4" controls id="test"></video>

然后,使用MediaMetadata,给navigator.mediaSession.metadata注册一个一个新的MediaSession对象。

js 复制代码
navigator.mediaSession.metadata = new MediaMetadata({
  title: "快来领优惠券", // 媒体标题
  artist: "开封菜", // 艺术家名字
  album: "疯狂星期三", // 专辑名称
  artwork: [{ src: "3.jpeg" }], // 封面
})

然后,当我们播放视频的时候,媒体控件就会变成如下。

这里需要注意一点,封面图片链接仅支持httphttpsdatablob,不支持file,因此想调试的同学可以在本地服务器或者将图片转为blob

有的同学就问了,为什么你的控件跟B站的长得不一样啊,它的下面有不同的按钮,并且,字体颜色也不一样。

按钮的多少与事件劫持与监听有关,我们之后再说。

这里需要提醒一点。字体颜色是浏览器根据背景色自动生成的,因此我们无法进行自定义。

还有同学发现了,封面字段artwork是一个数组。

那么它接受什么样的参数呢?我们截取一段官方的例子。

js 复制代码
artwork: [
  {
    src: "./96x96.png",
    sizes: "96x96",
    type: "image/png",
  },
  {
    src: "./128x128.png",
    sizes: "128x128",
    type: "image/png",
  },
]

从例子中就可以看出来,artwork就是包含封面图像信息的数组,每个信息是一个对象,它至少包括 src 属性表示图像的URL,以及可选的 sizestype 属性。

artwork 数组中,每个图像对象可以有以下属性:

  • src(必需):图像的 URL 地址。
  • sizes:图像的尺寸信息,通常以字符串形式描述,例如 '512x512'
  • type:图像的 MIME 类型,例如 'image/jpeg''image/png'

那么为什么是一个数组呢?

这个设计允许开发者提供多个封面图像的选择,而不仅限于一个。这样浏览器才可以根据出现的场景自动选择最优尺寸。

我们可以直接看看B站某个视频页面的metadata

可以看到,他们的确使用此API来创建的媒体控件。

事件

上文中我们提到按钮的多少与事件的监听与劫持有关,在这个API中,事件可以通过setActionHandler来进行操作。

js 复制代码
setActionHandler(type, callback)

type不同,callback的入参也是不同的。

因此我们针对type类型,来单独分析。

当然,并非所有类型我们都会分析,比如hangupskipad等,他们虽然使用了媒体控件,但这样的操作更适用于特定的应用场景,比如通话应用或视频平台中的广告跳过功能。

并不普适一般的媒体播放场景。

pause、play、previoustrack、nexttrack

pause类型用于媒体暂停的事件,当我们使用下面。代码,会在点击暂停按钮的时候,进入其中的callback

js 复制代码
navigator.mediaSession.setActionHandler("pause", function (p) {
  console.log(p) // {action: "pause"}
  video.pause()
})

这里需要注意,我前面提到事件的时候,提到了不止是监听,还有劫持。

意思就是,如果使用了setActionHandler注册了对应的是type,虽然对应的按钮会显示出来,但不会自动实现对应的功能。

对应的功能需要我们自己来实现,所以callback中我使用了video.pause()来实现视频的暂停。

如果没有使用任何setActionHandler注册事件,那么会默认显示播放、暂停按钮,这里的播放、暂停按钮是有对应的功能的,如果使用了setActionHandler注册了pauseplay事件,他们的默认功能将失效,改为我们自己实现。

这就是劫持。

当注册play事件,播放按钮会被劫持,callback的入参是{action: "play"}

当注册previoustrack事件,媒体控件会显示【上一首】按钮。点击按钮的时候,callback的入参是{action: "previoustrack"}

当注册nexttrack事件,媒体控件会显示【下一首】按钮。点击按钮的时候,callback的入参是{action: "nexttrack"}

seekbackward、seekforward

seekforwardseekforward类型表示前进和后退,如果注册它们,媒体控件会显示对应的【后退】、【前进】按钮。

但它们这样与上一节事件些许不同,callback可能会在入参多出一个属性。

请注意,这里说的可能,也就是说可能没有。如有。

js 复制代码
navigator.mediaSession.setActionHandler("seekbackward", function (p) {
  console.log(p) // {action: "seekbackward",seekOffset:10}
  let skipTime = p.seekOffset || 10;
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
})

除了固定的actionseekbackward或者seekforward以外,还可能存在seekOffset这个属性,seekOffset指示向前进后退的秒数。

如果这个属性存在,那么实现前进后退功能,就需要基于seekOffset来实现偏移时间,如果此属性不存在,则这些操作应选择合理的默认时间,比如一般是7秒或者10秒。

为什么seekOffset会是一个可选参数呢?那是因为Media Session被设计为与不同平台和设备兼容,各个平台的前进后退方案是不同的,并且可以个性化定制的,在某些平台,比如Chrome浏览器,默认是没有seekOffset

seekto

seekto 操作是用于移动媒体播放位置的。通过指定时间点(seekTime 属性),可以将媒体播放器定位到特定的时间。 当连续进行多次快速 seekto 操作时,fastSeektrue。这样做告诉浏览器可以优化这些连续的操作。浏览器可能会采取一些措施,例如使用更高效的方式来处理连续的寻找操作,以提高整体性能。

当我们使用setActionHandler注册了seekto,我们发现媒体控件并没有多出任何按钮,那我们如何触发这个事件呢?

我们提到Media Session是被设计为与不同平台和设备兼容,会发现在顶部也有一个类似的媒体控件按钮,这个媒体控件的按钮不会因为没有注册就隐藏,而是默认显示三个,【上一首】、【下一首】如果没有注册就会置灰。

点击之后会出现进度条,我们点击进度条就会触发seekto事件。

callback的入参如下。

js 复制代码
navigator.mediaSession.setActionHandler("seekto", function (p) {
  console.log(p) // {"action": "seekto","fastSeek": false,"seekTime": 105.247604}
})

callback的入参可能会包含一个可选属性 fastSeek,用于指示是否进行"快速"寻找。所谓"快速"寻找是指在媒体中进行快速跳跃,比如快进或快退时的快速跳过媒体片段。这个属性可以指示在寻找媒体时是否使用最快的方法。在这种情况下,最后一个动作中,fastSeek 属性可能不存在。

至于什么叫"快速"寻找?其实交给开发者自己来实现,如果完全不理会fastSeek也是可以实现功能的,但这个实现可能不是一种性能优先的方案。

移除

我们在前面了解如何注册事件,注册事件之后,相当于我们劫持了对应的事件,那么这个时候我们想要解除注册,返璞归真,应该怎么办呢?

js 复制代码
navigator.mediaSession.setActionHandler("nexttrack", null);

我们只需要再次注册一次这个事件,不同的是,如果callbacknull,那么就会移除对应事件的注册。

相关推荐
Larcher27 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐39 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭1 小时前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu2 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端
六月的可乐2 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程