解析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,那么就会移除对应事件的注册。

相关推荐
ZJ_.6 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营11 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood37 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端38 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8542 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235612 小时前
从零开始学前端之HTML(三)
前端·html