瀏覽代碼

新增Mpegts播放流

dedsudiyu 1 周之前
父節點
當前提交
218027ac09
共有 3 個文件被更改,包括 434 次插入1 次删除
  1. 265 0
      src/components/mpegtsVideo/mpegtsVideo.vue
  2. 8 1
      src/views/home.vue
  3. 161 0
      src/views/miniProgramVideoMpegts/index.vue

+ 265 - 0
src/components/mpegtsVideo/mpegtsVideo.vue

@@ -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>

+ 8 - 1
src/views/home.vue

@@ -3,16 +3,19 @@
     <div class="home">
       <miniProgramPlayback v-if="pageType == 'miniProgramPlayback'"></miniProgramPlayback>
       <miniProgramVideo v-if="pageType == 'miniProgramVideo'"></miniProgramVideo>
+      <miniProgramVideoMpegts v-if="pageType == 'miniProgramVideoMpegts'"></miniProgramVideoMpegts>
     </div>
 </template>
 <script>
     import miniProgramPlayback from '@/views/miniProgramPlayback/index.vue';
     import miniProgramVideo from '@/views/miniProgramVideo/index.vue';
+    import miniProgramVideoMpegts from '@/views/miniProgramVideoMpegts/index.vue';
     export default {
         name: 'home',
         components: {
           miniProgramPlayback,
           miniProgramVideo,
+          miniProgramVideoMpegts,
         },
         data() {
             return {
@@ -29,7 +32,11 @@
             // this.$router.push({
             //   path: '/miniProgramVideo'+'?'+text,
             // });
-            this.$set(this,'pageType','miniProgramVideo');
+            if(text.indexOf('mpegts') != -1){
+              this.$set(this,'pageType','miniProgramVideoMpegts');
+            }else{
+              this.$set(this,'pageType','miniProgramVideo');
+            }
           }else if(text.indexOf('cameraIndexCode') != -1){
             // this.$router.push({
             //   path: '/miniProgramPlayback'+'?'+text,

+ 161 - 0
src/views/miniProgramVideoMpegts/index.vue

@@ -0,0 +1,161 @@
+<template>
+  <div class="miniProgramVideoMpegts">
+    <p class="null-text" v-if="nullType">{{nullType}}</p>
+    <div class="video-max-box" v-if="buttonType&&!fullVideoType">
+      <mpegts-video class="video-box" :videoProps="item" v-for="(item,index) in videoList" :key="index"></mpegts-video>
+    </div>
+
+  </div>
+</template>
+<script>
+  import mpegtsVideo from '@/components/mpegtsVideo/mpegtsVideo.vue'
+  export default {
+    name: 'index',
+    components: {
+      mpegtsVideo,
+    },
+    data () {
+      return {
+        buttonType: false,
+        width: null,
+        height: null,
+        videoList: [],
+        nullType:false,
+        //报警视频数据
+        videoProps:null,
+        //全屏视频参数
+        fullVideoProps:{},
+        fullVideoType:false,
+      }
+    },
+    created(){
+      const ratio = 0.5625;
+      const winWidth = window.innerWidth;
+      let width = parseInt(winWidth - 20);
+      let height = parseInt(this.accMul(width, ratio));
+      this.$set(this, 'width', width);
+      this.$set(this, 'height', height)
+    },
+    mounted(){
+      this.getUrl();
+    },
+    methods:{
+      getUrl() {
+        let text = decodeURIComponent(window.location.href);
+        if(text.indexOf('touken') != -1){
+          this.subVideo(text);
+        }else{
+          this.$set(this,'nullType','参数异常,请联系管理员');
+        }
+      },
+      subVideo(text){
+        let self = this;
+        if(text.indexOf('touken') == -1){
+          this.$set(this,'nullType','touken参数异常,请联系管理员');
+          return
+        }
+        if(text.indexOf('source') == -1){
+          this.$set(this,'nullType','source参数异常,请联系管理员');
+          return
+        }
+        if(text.indexOf('type') == -1){
+          this.$set(this,'nullType','type参数异常,请联系管理员');
+          return
+        }
+        let urlList = text.split("?")[1].split("&");
+        let urlData = {};
+        urlList.forEach((item) => {
+          urlData[item.split("=")[0]] = item.split("=")[1];
+        });
+        localStorage.setItem('touken',urlData.touken)
+        // type 1.楼栋 2.楼层 3.楼道 4.实验室 5.楼道+实验室
+        let obj = {
+          page:'1',
+          pageSize:'4',
+          protocol:window.location.href.indexOf('https') !== -1?'wss':'ws',
+          streamType:1,
+        };
+        if(urlData.type == 1){
+          obj.buildId = urlData.buildId;
+        }else  if(urlData.type == 2){
+          obj.floorId = urlData.floorId;
+        }else  if(urlData.type == 3){
+          obj.passageway = urlData.floorId;
+        }else  if(urlData.type == 4){
+          obj.subIds = [urlData.subId];
+        }else if(urlData.type == 5){
+          obj.passageway = urlData.floorId;
+          obj.subIds = [urlData.subId];
+        }
+        if(urlData.source == '2'){
+          obj.source = 2;
+        }else if (urlData.source == '5'){
+          obj.source = 5;
+        }
+        if(urlData.type){
+          iotCameraFindByCondition(obj).then(response => {
+            if (!response.data.records[0]){
+              this.$set(this,'nullType','视频异常,请联系管理员');
+            }else{
+              let list = [];
+              for(let i=0;i<response.data.records.length;i++){
+                list.push(
+                  {
+                    width: this.width, //(宽度:非必传-默认600)
+                    height: this.height, //(高度:非必传-默认338)
+                    url: response.data.records[i].streamUrl,
+                    cameraIndexCode: response.data.records[i].deviceNo,
+                  }
+                )
+              }
+              this.$set(this,'videoList',list)
+              this.$nextTick(()=>{
+                setTimeout(function(){
+                  self.$set(self, 'buttonType', true);
+                },1000);
+              })
+            }
+          });
+        }else{
+          this.$set(this,'nullType','参数异常,请联系管理员');
+        }
+      },
+      //乘法
+      accMul(arg1, arg2) {
+        var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
+        try {
+          m += s1.split(".")[1].length
+        } catch (e) {
+        }
+        try {
+          m += s2.split(".")[1].length
+        } catch (e) {
+        }
+        return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m)
+      },
+    },
+  }
+</script>
+<style scoped lang="scss">
+  .miniProgramVideoMpegts{
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    .null-text{
+      text-align: center;
+      font-size:16px;
+      line-height:80px;
+    }
+    .video-max-box {
+      padding-top:10px;
+      overflow-y: scroll;
+      overflow-x: hidden;
+    }
+
+    .video-box {
+      margin: 0 auto 10px;
+      display: inline-block
+    }
+  }
+</style>