vue 海康视频插件

背景:

在vue项目中,在pc端对接视频,播放的视频包括视频实时、视频回放等。

写文思路:海康视频插件,项目引入依赖,前端开发封装的组件

效果展示:

一、海康视频

二、海康视频插件

海康视频插件运行环境需要安装插件VideoWebPlugin.exe,对浏览器也有兼容性要求,具体看官方文档

插件使用步骤总结

1.new 一个WebControl 插件实例

  1. 启动插件服务

3.创建视频播放窗口、绑定消息回调

4.初始化参数,其中secret参数需要通过RSA加密,加密公钥通过WebControl.JS_RequestInterface获取

5.通过WebControl 插件实例调用API方法操作功能(预览,回放,抓图、录像等)

6.离开页面断开与插件服务连接

海康视频插件下载地址:点击跳转

下载的压缩包并解压:

项目引入依赖:

必须在 vue项目 的 index.html 文件中引入, main.js 中引入无效;

三、代码

封装组件:

海康插件封装js,自定义命名为videoUtils.js

videoUtils.js

javascript 复制代码
import { haikangVideoPlayStore } from "@/store/haikangVideoPlayStore";
import { JSEncrypt } from "jsencrypt";
// import axios from 'axios';

class videoUtils {
  initCount = 0;
  pubKey = "";
  videoPlays = {};
  windowList = []; //所有窗口播放过的视频记录
  store = haikangVideoPlayStore();
  resCallMsg = null;
  layoutSting = "1x1";
  layoutInfo = { x: 1, y: 1 };
  wndNum = 1;
  myType = 100;//渡船摄像头
  widthOrHeight = { width: 0, height: 0 };
  common = toRaw(this.store.videoComParam);
  constructor(videoParam, controlParam, buttonIds) {
    // this.judgeIntranet();
    this.videoParam = videoParam; //需要初始化插件实例对象的id,类型为Array

    this.buttonIds = buttonIds; //视频对象需要展示的控件编码
    this.controlParam = controlParam; //视频窗口的其它配置
  }
  //初始化插件
  initPlugin(fn) {
    if (!Array.isArray(this.videoParam)) return;
    this.videoParam.forEach((el) => {
      this.createOWebControl(el, el.id, fn);
    });
  }

