Bootstrap

vue对接海康视频监控播放、回放的方法,流程,以及存在的注意点和踩过的坑!

一.对接视频流的方法

    既然要对接海康的视频,那自然少不了怎样去处理视频流。其实通过这几天的探索,我个人大概总结了如下几种方式。(如有解释不对,欢迎大佬指点)

首先 海康支持下图几种协议

     1.就是用 video.js 、videojs-flash、vue-video-player来播放 ‘rtmp’协议的视频流,这种例子有很  多,大家可以自行查阅一下。但是这种方法有个很大的弊端,就是它需要依赖于浏览器的  flash 插件,但是谷歌已经明确的表示,2020年底将不再用flash,因此,你没有flash插件,去使用这种方式播放浏览器是会报错的。

   2.用nginx搭建一个流媒体服务器,这种方式网上也很多例子,我对他的理解,就是通过ffmpe这个工具推流到nginx上,然后你去访问nginx,通过nginx监听的端口,他在将这个流转发给你,你再通过地址将流拉下来。然后播放,但是我们这海康安防平台貌似并没有开启推流功能。而且400多个人工摄像头推起来成本太大,所以这地方我就没在深入研究。感兴趣的可参考 ffmpeg+nginx-rtmp转发视频流_nginx 视频流-CSDN博客

   3.通过@easydarwin/easyplayer插件(亲测有效),地址 @easydarwin/easyplayer - npm 里面文档很详细,根据文档部署在vue项目中即可(知道你们不一定看,那,这有手把手教的Easy-player.js在vue中的使用教程 - 知乎)但是话说回来,这个插件对于海康返回的接口协议而言,只能播放 m3u8/HLS 协议了,这种视频流需要你在查询海康视频地址的时候(/api/video/v2/cameras/previewURLs)将 protocol=“hls”这个参数加上

  4.最简单,开发成本最少的。就是用人家海康的已经封装好的web插件了,如果没什么特别的项目需求的话,强烈推荐这种方式,下面会详细解说用法,以及踩坑

至于怎么获取海康的视频地址,以及接口安全认证, 参考我上篇文章vue对接海康安防平台接口,以及处理接口认证规则流程-CSDN博客

这里给大家一些现在视频流地址,迄今为止还有效,可供测试用,用Vlc打开即可

rtmp://ns8.indexforce.com/home/mystream

https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8

https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8

https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8

二、通过插件,实现视频播放、回放功能(末尾源码附上)

1.首先在海康平台下载 web插件 海康开放平台
2.解压下载好的压缩包,将bin目录下的程序安装好

3.可以将demo中的.html的必要参数修改,然后运行到浏览器中查看

 测试没问题后,既可以将他整理到Vue项目中了,

4.将

这里面的Js文件通通放到vue的public目录下

并且在必须!必须!必须!vue 的index.html中引用

3.这样在我们的.vue组件中就可以直接使用了,不墨迹了,直接上代码了

根据自己业务对立面的数据进行修改

<template>
  <div class="home">
    <el-container style="height: 100vh; border: 1px solid #eee">
      <el-aside width="200px">
        <el-menu>
          <div class="search-input">
            <el-input
              @keydown.enter="fuzzyRetrieval"
              clearable
              size="small"
              v-model="searhKey"
              placeholder="请输入关键字"
            ></el-input>
            <el-button class="serachButton" size="small" @click="fuzzyRetrieval"
              ><i class="el-icon-search"></i
            ></el-button>
          </div>

          <el-menu-item-group v-if="cameraList.length" style="margin-top: 20px">
            <el-menu-item
              v-for="item in cameraList"
              :key="item.cameraIndexCode"
              :index="item.cameraIndexCode"
            >
              <div
                @dblclick="videoDetails(item.cameraIndexCode)"
                @click="cameraIndexCode = item.cameraIndexCode"
                :title="item.cameraName"
                class="cameraName-list"
              >
                {{ item.cameraName }}
              </div>
            </el-menu-item>
          </el-menu-item-group>

          <el-empty v-else description="暂无数据"></el-empty>
        </el-menu>
      </el-aside>

      <el-container>
        <el-header>
          <div>
            <el-button type="primary" plain>停止所有预览</el-button>
            <el-button type="primary" plain>停止所有回放</el-button>
          </div>
          <div>
            <el-radio-group v-model="radioType" @input="changeVideoType">
              <el-radio-button label="0">实时预览</el-radio-button>
              <el-radio-button label="1">录像回放</el-radio-button>
            </el-radio-group>
            <el-date-picker
              v-model="playbackTime"
              type="datetimerange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :unlink-panels="false"
              value-format="timestamp"
              :disabled="radioType === '0'"
              @focus="focusEvent"
              @blur="blurEvent"
              @change="videoDetails(cameraIndexCode)"
            >
            </el-date-picker>
          </div>
        </el-header>

        <el-main>
          <div id="video_home"></div>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>


