【大华摄像机hls拉流vue使用videojs展示 一个页面多个视频同时播放】

  • Post author:
  • Post category:vue




前言

主要参考https://blog.csdn.net/qq_39923762/article/details/89149715?spm=1001.2014.3001.5506

我们的项目是在大华摄像机通过流媒体服务器进行拉流的方式进行的,前端调取视频数据只需要通过访问ip就可以获取到了。

cnpm install video.js

cnpm install videojs-contrib-hls –save

main.js中引入css

import ‘video.js/dist/video-js.css’

template页面:

<div v-show='!isError'>
   <video id="videobox" class="video-js vjs-default-skin vjs-big-play-centered vjs-16-9" controls preload="auto" webkit-playsinline="true" playsinline="true" type="application/x-mpegURL" allowsInlineMediaPlayback=YES  webview.allowsInlineMediaPlayback=YES  width='100%' ref='videoRef' x5-video-player-fullscreen="true" :poster="posterSrc" >
      <source id="sourceBox" :src="videoSrc">
      <p class="vjs-no-js">不支持播放</p>
   </video>
</div>
<div v-show='isError' class="errorTip"><p>视频出错了!</p></div>
<script>
组件页面引入:
import videojs from 'video.js'
import 'videojs-contrib-hls'
......
 
data(){
  return{
	videoSrc:'http://down.soundaer.com/live/stream_89003_sd/playlist.m3u8?k=d708550fbd49c58a1b8a8412c8623277&t=1553687908',
	posterSrc:'https://matrimony001.100msh.net.cn/public/code/material/mp-7261-1554175849.jpg',
   isError:false
   }
}
mounted() {
	//为避免在初始化video时播放源是空的,报播放源错误,需要先给source 的src赋值
        var player = videojs('videobox',{
                    bigPlayButton: true,
                    textTrackDisplay: true,
                    posterImage: true,
                    errorDisplay: false,
                    controlBar: false,
                    playbackRates: [0.5, 1, 1.5, 2],
                    ControlBar:{
                        customControlSpacer: true
                    }
                },
                function onPlayerReady(){
                    this.play();
                    setTimeout(() => {   //延时确保能监听到视频源错误
                        var mediaError = this.error();
                        if(mediaError!=null && mediaError.code){
                            _this.isError=true
                            Dialog.alert({
                                message: '啊哦,播放出错了。<br>请刷新重试,如无法播放建议您观看其它内容。',
                                confirmButtonText:'确定'
                            }).then(() => {
                                _this.goback();
                            });
                        }
                    },1000);
                });        
             // player.width(this.videoW)   //设置播放器宽度
},
 beforeDestroy(){
            const videoDom = this.$refs.videoRef;   //不能用document 获取节点
            videojs(videoDom).dispose();  //销毁video实例,避免出现节点不存在 但是flash一直在执行,也避免重新进入页面video未重新声明
   }
</script>

下面是自己的内容

请添加图片描述

上图是大华摄像机的拉流方式,需要知道的信息是平台ip,地址,设备编号,通道号和码流类型,这些都可以通过大华的摄像机管理平台进行获取,码流类型分为主码流和辅码流,两个的差别是视频的清晰度问题,因为我们需要在一个页面展示多个视频数据,所以

同时传输主码流会发生问题,造成卡顿,对网络造成压力。然而使用辅码流会对视频在全屏的时候造成视频模糊的问题。



问题一

请添加图片描述

由于项目图片不便于展示进行打码,页面进行视频展示采用的是videojs,在github上可以查到使用方法,这里出现了一个小问题是在github上的

视频播放器不支持h265格式的浏览器展现,所以在做vue页面之前需要保证视频流格式是h264的

。如果使用videojs对h265视频进行播放会发现

只有声音没有画面

,是因为声音的解码方式和画面解码方式不同。使用vlc工具可以对视频流格式进行分析
请添加图片描述

vlc下载地址


然后我们使用videojs的全屏检测功能进行处理,当我们是正常屏幕时我们使用辅码流,在全屏时使用主码流。

async initVideo () {
      await this.getUrlsList()
      this.urlsList.map((item, i) => {
        var player1 = videojs('myVideo' + i, {
          bigPlayButton: true,
          textTrackDisplay: true,
          posterImage: true,
          errorDisplay: false,
          controlBar: false,
          ControlBar: {
            customControlSpacer: true
          }
        },
        function onPlayerReady () {
          player1.play()
          // 捕捉是否全屏,对视频流进行主码流和辅码流的切换
          player1.on('fullscreenchange', function (e) {
            if (player1.isFullscreen()) {
              player1.src(item.src)
              player1.load()
              player1.play()
            } else {
              player1.src(item.lowsrc)
              player1.load()
              player1.play()
            }
          })
          const _this = this
          setTimeout(() => {
            var mediaError = this.error()
            if (mediaError != null && mediaError.code) {
              _this.isError = true
            }
          }, 1000)
        })
      })
    },

