自定义插件接入
1. 插件规范
插件能提供极大的权限,这是一把双刃剑,它可能会引入很多意料之外的问题,因此务必遵循以下原则:
2. 播放器对象模型
在编写插件之前,了解播放器对象模型,对编写插件有极大的帮助:
- 直通
┌─renderer # 播放器核心模块
│ ├─wrapper # 播放器最外层的 dom, 是 container 的父级节点
│ ├─container # 播放器渲挂载节点(一般也用于插件 dom 挂载),是渲染 dom 的父级节点
│ ├─dom # 视频渲染 dom(canvas | video)
│ └─decoder # 解码模块
└─renderer # 渲染管理器
├─decodeType # 渲染器类型
├─decodeType # 渲染器当前播放时间
└─vm # 渲染器(mse渲染器 | webGL渲染器)
├─session # s17 接口返回的播放 session
├─streamType # 码流类型
└─volume # 音量
- 回放
┌─channelPlayers[] # 通道播放器列表, v1.1 新增,取代 v1.0 的 mediaElements
│ ├─renderer # 播放器渲染模块
│ │ ├─wrapper # 播放器最外层的 dom, 是 container 的父级节点
│ │ ├─container # 播放器渲挂载节点(一般也用于插件 dom 挂载),是渲染 dom 的父级节点
│ │ ├─dom # 视频渲染 dom(canvas | video)
│ │ └─decoder # 解码模块
│ ├─session # s17 接口返回的播放 session
│ ├─channel # 通道号
│ ├─devId # 设备号号
│ ├─streamType # 码流类型
│ ├─currentTime # 当前时间,utc 秒
│ └─volume # 音量
├─streamType # 码流类型
├─currentTime # 当前时间,utc 秒
├─volume # 音量
├─status # 状态
└─requestParams # s17 接口请求参数
3. 创建自定义插件
3.1. 简介
创建插件非常简单,仅需两步:
- 创建一个函数,该函数接受一个 hooks 参数
export function customPlugin(hooks) {
// do something ...
}
tips: 插件本身是一个方法,没有作为构造函数,因此不具有实例。
- 将插件注册到我们的播放器中
player.plugin.use(customPlugin);
小伙伴们都惊呆了吧! 其实插件的主体部分才是重点,下面我们以全屏插件为例,逐步带领大家深入插件
3.2. 实现全屏插件
插件函数中接受hooks
参数,它提供了我们介入播放器各个生命周期(hooks)的入口。
1.分析需求 全屏插件需要为播放器拓展两个方法,分别用来进入全屏/取消全屏
2.确定拓展方法的时机 这两个方法需要在播放器较为靠前的时机拓展上,以便我们能尽早调用,因此最终从hooks中,选择afterPlayerInit
hook,即:播放器初始化后,作为我们介入的时机。
3.拓展方法 afterPlayerInit 为其回调注入了两个参数player
, config
,这里我们仅需要用到player
,它表示当前播放器实例,我们可以直接在其上新增我们的方法或者属性:
export function fullscreen(hooks) {
hooks.afterPlayerInit.tap('install-fullscreen', (player) => {
player.fullscreen = function () {
// ...
};
player.cancelFullscreen = function () {
// ...
};
});
}
tips: 每个 hook 回调里,都注入了一些参数,具体见hooks。 tips: 这里 tap 方法是由tapable提供
4.实现 fullscreen 函数
function fullscreen() {
let canFullScreen = true;
var mediaElement = this.renderer.container;
if (mediaElement.RequestFullScreen) {
mediaElement.RequestFullScreen();
} else if (mediaElement.webkitRequestFullScreen) {
mediaElement.webkitRequestFullScreen();
} else if (mediaElement.mozRequestFullScreen) {
mediaElement.mozRequestFullScreen();
} else if (mediaElement.msRequestFullscreen) {
mediaElement.msRequestFullscreen();
} else {
canFullScreen = false;
alert("This browser doesn't supporter fullscreen");
}
}
5.实现 cancelFullscreen 函数
function cancelFullscreen() {
let canFullScreen = true;
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
canFullScreen = false;
alert("Exit fullscreen doesn't work");
}
}
6.最后一步,将其注册到播放器上
player.plugin.use(fullscreen);
至此,就实现了一个完整的全屏插件!
3.3. 实现放大镜插件
1.需求分析 我们需要一些 dom 和对应的交互,用来选取一个范围,另外还需要更改我们的渲染器行为,用来将第一步选取的范围,应用到视频上,将其放大。
2.确定 dom 及对应交互的添加时机 需要添加的 dom,需要在视频渲染之前,但又必须在播放器初始化时,或者初始化后(合理即可),这里我们仍旧选择播放器初始化后这个 hook,即:afterPlayerInit
hooks.afterPlayerInit.tap('install-magnifier-dom', (player) => {
// 安装相关dom和交互
player.magnifier = new Magnifier(player);
});
tips: 这里 tap 方法是由tapable提供
Magnifier 类局部一览
export class Magnifier {
...
constructor(player) {
const { canvas, renderer } = player;
const container = canvas.parentNode;
canvas.addEventListener('mousedown', (e) => {
...
this.rect = new zoomRect(container, x, y);
});
canvas.addEventListener('mousemove', (e) => {
...
})
canvas.addEventListener('mouseup', (e) => {
...
})
...
}
...
}
3.确定介入渲染器的时机 我们需要更改渲染器行为,因此需要介入渲染阶段,可以在渲染器渲染开始时,也可以在渲染器初始化完毕后等(合理即可),这里我们选用了渲染器初始化完毕之后,即 afterRendererInit
4.为播放器的渲染器拓展一个方法 zoomIn 当用户在视频中选择一个区域后,会将区域的数据作为参数,调用 zoomIn,以此来完成将用户选择框进行放大的效果
export function magnifier(hooks) {
hooks.afterPlayerInit.tap('install-magnifier-dom', (player) => {
// 安装相关dom和交互
// 这里将player作为参数传进了Magnifier中,使其能够访问到下面的zoomIn方法(renderer是player的子、孙属性)
player.magnifier = new Magnifier(player);
});
hooks.afterRendererInit.tap('install-renderer-magnifier', (renderer) => {
// 更改渲染器行为,为其拓展一个缩放的方法;
renderer.zoomIn = MagnifierForGlRenderer;
// MagnifierForGlRenderer实现暂略,需要对播放器webgl有一定了解
// 如果需要对视频渲染层面有定制需求,可咨询sdk研发人员做一定了解
});
}
到此为止,就实现了一个电子放大镜插件,最后我们将它应用到到播放器上,让它生效
player.plugin.use(magnifier);
tips:放大镜插件过于硬核了点,这仅做关键插件实现步骤的讲解,对于业务层面,一般情况下不会有渲染层面的定制需求。