<script>
import request from "@/utils/request.js";
import {
  SIGNATURE,
  SECRET,
  VIDEO_IP,
  VIDEO_PORT,
  APPKEY,
} from "@/components/basicStaticData.js";

//声明公用变量
var initCount = 0;
var pubKey = "";
var oWebControl = null; //webc插件的实例对象

export default {
  data() {
    return {
      //监控站点列表数据
      cameraList: [],
      //监控站点列表数据 备用
      cameraList_standby: [],
      //预览与回放的切换
      radioType: "0",
      //搜索框绑定的值
      searhKey: "",
      //日期输入框
      playbackTime: [],
      //当前用户点击的 监控点编号
      cameraIndexCode: "",
      //视频窗口初始的固定宽度
      idWidth: "",
      //视频窗口初始的固定高度
      idHeight: "",
    };
  },
  watch: {
    searhKey(newValue, oldValue) {
      if (!newValue) {
        this.cameraList = this.cameraList_standby;
      }
    },
  },
  mounted() {
    this.$nextTick(() => {
      //视频窗口初始的固定宽度,当前屏幕父元素的宽度
      this.idWidth = $("#video_home").width();

      //视频窗口初始的固定高度,当前屏幕父元素的高度
      this.idHeight = $("#video_home").height();

      // 初始化web插件
      this.initPlugin();

      // 监听浏览器的变化
      this.monitoringWindow();
    });
    //查询监控列表
    this.getCamerasList();
  },
  destroyed() {
    // 页面销毁后,将插件先隐藏,再销毁
    oWebControl.JS_HideWnd();
    oWebControl.JS_Disconnect();
  },
  methods: {
    /**
     * 分页查询查询的监控列表
     *
     */
    async getCamerasList() {
      const res = await request({
        url: "artemis/api/resource/v1/cameras",
        headers: {
          "x-ca-signature": SIGNATURE,
        },
        method: "POST",
        data: {
          pageNo: 1,
          pageSize: 460,
        },
      });
      const {
        data: { list },
      } = res.data;
      this.cameraList = list;
      this.cameraList_standby = list;
    },

    /**
     * 创建WebControl实例与启动插件
     */
    initPlugin() {
      oWebControl = new WebControl({
        szPluginContainer: "video_home", //指定容器id
        iServicePortStart: 15900, //指定起止端口号,建议使用该值
        iServicePortEnd: 15900,
        cbConnectSuccess: () => {
          //实例创建成功后需要启动服务
          oWebControl
            .JS_StartService("window", {
              dllPath: "./VideoPluginConnect.dll",
            })
            .then(
              () => {
                oWebControl.JS_SetWindowControlCallback({
                  // 设置消息回调
                  cbIntegrationCallBack: this.cbIntegrationCallBack(),
                });

                oWebControl
                  .JS_CreateWnd("video_home", this.idWidth, this.idHeight)
                  .then(() => {
                    //JS_CreateWnd创建视频播放窗口,宽高可设定
                    this.init(); //创建播放实例成功后初始化
                  });
              },
              function () {}
            );
        },
        cbConnectError: function () {
          oWebControl = null;
          $("#playWnd").html("插件未启动,正在尝试启动,请稍候...");
          WebControl.JS_WakeUp("VideoWebPlugin://"); //程序未启动时执行error函数,采用wakeup来启动程序
          initCount++;
          if (initCount < 3) {
            setTimeout(() => {
              this.initPlugin();
            }, 3000);
          } else {
            $("#playWnd").html("插件启动失败,请检查插件是否安装!");
          }
        },
      });
    },

    /**
     * 海康视频插件初始化,用于为之后的回放,预览视频做准备
     */
    init() {
      // 获取公钥
      this.getPubKey(() => {
        var appkey = APPKEY; //综合安防管理平台提供的appkey,必填
        var secret = this.setEncrypt(SECRET); //综合安防管理平台提供的secret,必填
        var ip = VIDEO_IP; //综合安防管理平台IP地址,必填
        var playMode = Number(this.radioType); //初始播放模式:0-预览,1-回放
        var port = VIDEO_PORT; //综合安防管理平台端口,若启用HTTPS协议,默认443
        var snapDir = "D:\\SnapDir"; //抓图存储路径
        var videoDir = "D:\\VideoDir"; //紧急录像或录像剪辑存储路径
        var layout = "2x2"; //playMode指定模式的布局
        var enableHTTPS = 1; //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1
        var encryptedFields = "secret"; //加密字段,默认加密领域为secret
        var showToolbar = 1; //是否显示工具栏,0-不显示,非0-显示
        var showSmart = 0; //是否显示智能信息(如配置移动侦测后画面上的线框),0-不显示,非0-显示
        var buttonIDs =
          "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769"; //自定义工具条按钮
        var reconnectTimes = 2; // 重连次数,回放异常情况下有效
        //var reconnectTime = 4;  // 每次重连的重连间>=reconnectTime

        //  ****** 请自行修改以上变量值********

        oWebControl
          .JS_RequestInterface({
            funcName: "init",
            argument: JSON.stringify({
              appkey: appkey, //API网关提供的appkey
              secret: secret, //API网关提供的secret
              ip: ip, //API网关IP地址
              playMode: playMode, //播放模式(决定显示预览还是回放界面)
              port: port, //端口
              snapDir: snapDir, //抓图存储路径
              videoDir: videoDir, //紧急录像或录像剪辑存储路径
              layout: layout, //布局
              enableHTTPS: enableHTTPS, //是否启用HTTPS协议
              encryptedFields: encryptedFields, //加密字段
              showToolbar: showToolbar, //是否显示工具栏
              showSmart: showSmart, //是否显示智能信息
              buttonIDs: buttonIDs, //自定义工具条按钮
              reconnectTimes: reconnectTimes, //重连次数
              //reconnectDuration:reconnectTime           //重连间隔
            }),
          })
          .then(() => {
            // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
            oWebControl.JS_Resize(this.idWidth, this.idHeight);
          });
      });
    },

    /**
     * 监控视频实时预览
     * @param {String} cameraIndexCode 监控站点的编号
     */
    video_Preview(cameraIndexCode) {
      oWebControl.JS_RequestInterface({
        funcName: "startPreview",
        argument: JSON.stringify({
          cameraIndexCode, //监控点编号,必填
          streamMode: 0, //主子码流标识:0-主码流,1-子码流,选填
          transMode: 1, // 传输协议:0-UDP,1-TCP,选填
          gpuMode: 0, //是否启用 GPU 硬解,0-不启用,1-启用,选填
          // wndId: playback_wind, // 播放窗口序号(在 2x2 以上布局下可指定播放窗口)选填
        }),
      });
    },

    /**
     * 监控视频回放
     * @param {String} cameraIndexCode 监控站点的编号
     * @param {Number} startTimeStamp 录像查询开始时间戳,单位:秒
     * @param {Number} endTimeStamp 录像查询结束时间戳,单位:秒
     */
    video_Playback(cameraIndexCode, startTimeStamp, endTimeStamp) {
      oWebControl.JS_RequestInterface({
        funcName: "startPlayback",
        argument: JSON.stringify({
          cameraIndexCode, //监控点编号,必填
          startTimeStamp, // 录像查询开始时间戳,单位:秒
          endTimeStamp, // 录像查询结束时间戳,单位:秒
          recordLocation: 1, // 录像存储类型 0-中心存储 1-设备存储
          transMode: 1, // 传输协议 ,0-UDP 1-TCP
          gpuMode: 0, // 是否开启 GPU 硬解,0-不开启 1-开
          // wndId: playback_wind, // 播放窗口序号(在 2x2 以上布局下可指定播放窗口)选填
        }),
      });
    },
    // 获取公钥
    getPubKey(callback) {
      oWebControl
        .JS_RequestInterface({
          funcName: "getRSAPubKey",
          argument: JSON.stringify({
            keyLength: 1024,
          }),
        })
        .then(function (oData) {
          // console.log(oData);
          if (oData.responseMsg.data) {
            pubKey = oData.responseMsg.data;
            callback();
          }
        });
    },

    // RSA加密
    setEncrypt(value) {
      var encrypt = new JSEncrypt();
      encrypt.setPublicKey(pubKey);
      return encrypt.encrypt(value);
    },

    // 推送消息
    cbIntegrationCallBack(oData) {
      // console.log(55555, JSON.stringify(oData));
    },

    /**
     * 用户点击左侧监控列表,调用安防平台接口,获取视频流
     * @param {*} cameraIndexCode 当前点击的监控列表的唯一值,用于接口传参
     */
    videoDetails(cameraIndexCode) {
      if (this.radioType === "0") {
        this.video_Preview(cameraIndexCode);
      } else {
        // 判断是否填写了回放时间

        if (!this.playbackTime.length) {
          this.$message.warning("请选择回放时间");
          return;
        }
        this.video_Playback(
          cameraIndexCode,
          Number(this.playbackTime[0]) / 1000,
          Number(this.playbackTime[1]) / 1000
        );
      }
    },

    /**
     * 对监控列表进行模糊检索
     */
    fuzzyRetrieval() {
      const new_CameraList = this.cameraList_standby.filter((item) => {
        return item.cameraName.indexOf(this.searhKey) > -1 ? true : false;
      });
      this.cameraList = new_CameraList;
    },

    /**
     * 时间输入框focus事件
     */
    focusEvent() {
      oWebControl.JS_Resize(this.idWidth - 650, this.idHeight);
    },

    /**
     * 时间输入框blur事件
     */
    blurEvent() {
      oWebControl.JS_Resize(this.idWidth, this.idHeight);
    },

    /**
     * 回放与预览的切换事件
     */
    changeVideoType() {
      // 先销毁 在创建,如果不这么做,只是单纯调用this.init()方法修改里面的预览和回放的参数值是行不通的
      oWebControl.JS_Disconnect().then(() => {
        this.initPlugin();
      });
    },
    /**
     * 监听浏览器窗口变化,以及关闭标签页变,并对插件做出处理
     */
    monitoringWindow() {
      // 标签关闭
      $(window).unload(function () {
        if (oWebControl != null) {
          oWebControl.JS_HideWnd(); // 先让窗口隐藏,规避插件窗口滞后于浏览器消失问题
          oWebControl.JS_Disconnect().then(
            function () {},
            function () {}
          );
        }
        // 监听resize事件,使插件窗口尺寸跟随DIV窗口变化
        $(window).resize(() => {
          if (oWebControl != null) {
            oWebControl.JS_Resize(
              $("#video_home").width(),
              $("#video_home").height()
            );
          }
        });
      });
    },
  },
};
</script>