借鉴

videojs全屏设置



问题二

在这里插入图片描述

我们设置的是可以切换页面的,因为不只有6个视频流所以需要监听切换事件,但是因为不同步,在我们切换的时候可能会出现

视频没有加载出来的问题

,同样在上面的代码中进行呈现。我们使用

async和await配合

的方式进行处理,进行对获取url信息进行等待。



问题三


data-setup=”{}”设置陷阱


使用data-setup=”{}”,不需要对videojs对象进行初始化,比较方便,一开始我也是使用这种方式但是这种方式会出现白屏问题,无法显示。所以千万不要用data-setup=”{}”!!!



问题四


多个视频如何呈现的问题


首先建立urllist,对后台的url进行存储

<div v-show='!isError' v-for="(item,i) in urlsList" :key="i" :style="{width: list[i].width+ 'px', height: list[i].height+ 'px', left: list[i].left+ 'px', top: list[i].top+ 'px', position: 'absolute'}">
     <video :id="'myVideo'+i" ref='videoRef'   style="object-fit: fill; width: 100%; height: 100%" class="video-js vjs-default-skin vjs-big-play-centered " muted controls preload="auto" webkit-playsinline="true" playsinline="true" type="application/x-mpegURL" allowsInlineMediaPlayback=YES  webview.allowsInlineMediaPlayback=YES  width='100%'  x5-video-player-fullscreen="true">
       <source id="sourceBox" :src="item.lowsrc">
       <p class="vjs-no-js">不支持播放</p>
     </video>
   </div>

使用v-for对urllist数据进行查询,同时video标签中 :id=”‘myVideo’+i” 对每个视频进行id设置,在后面初始化的时候进行一一对应。同时设置视频的videoref,

一开始的时候以为多个组件只设置一个ref是不行的,在销毁的使用需要用到这个东西。后来发现对于这种多个组件使用一个ref的方式可以使用ref[i]

beforeDestroy () {
    console.log('destroy')
    this.urlsList.map((item, i) => {
      const videoDom = this.$refs.videoRef[i]
      videojs(videoDom).dispose()
    })
  }

对于多个视频的位置设置我们将六个视频的位置保存在list中,可以直接调用来固定每个视频的位置



全部代码

<template>
  <div>
    <el-form :inline="true"  class="demo-form-inline">
      <el-form-item label="摄像头设备编号">
        <el-input v-model="device_num" placeholder="例如:1000000"></el-input>
      </el-form-item>
      <el-form-item label="摄像头通道号">
        <el-input v-model="way_num" placeholder="摄像头通道号"></el-input>
      </el-form-item>
      <el-form-item label="摄像头名字">
        <el-input v-model="cameraName" placeholder="摄像头名字"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="addCamera">添加</el-button>
      </el-form-item>
      <el-form-item label="摄像头选择">
      <el-select v-model="cameraid" clearable placeholder="请选择" style='paddingLeft:20px;'>
        <el-option
          v-for="item in cameraOptions"
          :key="item.value"
          :label="item.label"
          :value="item.value">
        </el-option>
      </el-select>
      <el-button type="primary" @click="deleteCamera" style="color:#fff;margin-left:20px;">删除</el-button>
      </el-form-item>
    </el-form>
    <div v-show='!isError' v-for="(item,i) in urlsList" :key="i" :style="{width: list[i].width+ 'px', height: list[i].height+ 'px', left: list[i].left+ 'px', top: list[i].top+ 'px', position: 'absolute'}">
      <video :id="'myVideo'+i" ref='videoRef'   style="object-fit: fill; width: 100%; height: 100%" class="video-js vjs-default-skin vjs-big-play-centered " muted controls preload="auto" webkit-playsinline="true" playsinline="true" type="application/x-mpegURL" allowsInlineMediaPlayback=YES  webview.allowsInlineMediaPlayback=YES  width='100%'  x5-video-player-fullscreen="true">
        <source id="sourceBox" :src="item.lowsrc">
        <p class="vjs-no-js">不支持播放</p>
      </video>
    </div>
    <div v-show='isError' class="errorTip"><p>视频出错了!</p></div>
    <div class="footer">
      <el-pagination
        @current-change="handleCurrentChange"
        :current-page="queryInfo.pagenum"
        :page-size="queryInfo.pagesize"
        layout="total, prev, pager, next, jumper"
        :total="total">
      </el-pagination>
    </div>

  </div>