  createOWebControl(param, id, fn) {
    console.log("createOWebControl>>>", param.targetHeight);
    this.widthOrHeight = { width: param.targetWidth, height: param.targetHeight };
    let that = this;
    this.videoPlays[id] = new WebControl({
      szPluginContainer: param.target, // 指定容器id
      iServicePortStart: 15900, // 指定起止端口号,建议使用该值
      iServicePortEnd: 15900,
      szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11", // 用于IE10使用ActiveX的clsid
      cbConnectSuccess: function () {
        // 创建WebControl实例成功
        that.videoPlays[id]
          .JS_StartService("window", {
            // WebControl实例创建成功后需要启动服务
            dllPath: "./VideoPluginConnect.dll", // 值"./VideoPluginConnect.dll"写死
          })
          .then(
            function () {
              // 启动插件服务成功
              that.videoPlays[id].JS_SetWindowControlCallback({
                // 设置消息回调
                _that: that,
                cbIntegrationCallBack: that.cbIntegrationCallBack,
              });
              that.videoPlays[id]
                .JS_CreateWnd(
                  param.target,
                  param.targetWidth,
                  param.targetHeight
                )
                .then(function () {
                  //JS_CreateWnd创建视频播放窗口,宽高可设定
                  that.init(that.videoPlays[id], param, fn); // 创建播放实例成功后初始化
                });
            },
            function () {
              fn(true); // 启动插件服务失败
            }
          );
      },
      cbConnectError: function () {
        // 创建WebControl实例失败
        try {
          Object.keys(that.videoPlays).forEach((el) => {
            that.videoPlays[el]?.JS_WakeUp("VideoWebPlugin://"); //程序未启动时执行error函数,采用wakeup来启动程序
            that.videoPlays[el] = null;
          });
          that.initCount = 0;
          fn(true);
        } catch (error) { }
      },
      cbConnectClose: function (bNormalClose) {
        // 异常断开:bNormalClose = false
        // JS_Disconnect正常断开:bNormalClose = true
        console.log("cbConnectClose", bNormalClose);
        that.videoPlays[id] = null;
        fn(false, "暂未安装插件");
        setTimeout(() => {
          fn(true, "暂未安装插件");
        }, 5 * 1000);
      },
    });
    return this.videoPlays[id];
  }
  init(oWebControl, param, fn) {
    let that = this;
    this.getPubKey(oWebControl, () => {
      let appkey = this.common.appkey || '29671910'; //综合安防管理平台提供的appkey,必填
      let secret = this.setEncrypt(this.common.secret || 'SaW380VimBL8kYJX0DEJ'); //综合安防管理平台提供的secret,必填
      let ip = this.common.ip || '10.1.0.10'; //综合安防管理平台IP地址,必填
      let playMode = this.controlParam.playMode; //初始播放模式:0-预览,1-回放
      var port = this.common.port || 1443; //综合安防管理平台端口,若启用HTTPS协议,默认443
      var snapDir = "D:\\SnapDir"; //抓图存储路径
      var videoDir = "D:\\VideoDir"; //紧急录像或录像剪辑存储路径
      var layout = this.controlParam.layout; //playMode指定模式的布局
      var enableHTTPS = 1; //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1
      var encryptedFields = "secret"; //加密字段,默认加密领域为secret
      var showToolbar = this.controlParam.showToolbar; //是否显示工具栏,0-不显示,非0-显示
      var showSmart = 1; //是否显示智能信息(如配置移动侦测后画面上的线框),0-不显示,非0-显示
      var buttonIDs =
        "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769"; //自定义工具条按钮
      var reconnectTimes = this.controlParam.reconnectTimes || 0;
      var reconnectDuration = this.controlParam.reconnectDuration || 5;
      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,
            reconnectDuration,
          }),
        })
        .then(function (oData) {
          oWebControl.JS_Resize(param.targetWidth, param.targetHeight); // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
          fn(false, oData); //初始化成功,返回消息参数,用于判断执行视频播放接口
        });
    });
  }
  setMyType(type) {
    this.myType = type
  }
  cbIntegrationCallBack(oData) {
    const { msg: resMsg, type } = oData.responseMsg;
    this._that.resCallMsg = { resMsg, type };
    if (type === 2) {
      //播放成功
      console.log("播放成功 123" + JSON.stringify(resMsg));
      console.log('播放768', this._that.myType, '222', resMsg.result, resMsg.wndId);
      if (this._that.myType === 100 && resMsg.result === 768) {
        //768开始播放
        this._that.callText(0, 0, resMsg.wndId);//调用这个方法
      }
    } else if (type === 6) {
      const layoutInfos = resMsg.layout.split('x');
      this._that.layoutSting = resMsg.layout;
      this._that.layoutInfo = { x: layoutInfos[0], y: layoutInfos[1] };
      this._that.wndNum = resMsg.wndNum;
    } else if (type === 7) {
      const layoutInfos = resMsg.layout.split('x');
      this._that.layoutSting = resMsg.layout;
      this._that.layoutInfo = { x: layoutInfos[0], y: layoutInfos[1] };
      this._that.wndNum = resMsg.wndNum;
      // this._that.callText(0, 0, resMsg.wndId,50,600);//调用这个方法
    }
    if (
      Object.keys(resMsg).length === 3 &&
      Object.keys(resMsg).join("") === "cameraIndexCoderesultwndId"
    ) {
      this._that.store.videoSelectInfo = oData.responseMsg.msg;
    }

    // showCBInfo(JSON.stringify(oData.responseMsg));
    this._that.store.inspectionStateRecordList.push(oData.responseMsg.msg);

    if (oData.responseMsg.msg.cameraIndexCode) {
      this._that.windowList.push(oData.responseMsg.msg);
      const removeDuplicates = (arr, prop) => {
        const map = {};

        arr = arr.reduceRight((result, item) => {
          const pid = item[prop];
          if (!map[pid]) {
            map[pid] = true;
            result.push(item);
          }
          return result;
        }, []);

        return (arr = arr.sort((a, b) => a.wndId - b.wndId));
      };
      if (!this._that.controlParam.playMode) {
        //视频预览
      } else {
        //视频回放
        this._that.store.playbackList = removeDuplicates(
          this._that.windowList,
          "wndId"
        ).map((item) => item.cameraIndexCode);
      }
    }
  }
  //获取公钥
  getPubKey(oWebControl, callback) {
    let that = this;
    oWebControl
      .JS_RequestInterface({
        funcName: "getRSAPubKey",
        argument: JSON.stringify({
          keyLength: 1024,
        }),
      })
      .then(function (oData) {
        if (oData.responseMsg.data) {
          that.pubKey = oData.responseMsg.data;
          callback();
        }
      });
  }
  //RSA加密
  setEncrypt(value) {
    let encrypt = new JSEncrypt();
    encrypt.setPublicKey(this.pubKey);
    return encrypt.encrypt(value);
  }
  //重新调整视频窗口尺寸
  videoWindowResize(resizeParam) {
    if (Object.keys(this.videoPlays).length != 0) {
      let videoPlayKeys = Object.keys(this.videoPlays);
      resizeParam.forEach((el) => {
        if (videoPlayKeys.includes(el.id)) {
          this.videoPlays[el.id] &&
            this.videoPlays[el.id].JS_Resize(el.targetWidth, el.targetHeight);
        }
      });
    }
  }
  //隐藏播放窗口
  hideVideoWindow() {
    if (Object.keys(this.videoPlays).length != 0) {
      Object.keys(this.videoPlays).forEach((key) => {
        this.videoPlays[key].JS_HideWnd();
      });
    }
  }
  //显示播放窗口
  showVideoWindow() {
    if (Object.keys(this.videoPlays).length != 0) {
      Object.keys(this.videoPlays).forEach((key) => {
        this.videoPlays[key].JS_ShowWnd();
      });
    }
  }
  /**
   * 预览功能
   * @param {*} param 接口请求的视频数据
   * @param {*} currentPlay 当前播放的视频窗口id
   */
  playPreview(param, currentPlayId) {
    if (!this.videoPlays[currentPlayId]) return;
    const streamMode = param.networkStatus ?? this.store.networkStatus;
    // console.log(streamMode ? '子码流' : '主码流');
    return this.videoPlays[currentPlayId]
      .JS_RequestInterface({
        funcName: "startPreview",
        argument: JSON.stringify({
          cameraIndexCode: param.cameraid, // 监控点编号
          streamMode, // 主子码流标识,0-主码流 1-子码流
          transMode: 1, // 传输协议,0-UDP 1-TCP
          gpuMode: 0, // 是否开启 GPU 硬解,不建议开启,0-不开启 1-开启
          wndId: param.wndId || 0 || -1,
        }),
      })
      .then(function (oData) {
        return oData;
      });
  }
  stopPreview(currentPlayId) {
    this.videoPlays[currentPlayId]
      .JS_RequestInterface({
        funcName: "stopAllPreview",
      })
      .then(function (oData) {
        console.log("停止预览视频 结果:" + JSON.stringify(oData));
      });
  }
  playMultiPreview(param, currentPlayId, moreParam) {
    return new Promise(async (resolve, reject) => {
      try {
        let list = param.reduce((pre, cre) => {
          if (typeof cre === "string") {
            return [...pre, { cameraid: cre, wndId: -1 }];
          } else if (typeof cre === "object") {
            return [
              ...pre,
              { cameraid: cre.cameraIndexCode, wndId: cre.wndId },
            ];
          }
        }, []);
        let resultInfo;
        await Promise.all(
          list.map(async (aitem) => {
            try {
              aitem = { ...aitem, ...moreParam };
              resultInfo = await this.playPreview(aitem, currentPlayId);
            } catch (error) {
              reject("播放失败");
            }
          })
        );
        resolve(resultInfo);
      } catch (error) {
        reject("播放失败");
      }
    });
  }
  getVideoLayout(currentPlayId) {
    return this.videoPlays[currentPlayId]
      .JS_RequestInterface({
        funcName: "getLayout",
      })
      .then(function (oData) {
        console.log("获取窗口布局 结果:" + JSON.stringify(oData));
        return JSON.parse(oData.responseMsg.data);
      });
  }
  playBack(param, currentPlayId, timeRange) {
    if (!this.videoPlays[currentPlayId]) return;
    const that = this;
    return this.videoPlays[currentPlayId]
      .JS_RequestInterface({
        funcName: "startPlayback",
        argument: JSON.stringify({
          cameraIndexCode: param.cameraid, // 监控点编号
          startTimeStamp: timeRange
            ? Math.floor(timeRange[0] / 1000)
            : Math.floor((Date.now() - 8.64e7) / 1000), // 录像查询开始时间戳,单位:秒
          endTimeStamp: timeRange
            ? Math.floor(timeRange[1] / 1000)
            : Math.floor(Date.now() / 1000), // 录像查询结束时间戳,单位:秒
          recordLocation: 1, // 录像存储类型 0-中心存储 1-设备存储
          transMode: 1, // 传输协议 ,0-UDP 1-TCP
          gpuMode: 0, // 是否开启 GPU 硬解,0-不开启 1-开启
          wndId: 0,
        }),
      })
      .then(function (oData) {
        console.log("回放视频 结果:" + JSON.stringify(oData));
        return oData;
      });
  }
  // 播放窗口批量预览
  playMultiWnd(currentPlayId, cameraInfo, fn) {
    if (!cameraInfo) return;
    const { cameraIndexCode, wndId } = cameraInfo,
      streamMode = this.store.networkStatus;
    console.log(streamMode ? '子码流' : '主码流');
    const that = this;
    this.videoPlays[currentPlayId]
      ?.JS_RequestInterface({
        funcName: "startMultiPreviewByCameraIndexCode",
        argument: JSON.stringify({
          list: [{ cameraIndexCode, wndId, streamMode }],
        }),
      })
      .then(function (oData) {
        console.log("播放窗口批量播放 结果:", oData);
        return oData;
      });
  }
  // 播放窗口批量停止
  stopMultiWnd(currentPlayId, wndId) {
    this.videoPlays[currentPlayId]
      .JS_RequestInterface({
        funcName: "stopMultiPlay",
        argument: JSON.stringify({
          list: [{ wndId }],
        }),
      })
      .then(function (oData) {
        console.log("播放窗口批量停止 结果:", oData);
      });
  }
  /**
   * 停止所有预览
   * @param opt
   */
  stopAllPreview() {
    if (Object.keys(this.videoPlays).length != 0) {
      Object.keys(this.videoPlays).forEach((key) => {
        this.videoPlays[key]
          .JS_RequestInterface({
            funcName: "stopAllPreview",
          })
          .then(function (oData) {
            console.log("销毁数据", oData);
          });
      });
    }
  }
  /**
   * 组件销毁时调用或者路由切换时调用
   * 释放资源
   */
  destoryVideoWindow() {
    if (Object.keys(this.videoPlays).length != 0) {
      Object.keys(this.videoPlays).forEach((key) => {
        this.videoPlays[key].JS_HideWnd();
        this.videoPlays[key].JS_Disconnect().then(
          function () {
            // 断开与插件服务连接成功
          },
          function () {
            // 断开与插件服务连接失败
          }
        );
      });
    }
  }
  /**
   * 窗口添加文字信息
   */
  addWndText(totolNum = 0, unWearNum = 0, wid, x, y, wndNum) {
    if (!totolNum || !unWearNum) {
      totolNum = 0;
      unWearNum = 0;
    }
    const that = this;
    const { localX, localY } = that.drawFixedGlobalOSD(50, 600, that.widthOrHeight.width, that.widthOrHeight.height, that.layoutSting, "");
    const text = that.myType === 100 ? `总  人  数    :${totolNum} \n未穿救生衣  :${unWearNum}` : '';
    if (Object.keys(this.videoPlays).length != 0 && wid) {
      Object.keys(this.videoPlays).forEach((key) => {
        this.videoPlays[key].JS_RequestInterface({
          funcName: "drawOSD",
          argument: JSON.stringify({
            text: text, // 窗口布局
            x: Math.floor(localX),
            y: Math.floor(localY),
            // x: 50,
            // y: 600 / y,
            color: 16711680,
            wndId: wid,
            fontSize: 30,
            bold: 1,
          }),
        }).then(function (oData) {
          console.log("窗口文字信息 成功结果:" + JSON.stringify(oData));
          return oData;
        }).catch(function (oData) {
          console.log("窗口文字信息 失败结果:" + JSON.stringify(oData));
          return oData;
        });

      });
    }
  }
  callText(totolNum, unWearNum, wid, x, y) {
    const that = this
    if (Object.keys(this.videoPlays).length != 0) {
      that.addWndText(totolNum, unWearNum, wid, that.layoutInfo.x, that.layoutInfo.y, that.wndNum);
    }
  }
  /**
 * 在固定全局位置绘制 OSD
 * @param {Number} globalX 插件整体坐标X(固定)
 * @param {Number} globalY 插件整体坐标Y(固定)
 * @param {Number} pluginW 插件初始化宽度
 * @param {Number} pluginH 插件初始化高度
 * @param {String} layout 当前布局,例如 "1x1", "2x2", "3x3"
 * @param {String} text 叠加文本
 */
  drawFixedGlobalOSD(globalX, globalY, pluginW, pluginH, layout, text) {
    const [cols, rows] = layout.split("x").map(Number);
    const wndW = pluginW / cols;
    const wndH = pluginH / rows;

    const col = Math.floor(globalX / wndW);
    const row = Math.floor(globalY / wndH);
    const wndId = row * cols + col;

    const localX = globalX - col * wndW;
    const localY = globalY - row * wndH;

    console.log(`OSD 在布局 ${layout} 的窗口 ${wndId} 坐标 (${Math.floor(localX)}, ${Math.floor(localY)})`);
    return { localX, localY };
  }
}
export default videoUtils;

