vue 海康視頻插件

背景:

在vue項目中,需要在pc端播放視頻,播放的視頻包括視頻實時、視頻回放等。

寫文思路:

海康視頻對接流程,了解海康視頻插件,前端開發項目并引入依賴,前端開發封裝的組件,組件的調用

效果展示:

一、海康視頻

海康視頻官網:點擊跳轉

海康云眸:點擊跳轉

海康AI開放平臺:點擊跳轉

二、海康視頻插件

海康視頻插件運行環境需要安裝插件VideoWebPlugin.exe,對瀏覽器也有兼容性要求,具體看官方文檔

插件使用步驟總結
1.new 一個WebControl 插件實例
2. 啟動插件服務
3.創建視頻播放窗口、綁定消息回調
4.初始化參數,其中secret參數需要通過RSA加密,加密公鑰通過WebControl.JS_RequestInterface獲取
5.通過WebControl 插件實例調用API方法操作功能(預覽,回放,抓圖、錄像等)
6.離開頁面斷開與插件服務連接

海康視頻插件下載地址:點擊跳轉

下載的壓縮包并解壓:

項目引入依賴:

必須在 vue項目 的?index.html 文件中引入, main.js 中引入無效;

三、代碼

封裝組件:

海康插件封裝js,自定義命名為videoUtils.js

videoUtils.js

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,類型為Arraythis.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, // 指定容器idiServicePortStart: 15900, // 指定起止端口號,建議使用該值iServicePortEnd: 15900,szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11", // 用于IE10使用ActiveX的clsidcbConnectSuccess: 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 = trueconsole.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協議,默認443var snapDir = "D:\\SnapDir"; //抓圖存儲路徑var videoDir = "D:\\VideoDir"; //緊急錄像或錄像剪輯存儲路徑var layout = this.controlParam.layout; //playMode指定模式的布局var enableHTTPS = 1; //是否啟用HTTPS協議與綜合安防管理平臺交互,這里總是填1var encryptedFields = "secret"; //加密字段,默認加密領域為secretvar 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網關提供的appkeysecret: secret, //API網關提供的secretip: 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-TCPgpuMode: 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-TCPgpuMode: 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 = thisif (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

<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 /><spanstyle="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 }]);

};

核心代碼:

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頁面布局

<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 } = newValhaikangState.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:海康播放器

//海康視頻
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.wndIdconsole.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) {

? ? ? //type === 2播放成功

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

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

? ? ? if (resMsg.result === 768) {

? ? ? ? //768開始播放

? ? ? ? this._that.callText(0, 0, resMsg.wndId);//調用這個方法

? ? ? }

? ? } else if (type === 6) {

? ? ? //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) {

? ? ? //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;

? ? }

}

相關文檔:

海康播放器的插件文檔:

備注:上面這張圖說明了,預覽或者回放接口返回的是768,768代表播放成功。

只有播放成功才能添加畫面字符疊加:

視頻文檔:點擊跳轉

動態設置海康播放器的大小和位置:

