使用
提供占位元素
1 | <div id="mse"></div> |
初始化操作
1 | let player = new Player({ |
1 | new Player({ |
播放器
1.1 播放器生命周期
1.2 事件机制
1.3 架构图
过程
1 | let player = new Player({ |
- 初始化,入口文件加载其他插件 js
- 引入其他 js 文件时候,通过 Player.install(‘xxx’, xxx) 安装插件,到 Player.plugins 上
- 创建 Xgplayer 实例
- 它继承于 Proxy,先调用 Proxy 的构造函数
- videoConfig:将要添加到 video 标签的属性(有默认值)
- video:创建的 video 标签
- 通过 EventEmitter(this),为当前实例添加 on、emit、once、off 等方法用于监听事件与派发事件
- 遍历 ev 数组,为 video 标签添加 ‘play’, ‘playing’, ‘pause’, ‘ended’, ‘error’, ‘seeking’, ‘seeked’, ‘timeupdate’, ‘waiting’, ‘canplay’, ‘canplaythrough’, ‘durationchange’, ‘volumechange’, ‘loadeddata’ 事件监听
- 调用 Player 构造函数
- config:传入的参数与默认参数合并(宽、高、音量、插槽id,url 等)
- controls:自定义标签,挂载在 root(传入的 id 选择器的 dom 实例) 下,也会用于挂载 video 标签
- pluginsCall 方法:遍历 Player.plugins 列表,执行插件自己的业务。例如:
- 创建 DOM 元素并插入,为创建的 DOM 元素添加事件
- 订阅 eventEmitter 主题
- 遍历 ‘play’, ‘playing’, ‘pause’, ‘ended’, ‘error’, ‘seeking’, ‘seeked’, ‘timeupdate’, ‘waiting’, ‘canplay’, ‘canplaythrough’, ‘durationchange’, ‘volumechange’, ‘loadeddata’ 数组,添加订阅主题
- 事件通信:当创建的 DOM 元素事件触发的时候,同时触发 video 标签的监听事件,video 标签的监听事件回调里通知插件订阅的 eventEmitter 主题,从而完成通信
- 它继承于 Proxy,先调用 Proxy 的构造函数
插件说明
1.1 常用插件
xg-volume:音量
xg-time:进度时间
xg-progress:播放进度条
xg-play:播放按钮
xg-fullscreen:全屏按钮
xg-placeholder:占位
xg-replay:重播
xg-start:播放
xg-enter:点击播放后显示动画
xg-loading:loading显示
xg-error:错误显示(请刷新试试)
xg-texttrack:播放器外挂字幕
xg-poster:播放器贴图
xg-definition:控制条清晰度切换
xg-screenShot:截图
xg-rotate:播放器旋转控件
1.2 自定义插件开发
1 | // pluginName.js |
1.3 插件生命周期
初始化(注册)
- 添加到 Player.plugins 上
实例化
- 自定义 UI 生成
- 事件绑定
- 订阅事件
销毁
通过主动调用 player.destroy(isDelDom),发出销毁事件
移除 DOM 元素
- 删除 Player 实例上的属性
- 移除 eventEmitter 订阅事件
1.4 如何实现插件可插拔
什么是插件可插拔
我认为的插件可插拔:
- 不影响主体部分,通过插件来增加产品能力、灵活性与扩展能力。主体与插件之类松耦合关系。
- 最重要的是支持插件的自由组合。
我认为的插件可插拔
babel:
主体内容 解析 -> 遍历 -> 生成,而插件订阅感兴趣的节点,合并插件的 visitor,在遍历时候进行处理
koa:
主体内容:只做了请求的监听,依赖中间件来完成其他业务。
真实的可拔插:可以根据需求引入或移除插件,前提是在不改变源码的情况下实现。有热拔插和冷拔插,分别是运行时与编译时
例如:西瓜播放器使用 ignore 数组来禁用功能
1 | pluginsCall () { |
还可以考虑编译时就不打进包内,减少体积。构建时候根据配置插件,来指定打包的文件。
编译时可以解决的几种方式:
- 配置 ignore 数组,使用 webpack 的 plugins 插件,在 webpack buildModule(buildModule 是准备从入口文件开始,先使用 loader 对文件处理的第一步)之前,发现是不使用的插件,直接替换 resource 到一个空文件
- 根据命令行配置,打包的时候,选择不打进哪些插件
两种实现方式比较
其实两种实现的方式都是类似,都是通过替换文件路径来实现加载空的 js 文件。
第一种方式:考虑的只是让配置尽量简单一些,更多的关注点只是 ignore 数组,可以单独抽离一份配置文件,但是需要很多其他的东西来帮助实现,比如 webpack 插件,路径转换之类的函数。如果多用户使用不同插件的话,还必须得配多份配置。
第二种方式:通过配置多个不同的带参命令行实现,需要多个判断命令行参数,来忽略不同的插件,但它不需要像第一种方式一样,添加多份配置文件。
其他问题
1.1 PC/移动端为什么要自动切换,如何自动切换
首先判断环境
1 | get os () { |
1.2 安全白名单的作用是什么,如何实现
在 mobile 模式下,根据配置使用浏览器自带控件,或者插件,主要为了兼容性
如果没有配置白名单
显示 video 标签浏览器默认控件,并且禁止下载
设置了白名单
支持点击 video 背景,播放与暂停
1.3 如何有效的节省带宽、点播如何无缝切换
1.4 完整的产品机制包括哪些方面
1.5 错误监控如何上报
1.6 为什么要自动降级,如何降级
具体业务实现
1.1 screenshot
1 | import Player from '../player' |
1.2 download
1 | import Player from '../player' |
我认为需要优化的地方
1.1 错误处理未定义 onError 函数
1.2 destroy 的时候,是否需要主动解绑 video 绑定事件,还是移除对 video 实例的引用的时候,会被垃圾回收。但是子元素引用仍然保存,就不会被回收
1.3 关于设计可以优化的地方
包括可插拔设计
1.4 为什么 destroy 的时候,先将 video src 设置为空,再调用 load
补充
1.1 Object.defineProperties:直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
语法:
1 | Object.defineProperties(obj, props) // obj 在其上定义或修改属性的对象 // props |
例子:
1 | var obj = {}; |
1.2 IndexedDB
window.indexedDB || window.webkitindexedDB
window.IDBKeyRange || window.webkitIDBKeyRange
1.3 eventEmitter
1 | on |
1.4 选择器
1 |
|
不支持数值开头的
1 | util.findDom = function (el = document, sel) { |
1.5 图片不存在,加载其他图片
https://stackoverflow.com/questions/37588017/fallback-background-image-if-default-doesnt-exist
1 | background-image: url('http://placehol/1000x1000'), url('http://placehold.it/500x500'); |
Babel插件,用于批量导入的插件。
require.context函数接受三个参数
- directory {String} -读取文件的路径
- useSubdirectories {Boolean} -是否遍历文件的子目录
- regExp {RegExp} -匹配文件的正则
示例:
1 | require.context('./test', false, /.test.js$/); |
1.7 判断是否是 dom 节点
1 | //Returns true if it is a DOM node |
1.8 DefinePlugin
DefinePlugin
允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是 DefinePlugin
的用处,设置它,就可以忘记开发和发布构建的规则。
1.9 video 标签
属性:
- src :视频的属性
- poster:视频封面,没有播放时显示的图片
- preload:预加载
- autoplay:自动播放
- loop:循环播放
- controls:浏览器自带的控制条
- width:视频宽度
- height:视频高度
事件:
事件 | 描述 |
---|---|
loadstart | 浏览器开始在网上寻找媒体数据 |
progress | 浏览器正在获取媒体数据 |
suspend | 浏览器暂停获取媒体数据,但是下载过程并滑正常结束 |
abort | 浏览器在下载完全部媒体数据之前中止获取媒体数据,但是并不是由错误引起的 |
error | 获取媒体数据过程中出错 |
emptied | video元素或audio元素所在网络突然变为未初始化状态可能原因有两个:1.载入媒体过程中突然发生一个致命错误 2.在浏览器正在选择支持的播放格式时,又调用 了load方法重新载入媒体 |
stalled | 浏览器尝试获取媒体数据失败 |
play | 即将开始播放,当执行了play方法时触发,或数据下载后元素被设为autoplay属性 |
pause | 播放暂停,当执行了pause方式时触发 |
loadedmetadata | 浏览器获取完毕媒体的时间长和字节数 |
waiting | 播放过程由于得不到下一帧而暂停播放(例如下一帧尚未加载完毕),但很快就能够得到下一帧 |
canplay | 浏览器能够播放媒体,但估计以当前的播放速率不能直接播放完毕,播放期间需要缓冲 |
canplaythrough | 浏览器能够播放媒体,而且以当前播放速率能够将媒体播放完毕,不再需要进行缓冲 |
seeking | seeking属性变为true,浏览器正在请求数据 |
seeked | seeking属性变为false,浏览器停止请求数据 |
timeupdate | 由于播放位置被改变,可能是播放过程中的自然改变,也可能是被人为的改变,或由于播放不能连续而发生的跳变 |
ended | 播放结束后停止播放 |
ratechange | defaultplaybackRate属性(默认播放速率)或playbackRate属性(当前播放速率)被改变 |
druationchange | 播放时长被改变 |
volumechange | volume属性(音量)被改变或muted属性(静音状态)被改变 |
1.10 canvas.toDataUrl
HTMLCanvasElement.toDataURL()
方法返回一个包含图片展示的 data URI 。可以使用 type
参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。
- 如果画布的高度或宽度是0,那么会返回字符串“
data:,”。
- 如果传入的类型非“
image/png
”,但是返回的值以“data:image/png
”开头,那么该传入的类型是不支持的。 - Chrome支持“
image/webp
”类型。
语法:
1 | canvas.toDataURL(type, encoderOptions); |
参数:
type
可选
图片格式,默认为 image/png
encoderOptions
可选
在指定图片格式为 image/jpeg 或
image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量
。如果超出取值范围,将会使用默认值 0.92
。其他参数会被忽略。
返回值:
需要注意的是如果画布中有跨域图片,并且服务器不允许跨域,是无法导出的。
1.11 事件
- mouseenter
- mousedown
- mouseup
- mouseleave
touchstart
touchend
- animationend
- keydown