用户没有安装海康播放器的提示信息:

插件是后端部署在服务器上的,也可以自行去官网下载exe文件,例如:VideoWebPlugin.exe

VideoTipe.vue

html 复制代码
<template>
    <div class="tipe-box">
        <div style="text-align: center">
            <p>
                插件启动失败,请检查插件是否安装!未安装插件?点击
                <a :href="filePreUrl + '/VideoWebPlugin.exe'" style="color: #0085ff"
                    download="VideoWebPlugin.exe">下载</a>
            </p>
            <p>
                插件已安装?请试试修改浏览器设置,在浏览器地址栏输入:
                <br />
                <span
                    style="color: #2678cd ; user-select: all;cursor: text;">chrome://flags/#block-insecure-private-network-</span>
                &nbsp;并按照如下设置
            </p>
        </div>
    </div>
</template>

<script setup>
import { BASEUrl } from '@/utils/request';
const filePreUrl = BASEUrl.replace('windfarms', 'download');
</script>
<style scoped lang="scss">
.tipe-box {
    height: 100%;
    width: 100%;
    background: #ffff;
    z-index: 9999999;

    >div {
        overflow: hidden;
    }
}
</style>

调用封装组件的示例,如下:

let myVideo = new videoUtils(initVideoParam(), {

playMode: 0,

showToolbar: 1,

layout,

});

