video标签
遇到video相关的问题记录
- video占用内存在活动监视器里可以看到内存使用情况,Mac有一个进程是VTDecoderXPCServ可以注意下,就是视频编解码相关的进程,页面占用的内存不要太高了。
- 谷歌可以在perfomance monitor查看CPU占用率(我主要使用这个看)。或者timeline也会记录内存使用情况。javascript内存泄露及谷歌浏览器查看内存使用
- IOS和安卓的内存容量不同,有时候视频懒加载没写好,IOS会直接崩掉(闪退等等),还有视频的码率应该注意一下,4k的视频网页加载不动,1080P的视频才行,码率一般我们导出5M。
参考:
H5 video 开发问题及相关知识点
Video元素的使用和常见问题总结
video在安卓与ios实际应用中遇到的问题及解决
常用属性记录
-
autoPlay
自动播放,但是不同机型还需要其他属性配合,比如IOS必须加muted等。建议不使用自动的autoPlay属性,而采用懒加载的方式唤起播放。 -
readyState属性
只读属性。使用media.readyState返回媒介当前播放位置的就绪状态,共有5个可能值。
HAVE_NOTHING(数字值为0):当前播放位置无有效媒介资源;
HAVE_METADATA(数字值为1):加载中,媒介资源确认存在,但当前位置没有能够加载到有效媒介数据进行播放;
HAVE_CURRENT_DATA(数字值为2):已获取到当前播放数据,但没有足够的数据进行播放;
HAVE_FUTURE_DATA(数字值为3):已获取到后续播放数据,可以进行播放;
HAVE_ENOUGH_DATA(数字值为4):可以进行播放,且浏览器确认媒体数据以某一种速度进行加载,可以保证有足够的后续数据进行播放,而不会使浏览器的播放进度赶上加载数据的末端。 -
playsInline & webkit-playsinline
标志视频播放时局域播放(不全屏播放),不脱离文档流 。不希望用户来拖动进度条,可以加上playsInline标签,IOS10以下的使用webkit-playsinline。部分机型没有设置playsInLine会自动全屏播放。遇到了再说。 -
muted
视频里的音频设置。设置后,音频会初始化为静音。默认值是false。IOS必须要加上muted才能自动播放。 -
controls
加上这个属性,Gecko 会提供用户控制,允许用户控制视频的播放,包括音量,跨帧,暂停/恢复播放。 -
duration & currentTime
视频总时长以及当前播放时长,可以根据这两个数据得知视频目前的播放进度,做动画设置。
一、视频懒加载。
- 懒加载一开始采用监听scroll的方案,但是其实scroll监听挺耗费内存的,可以采用IntersectionObserver的方式完成懒加载监听的优化。
- 只是video.pause()无法阻止加载进程,和占用内存的优化没有关系,只有停止后同时删掉src,再load()刷新一下才行噢。但是停止不在视口窗内的视频播放,一定程度上还是会减轻渲染消耗的吧。(不知道这样的理解有没有错误)
- 如果暂停播放太频繁会有报错:The play() request was interrupted by a call to pause(). 可以通过判断是否处于播放状态/直接把错误catch掉两个方式来解决。
- scroll监听时,最好做一个判断加载完后自动卸载监听的操作,并且使用throttle节流,需要把所有的视频都摆出来(需要切换的视频使用opacity),否则滑动完了自动把事件卸载之后新的视频就无法加载了,PS:不会使用pause()。
useEffect(() => {
const handleVideoLazyLoad = () => {
const lazyVideos = [].slice.call(document.querySelectorAll('video'));
if (typeof IntersectionObserver !== 'undefined') {
const lazyVideoObserver = new IntersectionObserver((entries) => {
entries.forEach((_video) => {
const video = _video.target;
const isPlaying =
video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
if (_video.isIntersecting && !isPlaying) {
const videoSrc = video.getAttribute('src');
const videoPoster = video.getAttribute('poster');
const videoDataSrc = video.getAttribute('data-src');
const videoDataPoster = video.getAttribute('data-poster');
if (videoSrc) {
video.play().catch((e) => {});
} else {
if (!videoPoster && videoDataPoster) video.setAttribute('poster', videoDataPoster);
if (!videoSrc && videoDataSrc) {
video.setAttribute('src', videoDataSrc);
video.play().catch((e) => {});
}
}
} else if (isPlaying) {
video.pause();
// const playtime = video.currentTime;
// const videoDataSrc = video.getAttribute('data-src');
// video.setAttribute('src', '');
// video.load();
// video.setAttribute('src', videoDataSrc);
// video.currentTime = playtime;
}
});
});
lazyVideos.forEach((video) => lazyVideoObserver.observe(video));
}
};
handleVideoLazyLoad();
}, []);
// scroll
const videoPlay = useThrottle(() => {
if (videos.current.every((d) => d.isPlay)) document.removeEventListener('scroll', videoPlay);
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
videos.current.forEach((_v, i) => {
const offsetTop = _v.dom.getBoundingClientRect().top; // video顶部距离视口窗的距离
const top = offsetTop - viewPortHeight; // video顶部距离视窗底部的高度
if (top <= (isMobile ? 150 : 300) && !_v.isPlay) {
_v.dom.play();
_v.isPlay = true;
// _v.dom.style.height = _v.clientHeight !== 0 ? `${_v.clientHeight}px` : `${_v.height}px`;
}
// if(rect.bottom <= 0 && _v.isPlay) {
// _v.dom.pause();
// _v.isPlay = false;
// }
});
}, 300);
const lazyLoadVideos = () => {
const oVideos = document.querySelectorAll('video');
const _videos = [];
oVideos.forEach((dom, i) => {
if (!isEmpty(dom.src)) {
_videos.push({ dom, isPlay: false });
}
});
videos.current = _videos;
document.addEventListener('scroll', videoPlay);
};
useEffect(() => {
lazyLoadVideos();
return () => document.removeEventListener('scroll', videoPlay);
}, []);
二、视频自动播放的属性
只添加autoPlay在IOS无法自动播放。因为IOS有一个策略,静音的视频才能自动播放,也就是muted属性。
// 常用的video标签
<video
data-poster={poster}
data-src={src}
loop
muted
playsInline
controls={false}
/>
三、视频宽度一定时IOS上高度莫名拉长
某次开发中突然出现的,高度不知为何特别长,后来想了两种解决方案:
- 根据已知的视频比例直接设定高度
width: calc(100% - 48px);
height: calc((100% - 48px) * 16 / 9);
- 在懒加载的时候同步设定视频高度
_v.dom.style.height = _v.clientHeight !== 0 ?
${_v.clientHeight}px
:${_v.height}px
;
四、video相关常用事件的应用
object.onXXX=function(){myScript};
object.addEventListener(“XXX”, myScript);
- play
开始播放音频/视频,返回一个promise
document.querySelector(‘video’).pause()
- pause
暂停当前播放的音频/视频
document.querySelector(‘video’).pause()
- load
重新加载音频/视频元素
document.querySelector(‘video’).load()
- canplay
在媒体数据已经有足够的数据(至少播放数帧)可供播放时触发。
loop循环播放时每一次循环都会触发一次canplay - ended
播放结束时触发。 - progress
当浏览器正在下载指定的音频/视频时,会发生 progress 事件。告知媒体相关部分的下载进度时周期性地触发。(也就是说和播放的progress无关,仅与下载进度有关) - suspend
在媒体资源加载终止时触发,这可能是因为下载已完成或因为其他原因暂停。
当音频/视频处于加载过程中时,会依次发生以下事件:
loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough
useEffect(() => {
querySelector('#testVideo').then((video) => {
video.addEventListener('canplay', (e) => {
console.log('canplay', e);
});
video.addEventListener('pause', (e) => {
console.log('pause', e);
});
video.addEventListener('ended', (e) => {
console.log('ended', e);
});
video.addEventListener('play', (e) => {
console.log('play', e);
});
video.addEventListener('load', (e) => {
console.log('load', e);
});
video.addEventListener('progress', (e) => {
console.log('progress', e);
});
video.addEventListener('suspend', (e) => {
console.log('suspend', e);
});
});
}, []);
五、想要实现动画进度跟随视频进度,如何实现?
- 添加视频定时器监听,为了性能着想不能用setTimeout,采用
requestAnimationFrame
方式。 - 视频监听函数中,获取当前的播放进度
currentTime / duration
,然后根据progress进度处理动画。就算视频不播放/暂停等状态,也可以从progress上精确知道。
useEffect(() => {
cancelAnimationFrame(timer.current);
timer.current = requestAnimationFrame(animationVideo);
return () => cancelAnimationFrame(timer.current);
}, [index, animationVideo]);
// 动画跟随进度处理
// videoDomMap[index]是当前活跃元素
const animationVideo = useCallback(() => {
let percentage = 0;
if (videoDomMap[index]?.duration) {
if (lineWidth[index - 2]?.style) {
percentage = videoDomMap[index].currentTime / videoDomMap[index].duration;
lineWidth[index - 2].style.width = `${percentage * 100}%`;
}
}
if (percentage >= 1) {
// 播放完毕,切换下一个
changeVideo(index+1)
}
cancelAnimationFrame(timer.current);
timer.current = requestAnimationFrame(animationVideo);
});