最近在项目中使用了 Vue 3 结合海康Web插件来实现视频监控功能,过程中遇到了一些挑战和解决方案。为了帮助开发小伙伴们更好地理解和应用这一技术栈,特此分享一下我们的经验和代码实现。
目录
在当前的项目中,我们需要实现一个视频监控系统,能够展示多个监控点的实时视频流,并支持用户通过树形结构选择不同的监控点。为了实现这一需求,我们选择了 Vue 3 作为前端框架,并集成了海康Web插件来处理视频流的播放和管理。
在官网海康开放平台下载视频web插件
将这3个js文件引入vue项目中的public文件夹下新建文件夹放入
然后在index.html文件中根路径引入配置文件
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <link rel="icon" href="/favicon.ico">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>前端青山</title>
- </head>
- <body>
- <div id="screen"></div>
- <!-- 连接内网部署离线天地图 -->
- <script src="/h5player/h5player.min.js"></script>
- <script src="/webControl/jquery-1.12.4.min.js"></script>
- <script src="/webControl/jsencrypt.min.js"></script>
- <script src="/webControl/web-control_1.2.5.min.js"></script>
- <script type="module" src="/src/main.ts"></script>
- <script src="/src/utils/d3/d3.js" charset="utf-8"></script>
- <script src="/src/utils/d3/D3SvgOverlay.js"></script>
- </body>
- </html>
最后我们开始构建本次所需要调用的组件封装功能
- <template>
- <div class="play_windows" v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.8)">
- <div class="tree-form">
- <el-tree
- ref="tree"
- :data="dataTree"
- :props="defaultProps"
- :highlight-current="true"
- @node-click="pitchOns"
- >
- <template #default="{ node, data }">
- <span class="custom-tree-node">
- {{ data.name }}
- </span>
- </template>
- </el-tree>
- </div>
- <div class="videosp" ref="videosp">
- <div id='corpvideo' ref="corpvideo"></div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, nextTick, defineProps, defineExpose, defineEmits, watch, onBeforeUnmount } from 'vue';
- import { ElMessage } from 'element-plus'
- import { videoallList } from '@/api/screenVideo/index'
- import { getGetByCode } from "@/api/videoSurveillance/index";
- const emit = defineEmits(["handleSpjkPOIClick"]);
- const props = defineProps({
- playURL: String, // 视频url
- splitNum: Number, // 分屏播放,默认最大分屏4*4
- dataTree: Object, // 树 数据
- defaultProps: Object
- });
- let dataTree = ref<any>(props.dataTree);
- let defaultProps = ref<any>(props.defaultProps);
- let loading = ref<Boolean>(false);
- const corpvideo = ref<any>();
- const videosp = ref<any>(null);
- let width: any = 0;
- let height: any = 0;
- let oWebControl: any = null;
- let initCount: any = 0;
- let pubKey: any = '';
- const setEncrypt = (value: any) => {
- let encrypt = new JSEncrypt();
- encrypt.setPublicKey(pubKey);
- return encrypt.encrypt(value);
- }
- const initPlugin = () => {
- nextTick(() => {
- width = videosp.value.offsetWidth;
- height = videosp.value.offsetHeight;
- oWebControl = new webControl({
- szPluginContainer: "corpvideo",
- iServicePortStart: 15900,
- iServicePortEnd: 15900,
- szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11",
- cbConnectSuccess: function () {
- oWebControl.JS_StartService("window", {
- dllPath: "./VideoPluginConnect.dll"
- }).then(function () {
- oWebControl.JS_CreateWnd("corpvideo", width, height).then(function () {
- init();
- });
- }, function () {});
- },
- cbConnectError: function () {
- oWebControl = null;
- webControl.JS_WakeUp("VideoWebPlugin://");
- initCount++;
- if (initCount < 3) {
- setTimeout(function () {
- initPlugin();
- }, 3000);
- } else {
- console.log("插件启动失败,请检查插件是否安装!");
- }
- },
- cbConnectClose: function (bNormalClose: any) {
- oWebControl = null;
- webControl.JS_WakeUp("VideoWebPlugin://");
- initCount++;
- if (initCount < 3) {
- setTimeout(function () {
- initPlugin();
- }, 3000);
- } else {
- console.log("插件启动失败,请检查插件是否安装!");
- }
- }
- });
- });
- }
- const getPubKey = (callback: any) => {
- oWebControl.JS_RequestInterface({
- funcName: "funcName",
- argument: JSON.stringify({
- keyLength: 1024
- })
- }).then((oData: any) => {
- if (oData.responseMsg.data) {
- pubKey = oData.responseMsg.data;
- callback();
- }
- });
- }
- const init = () => {
- getPubKey(() => {
- appkey = "appkey ";
- secret = setEncrypt("secret");
- ip = "ip ";
- playMode = 0;
- port = 443;
- snapDir = "D:\\SnapDir";
- videoDir = "D:\\VideoDir";
- layout = "1x1";
- enableHTTPS = 1;
- encryptedFields = 'secret';
- showToolbar = 1;
- showSmart = 1;
- buttonIDs = "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769";
-
- oWebControl.JS_RequestInterface({
- funcName: "init",
- argument: JSON.stringify({
- appkey: appkey,
- secret: secret,
- ip: ip,
- playMode: playMode,
- port: port,
- snapDir: snapDir,
- videoDir: videoDir,
- layout: layout,
- enableHTTPS: enableHTTPS,
- encryptedFields: encryptedFields,
- showToolbar: showToolbar,
- showSmart: showSmart,
- buttonIDs: buttonIDs
- })
- }).then((oData: any) => {
- oWebControl.JS_Resize(width, height);
- });
- });
- }
- const JSRequestInterface = (code: any) => {
- cameraIndexCode = code.replace(/(^\s*)/g, "").replace(/(\s*$)/g, "");
-
- oWebControl.JS_RequestInterface({
- funcName: "startPreview",
- argument: JSON.stringify({
- cameraIndexCode: cameraIndexCode,
- streamMode: streamMode,
- transMode: transMode,
- gpuMode: gpuMode,
- wndId: wndId
- })
- });
- }
- const JSHideWnd = () => {
- oWebControl.JS_HideWnd();
- oWebControl.JS_DestroyWnd().then(function () {}, function () {});
- }
-
- const JSShowWnd = () => {
- initPlugin();
- oWebControl.JS_ShowWnd();
- }
- window.addEventListener('unload', JSHideWnd);
- const getElementPosition = () => {
- width = window.innerWidth * 0.3;
- height = window.innerHeight * 0.56;
- oWebControl.JS_Resize(width, height);
- };
- window.addEventListener('resize', getElementPosition);
- const pitchOns = (e: any) => {
- if (!e || !e.self) {
- if (e.equipmentCoding) {
- handleAddChild(e);
- }
- return;
- }
- if (e.children) {
- emit("handleSpjkPOIClick", e.self.indexCode, '');
- return;
- } else {
- handleAddChild(e);
- }
- }
-
- const handleAddChild = (e: any) => {
- if (!e || !e.self) {
- if (e.equipmentCoding) {
- videoUrl(e.equipmentCoding);
- }
- return;
- }
- if (e.self.indexCode) {
- let params = {
- UnitIndexCode: e.self.indexCode,
- };
- videoallList(params).then((res: any) => {
- if (res.data.rows.length == 0) {
- emit("handleSpjkPOIClick", e.self.indexCode, '');
- } else {
- e.children = e.children || [];
- res.data.rows = res.data.rows.map((child: any) => ({
- ...child,
- name: child.equipmentName,
- }));
- res.data.rows.forEach((child: any) => {
- e.children.push(child);
- });
- (e as any).expanded = true;
- }
- });
- }
- }
-
- const videoUrl = (equipmentCoding: string) => {
- let params = {
- equipmentCoding: equipmentCoding,
- };
- JSRequestInterface(equipmentCoding)
- }
- defineExpose({
- initPlugin,
- JSHideWnd,
- JSShowWnd,
- JSRequestInterface
- })
- <style scoped lang="scss">
- // 公共element样式
- @import '@/styles/eleCustomize.scss';
-
- /* 样式 */
- .play_windows {
- display: flex;
- width: 100% !important;
- .tree-form {
- width: 18vw;
- height: 28vw;
- overflow: auto;
- padding: 0;
- }
- }
- .videosp {
- width: 32vw;
- height: 60vh !important;
- #corpvideo {
- width: 100% !important;
- height: 100% !important;
- margin-top: 0.5vh;
- }
- #player-container-0 {
- width: 100% !important;
- height: 100% !important;
- }
- }
-
- /* 屏幕宽度超过1920px时应用 */
- @media (min-width: 8000px) {
- .play_windows {
- .tree-form {
- width: 10vw;
- height: 18vw;
- }
- }
- .videosp {
- width: 45vw;
- }
- }
-
- ::v-deep(.el-radio-button__inner) {
- width: 2vw;
- height: 1vw;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 0.6vw;
- }
-
- .video-button {
- width: 3vw;
- height: 1vw;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 0.6vw;
- }
-
- ::v-deep(.el-radio-button__inner) {
- background: transparent !important;
- color: white !important;
- border: 0 !important;
- display: flex;
- align-items: center;
- height: 3.5vh !important;
- color: white !important;
- margin: 0.2vw;
- font-size: 1.6vh !important;
- background: transparent !important;
- border: 0.1vw solid #009392 !important;
- border-radius: 0.2vw !important;
- }
-
- ::v-deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
- background: linear-gradient(90deg, rgba(0, 96, 204, 0.2) 0%, rgba(0, 165, 189, 0.6) 100%) !important;
- color: white !important;
- border-color: none !important;
- }
- </style>
子组件 ScreenMonitoring 主要实现了监控点的树形结构展示和视频播放控制。通过 el-tree 组件展示监控点的树形结构,并在节点被点击时调用视频播放插件的初始化和播放方法。子组件提供了 JSRequestInterface 方法请求视频流,initialize 方法初始化视频播放,以及 JSHideWnd 方法停止视频播放,确保视频监控功能的完整性和可控性。
- <template>
- <screenVideoDialog
- v-model="dialogVideo"
- title="公安监控"
-
- @close="onCloseDialog"
- @open="onOpenDialog"
- :draggable="false"
- >
- <div class="my_dialog_slot" style="height:60vh;" v-if="dialogVideo">
- <ScreenMonitoring
- ref="screenmonitoring"
- :dataTree="dataTree"
- :defaultProps="defaultProps"
- @handleSpjkPOIClick="handleSpjkPOIClick"
- />
- </div>
- </screenVideoDialog>
- </template>
- <script setup lang="ts">
- import { ref } from 'vue';
- import screenVideoDialog from '@/components/Dialog/screenVideoDialog.vue';
- import ScreenMonitoring from '@/components/Dialog/screenMonitoring.vue';
-
- const dialogVideo = ref(false);
- const dataTree = ref([
- // 树形结构数据
- ]);
- const defaultProps = ref({
- children: 'children',
- label: 'label'
- });
- const screenmonitoring = ref<InstanceType<typeof ScreenMonitoring> | null>(null);
-
- // 处理监控点点击事件
- const handleSpjkPOIClick = (poiId: string, coord: string) => {
- let params = {
- UnitIndexCode: poiId
- };
- screenmonitoring.value?.JSRequestInterface(poiId);
- // getGetByCodes(params).then(res => {
- // setTimeout(() => {
- // screenmonitoring.value?.initialize(res.data.urls[0], res.data.urls);
- // }, 1000);
- // });
- };
-
- // 关闭对话框时停止视频
- const onCloseDialog = (e: any) => {
- screenmonitoring.value?.JSHideWnd();
- };
-
- // 打开对话框时初始化视频
- const onOpenDialog = (e: any) => {
- screenmonitoring.value?.initPlugin();
- };
- </script>
- <style scoped>
- .my_dialog_slot {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- }
- </style>
通过上述代码,我们在父组件中实现了视频监控对话框的显示和隐藏,并在对话框打开和关闭时调用子组件的相应方法,以控制视频的播放和停止。
本文详细介绍了如何使用 Vue 3 框架集成海康Web插件实现视频监控功能。通过定义属性、事件、变量,以及编写初始化、播放视频、处理节点点击事件等方法,我们成功实现了视频监控系统的前端部分。同时,通过样式部分的定制,确保了良好的用户体验。希望本文对读者在开发类似项目时有所帮助。