/* 獲取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)})}},
//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;// }
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/920365.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/920365.shtml
英文地址,請注明出處:http://en.pswp.cn/news/920365.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【URP】Unity 插入自定義RenderPass

【從UnityURP開始探索游戲渲染】專欄-直達 自定義渲染通道是一種改變通用渲染管道&#xff08;URP&#xff09;如何渲染場景或場景中的對象的方法。自定義呈現通道(RenderPass)包含自己的Render代碼&#xff0c;可以在注入點將其添加到RenderPass中。 添加自定義呈現通道(Rend…

DevSecOps 集成 CI/CD Pipeline:實用指南

就在你以為軟件開發已無簡化的余地時&#xff0c;新的解決方案應運而生 隨著軟件開發幾乎每天都在攀升&#xff0c;組織不斷嘗試以前所未有的速度交付新功能和應用程序。雖然持續集成和持續交付 &#xff08;CI/CD&#xff09; Pipeline 徹底改變了軟件部署&#xff0c;但它們…

vue2+elementui 表格單元格增加背景色,根據每列數據的大小 顏色依次變淺顯示

注釋&#xff1a; vue2elementui 表格列實現一個功能&#xff0c;給定兩個顏色&#xff1a;紅色 #f96d6f 和 綠色 #63be7b&#xff0c;列數據正數時表格單元格背景色為紅色&#xff0c;列數據負數時表格單元格背景色為綠色&#xff0c;根據數據的大小顏色依次越來越淡&#xff…

【JavaEE】(19) MyBatis-plus

一、MyBatis Generator 為 MyBastis 框架設計的代碼生成工具&#xff0c;簡化持久層編碼工作。根據數據庫表自動生成 Java 實體類、Mapper 接口、SQL 的 xml 文件。讓開發者專注于業務邏輯。 1、引入插件 MyBatis 官網搜索 MyBatis Generator 插件&#xff1a;Running MyBatis…

Android之騰訊TBS文件預覽

文章目錄前言一、效果圖二、實現步驟1.去官網注冊并創建應用[騰訊官網](https://console.cloud.tencent.com/tbs/client)2.下載arr文件并引入[騰訊TBS](https://download.csdn.net/download/Android_Cll/91764395)3.application實例化4.activity實例化5.下載網絡文件6.PreviewA…

基于微信小程序的化妝品成分查詢系統源碼

源碼題目&#xff1a;基于微信小程序的化妝品成分查詢系統源碼?? 文末聯系獲取&#xff08;含源碼、技術文檔&#xff09;博主簡介&#xff1a;10年高級軟件工程師、JAVA技術指導員、Python講師、文章撰寫修改專家、Springboot高級&#xff0c;歡迎高校老師、同行交流合作。畢…

STM32 啟動執行邏輯與代碼燒入方法詳解:從底層原理到實操落地

STM32 啟動執行邏輯與代碼燒入方法詳解&#xff1a;從底層原理到實操落地背景概要STM32啟動和執行的核心邏輯鏈條代碼燒入到STM32的途徑方法結束語背景概要 在學習STM32時候我們知道代碼需要通過一些下載器&#xff08;如ST-Link、J-Link&#xff09;或者串口下載燒入到STM32芯…

Go對接印度股票數據源指南:使用StockTV API

一、StockTV API簡介 StockTV提供全球200國家的實時金融數據&#xff0c;覆蓋股票、外匯、期貨和加密貨幣市場。針對印度市場&#xff08;國家ID14&#xff09;&#xff0c;其主要優勢包括&#xff1a; 毫秒級低延遲響應7x24小時穩定服務日均處理億級數據免費技術支持 官方資源…

ESP8266:Arduino學習

ESP8266一&#xff1a;環境搭建使用Ardino框架&#xff0c;在官網下載&#xff0c;下載離線的支持包二&#xff1a;實現簡單的項目1. 點燈{pinMode(LED_PIN, OUTPUT); // 設置引腳為輸出模式digitalWrite(LED_PIN, HIGH); // 點亮 LED}I/O引腳的三種模式分別為&#xff1a;INPU…

青少年軟件編程(python六級)等級考試試卷-客觀題(2023年3月)

更多內容和歷年真題請查看網站&#xff1a;【試卷中心 -----> 電子學會 ----> 機器人技術 ----> 六級】 網站鏈接 青少年軟件編程歷年真題模擬題實時更新 青少年軟件編程&#xff08;python六級&#xff09;等級考試試卷-客觀題&#xff08;2023年3月&#xff09…

mongodb influxdb

、您需要提前配置 MongoDB 和 InfluxDB。讓我幫您說明配置步驟&#xff1a; MongoDB 配置 啟動 MongoDB 容器后&#xff0c;進入容器創建數據庫&#xff1a; # 進入 MongoDB 容器 docker exec -it mongo mongosh -u root -p 123456# 創建 product 數據庫 use product# 創建集合…

模擬電路中什么時候適合使用電流傳遞信號,什么時候合適使用電壓傳遞信號

一、應用 1.實際應用中&#xff0c;需要進行權衡&#xff0c;比如抗干擾能力&#xff0c;傳輸距離&#xff0c;功耗 2.電壓信號比較容易受到干擾&#xff0c;對噪聲比較敏感&#xff0c;有噪聲容限一說 3.電流信號對噪聲不敏感 4.電源電壓下降的穩定性或者長距離傳輸中的損耗問…

Flink2.0學習筆記:使用HikariCP 自定義sink實現數據庫連接池化

stevensu1/EC0823: Flink2.0學習筆記&#xff1a;使用HikariCP 自定義sink實現數據庫連接池化 在 Flink 流處理應用中使用 HikariCP 實現數據庫連接池化&#xff0c;對于寫入關系型數據庫&#xff08;如 MySQL、PostgreSQL&#xff09;的 自定義 Sink 來說&#xff0c;不僅是推…

Ubuntu安裝及配置Git(Ubuntu install and config Git Tools)

Setup Git sudo apt update sudo apt install git // 查看git版本 git --versionConfig Github // 不清楚username和email的可以直接在github網站上點擊頭像選擇settings來查看 git config --global user

將C++資源管理測試框架整合到GitLab CI/CD的完整實踐指南

將C資源管理測試框架整合到GitLab CI/CD的完整實踐指南 摘要 本文深入探討了如何將先進的C資源管理測試框架無縫集成到GitLab CI/CD流水線中&#xff0c;實現自動化資源監控、性能回歸檢測和高質量測試。通過實際案例和最佳實踐&#xff0c;展示了如何構建一個能夠精確控制CPU親…

Web漏洞

一、Sql注入 sql注入漏洞的成因是由于后端數據庫查詢語句沒有做過濾導致了前端輸入字符串可以直接拼接到語句而獲取數據庫信息。 1.類型 數字型和字符型 區分&#xff1a;數字型可以進行加減運算&#xff0c;id11會獲取id2的信息&#xff0c;而字符型只會獲取1的數據 2.方…

Java中使用Spring Boot+Ollama構建本地對話機器人

目錄結構Ollama是什么安裝 Ollama下載大模型運行模型Java和IDEA版本創建一個springboot項目創建一個簡單的對話接口啟動spring boot流式對話輸出用原生 HTML 打造可交互前端接入 OpenAI、DeepSeek 等云模型&#xff08;可選&#xff09;原文地址傳送門 我是想做一個大模型本地部…

學習設計模式《二十四》——訪問者模式

一、基礎概念 訪問者模式的本質是【預留后路&#xff0c;回調實現】。仔細思考訪問者模式&#xff0c;它的實現主要是通過預先定義好調用的通路&#xff0c;在被訪問的對象上定義accept方法&#xff0c;在訪問者的對象上定義visit方法&#xff1b;然后在調用真正發生的時候&…

Rust 符號體系全解析:分類、應用與設計意圖

Rust 的符號體系是其語法規則、內存安全與類型安全設計的核心載體。每個符號不僅承擔特定功能&#xff0c;更隱含 Rust 對 “安全” 與 “表達力” 的平衡邏輯。本文按功能維度&#xff0c;系統梳理 Rust 中所有常用符號&#xff0c;結合代碼示例與設計背景&#xff0c;提供全面…

神經網絡|(十六)概率論基礎知識-伽馬函數·上

【1】引言 前序學習進程中&#xff0c;對經典的二項分布和正態分布已經有一定的掌握。 今天為學習一種稍顯復雜的分布提前布局一下&#xff0c;學習伽馬函數。 【2】伽馬函數 伽馬函數有兩種經典寫法&#xff0c;一種是積分形式&#xff0c;另一種是無窮乘積形式。 【2.1】…