|
@@ -0,0 +1,265 @@
|
|
|
|
+<!--
|
|
|
|
+@name: mpegtsVideo
|
|
|
|
+@description: 通用视频组件
|
|
|
|
+@author: zc
|
|
|
|
+@time: 2023/11/30
|
|
|
|
+@引入:
|
|
|
|
+
|
|
|
|
+ <mpegts-video style="display: inline-block" :videoProps="item" v-for="(item,index) in videoList" :key="index"></mpegts-video>
|
|
|
|
+
|
|
|
|
+ 或
|
|
|
|
+
|
|
|
|
+ <mpegts-video style="display: inline-block" :videoProps="videoProps"></mpegts-video>
|
|
|
|
+
|
|
|
|
+ import mpegtsVideo from '@/components/mpegtsVideo/mpegtsVideo.vue'
|
|
|
|
+
|
|
|
|
+ components: {
|
|
|
|
+ mpegtsVideo,
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ videoProps:{
|
|
|
|
+ cameraIndexCode:50, //(ID:非必传-默认随机生成)
|
|
|
|
+ width:600, //(宽度:非必传-默认600)
|
|
|
|
+ height:338, //(高度:非必传-默认338)
|
|
|
|
+ type:'flv', //(视频类型:非必传-默认'flv')
|
|
|
|
+ isLive:true, //(是否直播流:非必传-默认true)
|
|
|
|
+ url:"" //(视频地址:必传)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+-->
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <div class="mpegtsVideo">
|
|
|
|
+ <p class="reconnectText"
|
|
|
|
+ :style="'height:'+videoData.height+'px;width:'+videoData.width+'px;line-height:'+videoData.height+'px;'"
|
|
|
|
+ v-if="reconnectType">连接已断开,请联系管理员或等待重连</p>
|
|
|
|
+ <video
|
|
|
|
+ :id="videoData.cameraIndexCode" :ref="videoData.cameraIndexCode"
|
|
|
|
+ :width="videoData.width" :height="videoData.height"
|
|
|
|
+ autoplay controls muted >
|
|
|
|
+ </video>
|
|
|
|
+ <!--<img :src="videoImg" style="width:600px;height:300px;">-->
|
|
|
|
+ <!--<p class="el-icon-full-screen full-screen-button" @click="fullScreen"></p>-->
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+<script>
|
|
|
|
+ import mpegts from 'mpegts.js'
|
|
|
|
+ export default {
|
|
|
|
+ name: 'mpegtsVideo',
|
|
|
|
+ props: {
|
|
|
|
+ videoProps:{},
|
|
|
|
+ },
|
|
|
|
+ data () {
|
|
|
|
+ return {
|
|
|
|
+ videoData:{},
|
|
|
|
+ flvPlayer:null,//连接数据
|
|
|
|
+ decodedFrames:null,//断流帧
|
|
|
|
+ decodedFramesNum:null,//断流计数器
|
|
|
|
+ reconnectType:false,//重连状态
|
|
|
|
+ videoImg:null,
|
|
|
|
+ errorNum:0//异常次数
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ created(){
|
|
|
|
+ this.$set(this,'videoData',{
|
|
|
|
+ cameraIndexCode:this.videoProps.cameraIndexCode?this.videoProps.cameraIndexCode:this.generateRandomString(),
|
|
|
|
+ width:this.videoProps.width?this.videoProps.width:600,
|
|
|
|
+ height:this.videoProps.height?this.videoProps.height:338,
|
|
|
|
+ type:this.videoProps.type?this.videoProps.type:'flv',
|
|
|
|
+ isLive:this.videoProps.isLive?this.videoProps.isLive:true,
|
|
|
|
+ url:this.videoProps.url,
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ mounted(){
|
|
|
|
+ this.initMpegts();
|
|
|
|
+ },
|
|
|
|
+ methods:{
|
|
|
|
+ //窗口全屏
|
|
|
|
+ fullScreen(){
|
|
|
|
+ this.$refs[this.videoData.cameraIndexCode].webkitRequestFullScreen();
|
|
|
|
+ },
|
|
|
|
+ //截取封面
|
|
|
|
+ videoCover(){
|
|
|
|
+ let video = document.getElementById(this.videoData.cameraIndexCode);
|
|
|
|
+ let canvas = document.createElement("canvas");
|
|
|
|
+ let width = video.width; //canvas的尺寸和图片一样
|
|
|
|
+ let height = video.height;
|
|
|
|
+ canvas.width = width;
|
|
|
|
+ canvas.height = height;
|
|
|
|
+ canvas.getContext("2d").drawImage(video, 0, 0, width, height); //绘制canvas
|
|
|
|
+ let videoImg = canvas.toDataURL('image/jpeg'); //转换为base64
|
|
|
|
+ },
|
|
|
|
+ //连接视频
|
|
|
|
+ initMpegts() {
|
|
|
|
+ //连接
|
|
|
|
+ if (mpegts.getFeatureList().mseLivePlayback) {
|
|
|
|
+ //视频DOM
|
|
|
|
+ let videoElement = document.getElementById(this.videoData.cameraIndexCode);
|
|
|
|
+ //设置
|
|
|
|
+ let config = {
|
|
|
|
+ liveBufferLatencyChasing: true,//自动追针
|
|
|
|
+ autoCleanupMaxBackwardDuration:3*60,//清除缓存 默认保留3*60
|
|
|
|
+ };
|
|
|
|
+ this.flvPlayer = mpegts.createPlayer({
|
|
|
|
+ type: this.videoData.type,
|
|
|
|
+ isLive: this.videoData.isLive,
|
|
|
|
+ url: this.videoData.url,
|
|
|
|
+ },config);
|
|
|
|
+ this.flvPlayer.attachMediaElement(videoElement);
|
|
|
|
+ this.flvPlayer.load();
|
|
|
|
+ this.flvPlayer.play();
|
|
|
|
+ let playPromise = this.flvPlayer.play();
|
|
|
|
+ if (playPromise !== undefined) {
|
|
|
|
+ playPromise.then((_) => {
|
|
|
|
+ this.$set(this,'reconnectType',false);
|
|
|
|
+ //播放
|
|
|
|
+ }).
|
|
|
|
+ catch ((error) => {
|
|
|
|
+ //暂停
|
|
|
|
+ this.$set(this,'reconnectType',true);
|
|
|
|
+ this.reconnectVideo();
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ //断流检测
|
|
|
|
+ this.flvPlayer.on(mpegts.Events.STATISTICS_INFO, (e) => {
|
|
|
|
+ // 已经解码的帧数
|
|
|
|
+ if(this.decodedFrames !== e.decodedFrames){
|
|
|
|
+ // 连接正常
|
|
|
|
+ this.$set(this,'decodedFrames',e.decodedFrames);
|
|
|
|
+ this.$set(this,'decodedFramesNum',0);
|
|
|
|
+ }else{
|
|
|
|
+ //连接异常
|
|
|
|
+ this.decodedFramesNum++
|
|
|
|
+ if(this.decodedFramesNum>30){
|
|
|
|
+ this.$set(this,'reconnectType',true);
|
|
|
|
+ this.reconnectVideo();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ this.flvPlayer.on(mpegts.Events.ERROR, e=> {
|
|
|
|
+ // 发生异常
|
|
|
|
+ if(this.errorNum > 5){
|
|
|
|
+ this.videoOff();
|
|
|
|
+ }else{
|
|
|
|
+ this.errorNum++
|
|
|
|
+ this.$set(this,'reconnectType',true);
|
|
|
|
+ this.reconnectVideo();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ this.flvPlayer.on(mpegts.Events.LOADING_COMPLETE, (e) => {
|
|
|
|
+ // 视频流已结束
|
|
|
|
+ this.$set(this,'reconnectType',true);
|
|
|
|
+ this.reconnectVideo();
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ //重新连接
|
|
|
|
+ reconnectVideo(){
|
|
|
|
+ let self = this;
|
|
|
|
+ this.flvPlayer.pause();
|
|
|
|
+ this.flvPlayer.unload();
|
|
|
|
+ this.flvPlayer.detachMediaElement();
|
|
|
|
+ this.flvPlayer.destroy();
|
|
|
|
+ this.flvPlayer = null;
|
|
|
|
+ setTimeout(function(){
|
|
|
|
+ self.initMpegts();
|
|
|
|
+ },2000);
|
|
|
|
+ },
|
|
|
|
+ //断开视频流
|
|
|
|
+ videoOff(){
|
|
|
|
+ // 断开连接
|
|
|
|
+ this.flvPlayer.pause();
|
|
|
|
+ this.flvPlayer.unload();
|
|
|
|
+ this.flvPlayer.detachMediaElement();
|
|
|
|
+ this.flvPlayer.destroy();
|
|
|
|
+ this.flvPlayer = null;
|
|
|
|
+ },
|
|
|
|
+ //生成随机ID
|
|
|
|
+ generateRandomString() {
|
|
|
|
+ let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
|
+ let randomString = "";
|
|
|
|
+ for (let i = 0; i < 8; i++) {
|
|
|
|
+ let randomIndex = Math.floor(Math.random() * chars.length);
|
|
|
|
+ randomString += chars.charAt(randomIndex);
|
|
|
|
+ }
|
|
|
|
+ return randomString;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ beforeDestroy() {
|
|
|
|
+ let self = this;
|
|
|
|
+ self.videoOff();
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+</script>
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+ .mpegtsVideo{
|
|
|
|
+ *{
|
|
|
|
+ margin:0;
|
|
|
|
+ padding:0;
|
|
|
|
+ }
|
|
|
|
+ position: relative;
|
|
|
|
+ .reconnectText{
|
|
|
|
+ position: absolute;
|
|
|
|
+ color:#fff;
|
|
|
|
+ z-index:2;
|
|
|
|
+ text-align: center;
|
|
|
|
+ top:0;
|
|
|
|
+ left:0;
|
|
|
|
+ }
|
|
|
|
+ .full-screen-button{
|
|
|
|
+ color:#fff;
|
|
|
|
+ position:absolute;
|
|
|
|
+ font-size:20px;
|
|
|
|
+ height:30px;
|
|
|
|
+ width:30px;
|
|
|
|
+ line-height:30px;
|
|
|
|
+ text-align: center;
|
|
|
|
+ top:0;
|
|
|
|
+ right:0;
|
|
|
|
+ z-index: 1;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ }
|
|
|
|
+ .full-screen-button:hover{
|
|
|
|
+ color:#00b7ee;
|
|
|
|
+ }
|
|
|
|
+ video::-webkit-media-controls-enclosure {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ // 音频开关
|
|
|
|
+ video::-webkit-media-controls-mute-button {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ // 音频大小
|
|
|
|
+ video::-webkit-media-controls-volume-slider {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ //全屏按钮
|
|
|
|
+ video::-webkit-media-controls-fullscreen-button {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ //开始暂停按钮
|
|
|
|
+ video::-webkit-media-controls-play-button {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ // 时间显示
|
|
|
|
+ video::-webkit-media-controls-current-time-display {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ // 未知时间
|
|
|
|
+ video::-webkit-media-controls-toggle-closed-captions-button {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ //进度条
|
|
|
|
+ video::-webkit-media-controls-timeline {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ //未知进度条
|
|
|
|
+ video::-webkit-media-controls-time-remaining-display {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+ video {
|
|
|
|
+ object-fit: fill; //视频全铺
|
|
|
|
+ pointer-events: none; //点击禁用
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+</style>
|