</template>

<script>
import videojs from 'video.js'
import 'videojs-contrib-hls'
export default {
  inject: ['reload'],
  data () {
    return {
      cameraOptions: [],
      cameraid: '',
      queryInfo: {
        query: '',
        // 当前的页数
        pagenum: 1,
        // 当前每页显示多少条数据
        pagesize: 6
      },
      urlsList: [],
      total: 0,
      device_num: '',
      way_num: '',
      cameraName: '',
      list: [
        {
          width: 416,
          height: 234,
          left: 100,
          top: 150
        }, {
          width: 416,
          height: 234,
          left: 550,
          top: 150
        }, {
          width: 416,
          height: 234,
          left: 1000,
          top: 150
        }, {
          width: 416,
          height: 234,
          left: 100,
          top: 400
        }, {
          width: 416,
          height: 234,
          left: 550,
          top: 400
        }, {
          width: 416,
          height: 234,
          left: 1000,
          top: 400
        }
      ],
      isError: false
    }
  },
  methods: {
    deleteCamera () {
      this.$confirm('此操作将永久删除该摄像头, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        const {
          meta
        } = await this.$api.cctv.removeCamera({
          params: {
            cctvName: this.cameraOptions[this.cameraid].label
          }
        })
        if (meta.status !== 200) {
          return this.$message.error('删除摄像头失败!')
        }
        await this.getCameraOptions()
        this.$router.go(0)
        this.$message.success('删除成功!')
      }).catch(() => {
        this.$message.info('取消删除!')
      })
    },
    async getCameraOptions () {
      const {
        meta,
        data
      } = await this.$api.cctv.getCameraOptions()
      console.log('meta', meta)
      if (meta.status !== 200) {
        return this.$message.error('获取播放列表失败!')
      }
      this.cameraOptions = data.cameraOptions
    },
    async addCamera () {
      const {
        meta
      } = await this.$api.cctv.addCamera({
        params: {
          device_num: this.device_num,
          way_num: this.way_num,
          cctvName: this.cameraName
        }
      })
      if (meta.status !== 200) {
        return this.$message.error('添加摄像头失败!')
      }
      this.$router.go(0)
    },
    async getUrlsList () {
      const {
        meta,
        data
      } = await this.$api.cctv.getRealplay({ params: this.queryInfo })
      console.log('meta', meta)
      if (meta.status !== 200) {
        return this.$message.error('获取播放列表失败!')
      }
      this.urlsList = data.urls
      this.total = data.total
      this.dataBack = data
      console.log(data)
    },
    async initVideo () {
      await this.getUrlsList()
      this.urlsList.map((item, i) => {
        var player1 = videojs('myVideo' + i, {
          bigPlayButton: true,
          textTrackDisplay: true,
          posterImage: true,
          errorDisplay: false,
          controlBar: false,
          ControlBar: {
            customControlSpacer: true
          }
        },
        function onPlayerReady () {
          player1.play()
          player1.on('fullscreenchange', function (e) {
            if (player1.isFullscreen()) {
              player1.src(item.src)
              player1.load()
              player1.play()
            } else {
              player1.src(item.lowsrc)
              player1.load()
              player1.play()
            }
          })
          const _this = this
          setTimeout(() => {
            var mediaError = this.error()
            if (mediaError != null && mediaError.code) {
              _this.isError = true
            }
          }, 1000)
        })
      })
    },
    async handleCurrentChange (newPage) {
      this.queryInfo.pagenum = newPage
      //  切换页面的时候需要添加这个将前一个页面的视频流关闭掉并且初始化urllist不然会导致页面不出现视频的问题,同时视频流一直在发送
      this.urlsList.map((item, i) => {
        const videoDom = this.$refs.videoRef[i]
        videojs(videoDom).dispose()
      })
      this.urlsList = ''
      this.initVideo()
    }
  },
  created () {
    this.initVideo()
  },
  mounted () {
    this.initVideo()
    this.getCameraOptions()
    this.$emit('change_index',
      {
        itemId: 52,
        subPath: '/realtime'
      })
  },
  beforeDestroy () {
    console.log('destroy')
    this.urlsList.map((item, i) => {
      const videoDom = this.$refs.videoRef[i]
      videojs(videoDom).dispose()
    })
  }
}
</script>

<style>
  .footer{
    position:fixed;
    bottom: 10px;
    left: 100px;

  }
 </style>

上面对videojs的使用作介绍,借鉴https://blog.csdn.net/qq_39923762/article/details/89149715?spm=1001.2014.3001.5506



版权声明:本文为qq_39523488原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。