多监控m3u8视频流,怎么获取每个监控的封面图(纯前端)
文章目录
- 1.背景
- 2.问题分析
- 3.解决方案
- 3.1解决思路
- 3.2解决过程
- 3.2.1 封装播放组件
- 3.2.2 隐形的视频div
- 3.2.3 截取封面图
- 3.3 结束
1.背景
有这样一个需求:
- 给你一个监控列表,每页展示多个监控(至少12个,m3u8格式);
- 且展示每个监控的第一帧画面的封面图,但是后端没给你返回封面图;
- 点击监控时可以查看具体的监控弹框,支持全屏;
那么、你会怎么办呢?
后端说:后端获取封面图会比较麻烦,还要单独引入一个模块,所以丢给前端来做喽
备注:本例仅为思路分析,具体代码需要具体实现
实现效果如下:
2.问题分析
- 首先,有的vue2的视频流播放组件,支持多个视频流同时播放,但那是定制的4宫格或9宫格,不够自由;
- 或者,有的支持多视频流播放,但是本人暂未找到,其实也可以自定义实现多流播放;
- 其次,即使有这种组件,但是毕竟是网页端,如果一直播放,性能肯定会扛不住,毕竟一直接收视频片段,这个是巨量的。
- 所以,还是老老实实地想办法获取第一帧封面图吧!
备注:前端用的vue2
下图为多视频流播放的示例图:
但是这样会出现一个问题,那就是你会获取无止境的m3u8推流,不久,你的浏览器就会内存溢出(内存将不断增大,下图非只是初始时的截图):
作为对比,看下只用封面截图的网页资源占用:
所以,上述方案作废!
由于上述方案作废,所以此处不做多视频流同时播放的代码示例了。
3.解决方案
3.1解决思路
- 在监控列表页放一个隐藏的div,div中放的是视频播放组件;
- 然后循环播放,在播放的时候截取封面,用的
html2canvas
; - 将截取的封面处理进监控列表的数组中;
- 销毁隐藏的视频流播放的div;
3.2解决过程
3.2.1 封装播放组件
首先,你要有一个支持m3u8播放的vue组件,本例用的是hls.min.js
,将这个js文件放在/public/js/hls.min.js
下。
然后封装的组件示例如下hlsVideo.vue
:
<!-- 视频巡检专用,其他组件勿用 -->
<template>
<div >
<video
id="hls"
class="img-responsive video-js vjs-default-skin"
controls
ref="videoRef"
preload="auto"
:autoplay="autoplay"
:muted="muted"
:style="{ width: width, height: height }"
>
</video>
</div>
</template>
<script>
let hlsPlayer
export default {
props: {
// 监控地址
videoUrl: { type: String, default: '' },
// 监控宽高
width: { type: String, default: '100%' },
height: { type: String, default: '100%' },
// 自动播放
autoplay: { type: Boolean, default: true },
// 是否静音,默认静音
muted: { type: Boolean, default: true }
},
watch: {
videoUrl: {
deep: true,
handler(val) {
if ( hlsPlayer != null ) {
hlsPlayer.destroy()
}
this.handlePlayer()
}
}
},
mounted() {
this.handlePlayer()
},
beforeDestroy() {
if ( hlsPlayer!= null ) {
hlsPlayer.destroy()
hlsPlayer = null
}
},
methods: {
// emit 发送播放状态
handlePlayer() {
if(this.videoUrl) {
const video = this.$refs.videoRef
hlsPlayer = new Hls();
hlsPlayer.loadSource(this.videoUrl);
hlsPlayer.attachMedia(video);//将视频元素绑定到此 HLS 对象
hlsPlayer.on(Hls.Events.MANIFEST_PARSED, () => {
video.play();
this.$emit("playing", true);
});
hlsPlayer.on(Hls.Events.ERROR, (eventName, data) => {
if ( data.fatal && data.type !== "networkError" ) {
video.pause();
this.$emit("playing", false);
}
});
} else {
if ( hlsPlayer != null ) {
hlsPlayer.destroy()
}
}
},
}
}
</script>
<style lang="scss" scoped>
::v-deep {
// 隐藏视频监控进度条
video::-webkit-media-controls-timeline {
display: none;
}
// 隐藏视频监控剩余时间
video::-webkit-media-controls-time-remaining-display {
display: none;
}
}
</style>
3.2.2 隐形的视频div
有了视频播放组件,我们只需要引入即可:
import HlsVideo from "@/componentsUser/m3u8Video/hlsVideo.vue";
然后在页面中写一个div元素来放置该组件:
<!-- 隐藏区域,用于截取封面图 -->
<div class="hide-video">
<hls-video
v-if="tempVideoUrl"
:video-url="tempVideoUrl"
id="hiddenVideo"
ref="hiddenRef"
:autoplay="true"
@playing="changePlayStatus"
/>
</div>
再加上样式,让我们看不见它,让它自己截图玩去吧,CSS样式如下:
.hide-video {
width: 300px;
height: 180px;
position: absolute;
top: 0;
left: 0;
z-index: -2;
}
同时在data中声明以下变量:
tempVideoUrl: null, // 隐藏的m3u8播放路径
tempVideoIndex: 0, // 隐藏播放的当前下标,用户循环
monitorList: [], // 监控列表
3.2.3 截取封面图
获取监控列表,然后进行截图处理
请确保已引入如下插件:
import html2canvas from ‘html2canvas’;
获取监控列表,也就是正常的接口请求:
// 获取监控列表
getList() {
this.loading = true
listDevice(this.queryParams).then(res => {
const tempArr = res.rows.map(item => {
return {
deviceCode: item.deviceCode,
type1Name: item.type1Name,
type2Name: item.type2Name,
name: item.name,
deviceLocation: item.deviceLocation,
status: item.status,
videoUrl: null, // 监控地址,接口未返回,下面单独的接口循环获取,最好还是让后端返回吧
posterImg: null, // 默认封面为空,下面循环截取
}
})
this.total = res.total
this.loading = false
this.isFirstLoading = false
this.requestAllVideoUrl(tempArr)
})
},
由于监控列表未返回m3u8的地址,所以此处需要再次循环请求获取地址:
你们最好让你们的后端把地址也返回,不然前端循环获取不太好!
/** 循环请求,获取监控地址,todo:并且截取封面图 */
requestAllVideoUrl(tempArr) {
for(let i = 0; i < tempArr.length; i++) {
const id = tempArr[i].deviceCode
// 离线(值为2)的直接空值,省略请求
if(!!id && tempArr[i].status != 2) {
getVideo({
channelId: id,
}).then(res => {
tempArr[i].videoUrl = res.data || null
})
}
}
this.monitorList = tempArr
// 初始设置第一个监控地址给隐藏的视频模仿组件,然后下面才可以循环截图
let timer2 = setInterval(() => {
if(tempArr[0].videoUrl) {
this.tempVideoUrl = tempArr[0].videoUrl
clearInterval(timer2)
}
}, 100)
},
我们在上面的hlsVideo.vue
组件中写了:
this.$emit("playing", true);
this.$emit("playing", false);
目的是当一个m3u8播放成功后,就告诉组件:我播放完了,你可以截图了,并且可以切换下一个m3u8地址了。
具体的截图代码如下:
// todo:视频改变播放状态,执行截图功能
changePlayStatus(e) {
if(e) {
setTimeout(() => {
this.$nextTick(() => {
if(this.$refs.hiddenRef) {
const video_width = 396
const video_height = 180
const dom = document.getElementById("hiddenVideo");
html2canvas(dom, {
windowWidth: video_width * 1.78,
windowHeight: video_height * 1.78,
x: 0,
y: 0,
useCORS: true, //跨域
scale: 0.8,
}).then(canvas => {
// 截图的base64位图片
const imgData = canvas.toDataURL('image/jpeg', 1.0);
// console.log('index', this.tempVideoIndex, imgData)
this.monitorList[this.tempVideoIndex].posterImg = imgData
if(this.tempVideoIndex < this.monitorList.length - 1) {
this.tempVideoIndex++
if(this.monitorList[this.tempVideoIndex].status != 0) {
this.tempVideoUrl = this.monitorList[this.tempVideoIndex].videoUrl
} else {
this.tempVideoIndex++
this.tempVideoUrl = this.monitorList[this.tempVideoIndex].videoUrl
}
} else {
this.tempVideoUrl = null
}
})
}
})
}, 500)
}
3.3 结束
至此,截图完毕!截图就是最上面放的截图,此处不重复放了
- 也成功地把封面图赋值给监控列表了,M3u8也销毁了,
- 监控列表现在放的是图片,
- 也不存在内存溢出等性能问题了
本例仅做思路,具体代码具体对待