项目背景
项目需要基于原生播放器定制开发一个视频播放器,大致需求是更换一套播放器ui,新增视频倍速控制以及弹窗,视频播放中商品卡片,静音键等功能。项目的技术栈用得是 taro + react。
开发实现
开发前看了下小程序市面上的视频类小程序,诸如腾讯视频小程序、爱奇艺小程序用的都是原生播放器。为啥这些一线视频大厂不做定制播放器开发,难道是有坑?我瞬间感觉到了不妙,后来又翻了拼多多和B站的小程序,相比来说,B站的小程序播放器基本没什么大问题,用起来也很流畅。拼多多的bug就比较多了,会不会是部分机型兼容问题呢,这里就不细说了,有兴趣的可以自己去小程序里面去看。
考察结束,想想该怎么开发吧!
首先肯定是基于原生播放器进行扩展。然后再新增一些目前需求里面需要用到的参数,下方都会是简化过的代码。
js
export interface IMMVideoProps extends VideoProps {
/** 视频高度 */
height?: string
/** 视频宽度 */
width?: string
/** currentId currentId必须保持唯一,用于获取视频实例 */
currentId: number
/** 商品id */
goodsId?: number
/** 商品开始显示的时间 */
startTime?: string
/** 商品结束显示的时间 */
endTime?: string
/** 商品是否被删除 */
delId?: number
}
const Component: FC<IMMVideoProps> = props => {
return (
<Video {...parentProps} />
)
}
const MMVideo = memo(Component)
export default MMVideo
其次,看了下ui图,基本上是换了套皮,那原生的那套就不能用了。隐藏掉,更换为自己的一套ui组件,所有的组件都是机遇播放器的absolute定位,因为考虑到后面可能会遇到很多多层级间的点击捕获的穿透问题。直接加了一个 FakeMusk 的顶层遮罩,点击播放器的第一时间会第一时间触发最上层的 FakeMusk ,FakeMusk 隐藏后才允许触发其他的操作。
js
<Video
{...parentProps}
id={`video${currentId}`}
controls={false}
>
{/* 顶层遮罩,防止点击穿透 */}
{FakeMusk}
<ControlContext.Provider value={controlContextValue}>
<Mask>
{/* 中间播放按钮 */}
<ControlBar.Play />
{/* 底部控制 */}
<ControlBar />
</Mask>
{/* 倍速弹窗 */}
<SpeedPopup />
{/* 商品卡片 */}
<VideoGoods />
{/* 视频正在加载的loading */}
<MMLoading />
</ControlContext.Provider>
</Video>
写好静态组件后,无非就是各种调用播放器的各种 api 啦。我这里的处理是让 video 的外层组件传进来一个 currentId,然后通过 currentId 去获取播放器的实例,最后把实例存到一个顶层的 state 里面,供后面的子组件使用。比较简单,代码就不赘述了。
遇到的一些问题
无刷新视频获取实例的问题
这里遇到了第一个问题。就是调用播放器组件的页面是个无刷新页面,在切换视频的时候仅变更 currentId 和 src,这种情况下,第一次确实可以拿到实例,但当变更完 currentId 后再去获取的播放器实例却无法调用再调用播放器的各种 api。
所以处理方案是必须让页面切换视频的卸载组件,这样每次的播放器组件都是重新生成的实例,这样使用起来没有什么问题。
至于为什么,大家可以一起评论区讨论一下。原因我稍后会更新出来。
播放器进度条抖动
抖动的问题之前有预料会碰到,这一次一下遇到两个抖动的问题。
第一个抖动问题是因为一开始用了小程序的 slider 组件,实测发现部分机型在拖动的时候 slider 会不停的抖。这个问题不知道其他同学遇到过没有?解决方案是重新手撸一个 slider 组件,放置在原生 slider 的上方,然后让原生 slider 的透明度为0。为什么还需要原生 slider,主要还是因为原生 slider 有现成的滑动事件 onchanging 和 滑动结束事件 onchange,这是我所需要的。
js
/** 手写的 slider 用来覆盖原生 slider */
<VideoSlider>
<Slider
block-size={16}
value={isSlide ? percentSlide : percent}
onChange={event => onChangeHandle(event)}
onChanging={event => onChangingHandle(event)}
/>
滑动结束后的抖动问题
onChange 事件结束后通常调用视频的 seek 方法,发现部分机型,主要是安卓,播放器的seek完成时触发的方法 onSeekComplete 有延时。这就导致 slider 的状态更新也有一定的延时,拖动一段距离必然有抖动的问题
js
/** 拖动结束后触发 */
const onChangeHandle = function(event) {
const { value } = event.detail
videoId?.seek((value / 100) * duration)
}
<Video onSeekComplete={() => {
controlDispatch({ type: 'setIsSlide', isSlide: false })
controlDispatch({ type: 'SetIsPlay', isPlay: true })
{/* 这个方法的执行部分安卓机型有延时 */}
}} />
这里的解决方案是设置一个 isSlider 是否正在拖动的状态,在 isSlider 状态变为 true 之前,进度条只会获取拖动的时候进度,直到 isSlide 变为 false。并且在拖动的时候我会暂停视频的进度,在拖动结束后才开始播放视频,通过这两种方式可以有效的解决这个 onSeekComplete 触发延时的问题。
js
/** 视频播放进度(百分比) */
const percent = useMemo(() => {
/** 这里的处理主要是部分安卓机器 video 的 onSeekComplete 回调事件有延时,导致 slider 快速滑动的时候滚动条出现抖动 */
if (isSlide) {
return percentSlide
}
return Number(((currentTime / duration) * 100).toFixed())
}, [currentTime, duration, isSlide, percentSlide])
/** 正在拖动时触发 */
const onChangingHandle = function(event) {
const { value } = event.detail
changeSlider(value)
controlDispatch({ type: 'SetIsPlay', isPlay: false })
}
连续滑动 slider 丢失 onChange 事件
当用户不断把 slider 拖到 0 进度时,会丢失 onChange 事件,这个算是微信 slider 的一个 bug。
模拟器状态下播放器的 onSeekComplete 回调不生效
这个问题小程序一直没有修复,所以在模拟器开发时,只能手动的在 onChange 事件通过执行一遍 onSeekComplete 内部的方法。
视频格式兼容性
实际使用发现这个视频播放器对视频支持程度非常差劲,官方文档对于编码参数压根没提,各种黑屏或者无声音。于是决定记录下来处理的过程和我测试过的兼容性列表,方便未来查阅。
首先先看看官方文档是怎么说的。这里摘录出官方文档的表格。
格式 | iOS | Android |
---|---|---|
mp4 | √ | √ |
mov | √ | x |
m4v | √ | x |
3gp | √ | √ |
avi | √ | x |
m3u8 | √ | √ |
webm | x | √ |
支持的编码格式
格式 | iOS | Android |
---|---|---|
H.264 | √ | √ |
HEVC | √ | √ |
MPEG-4 | √ | √ |
VP9 | x | √ |
但是按照这个标准上传完视频后,依然会有不少视频会出现卡顿,黑屏等问题。
原因是官方文档缺少了编码中对 profile 的描述,我自己做了各种尝试,完善了一下兼容性测试。首先由于 iOS 和 Android 都支持mp4,因此我主要测试了 mp4 支持的音频和视频编码,以及相关规格,最终整理成下文的表格,测试设备为 Android 和 ios设备
视频编码 | profile | Android、ios |
---|---|---|
H.264 | Baseline | √ |
H.264 | Main | √ |
H.264 | High | x |
- Baseline 主要用于视频通话、手机视频等;
- Main 用于主流消费类电子产品规格如低解码(相对而言)的mp4、便携的视频播放器、PSP 和 Ipod 等;
- High 用于广播及视频碟片存储(蓝光影片),高清电视的应用。
解决方法是将将 Format profile 调低进行转码,转到 High@L4 以下时,绝大多数 Android 或 ios 机就已经可以正常播放了。除了用客户端软件进行转码,还可以使用七牛云或者腾讯云提供的视频云转码服务,但是需要写简单的几何程序,转码完成后会提供回调通知。