很久没有静下心写博客了。近段时间接到一个任务,前端页面要加上视频播放功能。实现加排坑前后花了三天时间(别问我问什么这么久😂),觉得还是有必要记录一下的。
这一部分有必要写在最前面,避免你看了一长串安装、引入、代码,然后发现自己想要播放的视频格式或编码video.js不支持。
从另一篇博客[1]中了解到,它支持mp4、webm、ogv、m3u8、flv、rtmp
等扩展名的视频文件。
以mp4
为例,虽然扩展名都可以是mp4,但不同视频文件的编码可以是MPEG4、H.264
等,这里要注意,video.js只支持H.264
编码的mp4文件, 如果你要播放的文件编码不是H.264
,需要先转换编码,或者在录制的时候设置为H.264
。
查看视频文件编码方式的方法:
现在比较知名的前端库都支持node.js,video.js也不例外,所以安装只需要:
npm install video.js
起初按照一些博文抄代码到自己的项目,发现会有一些报错,后来耗时很久没有解决,找到了官方给的示例代码[2],简洁且跑起来没有报错。
我这里把注释翻译一下,略作改动:
import React from 'react';
import videojs from 'video.js';
// 记得引用css文件!
import 'video.js/dist/video-js.css';
export const VideoJS = (props) => {
// video标签的引用Hook
const videoRef = React.useRef(null);
// 播放器实例的引用Hook
const playerRef = React.useRef(null);
const {options, onReady} = props;
React.useEffect(() => {
// 确保video.js的播放器实例player仅被初始化一次,否则会报错
if (!playerRef.current) {
const videoElement = videoRef.current;
if (!videoElement) {
return;
}
const player = playerRef.current = videojs(videoElement, options, () => {
videojs.log('播放器准备就绪!');
onReady && onReady(player);
});
// 当props发生变化时,可以对已经存在的player实例作一些操作,如:
} else {
// const player = playerRef.current;
// player.autoplay(options.autoplay);
// player.src(options.sources);
}
}, [options, videoRef]);
// 控件被unmount卸载的时候,记得要对player实例执行反初始化dispose
React.useEffect(() => {
const player = playerRef.current;
return () => {
if (player) {
player.dispose();
playerRef.current = null;
}
};
}, [playerRef]);
return (
<div data-vjs-player>
<video ref={videoRef} className='video-js vjs-big-play-centered'/>
</div>
);
}
export default VideoJS;
有了上述的视频播放器控件,我们可以在任意需要播放视频的页面中引用它:
import React from 'react';
// 从控件的路径引用它,这里默认它和页面在同一目录下
import VideoJS from './VideoJS'
const App = () => {
const playerRef = React.useRef(null);
const videoJsOptions = {
// 自动播放:为true时,加载完毕自动播放
autoplay: true,
// 播放器子控件是否显示:为true时显示暂停、播放速率等按钮
controls: true,
// 响应性:为true时,播放视频进度条会自动移动
responsive: true,
// 流式布局:为true时尽可能大得填满父标签的剩余空间
fluid: true,
// 视频源
sources: [{
// 视频文件的路径,可以是一个前端相对路径、后台视频文件URL、直播源等
src: '/path/to/video.mp4',
// 视频源类型
type: 'video/mp4'
}]
};
// 播放器实例化完成后的事件动作,注意!不是视频加载成功
const handlePlayerReady = (player) => {
playerRef.current = player;
// 播放器的子事件在这里定义
player.on("canplaythrough", () => {
console.log("视频加载完成!")
});
player.on("error", () => {
console.log(`视频文件加载失败,请稍后重试!`);
});
player.on("stalled", () => {
console.log(`网络异常,请稍后重试!`);
});
};
return (
<>
<!-- ... -->
<VideoJS options={videoJsOptions} onReady={handlePlayerReady}/>
<!-- ... -->
</>
);
}
更多的播放器事件定义可以参考另一篇博文[3]
VIDEOJS ERROR (CODE4 MEDIA_ERR_SRC_NOT_SUPPORTED) No compatible source was found for this media
我自己遇到两种情况引起的这个报错:
MPEG4
编码的问题。
标签去初始化video.js。如果因为一些代码逻辑问题,DOM树中没有video标签,此时初始化video.js对象也会报这个错。
图片标签还是
视频标签。由于js代码逻辑有漏洞,渲染
图片标签的同时通过videojs(...)
初始化了一个播放器对象。严格意义上说是一个警告,原文不记得了,大致意思就是说player对象已经初始化过一次,所以不再接受options
里的新参数。
这个是因为在组件上次unmount
卸载的时候没有对player对象进行销毁dispose()
,按上述的代码去写,就不会有这个问题。
由于dispose()
函数会删除标签,之前借鉴其他博文里通过
id
初始化video.js对象,dispose()
的时候就报了这个错。
原本的预期应该是React底层先unmount
,这个时候就删除了标签,
dispose()
在其后执行就不会报错。但是借鉴了那些代码之后不知道为什么dispose()
执行在unmount
之前了,React底层找不到要删除的标签,就抛错了,并且导致了页面白屏。
解决方法同上,使用官方给出的示例代码去实现就好了,不会报这样的错。
首先,感谢video.js视频播放组件的开发者及贡献者。这个组件很简单易用,为音视频的门外汉节省了很多时间和代码量。
实现过程中,一开始参考一些个人博文的代码,大多数是基于class组件实现的,和我的函数式组件及React Hook框架有些水土不服。所以花了些时间在踩坑和排坑上。如果一开始就看了官方的示例,就能省些时间。另外遇到及处理视频编码的问题,也额外学习到了一些相应的知识。