const initVideoParam = () => {

// 初始化视频插件设置相关参数(传入自己元素目标和位置)

const { id, offsetWidth, offsetHeight } = videoPlaysRef.value;

return [

{

id,

target: id,

targetWidth: offsetWidth * getScale(),

targetHeight: offsetHeight * getScale(),

},

];

};

const selectVideo_haikang = (data) => {

videoPreview([{ cameraIndexCode: data.deviceId, wndId: haikangState.selectWndId || 1, ...data }]);

};

核心代码:

javascript 复制代码
const handleNodeClick = (val) => {
  if (!(val.type === 100 || val.type === 101)) return;
  if (!haikangState.isHaiKang) {
    selectVideo(val);
  } else {
    console.log('选择摄像头>>>', val);
    selectVideo_haikang({ deviceId: val.deviceId || 'DS-2DC7223IW-A20190108AACHC86420496W' });
    // selectVideo_haikang({ deviceId: '7af2175a02c34e20bb923c706790013e' || 'DS-2DC7223IW-A20190108AACHC86420496W' });
    const wid = JSON.parse(JSON.stringify(haikangState.selectWndId));
    const type100 = JSON.parse(JSON.stringify(val.type));
    const isOk = timerIDS.includes(val.pid);
    if (val.type === 100) {
      if (isOk && aWindow.value !== 1) {
        //存在,清除這個定時器
        clearInterval(timeIntervalArr[val.pid]);
        timerIDS = timerIDS.filter(item => item !== val.pid);
        return
      }
      haikangState.video && haikangState.video.setMyType(type100);
      timeIntervalArr[val.pid] = setInterval(() => {
        getTextWdIfone(val, wid);
      }, 10 * 1000)
      timerIDS.push(val.pid);
    } else {
      if (aWindow.value === 1) {
        for (let i = 1; i < 1000; i++) {
          clearInterval(i);
        }
      }
      haikangState.video && haikangState.video.setMyType(type100);
    }
  }
};
const selectVideo_haikang = (data) => {
  const cacheInfoSetData = JSON.parse(JSON.stringify(haikangState.cacheInfoData));
  cacheInfoSetData.videoInfo[haikangState.selectWndId - 1] = {
    cameraIndexCode: data.deviceId,
    wndId: haikangState.selectWndId || 1,
  };
  haikangState.cacheInfoData = cacheInfoSetData;
  videoPreview([{ cameraIndexCode: data.deviceId, wndId: haikangState.selectWndId || 1, ...data }]);
};
const videoPreview = (videoList) => {
  let videoids = Object.keys(haikangState.video.videoPlays);
  videoids.forEach(async (el) => {
    // await haikangState.video.playMultiPreview([...videoList], el);
    haikangState.video.playMultiWnd(el, videoList[0])
  });
};

