前端接入 海康 SDK WEB无插件开发包 V3.2
先看效果:
因为是分拆组件实现反控功能,后面会贴代码
1、前期工作准备
海康 SDK 下载链接 下载需要注册海康账号,注册下就好了
1-1、解压文件,找到webs文件夹
1-2、找到codebase文件夹将文件复制到工程项目public文件夹下
自己取个名字,在html文件里引入
2、项目中使用
在utils文件夹里新增一个 webVideo.js文件
实现双通道 主要代码:
init方法里面设置 此属性 iWndowType: 2, //分屏
在start方法里面 遍历所有通道 (self.channels)
self.channels.forEach((channel, index) => {
var oWndInfo = WebVideoCtrl.I_GetWindowStatus(index); // 获取当前窗口状态
var startRealPlay = function () {
console.log(`调用播放: 通道=${channel.id}, 窗口=${index}`);
WebVideoCtrl.I_StartRealPlay(self.szDeviceIdentify, {
iChannelID: channel.id,
bZeroChannel: false,
iStreamType: 2,
iWndIndex: index, // 指定窗口索引
success: function () {
console.log(`通道 ${channel.id} 播放成功, 分配到窗口 ${index}`); },
error: function (status) {
console.log(`通道 ${channel.id} 播放失败`, status); } });
};
if (oWndInfo != null) {
// 如果窗口已经有播放内容,先停止 console.log(`尝试停止窗口 ${index}`);
WebVideoCtrl.I_Stop({
iWndIndex: index,
success: function () {
setTimeout(() => startRealPlay(), 500);
},
error: function () {
console.log(`停止窗口 ${index} 失败`); } });
} else {
startRealPlay(); // 直接播放
}
});
完整代码
export function WebVideo() {
this.g_iWndIndex = 0//窗口索引
this.szDeviceIdentify = ''//设备标识(通道)
this.deviceport = ''//设备端口
this.rtspPort = ''//rtsp端口
this.channels = []//通道数组
this.ip = ''//ip地址
this.port = ''//端口号
this.username = ''//海康提供的监控登入用户名
this.password = ''//登入密码
this.init = function(ip, username, password, port) {
this.ip = ip
this.username = username
this.password = password
this.port = port
// 初始化插件参数及插入插件
WebVideoCtrl.I_InitPlugin(1160, 630, {
szColorProperty: 'plugin-background:#102749; sub-background:#102749; sub-border:#18293c; sub-border-select:red',
bWndFull: true, // 全屏
// iPackageType: 2,
iWndowType: 2, //分屏
bNoPlugin: true, // 支持无插件
cbInitPluginComplete: function () {
WebVideoCtrl.I_InsertOBJECTPlugin("divPlugin");
}
});
// console.log('ip和端口', ip, username, password,);
}
// 登录
this.clickLogin = function () {
let self = this
if ("" == self.ip || "" == self.port) {
return console.log('请输入ip和端口', WebVideoCtrl);
}
self.szDeviceIdentify = self.ip + "_" + self.port;
console.log(self.szDeviceIdentify,'----self.szDeviceIdentify-----');
WebVideoCtrl.I_Login(self.ip, 1, self.port, self.username, self.password, {//1 代表http协议,2 https
success: function (xmlDoc) {
setTimeout(function () {
// console.log('登录成功');
// Store.setIpProt(self.szDeviceIdentify)//存储已登入ip和端口
self.getChannelInfo();
self.getDevicePort();
}, 10);
setTimeout(function() {
self.clickStartRealPlay()
}, 500)
},
error: function (status, xmlDoc) {
console.log(status,'登录失败');
}
});
}
// 退出
this.clickLogout = function() {
var self = this
self.channels = []
if (null == self.szDeviceIdentify) {
return;
}
var iRet = WebVideoCtrl.I_Logout(self.szDeviceIdentify);
if (0 == iRet) {
self.getChannelInfo();
self.getDevicePort();
}
console.log(self.szDeviceIdentify,'退出成功');
}
// 获取通道
this.getChannelInfo = function(e) {
var self = this
self.channels = []
if (null == self.szDeviceIdentify) {
return;
}
// 模拟通道
WebVideoCtrl.I_GetAnalogChannelInfo(self.szDeviceIdentify, {
async: false,
success: function (xmlDoc) {
var oChannels = $(xmlDoc).find("VideoInputChannel");
$.each(oChannels, function (i) {
var id = $(this).find("id").eq(0).text(),
name = $(this).find("name").eq(0).text();
if ("" == name) {
name = "Camera " + (i < 9 ? "0" + (i + 1) : (i + 1));
}
self.channels.push({
id: id,
name: name
})
});
},
error: function (status, xmlDoc) {
console.log(self.channels,'获取模拟通道号失败')
}
});
// 数字通道
WebVideoCtrl.I_GetDigitalChannelInfo(self.szDeviceIdentify, {
async: false,
success: function (xmlDoc) {
var oChannels = $(xmlDoc).find("InputProxyChannelStatus");
$.each(oChannels, function (i) {
var id = $(this).find("id").eq(0).text(),
name = $(this).find("name").eq(0).text(),
online = $(this).find("online").eq(0).text();
if ("" == name) {
name = "IPCamera " + (i < 9 ? "0" + (i + 1) : (i + 1));
}
self.channels.push({
id: id,
name: name,
online:online
})
});
console.log(self.szDeviceIdentify +'--'+self.channels[0]+ "--szDeviceIdentify--channels[0]--- 获取数字通道成功!");
},
error: function (status, xmlDoc) {
console.log(self.szDeviceIdentify +'--'+self.channels[0] + " ---获取数字通道失---", status, xmlDoc);
}
});
}
// 获取端口
this.getDevicePort = function() {
var self = this
if (null == self.szDeviceIdentify) {
return;
}
var oPort = WebVideoCtrl.I_GetDevicePort(self.szDeviceIdentify);
if (oPort != null) {
self.deviceport = oPort.iDevicePort;
self.rtspPort = oPort.iRtspPort;
}
console.log('获取端口号成功')
}
// 开始预览
this.clickStartRealPlay = function() {
var self = this
console.log(self.channels, "---开始预览--通道号----", self.szDeviceIdentify);
if (null == self.szDeviceIdentify) {
console.log(self.szDeviceIdentify + ' --szDeviceIdentify--为空---');
return;
}
var result = WebVideoCtrl.I_ChangeWndNum(2);
console.log(`分屏设置结果: ${result}`);
self.channels.forEach((channel, index) => {
var oWndInfo = WebVideoCtrl.I_GetWindowStatus(index); // 获取当前窗口状态
var startRealPlay = function () {
console.log(`调用播放: 通道=${channel.id}, 窗口=${index}`);
WebVideoCtrl.I_StartRealPlay(self.szDeviceIdentify, {
iChannelID: channel.id,
bZeroChannel: false,
iStreamType: 2,
iWndIndex: index, // 指定窗口索引
success: function () {
console.log(`通道 ${channel.id} 播放成功, 分配到窗口 ${index}`);
},
error: function (status) {
console.log(`通道 ${channel.id} 播放失败`, status);
}
});
};
if (oWndInfo != null) { // 如果窗口已经有播放内容,先停止
console.log(`尝试停止窗口 ${index}`);
WebVideoCtrl.I_Stop({
iWndIndex: index,
success: function () {
setTimeout(() => startRealPlay(), 500);
},
error: function () {
console.log(`停止窗口 ${index} 失败`);
}
});
} else {
startRealPlay(); // 直接播放
}
});
}
// 停止预览
this.clickStopRealPlay = function() {
var self = this
if (self.channels.length === 0) {
console.log('没有可停止的通道');
return;
}
self.channels.forEach((channel, index) => {
var oWndInfo = WebVideoCtrl.I_GetWindowStatus(index);
if (oWndInfo != null) {
WebVideoCtrl.I_Stop({
iWndIndex: index, // 停止对应窗口
success: function () {
console.log(`通道 ${channel.id} 预览停止成功`);
},
error: function () {
console.log(`通道 ${channel.id} 预览停止失败`);
}
});
}
});
}
/* 云平台控制 */
// PTZ控制 9为自动,1,2,3,4,5,6,7,8为方向PTZ
let g_bPTZAuto = false;
this.mouseDownPTZControl=function (iPTZIndex) {
var self = this
var oWndInfo = WebVideoCtrl.I_GetWindowStatus(self.g_iWndIndex),
bZeroChannel = self.channels[0].id ,//通道id
iPTZSpeed = 4;//切换速度 $("#ptzspeed").val()
if (bZeroChannel==0) {// 零通道不支持云台
// return console.log(bZeroChannel+"-----云台");
}
if (oWndInfo != null) {
if (9 == iPTZIndex && g_bPTZAuto) {
iPTZSpeed = 0;// 自动开启后,速度置为0可以关闭自动
} else {
self.g_bPTZAuto = false;// 点击其他方向,自动肯定会被关闭
}
WebVideoCtrl.I_PTZControl(iPTZIndex, false, {
iPTZSpeed: iPTZSpeed,
success: function (xmlDoc) {
if (9 == iPTZIndex && g_bPTZAuto) {
} else {
}
if (9 == iPTZIndex) {
g_bPTZAuto = !g_bPTZAuto;
}
},
error: function (status, xmlDoc) {
}
});
}
}
// 方向PTZ停止
this.mouseUpPTZControl=function () {
let self = this
var oWndInfo = WebVideoCtrl.I_GetWindowStatus(self.g_iWndIndex);
if (oWndInfo != null) {
WebVideoCtrl.I_PTZControl(1, true, {
success: function (xmlDoc) {
},
error: function (status, xmlDoc) {
}
});
}
}
}
控制器代码:
<!-- 控制器 -->
<template>
<div>
<el-form-item label="控制模式" v-if="props.isRadio">
<el-radio-group v-model="radio">
<el-radio :value="1">任务模式</el-radio>
<el-radio :value="2">后台遥控</el-radio>
<el-radio :value="3">手持遥控</el-radio>
</el-radio-group>
</el-form-item>
<div class="w-full h-12.5" v-else></div>
<div class="flex justify-between items-center mb-8">
<div class="opacity-0 w-30% h-20 flex justify-center items-center border border-solid border-#d1d5db rounded-2">
</div>
<!-- 上 -->
<div
class="
w-30% h-20 flex justify-center items-center
border border-solid border-#d1d5db rounded-2 cursor-pointer
hover:text-#ff4500
"
@mousedown="handleOperate('mouseDownPTZControl')"
@mouseup="handleOperate('mouseUpPTZControl')"
>
<el-icon><ArrowUpBold /></el-icon>
</div>
<!-- 播放状态 -->
<div
class="
w-30% h-20 flex justify-center items-center
border border-solid border-#d1d5db rounded-2 cursor-pointer
hover:text-#ff4500
opacity-0
"
@click="handlePause"
>
<img v-if="!isPause" src="@/assets/images/pause.svg" class="w-10 h-10" alt="">
<el-icon class="text-4xl" v-else><CaretRight /></el-icon>
</div>
</div>
<!-- 左 -->
<div class="flex justify-between items-center">
<div
class="
w-30% h-20 flex justify-center items-center
border border-solid border-#d1d5db rounded-2 cursor-pointer
hover:text-#ff4500
"
@mousedown="handleOperate('mouseDownPTZControl3')"
@mouseup="handleOperate('mouseUpPTZControl3')"
>
<el-icon><ArrowLeftBold /></el-icon>
</div>
<!-- 下 -->
<div
class="
w-30% h-20 flex justify-center items-center
border border-solid border-#d1d5db rounded-2 cursor-pointer
hover:text-#ff4500
"
@mousedown="handleOperate('mouseDownPTZControl2')"
@mouseup="handleOperate('mouseUpPTZControl2')"
>
<el-icon><ArrowDownBold /></el-icon>
</div>
<!-- 右 -->
<div
class="
w-30% h-20 flex justify-center items-center
border border-solid border-#d1d5db rounded-2 cursor-pointer
hover:text-#ff4500
"
@mousedown="handleOperate('mouseDownPTZControl4')"
@mouseup="handleOperate('mouseUpPTZControl4')"
>
<el-icon><ArrowRightBold /></el-icon>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ArrowUpBold, ArrowDownBold, ArrowLeftBold, ArrowRightBold, CaretRight } from '@element-plus/icons-vue'
const emit = defineEmits([
'clickStartRealPlay1',
'clickStopRealPlay',
'mouseDownPTZControl',
'mouseUpPTZControl',
'mouseDownPTZControl2',
'mouseUpPTZControl2',
'mouseDownPTZControl3',
'mouseUpPTZControl3',
'mouseDownPTZControl4',
'mouseUpPTZControl4',
])
const radio = ref(1)
const isPause = ref(false)
const props = defineProps({
isRadio: {
type: Boolean,
default: false
}
})
const handlePause = () => {
isPause.value = !isPause.value
if(isPause.value) {
emit('clickStopRealPlay')
} else {
emit('clickStartRealPlay1')
}
}
const handleOperate = (type) => {
emit(type)
}
</script>
重点: 拆分摄像头逻辑写了个中间件
最重要的是拿到摄像头的ip、端口、账号密码
import { ref, nextTick } from "vue";
import { WebVideo } from '@/utils/webVideo.js'
/** 海康逻辑 */
export const useIndexHaikang = ()=>{
// 海康摄像头----------------------------------------
//登入信息
const loginInfo = ref({
ip: "xxx.xxx.x.x",
portNumber: "xx",
userName: "xxx",
password: "xxx",
})
const webVideo = new WebVideo()
// 登入
function initVideoPlay() {
try{
nextTick(() => {
webVideo.init(loginInfo.value.ip, loginInfo.value.userName, loginInfo.value.password, loginInfo.value.portNumber)
webVideo.clickLogin()
})
}catch(e){
console.error(e);
}
}
// 登出
function stopVideoPlay() {
webVideo.clickStopRealPlay()
webVideo.clickLogout()
}
// 控制器------------------------------------------
// 播放
function clickStartRealPlay1() {
initVideoPlay()
webVideo.clickStartRealPlay()
}
// 暂停播放
function clickStopRealPlay() {
webVideo.clickStopRealPlay()
stopVideoPlay()
}
// 开始移动控制 传入方向控制1-8
function mouseDownPTZControl() {
// console.log('鼠标按下了');
webVideo.mouseDownPTZControl(1)
}
// 结束移动控制
function mouseUpPTZControl() {
// console.log('鼠标抬起了');
webVideo.mouseUpPTZControl(1)
}
//向下移动
function mouseDownPTZControl2() {
webVideo.mouseDownPTZControl(2)
}
function mouseUpPTZControl2() {
webVideo.mouseUpPTZControl(2)
}
// 向左移动
function mouseDownPTZControl3() {
webVideo.mouseDownPTZControl(3)
}
function mouseUpPTZControl3() {
webVideo.mouseUpPTZControl(3)
}
// 向右移动
function mouseDownPTZControl4() {
webVideo.mouseDownPTZControl(4)
}
function mouseUpPTZControl4() {
webVideo.mouseUpPTZControl(4)
}
// 控制器------------------------------------------
// 海康摄像头----------------------------------------
return {
loginInfo,
initVideoPlay,
stopVideoPlay,
clickStartRealPlay1,
clickStopRealPlay,
mouseDownPTZControl,
mouseUpPTZControl,
mouseDownPTZControl2,
mouseUpPTZControl2,
mouseDownPTZControl3,
mouseUpPTZControl3,
mouseDownPTZControl4,
mouseUpPTZControl4,
}
}
/** websocket逻辑 */
export const useIndexWS = () => {
let socket
// 连接 WebSocket
const connectWebSocket = () => {
const wsUrl = import.meta.env.VITE_APP_BASE_WS;
socket = new WebSocket(wsUrl);
// WebSocket 打开事件
socket.onopen = () => {
console.log('建立连接成功');
};
// 接收消息事件
socket.onmessage = (event) => {
// console.log(`收到的消息: ${event.data}`);
// socket.send(message);
};
// WebSocket 关闭事件
socket.onclose = () => {
console.log('连接关闭');
};
// WebSocket 错误事件
socket.onerror = (error) => {
console.error('错误链接:', error);
};
};
return {
socket,
connectWebSocket,
}
}
主页面使用 删除了部分业务代码
<template>
<div class="w-full h-full">
<div class="flex justify-between p-4">
<div class="flex-1 h-full mx-4">
<div class="p-4 rounded-2 shadow-lg bg-white">
<cardTitle :title="'相机视图'" />
<div class="flex justify-between items-center w-full h-80 ">
<!-- 相机dom -->
<div class="w-full h-full " id="divPlugin"></div>
</div>
</div>
</div>
<div class="w-23.5% h-full ">
<div class="rounded-2 p-4 mb-4 shadow-lg bg-white ">
<cardTitle :title="'实时控制'" />
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="云台控制" name="1">
<controller
@clickStartRealPlay1="clickStartRealPlay1"
@clickStopRealPlay="clickStopRealPlay"
@mouseDownPTZControl="mouseDownPTZControl"
@mouseUpPTZControl="mouseUpPTZControl"
@mouseDownPTZControl2="mouseDownPTZControl2"
@mouseUpPTZControl2="mouseUpPTZControl2"
@mouseDownPTZControl3="mouseDownPTZControl3"
@mouseUpPTZControl3="mouseUpPTZControl3"
@mouseDownPTZControl4="mouseDownPTZControl4"
@mouseUpPTZControl4="mouseUpPTZControl4"
/>
</el-tab-pane>
<el-tab-pane label="移动控制" name="2">
<controller :isRadio="true" />
</el-tab-pane>
</el-tabs>
</div>
<div class="rounded-2 p-4 shadow-lg bg-white ">
<cardTitle :title="'实时警报'" />
<el-scrollbar class="h-156">
<warnItems v-for="i in 10" :key="i" />
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, onBeforeUnmount } from "vue";
import { ElMessage } from 'element-plus'
import { CaretRight } from "@element-plus/icons-vue";
import cardTitle from "@/components/cardTitle.vue";
import controller from '@/components/controller.vue'
import { useIndexHaikang, useIndexWS } from "@/middleware/index/index.js";
// 海康摄像头----------------------------------------
const {
initVideoPlay,
clickStartRealPlay1,
clickStopRealPlay,
mouseDownPTZControl,
mouseUpPTZControl,
mouseDownPTZControl2,
mouseUpPTZControl2,
mouseDownPTZControl3,
mouseUpPTZControl3,
mouseDownPTZControl4,
mouseUpPTZControl4,} = useIndexHaikang()
// 海康摄像头----------------------------------------
// websocket----------------------------------------
const {
socket,
connectWebSocket,
} = useIndexWS()
// websocket----------------------------------------
onMounted(() => {
initVideoPlay()
})
onBeforeUnmount(()=>{
if(socket){
socket.close()
}
clearInterval(timer.value)
})
</script>
3、nginx配置
代码写完开发环境是没法测试的,必须打包在生产环境下测试!
下载的解压包里面有个nginx的文件夹 start.bat 是启动 stop.bat是停止
把打包好的文件扔到ng同级目录或者上级目录(对应ng配置不一样),看你自己心情 我是直接扔到上级目录,nginx.conf配置如下:
4、测试
用ng配置的ip+端口测试就行,调试过程需要频繁打包
tip:网上有很多博客说网段必须一致,其实是不正确的,摄像头网段和你本地网段不一定要一致,ping的通就行,重点在测试之前先ping一下摄像头ip,ping不通代码写得再好也没用