相信大家在使用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" }], // 封面
})
然后,当我们播放视频的时候,媒体控件就会变成如下。
这里需要注意一点,封面图片链接仅支持http
、https
、data
、blob
,不支持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
,以及可选的 sizes
和 type
属性。
在 artwork
数组中,每个图像对象可以有以下属性:
src
(必需):图像的 URL 地址。sizes
:图像的尺寸信息,通常以字符串形式描述,例如'512x512'
。type
:图像的 MIME 类型,例如'image/jpeg'
或'image/png'
。
那么为什么是一个数组呢?
这个设计允许开发者提供多个封面图像的选择,而不仅限于一个。这样浏览器才可以根据出现的场景自动选择最优尺寸。
我们可以直接看看B站某个视频页面的metadata
。
可以看到,他们的确使用此API来创建的媒体控件。
事件
上文中我们提到按钮的多少与事件的监听与劫持有关,在这个API中,事件可以通过setActionHandler
来进行操作。
js
setActionHandler(type, callback)
type
不同,callback
的入参也是不同的。
因此我们针对type
类型,来单独分析。
当然,并非所有类型我们都会分析,比如hangup
、skipad
等,他们虽然使用了媒体控件,但这样的操作更适用于特定的应用场景,比如通话应用或视频平台中的广告跳过功能。
并不普适一般的媒体播放场景。
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
注册了pause
、play
事件,他们的默认功能将失效,改为我们自己实现。
这就是劫持。
当注册play
事件,播放按钮会被劫持,callback
的入参是{action: "play"}
。
当注册previoustrack
事件,媒体控件会显示【上一首】按钮。点击按钮的时候,callback
的入参是{action: "previoustrack"}
。
当注册nexttrack
事件,媒体控件会显示【下一首】按钮。点击按钮的时候,callback
的入参是{action: "nexttrack"}
。
seekbackward、seekforward
seekforward
和seekforward
类型表示前进和后退,如果注册它们,媒体控件会显示对应的【后退】、【前进】按钮。
但它们这样与上一节事件些许不同,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);
})
除了固定的action
是seekbackward
或者seekforward
以外,还可能存在seekOffset
这个属性,seekOffset
指示向前进后退的秒数。
如果这个属性存在,那么实现前进后退功能,就需要基于seekOffset
来实现偏移时间,如果此属性不存在,则这些操作应选择合理的默认时间,比如一般是7秒或者10秒。
为什么seekOffset
会是一个可选参数呢?那是因为Media Session
被设计为与不同平台和设备兼容,各个平台的前进后退方案是不同的,并且可以个性化定制的,在某些平台,比如Chrome浏览器,默认是没有seekOffset
。
seekto
seekto
操作是用于移动媒体播放位置的。通过指定时间点(seekTime
属性),可以将媒体播放器定位到特定的时间。 当连续进行多次快速 seekto
操作时,fastSeek
为 true
。这样做告诉浏览器可以优化这些连续的操作。浏览器可能会采取一些措施,例如使用更高效的方式来处理连续的寻找操作,以提高整体性能。
当我们使用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);
我们只需要再次注册一次这个事件,不同的是,如果callback
为null
,那么就会移除对应事件的注册。