//添加OSD到海康视频播放器,的接口请求
const getTextWdIfone = (data, wid) => {
  if (data.type === 100) {
    const params = {
      pid: data.pid,
    }
    api.videoApi.getTextWdIfone(params).then(res => {
      if (res.data.code == 200) {
        const resData = res.data.data;
        const { personnel: totalNum, jacket: wearNum, others: unWearNum } = resData;
        console.log("yes", wid, 22, data.type)
        haikangState.video && haikangState.video.callText(totalNum, unWearNum, wid);
        return resData
      }
    }).catch(err => {
      console.log(err);
    })
  }
}

完整代码1:html页面布局

html 复制代码
<template>
<div class="channel-monitor">
  <div class="channel-left-box">
    <div class="tree-select-box">
  <el-tree style="max-width: 600px" :data="state.videoLists" :props="state.defaultProps"
    @node-click="handleNodeClick" node-key="pid" default-expand-all />
    </div>
  </div>
  <div v-loading="data.isLoading" class="channel-right-box">
        <VideoTipe v-show="haikangState.isShowTipe"></VideoTipe>
        <div class="video-play-wrapper" id="videoPlayers" ref="videoPlaysRef" 
            v-show="!haikangState.isShowTipe">
        </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import videoUtils from '@/utils/video/videoUtils';
