最近项目中,用到h5的video去播放视频,N年前也做过,知道有不少坑,这次再次入坑,准备记录一下,同时也可以给用h5去实现视频播放的小伙伴,提供一些参考,能够少踩坑。
这次我们要兼容的手机:安卓和IOS
兼容的环境:h5、小程序、APP和钉钉
不采用视频自身的播放控制,全部由页面自己控制
video代码:
<div className='video-wrapper'>
<video
preload='auto'
ref={videoRef}
autoPlay={false}
loop={false}
muted={muted}
// poster={$getImage(poster)}
playsInline
x5-video-player-type="h5"
x5-playsinline='true'
webkit-playsinline='true'
onPause={onPause}
onEnded={onEnded}
onLoadedMetadata={onLoadedMetadata}
onError={onError}
>
<source src={src || ''} type='video/mp4'/>
您的浏览器不支持播放mp4。
</video>
{ poster && status === 'init' && (
<img className='video-poster' src={$getImage(poster) || ''} alt="" />
)}
</div>
属性问题说明:
1.autoplay
因为ios环境下,需要用户主动触发才能播放,另外进入页面后,直接自动播放,可能对用户也不太友好。所以为了保持统一,放弃了自动播放。
2.muted
静音切换,根据自己需要默认打开还是关闭,注意页面上的静音控制展示要和video播放器一致,别搞反了
3.loop
循环播放,我们要求可以循环播放,同时每次播放结束后上送数据。但是循环播放(loop为true)的时候,无法监听end事件,这就导致无法上送数据。
方法一:如果不想麻烦,可以设置循环播放,利用video的timeupdate事件监听,拿到当前播放时间currentTime,再拿currentTime和duration比较,接近时上送,可能会有<1s的时间误差,或者记录上次播放时间currentTime,如果这次currentTime比上次的小,说明又进入下一次循环播放,再上送数据。
方法二:曲线救国,设置loop为false,这样每次就可以监听到end,然后我们手动触发再次播放,同时上送数据,来达到我们的效果,对用户来说,无感知。
4.poster
视频未加载前的遮罩层图片,
我们放弃直接使用video自带的poster效果,自己用img来控制遮罩层。
video自带的poster对图片有要求,首先图片要和视频播放时的比例一致,因为要匹配不同机型,不然可能会出现,图片位置和视频位置不一致,不能完全遮罩,造成不好的体验。
我们一开始是使用了poster的,适配也刚好。但是在钉钉环境,视频加载完,直接把poster遮罩隐藏掉了,直接显示视频,我们的视频开头刚好是白色,所以给人感觉挂了,所以放弃video自带poster,自己控制,当用户点击了开始播放后,再把遮罩层隐藏掉。
5.loadedmetadata、loadeddata事件
ios事件监听函数必须要在用户手动触发播放后,才能监听到,比如我们想监听视频资源加载完毕后,进行页面按钮显示控制操作,所以我们统一做成了,必须用户手动触发才开始播放。
6.ended事件
播放结束监听函数,只能在整个视频播放结束的时候才能监听到。
如果循环播放,loop为true,则无法触发。
另外ended的时候,一定会触发pause事件监听,所以如果你同时有pause和ended回调的时候,需要注意边界处理
7.pause事件
暂停时监听函数,视频暂停时,会触发暂停监听函数,视频播放结束也会触发,我们这次自己模拟循环播放,所以同时使用了pause和ended,然而ended的时候又不能暂停,所以要过滤掉ended时触发的暂停回调,根据video的ended状态,进行过滤,这样就能模拟正常的循环播放。
业务问题:
1.自己开启了定时器,记录视频的播放时间
自己开启定时器,时间精度不够,每次暂停和播放,都会有一定延时,如果暂停次数太多,时间会出入比较大,因为我们这次注意是测试,精度问题忽略了,不考虑,如果要自己展示视频时长,建议利用updatetime去更新时长,这样更准确点。
2.应用切到后台
切到后台,触发了视频的暂停,但是不同环境表现形式不一样,比如,ios小程序,直接暂停,必须用户手动触发,应该是腾讯做了什么操作,所以我们利用visibilitychange来监听应用的切换
const visiabilityChangeHandle = ()=>{
if(document.hidden){
pause()
}else{
play()
}
}
document.addEventListener('visibilitychange', visiabilityChangeHandle)
3.应用切到列表状态
应用切到列表状态时,安卓和切到后台一样,我们可控,切后台暂停,切回来,触发播放。但是在ios多环境下,仍然继续播放,因为我们这次没有和原生交互,所以拿不到用户上滑进入应用列表的场景,产品认为没问题,就这样操作了,建议最好统一控制,避免表现形式不一致。
4.控制按钮显示
除了初始的播放,其他自定义的播放控制按钮和视频上层展示的内容,一定要视频开始播放后再显示出来,另外要注意计时器和播放状态的一致性。
5.内存飙升,可能会白屏
在内存小的一些老旧手机上,如果用户多次切换视频,或者从多个入口打开视频,导致内存可能飙升,直接爆掉,出现白屏。
如果出现这种情况,将video的src属性=null,再让父级清空自己,这样内存得到了释放。
6.不设置video的poster
如果我们自己控制poster,而没有设置video的poster,在安卓部分手机上会加载webview默认的poster作为视频背景,在视频播放前,闪一下,很尴尬,可以在webview中修改默认video背景即可。
image.png
7.canvas实现video
因为钉钉环境下,三星Galaxy G9730 Android10,vivo X21A Android9无法内联播放,导致部分功能无法继续进行,备用方案,canvas实现video就上了
let posterWidth = 0
let posterHeight = 0
let requestAnimationFrameId = null
const initVideo = (config) =>{
//创建video标签,并且设置相关属性
videoRef.current = document.createElement('video')
videoRef.current.preload = 'auto'
videoRef.current.autoplay = false
videoRef.current.src = config[businessType]?.video?.url
videoRef.current.poster = config[businessType]?.video?.poster
videoRef.current.addEventListener('ended', onceEndCallback, false)
videoRef.current.addEventListener('pause', onPauseCallback, false)
videoRef.current.addEventListener('loadedmetadata', onLoadedMetadataCallback, false)
}
const drawVideo = () =>{
ctxRef.current.drawImage(videoRef.current, 0, 0, posterWidth * 3, posterHeight * 3)
requestAnimationFrameId = requestAnimationFrame(drawVideo)
}
const draw = (canvas) => {
if (canvas) {
//获取canvas上下文
if(!ctxRef.current){
ctxRef.current = canvas.getContext('2d')
// 获取屏幕宽度作为canvas的宽度 这个设置的越大,画面越清晰(相当于绘制的图像大,然后被css缩小)
canvas.width = posterWidth * 3
canvas.height = posterHeight * 3
}
videoRef.current.play()
drawVideo()
}
}
这里采用poster遮罩层的宽高来适配canvas,如果你要全屏,可以用屏幕可视宽高来适配。
这次又把h5使用video播放视频的坑又过了一遍,下次直接跳过。。。
网友评论