背景:
在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> 并按照如下設置</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;// }
};