import api from "@/api/index";
import { haikangVideoPlayStore } from "@/store/haikangVideoPlayStore";
import { getScale } from '@/utils';
let timeInterval = null;
const store = videoPlayStore();
const haikangStore = haikangVideoPlayStore();
const layoutInfo = computed(() => {
  return {
    layout: haikangStore.layoutInfo.layout,
    wndNum: haikangStore.layoutInfo.wndNum,
    videoInfo: haikangStore.videoInfo
  }
})

watch(layoutInfo, (newVal, oldValue) => {
  nextTick(() => {
    if (newVal) {
      const { layout, wndNum, videoInfo } = newVal
      haikangState.cacheInfoData = {
        layoutInfo: {
          layout,
          wndNum
        },
        videoInfo: videoInfo
      }
    }
  })
}, { immediate: true })
const haikangState = reactive({
  isHaiKang: true,
  isShowTipe: false, //是否下载视频插件
  video: null, //WebControl视频对象
  noClassVideoList: [], //没有分类的摄像头数据
  videoLists: [], //已按照tree组件分类处理的所有摄像头数据
  cacheInfoData: null,
  // currentVideoList: [],//在插件窗口播放的视频
  selectWndId: 1, //当前选中窗口
})
onMounted(() => {
  //海康视频
  nextTick(() => {
    videoInit(); //初始化视频对象_海康视频
    videoPlaysRef.value && addVideoObserver();
  });
});
onBeforeUnmount(() => {
  console.log("销毁视频插件");
  videoObserver && videoObserver.unobserve(videoPlaysRef.value);
  !haikangState.isShowTipe && haikangState.video.destoryVideoWindow(); //视频插件安装过,才能销毁视频插件
  timeInterval && clearInterval(timeInterval);
  for (let i = 1; i < 1000; i++) {
    clearInterval(i);
  }
  timeIntervalArr.forEach((item) => {
    clearInterval(item);
  })
});
const handleNodeClick = (val) => {
  if (!(val.type === 100 || val.type === 101)) return;
  if (!haikangState.isHaiKang) {
    selectVideo(val);
  } else {
    console.log('选择摄像头>>>', val);
    selectVideo_haikang({ deviceId: val.deviceId || 'DS-2DC7223IW-A20190108AACHC86420496W' });
    // selectVideo_haikang({ deviceId: '7af2175a02c34e20bb923c706790013e' || 'DS-2DC7223IW-A20190108AACHC86420496W' });
    const wid = JSON.parse(JSON.stringify(haikangState.selectWndId));
    const type100 = JSON.parse(JSON.stringify(val.type));
    const isOk = timerIDS.includes(val.pid);
    if (val.type === 100) {
      if (isOk && aWindow.value !== 1) {
        //存在,清除這個定時器
        clearInterval(timeIntervalArr[val.pid]);
        timerIDS = timerIDS.filter(item => item !== val.pid);
        return
      }
      haikangState.video && haikangState.video.setMyType(type100);
      timeIntervalArr[val.pid] = setInterval(() => {
        getTextWdIfone(val, wid);
      }, 10 * 1000)
      timerIDS.push(val.pid);
    } else {
      if (aWindow.value === 1) {
        for (let i = 1; i < 1000; i++) {
          clearInterval(i);
        }
      }
      haikangState.video && haikangState.video.setMyType(type100);
    }
  }
};
</script>

完整代码2:海康播放器

javascript 复制代码
//海康视频
const videoPlaysRef = ref();
let videoObserver = null;
const initVideoParam = () => {
  // 初始化视频插件设置相关参数(传入自己元素目标和位置)
  const { id, offsetWidth, offsetHeight } = videoPlaysRef.value;
  return [
    {
      id,
      target: id,
      targetWidth: offsetWidth * getScale(),
      targetHeight: offsetHeight * getScale(),
    },
  ];
};
const videoInitFun = async (isShowTipe, initSucessData) => {
  haikangState.isShowTipe = isShowTipe;
  if (!isShowTipe && initSucessData) videoListPlay();
};
const videoInit = () => {
  let layout = haikangState.cacheInfoData?.layoutInfo.layout;
  if (typeof layout === 'string') {
  } else if (typeof layout === 'number') {
    layout = layout + 'x' + layout;
  }
  haikangState.video = new videoUtils(initVideoParam(), {
    playMode: 0,
    showToolbar: 1,
    layout,
  });
  haikangState.video.initPlugin(videoInitFun);
};
//插件窗口尺寸刷新
const videoWindowResize = () => {
  haikangState.video.videoWindowResize(initVideoParam());
};
const addVideoObserver = () => {
  //添加窗口尺寸监听
  videoObserver = new ResizeObserver(() => {
    videoWindowResize();
  });
  videoObserver.observe(videoPlaysRef.value);
};
const videoListPlay = () => {
  const { videoInfo } = haikangState.cacheInfoData;
  Object.keys(videoInfo).length && videoPreview({ ...videoInfo }); //配合在当前用户有缓存视频信息情况下播放视频
};
const videoPreview = (videoList) => {
  let videoids = Object.keys(haikangState.video.videoPlays);
  videoids.forEach(async (el) => {
    // await haikangState.video.playMultiPreview([...videoList], el);
    if (videoList instanceof Array) {
      videoList.length === 1 &&
        haikangState.video.playMultiWnd(el, videoList[0], getTextWdIfone(videoList[0], JSON.parse(JSON.stringify(haikangState.selectWndId)))); //一个一个选择
    } else if (videoList instanceof Object) {
      const videoInfoData = objArrExchange(videoList);
      videoInfoData.forEach((aitem, aindex) => {
        //初始化时多个视频播放
        if (typeof aitem === 'string')
          haikangState.video.playMultiWnd(el, {
            cameraIndexCode: aitem,
            wndId: aindex + 1,
          });
        else if (typeof aitem === 'object')
          haikangState.video.playMultiWnd(el, aitem);
      });
    }
  });
};
//对象数组互换
const objArrExchange = (target) => {
  if (target instanceof Object) {
    return Object.values(target).map((aitem) => {
      return Object.assign(
        {},
        ...Object.entries(aitem).map(([key, value]) => ({
          [key]: value,
        }))
      );
    });
  } else if (target instanceof Array) {
    return target
      .map((item) => ({ [item.key]: item.value }))
      .reduce((acc, curr) => {
        return acc;
      }, {});
  }
};
const aWindow = ref();
watch(
  () => haikangState.video?.resCallMsg,
  (newval, oldval) => {
    if (!newval) return;
    if (newval.type) {
      const { resMsg, type } = newval;
      if (type === 1) {
        //窗口选中消息
        haikangState.selectWndId = resMsg.wndId;
        console.log('===============1', haikangState.selectWndId);

      } else if (type === 2) {
        // 预览/回放播放视频消息
        if (!resMsg.cameraIndexCode) return;
        if (resMsg.result === 768) {
          aWindow.value = resMsg.wndId
          console.log('======!!!=========768', aWindow.value);
        } //开始播放
        if (resMsg.result === 816) {
          //结束播放
          const closeTarget = resMsg;
          const cacheInfoSetData = { ...haikangState.cacheInfoData };
          const videoInfo = objArrExchange(
            cacheInfoSetData.videoInfo
          );
          const target = videoInfo.find(
            (aitem) =>
              aitem && aitem.wndId === closeTarget.wndId
          );
          if (videoInfo.length) {
            delete cacheInfoSetData.videoInfo[target.wndId - 1];
            haikangState.cacheInfoData = cacheInfoSetData
          }
        }
      } else if (type === 6) {
        //播放窗口布局改变消息
        const cacheInfoSetData = { ...haikangState.cacheInfoData };
        cacheInfoSetData.layoutInfo = resMsg;
        haikangState.cacheInfoData = cacheInfoSetData;
        videoListPlay();
      }
    }
  },
  { deep: true }
);
const getTextWdIfone = (data, wid) => {
  console.log('inner>>>', data);
  if (data.type === 100) {
    const params = {
      pid: data.pid,
    }
    api.videoApi.getTextWdIfone(params).then(res => {
      if (res.data.code == 200) {
        const resData = res.data.data;
        const { personnel: totalNum, jacket: wearNum, others: unWearNum } = resData;
        console.log("yes", wid, 22, data.type)
        haikangState.video && haikangState.video.callText(totalNum, unWearNum, wid);
        return resData
      }
    }).catch(err => {
      console.log(err);
    })
  }
}

四、画面字符叠加OSD

海康播放插件本质是ActiveX控件,无法通过css控制位置和大小,当页面窗口大小变化或者遇到页面有滚动条情况就需要手动设置插件窗口位置和大小,可以通过JS_Resize (调整插件窗口大小、位置),JS_CuttingPartWindow (扣除部分插件窗口)两个api来实现。

背景:

海康播放器插件是浮在浏览器表面的,只要窗口大小或有页面滚动条的情况,就需要手动设置海康播放器插件的位置和大小。

核心代码:

必须是在海康视频播放成功才能添加画面字符OSD

所以在海康视频成功的回调函数里面,进行逻辑判断:

//

cbIntegrationCallBack(oData) {

const { msg: resMsg, type } = oData.responseMsg;

this._that.resCallMsg = { resMsg, type };

if (type === 2) {

//播放成功

console.log("播放成功 123" + JSON.stringify(resMsg));

console.log('播放768', this._that.myType, '222', resMsg.result, resMsg.wndId);

if (this._that.myType === 100 && resMsg.result === 768) {

//768开始播放

this._that.callText(0, 0, resMsg.wndId);//调用这个方法

}

} else if (type === 6) {

const layoutInfos = resMsg.layout.split('x');

this._that.layoutSting = resMsg.layout;

this._that.layoutInfo = { x: layoutInfos[0], y: layoutInfos[1] };

this._that.wndNum = resMsg.wndNum;

} else if (type === 7) {

const layoutInfos = resMsg.layout.split('x');

this._that.layoutSting = resMsg.layout;

this._that.layoutInfo = { x: layoutInfos[0], y: layoutInfos[1] };

this._that.wndNum = resMsg.wndNum;

}

}

相关文档:

视频文档:点击跳转

动态设置海康播放器的大小和位置:

javascript 复制代码
/* 获取div大小及位置 */
    getDomInfo(oWebControl) {
      const { width, height, top, left } = document.getElementById('playWnd0').getBoundingClientRect()
      if (oWebControl) {
        oWebControl.JS_Resize(width, height)
        oWebControl.JS_CuttingPartWindow(left, top, 0, 0)
      }else{
        this.plug.example.forEach(item=>{
          item.JS_Resize(width, height)
          item.JS_CuttingPartWindow(left, top, 0, 0)
        })
      }
    },
javascript 复制代码
//videoPlaysRef.value打印是vue中
//<div class="video-play-wrapper" id="videoPlayers" ref="videoPlaysRef"></div>
const getDomInfo = () => {
  // 初始化视频插件设置相关参数(传入自己元素目标和位置)
  const { id, offsetWidth, offsetHeight } = videoPlaysRef.value;
  return [
    {
      id,
      target: id,
      targetWidth: offsetWidth * getScale(),
      targetHeight: offsetHeight * getScale(),
    },
  ];
};
export const getScale = () => {
	const width = 1920,
		height = 1080;
	let ww = width / (window.innerWidth * 0.6);
	let wh = height / (window.innerHeight * 0.6);
	if (window.innerWidth <= 4000) {
		// let ww = window.innerWidth / width;
		// let wh = window.innerHeight / height;
		return 1;
	} else {
		return ww < wh ? ww : wh;
	}

	// if (window.innerWidth <= 1920) {
	// 	let ww = window.innerWidth / width;
	// 	let wh = window.innerHeight / height;
	// 	return ww < wh ? ww : wh;
	// } else {
	// 	return ww < wh ? ww : wh;
	// }
};