<style lang="scss" scoped>
#video_home {
  width: 100%;
  height: 92vh;
}
.cameraName-list {
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-user-select: none; /*webkit浏览器*/
  -moz-user-select: none; /*火狐*/
  -ms-user-select: none; /*IE10*/
  user-select: none;
}
.el-main {
  padding: 0.3vh;
}
.el-header {
  background-color: #fff;

  display: flex;
  align-items: center;
  justify-content: space-between;
}
::v-deep .el-radio-button__inner,
.el-radio-group {
  vertical-align: baseline;
}
.search-input {
  width: 180px;
  display: flex;
  align-items: center;
  position: fixed;
  z-index: 99;
  box-shadow: 0px 5px 5px #d6d2d2;
}
.serachButton {
  font-size: 15px;
  padding: 7px 11px;
  background-color: #ecf1f8;
}
</style>


来看效果吧

回放

4.注意点

1.这个插件会置于浏览器的最上层,也就是说他会挡住你所有的页面元素,导致我日期没办法在        插件上边选择,如有办法,还请不吝赐教

2. 回放与预览切换时,通过直接改数值是没有用的,要先销毁,再创建

3.有不懂得多看看看插件开发指南

ok 就这样,如再发现问题会及时更新的,后续更新uni.app播放海康 欢迎评论区指正

;