实现vue3通过videojs接入m3u8视频,一天多总算搞成功了。下面就扒一扒我在实现过程中踩的那些坑。。。。。。
前言
我们最常见的mp4类型的视频,直接通过vue原生的video引入就可以了。
<video :src="video" autoPlay controls muted loop />
但是当接入的视频是直播视频流时,video是不支持的,所以我们需要通过一定的插件辅助,在实现功能前先介绍几个概念。
1.HLS,M3U8
一听需求是要接入海康的视频,什么hls,m3u8视频,孤陋寡闻的我立马去找度娘。
HLS是一个基于http的流媒体网络传输协议,传输内容包括两部分,M3U8描述文件和TS媒体文件。
M3U8文件是指UTF-8编码格式的M3U文件。M3U文件是一个文本文件,记录视频文件的索引。
概括说,hls协议将流媒体切片成的ts文件和m3u8文件,通过m3u8索引文件按序访问ts文件,客户端不停地从服务器获取视频文件,进而实现在线播放音视频的功能。
本文是通过videojs插件来实现视频接入的。
功能实现
1.安装依赖和引入
npm install --save video.js
npm install --save videojs-contrib-hls
在index.html引入
<!-- 视频取流 -->
<link href="https://cdn.bootcss.com/video.js/7.20.3/alt/video-js-cdn.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/video.js/7.20.3/video.js"></script>
<script src="https://cdn.bootcss.com/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script>
注:这里是第一个坑,看了很多方法都是直接安装依赖就可以,没有这一步。但是,如果不引入请求是成功的,但是视频却加载不出来。
加上后就能正常显示了
文件内局部引入
import videojs from 'video.js';
import 'videojs-contrib-hls';
import 'video.js/dist/video-js.css';
2.代码实现
template代码
<div id="video-box" class="video-item" v-show="showFlag">
<video
id="my-video"
class="video-js vjs-default-skin"
controls
preload="auto"
style="width: 100%;height:100%"
>
<source id="source" :src="xxxx视频请求网址xxxx" type="application/x-mpegURL" />
</video>
</div>
初始化
const myPlayer = ref(null);
const initVideo = () => {
//初始化配置
myPlayer.value = videojs('my-video',{
bigPlayButton: false,
textTrackDisplay: false,
posterImage: true,
errorDisplay: false,
controlBar: true,
});
// 播放
myPlayer.value.play();
};
onMounted(() => {
setTimeout(() => {
initVideo();
}, 300);
});
注:第二个坑来了!当视频组件放到弹窗里,如果报错 TypeError: The element or ID supplied is not valid. (videojs)
这时就需要注意两点:
- 组件外层的div不能使用v-if,要使用v-show控制显隐
- videojs的初始化要在dom全部挂载后的,所以可以采用使用setTimeout延迟加载
以上是固定地址的写法,但是需求是要切换不同视频地址来源,还需要进一步优化
先将视频地址设为变量
<source id="source" :src="cameraURL" type="application/x-mpegURL" />
const srcPath = ref<string>('');
const initVideo = (url:string) => {
myPlayer.value = videojs('my-video',{
bigPlayButton: false,
textTrackDisplay: false,
posterImage: true,
errorDisplay: false,
controlBar: true,
});
// 设置url
myPlayer.value.src(url);
myPlayer.value.play();
};
注:第三个坑来了!在网上学习的过程中有些是下面这种写法,直接赋值属性值并且不在template里写source部分。我开始用这种方法,发现怎么都不生效。最后换成了上面的写法。(如果有大神知道原因还请指教)
myPlayer.src([
{
type: "application/x-mpegURL",
src: url
}
]);
注:紧接着问题又来了,切换url后视频加载不出来,查看网络发现切换url后的第一个请求会显示已取消
解决方法是将这条请求过滤
videojs.hook('beforeerror', (player, err) => {
// Video.js 在切换/指定 source 后立即会触发一个 err=null 的错误,这里过滤一下
if (err !== null) {
myPlayer.value.src(url);
}
// 清除错误,避免 error 事件在控制台抛出错误
return null;
});
请求都正常发现视频还是加载不出来,还会报错 解决办法:只能在切换来源的时候销毁原来的组件,再重新添加一个videojs并赋值。
补充:控制台报错但不影响视频正常显示。原因:video.js and videojs-contrib-hls版本冲突,解决:将videojs版本从7.20.3降为videojs 7.5.5\videojs-contrib-hls 5.15.0,无报错。
补充:中文设置
// 引入中文插件
import VideojsZhcnLanguage from 'video.js/dist/lang/zh-CN.json';
videojs.addLanguage('zh-CN',VideojsZhcnLanguage);
// 初始化是配置项加入
language: 'zh-CN',
最后附上成功实现的完整代码
template部分
<template>
<div id="video-box" class="video-item" v-show="showVideo">
</div>
</template>
Js部分
import videojs from 'video.js';
import 'videojs-contrib-hls';
import 'video.js/dist/video-js.css';
import VideojsZhcnLanguage from 'video.js/dist/lang/zh-CN.json';
videojs.addLanguage('zh-CN',VideojsZhcnLanguage);
const myPlayer = ref<any>(null);
const mountedFlag = ref<boolean>(false);
// 初始化视频组件
const initVideo = (url:string) => {
if (myPlayer.value) {
myPlayer.value.pause();
myPlayer.value.dispose();
}
// 向Dom中写入视频组件
document.getElementById('video-box').innerHTML = '';
const html = `<video id="fire-video" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" style="width: 100%;height:100%">
<source id="source" src="${url}" type="application/x-mpegURL">
</video>`;
document.getElementById('video-box').innerHTML = html;
// 初始化声明
myPlayer.value = videojs('fire-video',{
language: 'zh-CN',
autoplay: 'muted',
bigPlayButton: false,
textTrackDisplay: false,
posterImage: true,
errorDisplay: false,
controlBar: true,
});
videojs.hook('beforeerror', (player, err) => {
// Video.js 在切换/指定 source 后立即会触发一个 err=null 的错误,这里过滤一下
if (err !== null) {
myPlayer.value.src(url);
}
// 清除错误,避免 error 事件在控制台抛出错误
return null;
});
myPlayer.value.play();
};
// 监听,需要视频url由其他方法赋值且组件挂载完成后,才能进行初始化
watch([cameraURL,mountedFlag,], ([url,flag,]): void => {
if (url && flag) {
initVideo(url);
}
});
onMounted(() => {
setTimeout(() => {
mountedFlag.value = true;
}, 500);
});
// 组件销毁
onUnmounted(() => {
if (myPlayer.value) {
mountedFlag.value = false;
}
});
参考文档: