Initial commit of secondary development sample code
42
README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# 二次开发示例代码
|
||||
工程总共包含几个模块,快速接收设备告警、深度对接设备接口 和 心跳保活。
|
||||
|
||||
## 快速接收设备告警
|
||||
### http-server-demo 以HTTP协议接收设备告警(最常用)
|
||||
参考具体文件下的源代码demo即可。
|
||||
### tcp-server-demo 以TCP协议接收设备告警(常见PLC控制等)
|
||||
参考具体文件下的源代码demo即可。
|
||||
### custom-api-demo 自定义API(部分用户的平台接口规则限定了可用这个)
|
||||
参考具体文件下的源代码demo即可。
|
||||
## 深度对接设备接口
|
||||
可将代码下载到Windows环境,用谷歌浏览器直接打开.html文件运行,内含相关功能的源代码,用户可根据我司提供的代码修改做二次开发。本工程的代码,可直接双击运行,除此之外,恕不提供其它的售后支持,见谅。
|
||||
### 实时画面播放&实时检测框
|
||||
live.html是实时画面播放和实时检测结果可视化的demo,将文件中的IP地址替换成KS设备的IP地址即可:
|
||||

|
||||
### 摄像头配置&算法绑定
|
||||
source.html是视频流管理demo,将文件中的IP地址替换成KS设备的IP地址即可:
|
||||

|
||||
|
||||
### 底库分组&人脸底库
|
||||
facelib.html是底库分组管理和人脸底库增删改查的demo,将文件中的IP地址替换成KS设备的IP地址即可:
|
||||

|
||||
|
||||
### 查看设备信息
|
||||
device.html是设备信息demo,将文件中的IP地址替换成KS设备的IP地址即可:
|
||||

|
||||
|
||||
## 心跳保活
|
||||
分别提供了python和java两种语言的服务端对接KS设备心跳的demo代码。
|
||||
|
||||
## 接收大模型复审告警
|
||||
启动main文件,在接收端创建一个server,在设备端【大模型】-【复审任务】-【结果推送】处,填上http://IP:port/vlreview ,即可接收设备的大模型的复审告警。
|
||||
|
||||
|
||||
## 上位机实时画面
|
||||
1. 示例代码下载到本地(windows即可)。
|
||||
2. 修改index.html中的serverIp、accessKey、accessSecret的三个字段。 三个修改该项在下图位置处修改。
|
||||
|
||||

|
||||

|
||||
|
||||
3. 双击index.html打开,即可使用示例代码。
|
||||
862
上位机实时画面/ZQL_common.js
Normal file
@ -0,0 +1,862 @@
|
||||
const ZQL_multivideo = {
|
||||
setVideoEl: () => {
|
||||
let videoContainer = document.querySelector("#video-container");
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
videoContainer.className = "one-video";
|
||||
videoContainer.innerHTML = `
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip0">
|
||||
<div class="icon-dot"></div>
|
||||
<div class="deviceoffline">
|
||||
<i class="z-icon-jiankonglixian" style="font-size: 40rem"></i>
|
||||
<span>离线</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title-container" id="video-title0"></div>
|
||||
<video ref="video" muted id="video0" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas0"></canvas>
|
||||
</div>
|
||||
`
|
||||
} else {
|
||||
videoContainer.className = "four-video";
|
||||
videoContainer.innerHTML = `
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip0">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title0"></div>
|
||||
<video ref="video" muted id="video0" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas0"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip1">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title1"></div>
|
||||
<video ref="video" muted id="video1" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas1"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip2">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title2"></div>
|
||||
<video ref="video" muted id="video2" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas2"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip3">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title3"></div>
|
||||
<video ref="video" muted id="video3" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas3"></canvas>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
},
|
||||
liveLoading: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
tipel.innerHTML = `<div class="icon-dot"></div>`
|
||||
},
|
||||
liveOffline: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
tipel.innerHTML = `
|
||||
<div class="deviceoffline">
|
||||
<i class="z-icon-jiankonglixian" style="font-size: 40rem"></i>
|
||||
<span>离线</span>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
liveStopLoading: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
if (tipel) {
|
||||
tipel.innerHTML = ``
|
||||
}
|
||||
},
|
||||
setAlgList(index) {
|
||||
let el = document.querySelector(`#video-title${index}`);
|
||||
let algList = ZQL_sources[ZQL_playingSource[index]].alg;
|
||||
let algEl = '<ul>';
|
||||
for (let alg in algList) {
|
||||
let name = algList[alg].reserved_args.ch_name;
|
||||
algEl = algEl + `<li alg="${alg}" index="${index}">${name}</li>`
|
||||
}
|
||||
algEl = algEl + '</ul>'
|
||||
el.innerHTML = `
|
||||
<div class="camera">${ZQL_sources[ZQL_playingSource[index]].desc}</div>
|
||||
<div class="alg">
|
||||
<div class="algname">算法: ${ZQL_playingSource[index].alg ? ZQL_sources[ZQL_playingSource[index]].alg[alg].reserved_args.ch_name : ''}</div>
|
||||
${algEl}
|
||||
</div>
|
||||
<div id="close${index}">关闭</div>
|
||||
`;
|
||||
el.querySelectorAll('li').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
let index = e.currentTarget.getAttribute("index");
|
||||
let alg = e.currentTarget.getAttribute("alg")
|
||||
ZQL_videosInfos[index].alg = alg;
|
||||
let videlel = document.querySelector(`#video-title${index}`);
|
||||
videlel.querySelector(".algname").innerHTML = '算法:' + ZQL_sources[ZQL_playingSource[index]].alg[alg].reserved_args.ch_name
|
||||
})
|
||||
})
|
||||
document.querySelector(`#close${index}`).addEventListener('click', () => {
|
||||
ZQL_multivideo.clearAlgList(index);
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_multivideo.destoryVideoByIndex(index);
|
||||
ZQL_playingSource[index] = null;
|
||||
ZQL_videosInfos[index] = null;
|
||||
})
|
||||
},
|
||||
clearAlgList(index) {
|
||||
let el = document.querySelector(`#video-title${index}`);
|
||||
if (el) {
|
||||
el.innerHTML = ""
|
||||
}
|
||||
|
||||
},
|
||||
detectSrs() {
|
||||
if (ZQLGLOBAL.detectSrsTimer) {
|
||||
clearInterval(ZQLGLOBAL.detectSrsTimer)
|
||||
}
|
||||
ZQLGLOBAL.detectSrsTimer = setInterval(() => {
|
||||
ZQL_apis.detectStream().then((res) => {
|
||||
if (document.visibilityState == "visible" && res.code == 0) {
|
||||
for (let i = 0; i < ZQL_playingSource.videonum; i++) {
|
||||
let deviceId_cameraId = ZQL_playingSource[i];
|
||||
if (
|
||||
deviceId_cameraId &&
|
||||
ZQL_videosInfos[i] &&
|
||||
ZQL_videosInfos[i].status != "离线"
|
||||
) {
|
||||
let deviceId = ZQL_sources[deviceId_cameraId].deviceId;
|
||||
let cameraId = ZQL_sources[deviceId_cameraId].sourceId;
|
||||
let streamInfo = res.streams.find(
|
||||
(item) => item.url.indexOf(`${deviceId}/${cameraId}`) > 0
|
||||
);
|
||||
if (
|
||||
!streamInfo ||
|
||||
(streamInfo && streamInfo.publish.active == false)
|
||||
) {
|
||||
ZQL_multivideo.destoryVideoByIndex(i);
|
||||
ZQL_multivideo.subscribeLive(deviceId_cameraId, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}, 60000)
|
||||
|
||||
},
|
||||
handleRefresh(index) {
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
return;
|
||||
}
|
||||
if (ZQL_videosInfos[index].status == "离线") {
|
||||
ZQL_multivideo.destoryVideoByIndex(index);
|
||||
ZQL_multivideo.subscribeLive(ZQL_playingSource[index], index);
|
||||
} else {
|
||||
if (!ZQL_videosInfos[index].stream) {
|
||||
return;
|
||||
}
|
||||
let video = document.getElementById("video" + index);
|
||||
video && (video.srcObject = null);
|
||||
if (ZQL_videosInfos[index] && ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
ZQL_videosInfos[index].replayTimer = null;
|
||||
}
|
||||
ZQL_videosInfos[index] &&
|
||||
ZQL_videosInfos[index].srsrtc &&
|
||||
ZQL_videosInfos[index].srsrtc.destroy();
|
||||
ZQL_videosInfos[index].srsrtc = null;
|
||||
ZQL_videosInfos[index].status = "";
|
||||
ZQL_multivideo.playVideo(ZQL_playingSource[index], index);
|
||||
}
|
||||
},
|
||||
subscribeLive(cameraId, index) {
|
||||
ZQL_multivideo.getCameraSize(cameraId, index);
|
||||
ZQL_multivideo.liveLoading(index);
|
||||
ZQL_apis
|
||||
.subscribeLive(
|
||||
ZQL_sources[cameraId].deviceId,
|
||||
ZQL_sources[cameraId].sourceId
|
||||
)
|
||||
.then((data) => {
|
||||
let stream = data.data;
|
||||
if (data && stream) {
|
||||
ZQL_videosInfos[index].stream = stream;
|
||||
ZQL_multivideo.playVideo(cameraId, index);
|
||||
} else {
|
||||
if (ZQL_playingSource[index] == cameraId) {
|
||||
ZQL_multivideo.liveOffline(index);
|
||||
// ZQL_videosInfos[index].status = "离线";
|
||||
// ZQL_videosInfos[index].loading = false;
|
||||
// this.reSubcribe(cameraId, index);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
ZQL_playingSource[index] == cameraId &&
|
||||
ZQL_videosInfos[index]
|
||||
) {
|
||||
ZQL_multivideo.liveOffline(index);
|
||||
// ZQL_videosInfos[index].status = "离线";
|
||||
// ZQL_videosInfos[index].loading = false;
|
||||
// this.reSubcribe(cameraId, index);
|
||||
}
|
||||
});
|
||||
},
|
||||
playVideo(cameraId, index) {
|
||||
if (ZQL_videosInfos[index].srsrtc) {
|
||||
return;
|
||||
}
|
||||
ZQL_videosInfos[index].loading = true;
|
||||
|
||||
let video = document.getElementById("video" + index);
|
||||
let stream = ZQL_videosInfos[index].stream;
|
||||
var srsrtc;
|
||||
if (stream.indexOf("webrtc") >= 0) {
|
||||
let videosrc =
|
||||
"webrtc://" + ZQLGLOBAL.serverIp + "/live" + stream.split("/live")[1];
|
||||
srsrtc = new JSWebrtc.Player(videosrc, {
|
||||
video: video,
|
||||
autoplay: true,
|
||||
onPlay: (obj) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
}
|
||||
ZQL_videosInfos[index].refreshTime =
|
||||
parseInt((Math.random() * 5 + 5) * 1000) * 60;
|
||||
ZQL_videosInfos[index].refreshTimeInterval = setInterval(() => {
|
||||
ZQL_multivideo.handleRefresh(index);
|
||||
}, ZQL_videosInfos[index].refreshTime);
|
||||
},
|
||||
});
|
||||
} else if (stream.indexOf(".flv") >= 0) {
|
||||
let videosrc = `http://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.srs_http_server}/live${
|
||||
stream.split("/live")[1]
|
||||
}`;
|
||||
srsrtc = mpegts.createPlayer(
|
||||
{
|
||||
type: "flv",
|
||||
url: videosrc,
|
||||
isLive: true,
|
||||
},
|
||||
{ enableWorker: true }
|
||||
);
|
||||
srsrtc.attachMediaElement(video);
|
||||
srsrtc.load();
|
||||
|
||||
srsrtc
|
||||
.play()
|
||||
.then((res) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
}
|
||||
ZQL_videosInfos[index].refreshTime =
|
||||
parseInt((Math.random() * 5 + 5) * 1000) * 60;
|
||||
ZQL_videosInfos[index].refreshTimeInterval = setInterval(() => {
|
||||
ZQL_multivideo.handleRefresh(index);
|
||||
}, ZQL_videosInfos[index].refreshTime);
|
||||
})
|
||||
.catch((err) => { });
|
||||
if (ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
}
|
||||
ZQL_videosInfos[index].replayTimer = setTimeout(() => {
|
||||
ZQL_multivideo.replayflv(srsrtc, cameraId, index);
|
||||
}, 3000);
|
||||
} else {
|
||||
video.src = "staticdata/" + stream.split("/home/linaro/ks/")[1];
|
||||
}
|
||||
|
||||
ZQL_videosInfos[index].srsrtc = srsrtc;
|
||||
|
||||
},
|
||||
replayflv(srsrtc, cameraId, index) {
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
return;
|
||||
}
|
||||
if (ZQL_videosInfos[index].playerState == "success") {
|
||||
return;
|
||||
} else {
|
||||
srsrtc.unload();
|
||||
srsrtc.load();
|
||||
srsrtc
|
||||
.play()
|
||||
.then((res) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
}
|
||||
ZQL_videosInfos[index].refreshTime =
|
||||
parseInt((Math.random() * 5 + 5) * 1000) * 60;
|
||||
ZQL_videosInfos[index].refreshTimeInterval = setInterval(() => {
|
||||
ZQL_multivideo.handleRefresh(index);
|
||||
}, ZQL_videosInfos[index].refreshTime);
|
||||
})
|
||||
.catch((err) => {
|
||||
// this.destoryVideoByIndex(index);
|
||||
// this.subscribeLive(cameraId, index);
|
||||
});
|
||||
if (ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
}
|
||||
ZQL_videosInfos[index].replayTimer = setTimeout(() => {
|
||||
ZQL_multivideo.replayflv(srsrtc, cameraId, index);
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
reSubcribe(cameraId, index) {
|
||||
if (ZQL_videosInfos[index].subscribeTimeout) {
|
||||
clearTimeout(ZQL_videosInfos[index].subscribeTimeout);
|
||||
ZQL_videosInfos[index].subscribeTimeout = null;
|
||||
}
|
||||
ZQL_multivideo.videosInfos[index].subscribeTimeout = setTimeout(() => {
|
||||
ZQL_multivideo.subscribeLive(cameraId, index);
|
||||
}, 1000);
|
||||
},
|
||||
getCameraSize(id, index) {
|
||||
ZQL_multivideo.setOrisize(
|
||||
ZQL_sources[id].draw_size[0],
|
||||
ZQL_sources[id].draw_size[1],
|
||||
index, id
|
||||
);
|
||||
},
|
||||
setOrisize(width, height, index, id) {
|
||||
let container = document.querySelector(".video-box");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
let alg = null;
|
||||
if (sessionStorage.getItem("curalgs")) {
|
||||
let cameraId = ZQL_playingSource[index];
|
||||
let curalgs = JSON.parse(sessionStorage.getItem("curalgs"));
|
||||
alg = curalgs[cameraId]
|
||||
? JSON.parse(JSON.stringify(curalgs[cameraId]))
|
||||
: null;
|
||||
}
|
||||
ZQL_videosInfos[index] = {
|
||||
id: id,
|
||||
loading: true,
|
||||
openWs: true,
|
||||
alg: alg,
|
||||
algListShow: false,
|
||||
subscribeTimeout: null,
|
||||
refreshTimeInterval: null, // 定时刷新定时器
|
||||
refreshTime: null, // 定时刷新时间
|
||||
replayTimer: null,
|
||||
playerState: "pending",
|
||||
detectInterval: null,
|
||||
quanping: false,
|
||||
srsrtc: null,
|
||||
stream: "",
|
||||
status: "",
|
||||
};
|
||||
}
|
||||
if (ZQL_videosInfos[index]) {
|
||||
let oriWidth = width;
|
||||
let oriHeight = height;
|
||||
ZQL_videosInfos[index].oriWidth = oriWidth;
|
||||
ZQL_videosInfos[index].oriHeight = oriHeight;
|
||||
|
||||
if (
|
||||
oriWidth / container.offsetWidth >
|
||||
oriHeight / container.offsetHeight
|
||||
) {
|
||||
ZQL_videosInfos[index].actualHeight = container.offsetWidth / (oriWidth / oriHeight)
|
||||
ZQL_videosInfos[index].actualWidth = container.offsetWidth;
|
||||
} else {
|
||||
ZQL_videosInfos[index].actualHeight = container.offsetHeight
|
||||
ZQL_videosInfos[index].actualWidth = container.offsetHeight * (oriWidth / oriHeight)
|
||||
}
|
||||
// videoWidth = ZQL_videosInfos[index].actualWidth;
|
||||
ZQL_multivideo.setPosition(index);
|
||||
}
|
||||
},
|
||||
setPosition(index) {
|
||||
let container = document.querySelector(".video-box");
|
||||
let video = document.querySelector("#video" + index);
|
||||
let canvas = document.getElementById("canvas" + index);
|
||||
let width = ZQL_videosInfos[index].actualWidth, height = ZQL_videosInfos[index].actualHeight;
|
||||
video.style.position = "absolute";
|
||||
video.style.width = width + "px";
|
||||
video.style.height = height + "px";
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
if (width / container.offsetWidth < height / container.offsetHeight) {
|
||||
let left = (container.offsetWidth - width) / 2;
|
||||
video.style.left = Math.floor(left) + "px";
|
||||
video.style.top = 0 + "px";
|
||||
canvas.style.left = Math.floor(left) + "px";
|
||||
canvas.style.top = "0px";
|
||||
} else {
|
||||
let top = (container.offsetHeight - height) / 2;
|
||||
video.style.top = Math.floor(top) + "px";
|
||||
video.style.left = 0 + "px";
|
||||
canvas.style.top = Math.floor(top) + "px";
|
||||
canvas.style.left = "0px";
|
||||
}
|
||||
},
|
||||
setAlarms: (data, index) => {
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
if (ZQL_videosInfos[index] && !ZQL_videosInfos[index].canvas) {
|
||||
ZQL_videosInfos[index].canvas = document.getElementById("canvas" + index)
|
||||
}
|
||||
if (
|
||||
!ZQL_videosInfos[index] ||
|
||||
!ZQL_videosInfos[index].actualWidth ||
|
||||
!ZQL_videosInfos[index].actualHeight ||
|
||||
!ZQL_videosInfos[index].oriWidth ||
|
||||
!ZQL_videosInfos[index].oriHeight
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// let bbox = data.result.data.bbox;
|
||||
let bbox = data.bbox;
|
||||
if (Object.values(bbox.polygons).length > 0) {
|
||||
Object.values(bbox.polygons).forEach((item) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
// let color = item.color;
|
||||
let points = item.polygon.map((point) => {
|
||||
return [
|
||||
Math.round(
|
||||
(point[0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
Math.round(
|
||||
(point[1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
];
|
||||
});
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "transparent";
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
ZQL_multivideo.drawPolygons(points, context);
|
||||
ZQL_multivideo.drawPolygonInfo(context, Object.values(bbox.polygons), index);
|
||||
});
|
||||
}
|
||||
|
||||
if (bbox.rectangles.length > 0) {
|
||||
bbox.rectangles.forEach((item, i) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
let coordinates = {
|
||||
x: Math.round(
|
||||
(item.xyxy[0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y: Math.round(
|
||||
(item.xyxy[1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
x1: Math.round(
|
||||
(item.xyxy[2] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y1: Math.round(
|
||||
(item.xyxy[3] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
};
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "rgb(" + color.join(",") + ")";
|
||||
context.fillText(item.label || "", coordinates.x, coordinates.y - 10);
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
// context.strokeRect(
|
||||
// coordinates.x,
|
||||
// coordinates.y,
|
||||
// coordinates.x1 - coordinates.x,
|
||||
// coordinates.y1 - coordinates.y
|
||||
// );
|
||||
let lines = [];
|
||||
let lineWidth = (coordinates.x1 - coordinates.x) / 4;
|
||||
let lineHeight = (coordinates.y1 - coordinates.y) / 4;
|
||||
lines[0] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x + lineWidth,
|
||||
y1: coordinates.y,
|
||||
};
|
||||
lines[1] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x,
|
||||
y1: coordinates.y + lineHeight,
|
||||
};
|
||||
lines[2] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x1 - lineWidth,
|
||||
y1: coordinates.y,
|
||||
};
|
||||
lines[3] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x1,
|
||||
y1: coordinates.y + lineHeight,
|
||||
};
|
||||
lines[4] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x + lineWidth,
|
||||
y1: coordinates.y1,
|
||||
};
|
||||
lines[5] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x,
|
||||
y1: coordinates.y1 - lineHeight,
|
||||
};
|
||||
lines[6] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x1 - lineWidth,
|
||||
y1: coordinates.y1,
|
||||
};
|
||||
lines[7] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x1,
|
||||
y1: coordinates.y1 - lineHeight,
|
||||
};
|
||||
lines.forEach((item) => {
|
||||
ZQL_multivideo.drawLine(context, item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.values(bbox.lines).length > 0) {
|
||||
Object.values(bbox.lines).forEach((item, i) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
let coordinates = {
|
||||
x: Math.round(
|
||||
(item.line[0][0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y: Math.round(
|
||||
(item.line[0][1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
x1: Math.round(
|
||||
(item.line[1][0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y1: Math.round(
|
||||
(item.line[1][1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
};
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "rgb(" + color.join(",") + ")";
|
||||
if (item.ext.direction) {
|
||||
context.fillText(item.name, (coordinates.x + coordinates.x1) / 2, (coordinates.y + coordinates.y1) / 2 + 20);
|
||||
}
|
||||
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
ZQL_multivideo.drawLine(context, coordinates);
|
||||
ZQL_multivideo.drawCountingInfo(context, Object.values(bbox.lines));
|
||||
});
|
||||
}
|
||||
},
|
||||
drawPolygons(points, context) {
|
||||
context.beginPath();
|
||||
context.moveTo(points[0][0], points[0][1]);
|
||||
|
||||
for (var i = 1; i < points.length; i++) {
|
||||
context.lineTo(points[i][0], points[i][1]);
|
||||
}
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
},
|
||||
drawLine(ctx, line) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(line.x, line.y);
|
||||
ctx.lineTo(line.x1, line.y1);
|
||||
ctx.stroke();
|
||||
},
|
||||
drawCountingInfo(context, lines) {
|
||||
lines.forEach((item, index) => {
|
||||
context.fillStyle = "rgb(255,0,0)";
|
||||
if (item.ext.direction.length == 2) {
|
||||
context.fillText(`[${item.name}] ${item.ext.action.count}: ${item.ext.result.count}`, 0, 20 * index + 20);
|
||||
} else {
|
||||
context.fillText(`[${item.name}] ${item.ext.action.increase}: ${item.ext.result.increase},${item.ext.action.decrease}: ${item.ext.result.decrease},${item.ext.action.delta}: ${item.ext.result.delta}`, 0, 20 * index + 20);
|
||||
}
|
||||
});
|
||||
},
|
||||
drawPolygonInfo(context, polygons, videoindex) {
|
||||
polygons.forEach((item, index) => {
|
||||
context.fillStyle =
|
||||
"rgb(" +
|
||||
JSON.parse(JSON.stringify(item.color)).reverse().join(",") +
|
||||
")";
|
||||
let leftPoint = item.polygon[0];
|
||||
for (let i = 1; i < item.polygon.length; i++) {
|
||||
if (item.polygon[i][0] < leftPoint[0]) {
|
||||
leftPoint = item.polygon[i];
|
||||
}
|
||||
}
|
||||
context.fillText(
|
||||
`${item.name}`,
|
||||
(leftPoint[0] * ZQL_videosInfos[videoindex].actualWidth) /
|
||||
ZQL_videosInfos[videoindex].oriWidth,
|
||||
(leftPoint[1] * ZQL_videosInfos[videoindex].actualHeight) /
|
||||
ZQL_videosInfos[videoindex].oriHeight + 20
|
||||
);
|
||||
if (item.ext.result) {
|
||||
context.fillStyle = "rgb(255,0,0)";
|
||||
context.fillText(`${item.name}: ${item.ext.result}`, 0, 20 * index + 20);
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyVideo(videonum) {
|
||||
for (let i = 0; i < videonum; i++) {
|
||||
ZQL_multivideo.destoryVideoByIndex(i);
|
||||
}
|
||||
},
|
||||
destoryVideoByIndex(index) {
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
if (ZQL_videosInfos[index]) {
|
||||
if (
|
||||
ZQL_videosInfos[index] &&
|
||||
ZQL_videosInfos[index].subscribeTimeout
|
||||
) {
|
||||
clearTimeout(ZQL_videosInfos[index].subscribeTimeout);
|
||||
ZQL_videosInfos[index].subscribeTimeout = null;
|
||||
}
|
||||
if (ZQL_videosInfos[index] && ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
ZQL_videosInfos[index].replayTimer = null;
|
||||
}
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
ZQL_videosInfos[index].refreshTimeInterval = null;
|
||||
}
|
||||
let video = document.getElementById("video" + index);
|
||||
video && (video.srcObject = null);
|
||||
ZQL_videosInfos[index].srsrtc &&
|
||||
ZQL_videosInfos[index].srsrtc.destroy();
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
ZQL_videosInfos[index] = null;
|
||||
}
|
||||
},
|
||||
clearCanvas(index) {
|
||||
let canvas = document.getElementById("canvas" + index);
|
||||
if (canvas && canvas.getContext("2d")) {
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
|
||||
}
|
||||
},
|
||||
connectMqtt() {
|
||||
let mqttclient = mqtt.connect(`ws://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.websocket}/mqtt`);
|
||||
mqttclient.subscribe(
|
||||
ZQLGLOBAL.resultTopic,
|
||||
{ qos: 0 },
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.log("subscribe error:", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
mqttclient.on("message", (topic, payload) => {
|
||||
let msg = JSON.parse(payload.toString());
|
||||
if (msg.msg_type == "result") {
|
||||
let id = msg.data.device.id + "_" + msg.data.source.id;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (ZQL_videosInfos[i]) {
|
||||
let alg =
|
||||
ZQL_videosInfos[i].alg && ZQL_videosInfos[i].alg.algname;
|
||||
|
||||
if (
|
||||
id == ZQL_playingSource[i] &&
|
||||
msg.data.alg.name == ZQL_videosInfos[i].alg
|
||||
) {
|
||||
ZQL_multivideo.setAlarms(msg.data.reserved_data, i);
|
||||
if (
|
||||
ZQL_videosInfos[i] &&
|
||||
ZQL_videosInfos[i].canvasTimeout
|
||||
) {
|
||||
clearTimeout(ZQL_videosInfos[i].canvasTimeout);
|
||||
}
|
||||
ZQL_videosInfos[i].canvasTimeout = setTimeout(() => {
|
||||
ZQL_multivideo.clearCanvas(i);
|
||||
}, 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ZQL_apis = {
|
||||
getDevices: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.getDevices}`,
|
||||
// header: { Authorization: `Bearer ${ZQLGLOBAL.token}`},
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${ZQLGLOBAL.token}`);
|
||||
},
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getSources: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.getSources}`,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${ZQLGLOBAL.token}`);
|
||||
},
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
subscribeLive: (device_id, source_id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.subscribe}?device_id=${device_id}&source_id=${source_id}`,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${ZQLGLOBAL.token}`);
|
||||
},
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
sysArgs: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.sysArgs}`,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${ZQLGLOBAL.token}`);
|
||||
},
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
detectStream: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.srs_http_api}/api/v1/streams?start=0&count=10000`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
detectVideo: (device_id, stream) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.detect}?device_id=${device_id}&stream=${stream}&draw_size=1280`,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${ZQLGLOBAL.token}`);
|
||||
},
|
||||
success: function (res) {
|
||||
if (res.error == 0) {
|
||||
resolve({ status: 1 })
|
||||
} else {
|
||||
resolve({ status: 0 })
|
||||
}
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
gettoken: () => {
|
||||
var ak = ZQLGLOBAL.accessKey;
|
||||
var sk = ZQLGLOBAL.accessSecret;
|
||||
var timestamp = parseInt(new Date().getTime() / 1000);
|
||||
var nonce = ZQL_apis.generateRandomString(10);
|
||||
let signature = ZQL_apis.generateSignature(ak,sk,timestamp, nonce)
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.getToken}?signature=${signature}&ak=${ak}×tamp=${timestamp}&nonce=${nonce}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
generateSignature: (ak,sk,timestamp, nonce) => {
|
||||
var message = `${ak}:${timestamp}:${nonce}`;
|
||||
var hash = CryptoJS.HmacSHA256(message, sk);
|
||||
var signature = CryptoJS.enc.Hex.stringify(hash);
|
||||
return signature
|
||||
},
|
||||
generateRandomString(length) {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
BIN
上位机实时画面/assets/accesskey.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
上位机实时画面/assets/index.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
137
上位机实时画面/index.css
Normal file
@ -0,0 +1,137 @@
|
||||
.main-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#video-container {
|
||||
width: calc(100% - 310px);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#video-container .video-box {
|
||||
background-color: #000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#video-container .video-box .tips {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
#video-container .video-box .tips .deviceoffline{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#video-container .video-box canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#video-container.one-video .video-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box {
|
||||
width: calc(50% - 2px);
|
||||
height: calc(50% - 2px);
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box:nth-child(1) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box:nth-child(1) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.video-box .title-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: rgb(131 186 233 / 45%);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-box .title-container .alg {
|
||||
width: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-box .title-container ul {
|
||||
display: none;
|
||||
background-color: rgb(131 186 233 / 45%);
|
||||
}
|
||||
|
||||
.video-box .title-container:hover ul {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 40px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.video-box .title-container:hover ul li {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* 加载中 */
|
||||
.icon-dot {
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
background-color: #39f;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: ani-spin-bounce 1s 0s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ani-spin-bounce {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-tree-main{
|
||||
position: relative;
|
||||
}
|
||||
68
上位机实时画面/index.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>webrtc</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="js/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="index.css" rel="stylesheet">
|
||||
<script>
|
||||
let ZQLGLOBAL = {
|
||||
token:'',
|
||||
accessKey:'672439f1c3806207f0540f2d', // 修改成你自己的
|
||||
accessSecret:'62960c23-fe35-4449-8780-719ab3c7b9de',// 修改成你自己的
|
||||
serverIp: '192.168.1.88' ,// 修改成你自己的
|
||||
getDevices: `:9192/ks/proxy/device`,
|
||||
getSources: `:9192/ks/proxy/source`,
|
||||
subscribe: `:9192/stream/live/subscribe`,
|
||||
detect: `:9192/stream/attr`,
|
||||
sysArgs: `:9192/ks/proxy/system/args`,
|
||||
getToken:':9192/ks/proxy/user/token',
|
||||
resultTopic: 'ks/proxy/result/+',
|
||||
srs_server: 1935,
|
||||
srs_http_api: 1985,
|
||||
srs_http_server: 8080,
|
||||
websocket: 8083,
|
||||
detectSrsTimer:null
|
||||
}
|
||||
|
||||
let ZQL_videosInfos = { 0: null, 1: null, 2: null, 3: null };
|
||||
let ZQL_playingSource = {
|
||||
0: null, 1: null, 2: null, 3: null,
|
||||
videoNum: 4,
|
||||
curposition: -1
|
||||
}
|
||||
let ZQL_sources = {};
|
||||
</script>
|
||||
<script type="text/javascript" src="js/crypto-js.js"></script>
|
||||
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/jswebrtc.min.js"></script>
|
||||
<script type="text/javascript" src="js/mpegts.js"></script>
|
||||
<script type="text/javascript" src="js/mqtt.min.js"></script>
|
||||
<script type="text/javascript" src="js/layui/layui.js"></script>
|
||||
<script type="text/javascript" src="ZQL_common.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<div class="main-container">
|
||||
<div id="video-container" class="one-video">
|
||||
<div class="video-box">
|
||||
<div class="title-container" id="video-title1"></div>
|
||||
<video ref="video" muted id="video1" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-container">
|
||||
<!-- <div class="btn-container">
|
||||
<i class="z-icon-onevideo" id="icon-oneviveo"></i>
|
||||
<i class="z-icon-fourvideo" id="icon-fourviveo"></i>
|
||||
</div> -->
|
||||
<div id="ZQL_source_tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
118
上位机实时画面/index.js
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
// 设置文档结构
|
||||
ZQL_multivideo.setVideoEl();
|
||||
// 设置事件监听
|
||||
// document.querySelector("#icon-oneviveo").addEventListener('click', () => {
|
||||
// playingSource.videoNum = 1;
|
||||
// setVideoEl();
|
||||
// })
|
||||
// document.querySelector("#icon-fourviveo").addEventListener('click', () => {
|
||||
// playingSource.videoNum = 4;
|
||||
// ZQL_multivideo.setVideoEl();
|
||||
// })
|
||||
|
||||
ZQL_apis.gettoken().then(res => {
|
||||
if(res.error_code == 0){
|
||||
ZQLGLOBAL.token = res.data;
|
||||
init();
|
||||
}
|
||||
})
|
||||
|
||||
// 1. 获取设备列表,摄像头列表,组成树形结构
|
||||
function init() {
|
||||
Promise.all([ZQL_apis.getDevices(), ZQL_apis.getSources()]).then(res => {
|
||||
let devices = res[0].data;
|
||||
for (let deviceId in res[1].data) {
|
||||
for (let sourceId in res[1].data[deviceId]) {
|
||||
res[1].data[deviceId][sourceId].sourceId = sourceId
|
||||
res[1].data[deviceId][sourceId].deviceId = deviceId
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
devices[i].deviceId = devices[i].id;
|
||||
devices[i].title = devices[i].name;
|
||||
devices[i].type = 'device';
|
||||
if (res[1].data[devices[i].id]) {
|
||||
devices[i].children = Object.values(res[1].data[devices[i].id]).map(item => {
|
||||
item.id = devices[i].deviceId + '_' + item.sourceId;
|
||||
item.title = item.desc;
|
||||
item.type = 'source';
|
||||
item.checked = false;
|
||||
ZQL_sources[devices[i].deviceId + '_' + item.sourceId] = item
|
||||
return item
|
||||
})
|
||||
} else {
|
||||
// 没有摄像头的设备不显示
|
||||
devices.splice(i, 1)
|
||||
i = i - 1
|
||||
}
|
||||
}
|
||||
layui.use(function () {
|
||||
var tree = layui.tree;
|
||||
var layer = layui.layer;
|
||||
tree.render({
|
||||
elem: '#ZQL_source_tree',
|
||||
data: devices,
|
||||
// showCheckbox:true,
|
||||
onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩
|
||||
click: function (obj) {
|
||||
if (obj.data.sourceId) {
|
||||
let key = obj.data.deviceId + '_' + obj.data.sourceId;
|
||||
if (ZQL_sources[key].checked == false) {
|
||||
ZQL_sources[key].checked = true
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
ZQL_playingSource[0] = key;
|
||||
ZQL_multivideo.subscribeLive(key, 0);
|
||||
ZQL_multivideo.setAlgList(0);
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (!ZQL_playingSource[i]) {
|
||||
ZQL_playingSource[i] = key;
|
||||
ZQL_multivideo.subscribeLive(key, i);
|
||||
ZQL_multivideo.setAlgList(i)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZQL_sources[key].checked = false
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
ZQL_playingSource[0] = null;
|
||||
ZQL_multivideo.destoryVideoByIndex(0);
|
||||
ZQL_multivideo.clearAlgList(0);
|
||||
ZQL_multivideo.liveStopLoading(0);
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (ZQL_playingSource[i] == key) {
|
||||
ZQL_playingSource[i] = null;
|
||||
ZQL_multivideo.destoryVideoByIndex(i);
|
||||
ZQL_multivideo.clearAlgList(i)
|
||||
ZQL_multivideo.liveStopLoading(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
// 获取系统参数,检测srs、连接mqtt
|
||||
ZQL_apis.sysArgs().then(res => {
|
||||
if (res.error_code == 0) {
|
||||
let map = res.data.map;
|
||||
ZQLGLOBAL = Object.assign(ZQLGLOBAL, map)
|
||||
}
|
||||
ZQL_multivideo.detectSrs();
|
||||
ZQL_multivideo.connectMqtt()
|
||||
}).catch(err => { })
|
||||
}
|
||||
|
||||
|
||||
|
||||
//遗留
|
||||
// 1.刷新
|
||||
// 2.保存已选择的摄像头,
|
||||
// 3.切换一分屏四分屏
|
||||
5988
上位机实时画面/js/crypto-js.js
Normal file
6
上位机实时画面/js/jquery-1.10.2.min.js
vendored
Normal file
1
上位机实时画面/js/jswebrtc.min.js
vendored
Normal file
1
上位机实时画面/js/layui/css/layui.css
Normal file
BIN
上位机实时画面/js/layui/font/iconfont.eot
Normal file
405
上位机实时画面/js/layui/font/iconfont.svg
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
上位机实时画面/js/layui/font/iconfont.ttf
Normal file
BIN
上位机实时画面/js/layui/font/iconfont.woff
Normal file
BIN
上位机实时画面/js/layui/font/iconfont.woff2
Normal file
1
上位机实时画面/js/layui/layui.js
Normal file
9
上位机实时画面/js/mpegts.js
Normal file
12
上位机实时画面/js/mqtt.min.js
vendored
Normal file
59
心跳保活/UdpServer.java
Normal file
@ -0,0 +1,59 @@
|
||||
package com.github.paicoding.forum.web.front.test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramPacket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
||||
/**
|
||||
* UDP 服务器
|
||||
* 本类使用单线程的线程池实现一个简单的UDP服务器,用于接收客户端发送的心跳信息。
|
||||
* 实现方法多种多样,需要根据具体的业务场景进行选择。
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UdpServer {
|
||||
|
||||
|
||||
private static final int PORT = 10002;
|
||||
private volatile boolean isRunning = true; // 标志变量
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@PostConstruct
|
||||
public void startServer() {
|
||||
executor.submit(this::listenUdpPackets);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stopServer() {
|
||||
isRunning = false;
|
||||
executor.shutdown();
|
||||
log.info("UDP服务器已停止");
|
||||
}
|
||||
|
||||
|
||||
private void listenUdpPackets() {
|
||||
try (DatagramSocket socket = new DatagramSocket(PORT)) {
|
||||
log.info("UDP Server started on port {}", PORT);
|
||||
byte[] buffer = new byte[102400];
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
while (isRunning) {
|
||||
socket.receive(packet);
|
||||
String message = new String(packet.getData(), 0, packet.getLength()).trim();
|
||||
log.info("❤️ Heartbeat from: {} ", packet.getAddress());
|
||||
log.info("📦 Data received: {}", message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("UDP Server error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
心跳保活/server.py
Normal file
@ -0,0 +1,38 @@
|
||||
import json
|
||||
import socket
|
||||
import traceback
|
||||
|
||||
|
||||
class UdpServer:
|
||||
def __init__(self):
|
||||
self.host = '0.0.0.0'
|
||||
self.port = 10002
|
||||
self.socket_server = self.__init()
|
||||
|
||||
def __init(self):
|
||||
socket_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
socket_server.bind((self.host, self.port))
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
finally:
|
||||
return socket_server
|
||||
|
||||
def recv(self, buff_size=102400):
|
||||
while True:
|
||||
try:
|
||||
data, addr = self.socket_server.recvfrom(buff_size)
|
||||
data = json.loads(data.decode('utf-8'))
|
||||
print('Received message: {}, from: {}'.format(data, addr))
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
def main():
|
||||
udp_server = UdpServer()
|
||||
udp_server.recv()
|
||||
return True
|
||||
|
||||
|
||||
if '__main__' == __name__:
|
||||
main()
|
||||
171
快速接收设备告警/custom-api-demo/README.md
Normal file
@ -0,0 +1,171 @@
|
||||
## 一、功能概述
|
||||
|
||||
`Api` 类是一个用于处理检测结果、告警信息和告警视频的工具类。
|
||||
|
||||
[api_demo.py](api_demo.py) 为原始默认实现。
|
||||
|
||||
[api_demo_tcp.py](api_demo_tcp.py)实现最简单的tcp通讯,如果需要自动重连,队列等业务代码请自行实现。
|
||||
|
||||
|
||||
此类名、方法名等框架是固定的,不可修改。你可以通过实现回调方法的具体逻辑(如 `send_result_callback`、`send_alert_callback` 和 `send_alert_video_callback`)以及配置类的属性(如 `ignore_result`、`ignore_alert` 等)来实现具体功能。
|
||||
|
||||
## 二、类的属性
|
||||
|
||||
`Api` 类**必须**包含以下主要属性,用于控制其行为:
|
||||
|
||||
| 属性名称 | 默认值 | 描述 |
|
||||
| :------------------- | :----- | :----------------------------------------------------------- |
|
||||
| `ignore_result` | `True` | 是否发送检测结果。`True` 表示不发送,`False` 表示发送。 |
|
||||
| `ignore_alert` | `True` | 是否发送告警信息。`True` 表示不发送,`False` 表示发送。 |
|
||||
| `draw_image` | `True` | 是否在告警图片上绘制告警信息。`True` 表示绘制,`False` 表示不绘制。 |
|
||||
| `ignore_alert_video` | `True` | 是否发送告警视频。`True` 表示不发送,`False` 表示发送。 |
|
||||
|
||||
## 三、类的方法
|
||||
|
||||
`Api` 类**必须**包含以下三个方法,用于发送检测结果、告警信息和告警视频。可根据需求实现具体的回调方法。
|
||||
|
||||
### 1. 检测结果
|
||||
|
||||
- **方法**:send_result_callback(self, result):
|
||||
|
||||
- **功能**:发送检测结果。
|
||||
|
||||
- **参数**:
|
||||
|
||||
- `result`:检测结果的内容。具体格式和内容如下:
|
||||
|
||||
- **示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"hit": false, //是否命中
|
||||
"time": 1742458167.288579, //告警时间戳
|
||||
"device": {
|
||||
"id": "设备id",
|
||||
"name": "设备名称",
|
||||
"desc": "设备描述"
|
||||
},
|
||||
"source": {
|
||||
"id": "数据源id",
|
||||
"ipv4": "ip地址",
|
||||
"desc": "数据源描述"
|
||||
},
|
||||
"alg": {
|
||||
"name": "算法名称英文",
|
||||
"ch_name": "算法名称中文",
|
||||
"type": "general"
|
||||
},
|
||||
"reserved_data": {
|
||||
"bbox": {
|
||||
"rectangles": [
|
||||
{
|
||||
"xyxy": [668,562,790,656], //左上角、右下角坐标
|
||||
"color": [0,0,255], //BGR颜色
|
||||
"label": "未佩戴安全帽", //标签
|
||||
"conf": 0.91, //置信度
|
||||
"ext": {} //扩展字段
|
||||
}
|
||||
],
|
||||
"polygons": {}, //多边形对象
|
||||
"lines": {} //线段对象
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"hazard_level": "", //危险等级
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 告警信息
|
||||
|
||||
- **方法**:send_alert_callback(self, alert)
|
||||
|
||||
- **功能**:发送告警信息。
|
||||
|
||||
- **参数**:
|
||||
|
||||
- `alert`:告警信息的内容。具体格式和内容如下:
|
||||
|
||||
- **示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "67dbcd3c5dc58a7aaa019e41", //告警id
|
||||
"alert_time": 1742458171.808598, //告警时间戳
|
||||
"device": {
|
||||
"id": "设备id",
|
||||
"name": "设备名称",
|
||||
"desc": "设备描述"
|
||||
},
|
||||
"source": {
|
||||
"id": "数据源id",
|
||||
"ipv4": "ip地址",
|
||||
"desc": "数据源描述"
|
||||
},
|
||||
"alg": {
|
||||
"name": "算法名称英文",
|
||||
"ch_name": "算法名称中文",
|
||||
"type": "general"
|
||||
},
|
||||
"hazard_level": "", //危险等级
|
||||
"image": "img_base64", //base64编码的图片数据
|
||||
"reserved_data": {
|
||||
"bbox": {
|
||||
"rectangles": [
|
||||
{
|
||||
"xyxy": [668,560,790,656], //左上角、右下角坐标
|
||||
"color": [0,0,255], //BGR颜色
|
||||
"label": "未佩戴安全帽", //标签
|
||||
"conf": 0.91, //置信度
|
||||
"ext": {} //扩展字段
|
||||
}
|
||||
],
|
||||
"polygons": {},//多边形对象
|
||||
"lines": {} //线段对象
|
||||
},
|
||||
"custom": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 告警视频
|
||||
|
||||
- **方法**:send_alert_video_callback(self, alert_video):
|
||||
|
||||
- **功能**:发送告警视频。
|
||||
|
||||
- **参数**:
|
||||
|
||||
- `alert_video`:告警视频的内容。具体格式和内容如下:
|
||||
|
||||
- **示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "67dbcd3c5dc58a7aaa019e41", //告警id
|
||||
"alert_time": 1742458171.808598, //告警时间戳
|
||||
"device": {
|
||||
"id": "设备id",
|
||||
"name": "设备名称",
|
||||
"desc": "设备描述"
|
||||
},
|
||||
"source": {
|
||||
"id": "数据源id",
|
||||
"ipv4": "ip地址",
|
||||
"desc": "数据源描述"
|
||||
},
|
||||
"alg": {
|
||||
"name": "算法名称英文",
|
||||
"ch_name": "算法名称中文",
|
||||
"type": "general"
|
||||
},
|
||||
"hazard_level": "", //危险等级
|
||||
"video": "video_base64" //base64编码的视频数据
|
||||
}
|
||||
```
|
||||
|
||||
## 四、注意事项
|
||||
|
||||
1. **属性配置**:在调用发送方法之前,确保已经正确配置了类的属性,以启用或禁用所需的功能。
|
||||
2. **方法实现**:默认情况下,`send_result_callback`、`send_alert_callback` 和 `send_alert_video_callback` 方法是空方法。在实际使用中,需要根据具体需求实现这些方法的逻辑,例如将数据发送到服务器。
|
||||
|
||||
|
||||
40
快速接收设备告警/custom-api-demo/api_demo.py
Normal file
@ -0,0 +1,40 @@
|
||||
class Api:
|
||||
def __init__(self):
|
||||
"""
|
||||
Attributes:
|
||||
self.ignore_result: 为True时,不发送检测结果,为False则发送检测结果
|
||||
self.ignore_alert: 为True时,不发送告警信息,为False则发送告警信息
|
||||
self.draw_image: 为True时,告警图片会画上告警信息,为False则不画
|
||||
self.ignore_alert_video: 为True时,不发送告警视频,为False则发送
|
||||
"""
|
||||
self.ignore_result = True
|
||||
self.ignore_alert = True
|
||||
self.draw_image = True
|
||||
self.ignore_alert_video = True
|
||||
|
||||
def send_result_callback(self, result):
|
||||
"""
|
||||
发送检测结果回调函数
|
||||
Args:
|
||||
result: 检测结果数据
|
||||
Returns:
|
||||
"""
|
||||
pass
|
||||
|
||||
def send_alert_callback(self, alert):
|
||||
"""
|
||||
发送告警信息回调函数
|
||||
Args:
|
||||
alert: 告警数据
|
||||
Returns:
|
||||
"""
|
||||
pass
|
||||
|
||||
def send_alert_video_callback(self, alert_video):
|
||||
"""
|
||||
发送告警视频回调函数
|
||||
Args:
|
||||
alert_video: 告警视频数据
|
||||
Returns:
|
||||
"""
|
||||
pass
|
||||
68
快速接收设备告警/custom-api-demo/api_demo_tcp.py
Normal file
@ -0,0 +1,68 @@
|
||||
import json
|
||||
import socket
|
||||
# 导入日志
|
||||
from logger import LOGGER
|
||||
|
||||
|
||||
class Api:
|
||||
"""
|
||||
API类,实现最简单的tcp通讯,如果需要自动重连,队列等业务代码请自行实现
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Attributes:
|
||||
self.ignore_result: 为True时,不发送检测结果,为False则发送检测结果
|
||||
self.ignore_alert: 为True时,不发送告警信息,为False则发送告警信息
|
||||
self.draw_image: 为True时,告警图片会画上告警信息,为False则不画
|
||||
self.ignore_alert_video: 为True时,不发送告警视频,为False则发送
|
||||
"""
|
||||
self.ignore_result = True
|
||||
self.ignore_alert = False
|
||||
self.draw_image = True
|
||||
self.ignore_alert_video = True
|
||||
|
||||
def send_result_callback(self, result):
|
||||
"""
|
||||
发送检测结果回调函数
|
||||
Args:
|
||||
result: 检测结果数据
|
||||
Returns:
|
||||
"""
|
||||
pass
|
||||
|
||||
def send_alert_callback(self, alert):
|
||||
"""
|
||||
发送告警信息回调函数
|
||||
Args:
|
||||
alert: 告警数据
|
||||
Returns:
|
||||
"""
|
||||
|
||||
try:
|
||||
LOGGER.info('发送TCP告警')
|
||||
# 创建TCP socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# 连接到目标服务器(这里用示例地址和端口)
|
||||
s.connect(('192.168.0.4', 10001))
|
||||
|
||||
# 为了方便查看日志,去掉base64编码图片
|
||||
alert.pop('image')
|
||||
|
||||
json_data = json.dumps(alert, ensure_ascii=False)
|
||||
data = json_data.encode('utf-8')
|
||||
|
||||
# 发送完整数据
|
||||
s.sendall(data)
|
||||
|
||||
LOGGER.info(f'告警已发送至TCP服务器: {data}')
|
||||
except Exception as e:
|
||||
LOGGER.error(f'发送TCP告警失败: {str(e)}')
|
||||
|
||||
def send_alert_video_callback(self, alert_video):
|
||||
"""
|
||||
发送告警视频回调函数
|
||||
Args:
|
||||
alert_video: 告警视频数据
|
||||
Returns:
|
||||
"""
|
||||
pass
|
||||
83
快速接收设备告警/http-server-demo/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
# HTTP告警推送
|
||||
|
||||
> **http-server-demo** 分为三个文件夹。
|
||||
>
|
||||
> 1. **headers** :http请求头的demo代码
|
||||
> 2. **http-server** :http服务端,接收告警推送(无token版本)
|
||||
> 3. **http-server-token** :http服务端,接收告警推送(有token版本)
|
||||
|
||||
## headers
|
||||
|
||||
如果需要将盒子产生的告警推送到您自建平台,并且你的平台需要验证**token**,则需要用到该文件夹下的`headers_demo.py`文件。
|
||||
你可自行修改`headers_demo.py`文件,并将此文件上传到盒子平台的【数据推送】-【告警】-【HTTP】-【配置token】。
|
||||
`headers_demo.py`文件说明:
|
||||
|
||||
- 类名必须为`Headers`,继承`BaseHeaders`类。`BaseHeaders`类通过`api.http`导入
|
||||
|
||||
```python
|
||||
from api.http import Headers as BaseHeaders
|
||||
```
|
||||
|
||||
- 定义三个实例变量:`get_headers_url`、`timeout`、`interval`。
|
||||
|
||||
`get_headers_url`:指定获取`token`的**URL**地址。
|
||||
|
||||
`timeout`:指定获取`token`的超时时间(单位:秒)。
|
||||
|
||||
`interval`:定时刷新`headers`的时间间隔(单位:秒)。
|
||||
|
||||
```python
|
||||
class Headers(BaseHeaders):
|
||||
def __init__(self):
|
||||
self.get_headers_url = None
|
||||
self.timeout = 5
|
||||
interval = 60 * 10
|
||||
super().__init__(interval)
|
||||
```
|
||||
|
||||
- 必须实现`_generate_headers`方法。返回请求头字典`headers`。返回示例:
|
||||
|
||||
|
||||
```python
|
||||
{'authorization': 'Bearer abcdefghijklmnopqrstuvwxyz'}
|
||||
```
|
||||
|
||||
|
||||
- 完整实例如下:
|
||||
|
||||
```python
|
||||
import requests
|
||||
from api.http import Headers as BaseHeaders
|
||||
from logger import LOGGER
|
||||
|
||||
class Headers(BaseHeaders):
|
||||
def __init__(self):
|
||||
self.get_headers_url = None
|
||||
self.timeout = 5
|
||||
interval = 60 * 10
|
||||
super().__init__(interval)
|
||||
|
||||
def _generate_headers(self):
|
||||
try:
|
||||
headers = {
|
||||
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkXXX'
|
||||
}
|
||||
return headers
|
||||
except:
|
||||
LOGGER.exception('_generate_headers')
|
||||
return None
|
||||
```
|
||||
|
||||
## http-server
|
||||
|
||||
该文件夹为**http**服务端代码,提供**python**和**java**代码。它主要用于接收盒子的**http**告警推送。如果您需要验证盒子的**http**推送功能是否正常,可使用此文件夹进行测试。
|
||||
运行该文件夹下的代码,即可开启一个**http**服务端。在盒子平台的【数据推送】-【告警】-【HTTP】中启用推送管理,并填写**http**服务端地址,即可开启推送功能。
|
||||
|
||||
|
||||
## http-server-token
|
||||
|
||||
此文件夹同**http-server**文件夹,只是增加了**token**验证功能。
|
||||
|
||||
你首先需把其下的`headers_demo1.py`或`headers_demo2.py`文件上传到盒子平台的【数据推送】-【告警】-【HTTP】-【配置TOKEN】。
|
||||
`headers_demo1.py`:通过调用url接口获取token。(盒子必须可以ping通该url,文件中的`get_headers_url`变量为**http**服务端URL)
|
||||
`headers_demo2.py`:固定token。
|
||||
46
快速接收设备告警/http-server-demo/headers/headers_demo.py
Normal file
@ -0,0 +1,46 @@
|
||||
import requests
|
||||
|
||||
from api.http import Headers as BaseHeaders
|
||||
from logger import LOGGER
|
||||
|
||||
|
||||
class Headers(BaseHeaders):
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化Headers类
|
||||
- `self.get_headers_url`: 获取token的URL地址,根据实际环境修改。
|
||||
- `self.timeout`: 请求超时时间,设置为5秒。
|
||||
- `interval`: 定时刷新headers的时间间隔,设置为10分钟(60秒 * 10)。
|
||||
"""
|
||||
self.get_headers_url = None
|
||||
self.timeout = 5
|
||||
interval = 60 * 10
|
||||
super().__init__(interval)
|
||||
|
||||
def _generate_headers(self):
|
||||
"""
|
||||
生成请求头的方法,_generate_headers方法名不允许修改
|
||||
通过向指定的URL发送GET请求获取token,并将token添加到请求头中
|
||||
:return: 请求头字典
|
||||
"""
|
||||
try:
|
||||
# 定义请求参数
|
||||
params = {
|
||||
'arg1': 'xxx',
|
||||
'arg2': 'xxx'
|
||||
}
|
||||
if self.get_headers_url is not None:
|
||||
# 发送GET请求获取token
|
||||
resp = requests.get(self.get_headers_url, params=params, timeout=self.timeout)
|
||||
if resp.status_code == 200:
|
||||
token = resp.text
|
||||
headers = {
|
||||
'authorization': 'Bearer {}'.format(token)
|
||||
}
|
||||
return headers
|
||||
else:
|
||||
LOGGER.error('Get headers failed')
|
||||
return None
|
||||
except:
|
||||
LOGGER.exception('_generate_headers')
|
||||
return None
|
||||
47
快速接收设备告警/http-server-demo/http-server-token/headers_demo1.py
Normal file
@ -0,0 +1,47 @@
|
||||
import requests
|
||||
|
||||
from api.http import Headers as BaseHeaders
|
||||
from logger import LOGGER
|
||||
|
||||
|
||||
class Headers(BaseHeaders):
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化Headers类
|
||||
- `self.get_headers_url`: 获取token的URL地址,根据实际环境修改。
|
||||
- `self.timeout`: 请求超时时间,设置为5秒。
|
||||
- `interval`: 定时刷新headers的时间间隔,设置为10分钟(60秒 * 10)。
|
||||
"""
|
||||
self.get_headers_url = 'http://192.168.1.75:10000/token'
|
||||
self.timeout = 5
|
||||
interval = 60 * 10
|
||||
super().__init__(interval)
|
||||
|
||||
def _generate_headers(self):
|
||||
"""
|
||||
生成请求头的方法,_generate_headers方法名不允许修改
|
||||
通过向指定的URL发送GET请求获取token,并将token添加到请求头中
|
||||
:return: 请求头字典
|
||||
"""
|
||||
try:
|
||||
# 定义请求参数
|
||||
params = {
|
||||
'arg1': 'xxx',
|
||||
'arg2': 'xxx'
|
||||
}
|
||||
if self.get_headers_url is not None:
|
||||
# 发送GET请求获取token
|
||||
resp = requests.get(self.get_headers_url, params=params, timeout=self.timeout)
|
||||
LOGGER.info('Get headers resp {}'.format(resp))
|
||||
if resp.status_code == 200:
|
||||
token = resp.text
|
||||
headers = {
|
||||
'authorization': 'Bearer {}'.format(token)
|
||||
}
|
||||
return headers
|
||||
else:
|
||||
LOGGER.error('Get headers failed')
|
||||
return None
|
||||
except:
|
||||
LOGGER.exception('_generate_headers')
|
||||
return None
|
||||
35
快速接收设备告警/http-server-demo/http-server-token/headers_demo2.py
Normal file
@ -0,0 +1,35 @@
|
||||
import requests
|
||||
|
||||
from api.http import Headers as BaseHeaders
|
||||
from logger import LOGGER
|
||||
|
||||
|
||||
class Headers(BaseHeaders):
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化Headers类
|
||||
- `self.get_headers_url`: 获取token的URL地址,根据实际环境修改。
|
||||
- `self.timeout`: 请求超时时间,设置为5秒。
|
||||
- `interval`: 定时刷新headers的时间间隔,设置为10分钟(60秒 * 10)。
|
||||
"""
|
||||
self.get_headers_url = None
|
||||
self.timeout = 5
|
||||
interval = 60 * 10
|
||||
super().__init__(interval)
|
||||
|
||||
def _generate_headers(self):
|
||||
"""
|
||||
生成请求头的方法,_generate_headers方法名不允许修改
|
||||
固定http请求头,authorization以及值可自定义
|
||||
:return: 请求头字典
|
||||
"""
|
||||
try:
|
||||
|
||||
headers = {
|
||||
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkXXX'
|
||||
}
|
||||
return headers
|
||||
|
||||
except:
|
||||
LOGGER.exception('_generate_headers')
|
||||
return None
|
||||
@ -0,0 +1,45 @@
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(path = "")
|
||||
public class AlertController {
|
||||
|
||||
|
||||
/**
|
||||
* 目标平台接收告警及告警图片
|
||||
* @param alertMsg
|
||||
* @param authorization
|
||||
*/
|
||||
|
||||
@PostMapping(path = "/alert")
|
||||
public void getAlertMsg(@RequestBody AlertMsg alertMsg, @RequestHeader("Authorization") String authorization) {
|
||||
log.info("authorization:{}", authorization);
|
||||
log.info("示例接收告警及告警图片:{}", alertMsg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 目标平台接收告警及告警视频
|
||||
*
|
||||
* @param alertVideo
|
||||
*/
|
||||
|
||||
@PostMapping(path = "/video")
|
||||
public void getAlertVideo(@RequestBody AlertVideo alertVideo,@RequestHeader("Authorization") String authorization) {
|
||||
log.info("authorization:{}", authorization);
|
||||
log.info("示例接收告警及告警视频:{}", alertVideo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping(path = "/token")
|
||||
public String token() {
|
||||
return RandomUtil.randomString(16);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class AlertMsg {
|
||||
private String id;
|
||||
@JsonProperty("alert_time")
|
||||
private Double alertTime;
|
||||
private Object device;
|
||||
private Object source;
|
||||
private Object alg;
|
||||
private String image;
|
||||
@JsonProperty("reserved_data")
|
||||
private Object reservedData;
|
||||
@JsonProperty("hazard_leve")
|
||||
private String hazardLeve;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class AlertVideo {
|
||||
private String id;
|
||||
@JsonProperty("alert_time")
|
||||
private Double alertTime;
|
||||
private Object device;
|
||||
private Object source;
|
||||
private Object alg;
|
||||
private String video;
|
||||
@JsonProperty("hazard_leve")
|
||||
private String hazardLeve;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from flask import Flask
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(CURRENT_PATH)
|
||||
|
||||
url_prefix = '/'
|
||||
|
||||
from app import alert
|
||||
|
||||
|
||||
def create_app():
|
||||
# 初始化Flask对象
|
||||
app_ = Flask(__name__)
|
||||
|
||||
# 注册蓝图
|
||||
app_.register_blueprint(alert.bp)
|
||||
return app_
|
||||
|
||||
|
||||
app = create_app()
|
||||
@ -0,0 +1,31 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import secrets
|
||||
import time
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from app import url_prefix
|
||||
|
||||
bp = Blueprint('alert', __name__, url_prefix=url_prefix)
|
||||
|
||||
|
||||
@bp.route('alert', methods=['POST'])
|
||||
def post_alert():
|
||||
# 获取token
|
||||
auth_header = request.headers.get('authorization')
|
||||
print(f"Authorization Header: {auth_header}")
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
image = data.pop('image')
|
||||
print(data)
|
||||
with open('image.jpg', 'wb') as f:
|
||||
f.write(base64.b64decode(image.encode('utf-8')))
|
||||
return data
|
||||
|
||||
|
||||
@bp.route('/token', methods=['GET'])
|
||||
def gen_token():
|
||||
print(request.args)
|
||||
token = secrets.token_hex(32)
|
||||
return token
|
||||
BIN
快速接收设备告警/http-server-demo/http-server-token/python/image.jpg
Normal file
|
After Width: | Height: | Size: 111 KiB |
@ -0,0 +1,4 @@
|
||||
from app import app
|
||||
|
||||
if '__main__' == __name__:
|
||||
app.run(host='0.0.0.0', port=10000, debug=False)
|
||||
@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(path = "")
|
||||
public class AlertController {
|
||||
|
||||
|
||||
/**
|
||||
* 目标平台接收告警及告警图片
|
||||
*
|
||||
* @param alertMsg
|
||||
*/
|
||||
|
||||
@PostMapping(path = "/alert")
|
||||
public void getAlertMsg(@RequestBody AlertMsg alertMsg) {
|
||||
log.info("示例接收告警及告警图片:{}", alertMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标平台接收告警及告警视频
|
||||
*
|
||||
* @param alertVideo
|
||||
*/
|
||||
|
||||
@PostMapping(path = "/video")
|
||||
public void getAlertVideo(@RequestBody AlertVideo alertVideo) {
|
||||
log.info("示例接收告警及告警视频:{}", alertVideo);
|
||||
}
|
||||
|
||||
}
|
||||
21
快速接收设备告警/http-server-demo/http-server/java/AlertMsg.java
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class AlertMsg {
|
||||
private String id;
|
||||
@JsonProperty("alert_time")
|
||||
private Double alertTime;
|
||||
private Object device;
|
||||
private Object source;
|
||||
private Object alg;
|
||||
private String image;
|
||||
@JsonProperty("reserved_data")
|
||||
private Object reservedData;
|
||||
@JsonProperty("hazard_leve")
|
||||
private String hazardLeve;
|
||||
}
|
||||
19
快速接收设备告警/http-server-demo/http-server/java/AlertVideo.java
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class AlertVideo {
|
||||
private String id;
|
||||
@JsonProperty("alert_time")
|
||||
private Double alertTime;
|
||||
private Object device;
|
||||
private Object source;
|
||||
private Object alg;
|
||||
private String video;
|
||||
@JsonProperty("hazard_leve")
|
||||
private String hazardLeve;
|
||||
}
|
||||
23
快速接收设备告警/http-server-demo/http-server/python/app/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from flask import Flask
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(CURRENT_PATH)
|
||||
|
||||
url_prefix = '/'
|
||||
|
||||
from app import alert
|
||||
|
||||
|
||||
def create_app():
|
||||
# 初始化Flask对象
|
||||
app_ = Flask(__name__)
|
||||
|
||||
# 注册蓝图
|
||||
app_.register_blueprint(alert.bp)
|
||||
return app_
|
||||
|
||||
|
||||
app = create_app()
|
||||
28
快速接收设备告警/http-server-demo/http-server/python/app/alert.py
Normal file
@ -0,0 +1,28 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from app import url_prefix
|
||||
|
||||
bp = Blueprint('alert', __name__, url_prefix=url_prefix)
|
||||
|
||||
|
||||
@bp.route('alert', methods=['POST'])
|
||||
def post_alert():
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
image = data.pop('image')
|
||||
print(data)
|
||||
with open('image.jpg', 'wb') as f:
|
||||
f.write(base64.b64decode(image.encode('utf-8')))
|
||||
return data
|
||||
|
||||
|
||||
@bp.route('alert/video', methods=['POST'])
|
||||
def post_alert_video():
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
video = data.pop('video')
|
||||
print(data)
|
||||
with open('video.mp4', 'wb') as f:
|
||||
f.write(base64.b64decode(video.encode('utf-8')))
|
||||
return data
|
||||
BIN
快速接收设备告警/http-server-demo/http-server/python/image.jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
4
快速接收设备告警/http-server-demo/http-server/python/main.py
Normal file
@ -0,0 +1,4 @@
|
||||
from app import app
|
||||
|
||||
if '__main__' == __name__:
|
||||
app.run(host='0.0.0.0', port=10000, debug=False)
|
||||
107
快速接收设备告警/tcp-server-demo/server.py
Normal file
@ -0,0 +1,107 @@
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
class SocketServer:
|
||||
def __init__(self):
|
||||
self.server_host = '0.0.0.0'
|
||||
self.server_port = 10001
|
||||
self.socket_server = self.__listen()
|
||||
self.conns = {}
|
||||
self.__accept()
|
||||
|
||||
@staticmethod
|
||||
def __set_reuse_addr(socket_obj):
|
||||
"""
|
||||
断开连接之后立马释放本地端口
|
||||
Args:
|
||||
socket_obj: socket对象
|
||||
Returns: True or False
|
||||
"""
|
||||
socket_obj.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
return True
|
||||
|
||||
def __listen(self):
|
||||
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
self.__set_reuse_addr(socket_server)
|
||||
# 绑定IP/端口
|
||||
socket_server.bind((self.server_host, self.server_port))
|
||||
# 最多同时处理5个连接请求
|
||||
socket_server.listen(5)
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
finally:
|
||||
return socket_server
|
||||
|
||||
def __disconnect(self, addr):
|
||||
print('Disconnected, client={}'.format(addr))
|
||||
self.__close(self.conns[addr])
|
||||
self.conns.pop(addr)
|
||||
return True
|
||||
|
||||
def __accept(self):
|
||||
def accept():
|
||||
while True:
|
||||
try:
|
||||
conn, addr = self.socket_server.accept()
|
||||
print('Connection established, client={}'.format(addr))
|
||||
self.__set_reuse_addr(conn)
|
||||
self.conns[addr] = conn
|
||||
threading.Thread(target=self.__recv, args=(addr, conn), daemon=True).start()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
|
||||
threading.Thread(target=accept, daemon=True).start()
|
||||
return True
|
||||
|
||||
def __recv(self, addr, client_socket, buff_size=1024):
|
||||
while True:
|
||||
try:
|
||||
data_length = client_socket.recv(4)
|
||||
# 读取data_length
|
||||
if data_length:
|
||||
data_length = struct.unpack('i', data_length)[0]
|
||||
print('Recv from: {}, data_length: {}'.format(addr, data_length))
|
||||
# 读取data
|
||||
if data_length <= buff_size:
|
||||
data = client_socket.recv(data_length)
|
||||
else:
|
||||
buff_size_ = buff_size
|
||||
# 已接收到的size
|
||||
total_recv_size = 0
|
||||
data = b''
|
||||
while total_recv_size < data_length:
|
||||
recv_data = client_socket.recv(buff_size_)
|
||||
data += recv_data
|
||||
total_recv_size += len(recv_data)
|
||||
left_size = data_length - total_recv_size
|
||||
if left_size < buff_size:
|
||||
buff_size_ = left_size
|
||||
data = json.loads(data.decode('utf-8'))
|
||||
print('Recv from: {}, data: {}'.format(addr, data))
|
||||
else:
|
||||
break
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
break
|
||||
self.__disconnect(addr)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def __close(socket_obj):
|
||||
try:
|
||||
socket_obj.close()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
return True
|
||||
|
||||
|
||||
if '__main__' == __name__:
|
||||
tcp_server = SocketServer()
|
||||
while True:
|
||||
time.sleep(3)
|
||||
23
接收大模型复审告警/app/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from flask import Flask
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(CURRENT_PATH)
|
||||
|
||||
url_prefix = '/'
|
||||
|
||||
from app import vlreview
|
||||
|
||||
|
||||
def create_app():
|
||||
# 初始化Flask对象
|
||||
app_ = Flask(__name__)
|
||||
|
||||
# 注册蓝图
|
||||
app_.register_blueprint(vlreview.bp)
|
||||
return app_
|
||||
|
||||
|
||||
app = create_app()
|
||||
15
接收大模型复审告警/app/vlreview.py
Normal file
@ -0,0 +1,15 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from app import url_prefix
|
||||
|
||||
bp = Blueprint('vlreview', __name__, url_prefix=url_prefix)
|
||||
|
||||
|
||||
@bp.route('vlreview', methods=['POST'])
|
||||
def post_alert():
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
print(data)
|
||||
return data
|
||||
4
接收大模型复审告警/main.py
Normal file
@ -0,0 +1,4 @@
|
||||
from app import app
|
||||
|
||||
if '__main__' == __name__:
|
||||
app.run(host='0.0.0.0', port=10010, debug=False)
|
||||
BIN
深度对接设备接口/assets/device.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
深度对接设备接口/assets/facelib.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
深度对接设备接口/assets/live.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
深度对接设备接口/assets/source.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
65
深度对接设备接口/device.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>设备信息</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/layui@2.8.18/dist/css/layui.css">
|
||||
<link rel="stylesheet" href="public/style.css">
|
||||
<script>
|
||||
let ZQLGLOBAL = {
|
||||
serverIp: '192.168.1.169',// 修改成您的盒子aiboxd的IP地址
|
||||
device: `:9091/ks/device`, // 视频源增删改查
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-container" style="padding: 20px;">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<h2>设备信息</h2>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" action="" lay-filter="deviceForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">设备ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="device_id" disabled lay-verify="required" placeholder="请输入" autocomplete="off"
|
||||
class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="device_name" lay-verify="required" placeholder="请输入" autocomplete="off"
|
||||
class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">设备描述</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="device_desc" lay-verify="required" placeholder="请输入" autocomplete="off"
|
||||
class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="saveData">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 引入Layui JS -->
|
||||
<script src="https://unpkg.com/layui@2.8.18/dist/layui.js"></script>
|
||||
<script src="public/jquery-1.10.2.min.js"></script>
|
||||
<script src="device.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
67
深度对接设备接口/device.js
Normal file
@ -0,0 +1,67 @@
|
||||
class deviceInfo {
|
||||
constructor() {
|
||||
this.info = {};
|
||||
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
deviceApis.getDeviceInfo().then(res => {
|
||||
console.log(res)
|
||||
this.info = res.data;
|
||||
this.initForm()
|
||||
})
|
||||
}
|
||||
initForm() {
|
||||
layui.use(() => {
|
||||
var form = layui.form;
|
||||
form.val('deviceForm', this.info);
|
||||
form.on('submit(saveData)', (formData) => {
|
||||
deviceApis.editDeviceInfo(Object.assign(this.info, formData.field)).then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
return false;
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 等待Layui加载完成后初始化
|
||||
layui.use(['layer', 'form'], () => {
|
||||
// 初始化表格管理器
|
||||
window.deviceInfo = new deviceInfo();
|
||||
});
|
||||
|
||||
const deviceApis = {
|
||||
getDeviceInfo: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.device}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
editDeviceInfo: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.device}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
100
深度对接设备接口/facelib.html
Normal file
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>人脸底库管理</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/layui@2.8.18/dist/css/layui.css">
|
||||
<link rel="stylesheet" href="public/style.css">
|
||||
<style>
|
||||
#addGroup {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 100px;
|
||||
}
|
||||
|
||||
#group-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#group-container li {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #d2d2d2;
|
||||
border-radius: 4px;
|
||||
|
||||
}
|
||||
|
||||
#group-container li.active {
|
||||
background-color: #00e4ff;
|
||||
border-color: #00e4ff;
|
||||
}
|
||||
.layui-table-cell{
|
||||
height: fit-content;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
let ZQLGLOBAL = {
|
||||
serverIp: '192.168.1.169',// 修改成您的盒子aiboxd的IP地址
|
||||
face: `:9091/ks/face`, // 人脸增删改查
|
||||
image: `:9091/ks/face/image`, // 图片
|
||||
group: `:9091/ks/group`, // 底库分组
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-container" style="padding: 20px;">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<h2>人脸底库管理</h2>
|
||||
</div>
|
||||
<div style="padding: 10px; position:relative;">
|
||||
<h3 style="margin-bottom: 10px;">人脸分组</h3>
|
||||
<ul id="group-container">
|
||||
</ul>
|
||||
<button class="layui-btn layui-btn-normal layui-btn-sm " id="addGroup">
|
||||
<i class="layui-icon layui-icon-add-1"></i> 添加分组
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-card-body">
|
||||
<!-- 搜索和操作区域 -->
|
||||
<div style="margin-bottom: 20px; display: flex;justify-content: space-between;">
|
||||
<button class="layui-btn layui-btn-primary" id="searchBtn">
|
||||
<i class="layui-icon layui-icon-search"></i> 查询
|
||||
</button>
|
||||
<button class="layui-btn layui-btn-normal" id="addBtn">
|
||||
<i class="layui-icon layui-icon-add-1"></i> 添加数据
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
<script type="text/html" id="image">
|
||||
<img style="width:100px" src="{{= d.image }}"/>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格操作按钮模板 -->
|
||||
<script type="text/html" id="operationBar">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">
|
||||
<i class="layui-icon layui-icon-edit"></i> 编辑
|
||||
</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">
|
||||
<i class="layui-icon layui-icon-delete"></i> 删除
|
||||
</a>
|
||||
</script>
|
||||
|
||||
<!-- 引入Layui JS -->
|
||||
<script src="https://unpkg.com/layui@2.8.18/dist/layui.js"></script>
|
||||
<script src="public/jquery-1.10.2.min.js"></script>
|
||||
<script src="facelib.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
560
深度对接设备接口/facelib.js
Normal file
@ -0,0 +1,560 @@
|
||||
|
||||
class FaceLibManager {
|
||||
constructor() {
|
||||
this.page = 1;
|
||||
this.size = 10;
|
||||
this.group_id = '';
|
||||
this.groupData = [];
|
||||
this.data = [];
|
||||
this.isEditing = false;
|
||||
this.currentEditId = null;
|
||||
this.table = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.initLayui();
|
||||
this.bindEvents();
|
||||
await this.getGroupData();
|
||||
this.getTableData();
|
||||
}
|
||||
|
||||
// 初始化Layui
|
||||
initLayui() {
|
||||
layui.use(['table', 'layer', 'form'], () => {
|
||||
const table = layui.table;
|
||||
const layer = layui.layer;
|
||||
const form = layui.form;
|
||||
// 初始化表格
|
||||
this.table = table.render({
|
||||
elem: '#dataTable',
|
||||
data: this.data,
|
||||
cols: [[
|
||||
// { field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'external_id', title: '外部ID', width: 150 },
|
||||
{ field: 'name', title: '姓名', width: 150 },
|
||||
{ field: 'image', title: '人脸图片', width: 150, templet: '#image' },
|
||||
{ field: 'age', width: 100, title: '年龄', },
|
||||
{ field: 'desc', title: '简介' },
|
||||
{ field: 'update_time', width: 100, title: '更新时间', },
|
||||
{ title: '操作', width: 200, toolbar: '#operationBar', fixed: 'right' }
|
||||
]],
|
||||
page: true,
|
||||
limit: 10,
|
||||
limits: [10, 20, 50],
|
||||
height: 'full-220',
|
||||
text: {
|
||||
none: '暂无数据'
|
||||
}
|
||||
});
|
||||
|
||||
// 监听工具条事件
|
||||
table.on('tool(dataTable)', (obj) => {
|
||||
const data = obj.data;
|
||||
if (obj.event === 'edit') {
|
||||
this.editFaceModal(data, 'edit');
|
||||
} else if (obj.event === 'del') {
|
||||
this.deleteData(data.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getGroupData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
faceLibApis.getGrpup().then(res => {
|
||||
this.groupData = res.data.map(item => {
|
||||
item.ext = JSON.parse(item.ext);
|
||||
item.quality = item.ext.quality;
|
||||
return item
|
||||
});
|
||||
if (this.groupData.length > 0) {
|
||||
this.group_id = this.groupData[0].id;
|
||||
resolve()
|
||||
}
|
||||
let el = document.querySelector("#group-container");
|
||||
let innerHTML = ''
|
||||
for (let i = 0; i < this.groupData.length; i++) {
|
||||
if (i == 0) {
|
||||
innerHTML += `<li class="group active" id="${this.groupData[i].id}">
|
||||
${this.groupData[i].name}
|
||||
<i class="layui-icon layui-icon-edit" editId="${this.groupData[i].id}"></i>
|
||||
<i class="layui-icon layui-icon-delete" delId="${this.groupData[i].id}"></i>
|
||||
</li>`;
|
||||
} else {
|
||||
innerHTML += `<li class="group" id="${this.groupData[i].id}">
|
||||
${this.groupData[i].name}
|
||||
<i class="layui-icon layui-icon-edit" editId="${this.groupData[i].id}"></i>
|
||||
<i class="layui-icon layui-icon-delete" delId="${this.groupData[i].id}"></i>
|
||||
</li>`;
|
||||
}
|
||||
}
|
||||
el.innerHTML = innerHTML;
|
||||
|
||||
let editBtns = document.querySelectorAll("#group-container .layui-icon-edit");
|
||||
editBtns.forEach(editBtn => {
|
||||
editBtn.addEventListener('click', (e) => {
|
||||
let group_id = e.target.getAttribute("editId");
|
||||
this.editGroupModal(this.groupData.find(item => item.id == group_id), "edit")
|
||||
})
|
||||
})
|
||||
|
||||
let delBtns = document.querySelectorAll("#group-container .layui-icon-delete");
|
||||
delBtns.forEach(delBtn => {
|
||||
delBtn.addEventListener('click', (e) => {
|
||||
let group_id = e.target.getAttribute("delId");
|
||||
faceLibApis.delGroup({ ids: [group_id] }).then(res => {
|
||||
this.getGroupData();
|
||||
this.showNotification('数据更新成功!');
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
getTableData() {
|
||||
faceLibApis.getFaceList({
|
||||
page: this.page,
|
||||
size: this.size,
|
||||
group_id: this.group_id
|
||||
}).then(res => {
|
||||
this.data = res.data.data.map(item => {
|
||||
// item.image = ZQLGLOBAL.serverIp + ':9092/staticdata' + item.image;
|
||||
item.image = `http://${ZQLGLOBAL.serverIp}:9092/staticdata${item.image}`
|
||||
return item
|
||||
});
|
||||
if (this.table) {
|
||||
layui.use('table', () => {
|
||||
layui.table.reload('dataTable', {
|
||||
data: this.data
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvents() {
|
||||
// 添加按钮事件
|
||||
document.getElementById('addGroup').addEventListener('click', () => {
|
||||
this.editGroupModal({
|
||||
name: '',
|
||||
quality: 0.35,
|
||||
}, 'add');
|
||||
});
|
||||
|
||||
document.getElementById('addBtn').addEventListener('click', () => {
|
||||
this.editFaceModal({
|
||||
name: '',
|
||||
age: '',
|
||||
sex: '',
|
||||
desc: '',
|
||||
external_id: '',
|
||||
feature: '',
|
||||
}, 'add');
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
document.getElementById('searchBtn').addEventListener('click', () => {
|
||||
this.getTableData();
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑分组
|
||||
editGroupModal(data, type) {
|
||||
layui.use(['layer', 'form'], () => {
|
||||
const layer = layui.layer;
|
||||
const form = layui.form;
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: type == 'add' ? '添加分组' : '编辑分组',
|
||||
area: ['40vw', '40vh'],
|
||||
content: `
|
||||
<form class="layui-form" lay-filter="dataForm" style="padding: 20px;">
|
||||
<input type="hidden" name="id" value="${data.id}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">分组名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" value="${data.name}" required lay-verify="required" placeholder="请输入描述" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">人脸质量</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="quality" value="${data.quality}" required lay-verify="required" placeholder="请输入描述" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="saveData">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
success: () => {
|
||||
form.render();
|
||||
// 监听表单提交
|
||||
form.on('submit(saveData)', (formData) => {
|
||||
if (type == 'add') {
|
||||
faceLibApis.addGroup({
|
||||
alg: 'face',
|
||||
ext: { quality: formData.field.quality },
|
||||
name: formData.field.name
|
||||
}).then(res => {
|
||||
this.getGroupData();
|
||||
|
||||
// 关闭模态框
|
||||
layui.use('layer', () => {
|
||||
layui.layer.closeAll();
|
||||
});
|
||||
this.showNotification('数据更新成功!');
|
||||
})
|
||||
} else {
|
||||
faceLibApis.editGroup({
|
||||
id: data.id,
|
||||
alg: 'face',
|
||||
ext: { quality: formData.field.quality },
|
||||
name: formData.field.name
|
||||
}).then(res => {
|
||||
this.getGroupData();
|
||||
// 关闭模态框
|
||||
layui.use('layer', () => {
|
||||
layui.layer.closeAll();
|
||||
});
|
||||
this.showNotification('数据更新成功!');
|
||||
})
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 打开模态框
|
||||
editFaceModal(data, type) {
|
||||
layui.use(['layer', 'form'], () => {
|
||||
const layer = layui.layer;
|
||||
const form = layui.form;
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: type == 'add' ? '添加数据' : '编辑数据',
|
||||
area: ['80vw', '80vh'],
|
||||
content: `
|
||||
<form class="layui-form" lay-filter="dataForm" style="padding: 20px;">
|
||||
<input type="hidden" name="id" value="${data.id}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">图片</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="file" id="faceImage" placeholder="选择人脸" style="cursor: pointer;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">姓名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" value="${data.name}" required lay-verify="required" placeholder="请输入姓名" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">外部ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="external_id" value="${data.external_id}" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">性别</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="sex" value="${data.sex}" placeholder="请输入性别" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">年龄</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="age" value="${data.age}" placeholder="请输入年龄" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">简介</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="desc" value="${data.desc}" placeholder="请输入简介" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="saveData">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
success: () => {
|
||||
form.render();
|
||||
document.getElementById('faceImage').addEventListener('change', (event) => {
|
||||
const files = event.target.files;
|
||||
data.file = files[0]
|
||||
});
|
||||
|
||||
// 监听表单提交
|
||||
form.on('submit(saveData)', (formData) => {
|
||||
console.log(formData)
|
||||
this.handleFormSubmit(formData.field, data);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 提交:添加/编辑
|
||||
handleFormSubmit(formData, data) {
|
||||
// 验证数据
|
||||
if (!this.validateData(formData)) {
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
group_id: this.group_id,
|
||||
name: formData.name,
|
||||
age: formData.age,
|
||||
sex: formData.sex,
|
||||
desc: formData.desc,
|
||||
external_id: formData.external_id
|
||||
}
|
||||
|
||||
if (!data.id) {
|
||||
faceLibApis.addFace(params).then(res => {
|
||||
if (data.file) {
|
||||
let formdata = new FormData();
|
||||
formdata.append('id', res.data);
|
||||
formdata.append('image', data.file)
|
||||
faceLibApis.addImage(formdata).then(res => {
|
||||
this.facecb('add')
|
||||
})
|
||||
} else {
|
||||
this.facecb('add')
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
params.id = data.id;
|
||||
faceLibApis.editFace(params).then(res => {
|
||||
if (data.file) {
|
||||
let formdata = new FormData();
|
||||
formdata.append('id', params.id);
|
||||
formdata.append('image', data.file)
|
||||
faceLibApis.addImage(formdata).then(res => {
|
||||
this.facecb('edit')
|
||||
})
|
||||
} else {
|
||||
this.facecb('edit')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
facecb(type) {
|
||||
// 关闭模态框
|
||||
layui.use('layer', () => {
|
||||
layui.layer.closeAll();
|
||||
});
|
||||
this.showNotification(type == 'add' ? '数据添加成功!' : '数据更新成功!');
|
||||
setTimeout(() => {
|
||||
this.getTableData();
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 验证数据
|
||||
validateData(data) {
|
||||
if (!data.name) {
|
||||
this.showNotification('请输入姓名', 'error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
deleteData(id) {
|
||||
layui.use('layer', () => {
|
||||
layui.layer.confirm('确定要删除这条数据吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
faceLibApis.delSource({ id: id }).then(res => {
|
||||
this.getTableData();
|
||||
this.showNotification('数据删除成功!');
|
||||
layui.layer.close(index);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
showNotification(message, type = 'success') {
|
||||
layui.use('layer', () => {
|
||||
layui.layer.msg(message, {
|
||||
icon: type === 'success' ? 1 : 2,
|
||||
time: 2000
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 等待Layui加载完成后初始化
|
||||
layui.use(['table', 'layer', 'form'], () => {
|
||||
// 初始化人脸列表
|
||||
window.faceLibManager = new FaceLibManager();
|
||||
});
|
||||
|
||||
const faceLibApis = {
|
||||
getFaceList: (params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.face}?page=${params.page}&size=${params.size}&group_id=${params.group_id}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
addFace: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.face}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
editFace: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.face}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
delFace: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.face}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
addImage: (formdata) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.image}`,
|
||||
data: formdata, // 使用FormData对象
|
||||
processData: false, // 告诉jQuery不要处理发送的数据
|
||||
contentType: false,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getGrpup: () => {
|
||||
let type = 'face';
|
||||
// 获取人脸分组
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.group}?alg=${type}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
addGroup: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.group}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
editGroup: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.group}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
delGroup: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.group}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
32
深度对接设备接口/index.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-container" style="padding: 20px;">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<h2>索引</h2>
|
||||
</div>
|
||||
<h3>
|
||||
<a href="./live.html" target="_blank" rel="noopener noreferrer">实时画面:live.html</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="./source.html" target="_blank" rel="noopener noreferrer">视频流管理:source.html</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="./facelib.html" target="_blank" rel="noopener noreferrer">人脸底库管理:facelib.html</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="./device.html" target="_blank" rel="noopener noreferrer">设备信息:device.html</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
137
深度对接设备接口/live.css
Normal file
@ -0,0 +1,137 @@
|
||||
.main-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#video-container {
|
||||
width: calc(100% - 310px);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#video-container .video-box {
|
||||
background-color: #000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#video-container .video-box .tips {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
#video-container .video-box .tips .deviceoffline{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#video-container .video-box canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#video-container.one-video .video-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box {
|
||||
width: calc(50% - 2px);
|
||||
height: calc(50% - 2px);
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box:nth-child(1) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#video-container.four-video .video-box:nth-child(1) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.video-box .title-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: rgb(73 162 238 / 45%);
|
||||
color: #00faff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-box .title-container .alg {
|
||||
width: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-box .title-container ul {
|
||||
display: none;
|
||||
background-color: rgb(131 186 233 / 45%);
|
||||
}
|
||||
|
||||
.video-box .title-container:hover ul {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 40px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.video-box .title-container:hover ul li {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* 加载中 */
|
||||
.icon-dot {
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
background-color: #39f;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: ani-spin-bounce 1s 0s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ani-spin-bounce {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.layui-tree-main{
|
||||
position: relative;
|
||||
}
|
||||
66
深度对接设备接口/live.html
Normal file
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>实时画面叠加实时检测结果</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/layui@2.8.18/dist/css/layui.css">
|
||||
<link href="live.css" rel="stylesheet">
|
||||
<script>
|
||||
let ZQLGLOBAL = {
|
||||
serverIp: '192.168.1.169' ,// 修改成您的盒子aiboxd的IP地址
|
||||
getDevices: `:9091/ks/device`,
|
||||
getSources: `:9091/ks/source`,
|
||||
subscribe: `:9089/ks/stream/live/subscribe`,
|
||||
detect: `:9089/ks/stream/attr`,
|
||||
tunnel:`:9091/ks/system/tunnel`,
|
||||
sysArgs: `:9091/ks/system/args`,
|
||||
resultTopic: 'ks/sink_local_result', //实时检测结果
|
||||
streamCodeTopic: 'ks/stream',// 视频流状态码,状态码改变后应重新播放
|
||||
srs_server: 1935,
|
||||
srs_http_api: 1985,
|
||||
srs_http_server: 8080,
|
||||
websocket: 8083,
|
||||
}
|
||||
|
||||
let ZQL_videosInfos = { 0: null, 1: null, 2: null, 3: null };
|
||||
let ZQL_playingSource = {
|
||||
0: null, 1: null, 2: null, 3: null,
|
||||
videoNum: 4,
|
||||
curposition: -1
|
||||
}
|
||||
let ZQL_sources = {};
|
||||
</script>
|
||||
<script type="text/javascript" src="public/crypto-js.js"></script>
|
||||
<script type="text/javascript" src="public/jquery-1.10.2.min.js"></script>
|
||||
<script type="text/javascript" src="public/jswebrtc.min.js"></script>
|
||||
<script type="text/javascript" src="public/mpegts.js"></script>
|
||||
<script type="text/javascript" src="public/mqtt.min.js"></script>
|
||||
<script src="https://unpkg.com/layui@2.8.18/dist/layui.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main-container">
|
||||
<div id="video-container" class="one-video">
|
||||
<div class="video-box">
|
||||
<div class="title-container" id="video-title1"></div>
|
||||
<video ref="video" muted id="video1" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-container">
|
||||
<!-- <div class="btn-container">
|
||||
<i class="z-icon-onevideo" id="icon-oneviveo"></i>
|
||||
<i class="z-icon-fourvideo" id="icon-fourviveo"></i>
|
||||
</div> -->
|
||||
<div id="ZQL_source_tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="live.js"></script>
|
||||
<script>
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
920
深度对接设备接口/live.js
Normal file
@ -0,0 +1,920 @@
|
||||
|
||||
const ZQL_multivideo = {
|
||||
setVideoEl: () => {
|
||||
let videoContainer = document.querySelector("#video-container");
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
videoContainer.className = "one-video";
|
||||
videoContainer.innerHTML = `
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip0">
|
||||
<div class="icon-dot"></div>
|
||||
<div class="deviceoffline">
|
||||
<i class="z-icon-jiankonglixian" style="font-size: 40rem"></i>
|
||||
<span>离线</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title-container" id="video-title0"></div>
|
||||
<video ref="video" muted id="video0" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas0"></canvas>
|
||||
</div>
|
||||
`
|
||||
} else {
|
||||
videoContainer.className = "four-video";
|
||||
videoContainer.innerHTML = `
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip0">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title0"></div>
|
||||
<video ref="video" muted id="video0" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas0"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip1">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title1"></div>
|
||||
<video ref="video" muted id="video1" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas1"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip2">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title2"></div>
|
||||
<video ref="video" muted id="video2" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas2"></canvas>
|
||||
</div>
|
||||
<div class="video-box">
|
||||
<div class="tips" id="tip3">
|
||||
|
||||
</div>
|
||||
<div class="title-container" id="video-title3"></div>
|
||||
<video ref="video" muted id="video3" class="video-js" autoplay="autoplay" preload="auto"></video>
|
||||
<canvas class="canvas-shuju" id="canvas3"></canvas>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
},
|
||||
liveLoading: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
tipel.innerHTML = `<div class="icon-dot"></div>`
|
||||
},
|
||||
liveOffline: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
tipel.innerHTML = `
|
||||
<div class="deviceoffline">
|
||||
<i class="z-icon-jiankonglixian" style="font-size: 40rem"></i>
|
||||
<span>离线</span>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
liveStopLoading: (index) => {
|
||||
let tipel = document.querySelector("#tip" + index);
|
||||
if (tipel) {
|
||||
tipel.innerHTML = ``
|
||||
}
|
||||
},
|
||||
setAlgList(index) {
|
||||
let el = document.querySelector(`#video-title${index}`);
|
||||
let algList = ZQL_sources[ZQL_playingSource[index]].alg;
|
||||
let algEl = '<ul>';
|
||||
for (let alg in algList) {
|
||||
let name = algList[alg].reserved_args.ch_name;
|
||||
algEl = algEl + `<li alg="${alg}" index="${index}">${name}</li>`
|
||||
}
|
||||
algEl = algEl + '</ul>'
|
||||
el.innerHTML = `
|
||||
<div class="camera">${ZQL_sources[ZQL_playingSource[index]].desc}</div>
|
||||
<div class="alg">
|
||||
<div class="algname">算法: ${ZQL_playingSource[index].alg ? ZQL_sources[ZQL_playingSource[index]].alg[alg].reserved_args.ch_name : ''}</div>
|
||||
${algEl}
|
||||
</div>
|
||||
<div id="close${index}">关闭</div>
|
||||
`;
|
||||
el.querySelectorAll('li').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
let index = e.currentTarget.getAttribute("index");
|
||||
let alg = e.currentTarget.getAttribute("alg")
|
||||
ZQL_videosInfos[index].alg = alg;
|
||||
let videlel = document.querySelector(`#video-title${index}`);
|
||||
videlel.querySelector(".algname").innerHTML = '算法:' + ZQL_sources[ZQL_playingSource[index]].alg[alg].reserved_args.ch_name
|
||||
})
|
||||
})
|
||||
document.querySelector(`#close${index}`).addEventListener('click', () => {
|
||||
ZQL_multivideo.clearAlgList(index);
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_multivideo.destoryVideoByIndex(index);
|
||||
ZQL_playingSource[index] = null;
|
||||
ZQL_videosInfos[index] = null;
|
||||
})
|
||||
},
|
||||
clearAlgList(index) {
|
||||
let el = document.querySelector(`#video-title${index}`);
|
||||
if (el) {
|
||||
el.innerHTML = ""
|
||||
}
|
||||
|
||||
},
|
||||
handleRefresh(index) {
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
return;
|
||||
}
|
||||
if (ZQL_videosInfos[index].status == "离线") {
|
||||
ZQL_multivideo.destoryVideoByIndex(index);
|
||||
ZQL_multivideo.subscribeLive(ZQL_playingSource[index], index);
|
||||
} else {
|
||||
if (!ZQL_videosInfos[index].stream) {
|
||||
return;
|
||||
}
|
||||
let video = document.getElementById("video" + index);
|
||||
video && (video.srcObject = null);
|
||||
if (ZQL_videosInfos[index] && ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
ZQL_videosInfos[index].replayTimer = null;
|
||||
}
|
||||
ZQL_videosInfos[index] &&
|
||||
ZQL_videosInfos[index].srsrtc &&
|
||||
ZQL_videosInfos[index].srsrtc.destroy();
|
||||
ZQL_videosInfos[index].srsrtc = null;
|
||||
ZQL_videosInfos[index].status = "";
|
||||
ZQL_multivideo.playVideo(ZQL_playingSource[index], index);
|
||||
}
|
||||
},
|
||||
subscribeLive(cameraId, index) {
|
||||
ZQL_multivideo.getCameraSize(cameraId, index);
|
||||
ZQL_multivideo.liveLoading(index);
|
||||
ZQL_apis
|
||||
.subscribeLive(
|
||||
// ZQL_sources[cameraId].deviceId,
|
||||
// ZQL_sources[cameraId].sourceId
|
||||
cameraId
|
||||
)
|
||||
.then((data) => {
|
||||
let stream = data.data;
|
||||
if (data && stream) {
|
||||
ZQL_videosInfos[index].stream = stream;
|
||||
ZQL_multivideo.playVideo(cameraId, index);
|
||||
} else {
|
||||
if (ZQL_playingSource[index] == cameraId) {
|
||||
ZQL_multivideo.liveOffline(index);
|
||||
// ZQL_videosInfos[index].status = "离线";
|
||||
// ZQL_videosInfos[index].loading = false;
|
||||
// this.reSubcribe(cameraId, index);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
ZQL_playingSource[index] == cameraId &&
|
||||
ZQL_videosInfos[index]
|
||||
) {
|
||||
ZQL_multivideo.liveOffline(index);
|
||||
// ZQL_videosInfos[index].status = "离线";
|
||||
// ZQL_videosInfos[index].loading = false;
|
||||
// this.reSubcribe(cameraId, index);
|
||||
}
|
||||
});
|
||||
},
|
||||
playVideo(cameraId, index) {
|
||||
if (ZQL_videosInfos[index].srsrtc) {
|
||||
return;
|
||||
}
|
||||
ZQL_videosInfos[index].loading = true;
|
||||
|
||||
let video = document.getElementById("video" + index);
|
||||
let stream = ZQL_videosInfos[index].stream;
|
||||
var srsrtc;
|
||||
if (stream.indexOf("webrtc") >= 0) {
|
||||
let src =
|
||||
"webrtc://" + ZQLGLOBAL.serverIp + "/live" + stream.split("/live")[1];
|
||||
srsrtc = new JSWebrtc.Player(src, {
|
||||
video: video,
|
||||
autoplay: true,
|
||||
onPlay: (obj) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
},
|
||||
});
|
||||
} else if (stream.indexOf(".flv") >= 0) {
|
||||
let src = `http://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.srs_http_server}/live${stream.split("/live")[1]
|
||||
}`;
|
||||
srsrtc = mpegts.createPlayer(
|
||||
{
|
||||
type: "flv",
|
||||
url: src,
|
||||
isLive: true,
|
||||
},
|
||||
{ enableWorker: true }
|
||||
);
|
||||
srsrtc.attachMediaElement(video);
|
||||
srsrtc.load();
|
||||
ZQL_videosInfos[index].playerState = "";
|
||||
srsrtc
|
||||
.play()
|
||||
.then((res) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
}
|
||||
ZQL_videosInfos[index].refreshTime =
|
||||
parseInt((Math.random() * 5 + 5) * 1000) * 60;
|
||||
ZQL_videosInfos[index].refreshTimeInterval = setInterval(() => {
|
||||
handleRefresh(index);
|
||||
}, ZQL_videosInfos[index].refreshTime);
|
||||
})
|
||||
.catch((err) => { });
|
||||
if (ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
}
|
||||
ZQL_videosInfos[index].replayTimer = setTimeout(() => {
|
||||
ZQL_multivideo.replayflv(srsrtc, cameraId, index);
|
||||
}, 3000);
|
||||
}
|
||||
ZQL_videosInfos[index].srsrtc = srsrtc;
|
||||
},
|
||||
replayflv(srsrtc, cameraId, index) {
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
return;
|
||||
}
|
||||
if (ZQL_videosInfos[index].playerState == "success") {
|
||||
return;
|
||||
} else {
|
||||
srsrtc.unload();
|
||||
srsrtc.load();
|
||||
srsrtc
|
||||
.play()
|
||||
.then((res) => {
|
||||
ZQL_multivideo.liveStopLoading(index);
|
||||
ZQL_videosInfos[index].playerState = "success";
|
||||
ZQL_videosInfos[index].loading = false;
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
}
|
||||
ZQL_videosInfos[index].refreshTime =
|
||||
parseInt((Math.random() * 5 + 5) * 1000) * 60;
|
||||
ZQL_videosInfos[index].refreshTimeInterval = setInterval(() => {
|
||||
ZQL_multivideo.handleRefresh(index);
|
||||
}, ZQL_videosInfos[index].refreshTime);
|
||||
})
|
||||
.catch((err) => {
|
||||
// this.destoryVideoByIndex(index);
|
||||
// this.subscribeLive(cameraId, index);
|
||||
});
|
||||
if (ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
}
|
||||
ZQL_videosInfos[index].replayTimer = setTimeout(() => {
|
||||
ZQL_multivideo.replayflv(srsrtc, cameraId, index);
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
reSubcribe(cameraId, index) {
|
||||
if (ZQL_videosInfos[index].subscribeTimeout) {
|
||||
clearTimeout(ZQL_videosInfos[index].subscribeTimeout);
|
||||
ZQL_videosInfos[index].subscribeTimeout = null;
|
||||
}
|
||||
ZQL_multivideo.videosInfos[index].subscribeTimeout = setTimeout(() => {
|
||||
ZQL_multivideo.subscribeLive(cameraId, index);
|
||||
}, 1000);
|
||||
},
|
||||
getCameraSize(id, index) {
|
||||
ZQL_multivideo.setOrisize(
|
||||
ZQL_sources[id].draw_size[0],
|
||||
ZQL_sources[id].draw_size[1],
|
||||
index, id
|
||||
);
|
||||
},
|
||||
setOrisize(width, height, index, id) {
|
||||
let container = document.querySelector(".video-box");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
if (!ZQL_videosInfos[index]) {
|
||||
let alg = null;
|
||||
if (sessionStorage.getItem("curalgs")) {
|
||||
let cameraId = ZQL_playingSource[index];
|
||||
let curalgs = JSON.parse(sessionStorage.getItem("curalgs"));
|
||||
alg = curalgs[cameraId]
|
||||
? JSON.parse(JSON.stringify(curalgs[cameraId]))
|
||||
: null;
|
||||
}
|
||||
ZQL_videosInfos[index] = {
|
||||
id: id,
|
||||
loading: true,
|
||||
openWs: true,
|
||||
alg: alg,
|
||||
algListShow: false,
|
||||
subscribeTimeout: null,
|
||||
refreshTimeInterval: null, // 定时刷新定时器
|
||||
refreshTime: null, // 定时刷新时间
|
||||
replayTimer: null,
|
||||
playerState: "pending",
|
||||
detectInterval: null,
|
||||
quanping: false,
|
||||
srsrtc: null,
|
||||
stream: "",
|
||||
status: "",
|
||||
stream_code: "",
|
||||
};
|
||||
}
|
||||
if (ZQL_videosInfos[index]) {
|
||||
let oriWidth = width;
|
||||
let oriHeight = height;
|
||||
ZQL_videosInfos[index].oriWidth = oriWidth;
|
||||
ZQL_videosInfos[index].oriHeight = oriHeight;
|
||||
|
||||
if (
|
||||
oriWidth / container.offsetWidth >
|
||||
oriHeight / container.offsetHeight
|
||||
) {
|
||||
ZQL_videosInfos[index].actualHeight = container.offsetWidth / (oriWidth / oriHeight)
|
||||
ZQL_videosInfos[index].actualWidth = container.offsetWidth;
|
||||
} else {
|
||||
ZQL_videosInfos[index].actualHeight = container.offsetHeight
|
||||
ZQL_videosInfos[index].actualWidth = container.offsetHeight * (oriWidth / oriHeight)
|
||||
}
|
||||
// videoWidth = ZQL_videosInfos[index].actualWidth;
|
||||
ZQL_multivideo.setPosition(index);
|
||||
}
|
||||
},
|
||||
setPosition(index) {
|
||||
let container = document.querySelector(".video-box");
|
||||
let video = document.querySelector("#video" + index);
|
||||
let canvas = document.getElementById("canvas" + index);
|
||||
let width = ZQL_videosInfos[index].actualWidth, height = ZQL_videosInfos[index].actualHeight;
|
||||
video.style.position = "absolute";
|
||||
video.style.width = width + "px";
|
||||
video.style.height = height + "px";
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
if (width / container.offsetWidth < height / container.offsetHeight) {
|
||||
let left = (container.offsetWidth - width) / 2;
|
||||
video.style.left = Math.floor(left) + "px";
|
||||
video.style.top = 0 + "px";
|
||||
canvas.style.left = Math.floor(left) + "px";
|
||||
canvas.style.top = "0px";
|
||||
} else {
|
||||
let top = (container.offsetHeight - height) / 2;
|
||||
video.style.top = Math.floor(top) + "px";
|
||||
video.style.left = 0 + "px";
|
||||
canvas.style.top = Math.floor(top) + "px";
|
||||
canvas.style.left = "0px";
|
||||
}
|
||||
},
|
||||
setAlarms: (data, index) => {
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
if (ZQL_videosInfos[index] && !ZQL_videosInfos[index].canvas) {
|
||||
ZQL_videosInfos[index].canvas = document.getElementById("canvas" + index)
|
||||
}
|
||||
if (
|
||||
!ZQL_videosInfos[index] ||
|
||||
!ZQL_videosInfos[index].actualWidth ||
|
||||
!ZQL_videosInfos[index].actualHeight ||
|
||||
!ZQL_videosInfos[index].oriWidth ||
|
||||
!ZQL_videosInfos[index].oriHeight
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// let bbox = data.result.data.bbox;
|
||||
let bbox = data.bbox;
|
||||
if (Object.values(bbox.polygons).length > 0) {
|
||||
Object.values(bbox.polygons).forEach((item) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
// let color = item.color;
|
||||
let points = item.polygon.map((point) => {
|
||||
return [
|
||||
Math.round(
|
||||
(point[0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
Math.round(
|
||||
(point[1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
];
|
||||
});
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "transparent";
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
ZQL_multivideo.drawPolygons(points, context);
|
||||
ZQL_multivideo.drawPolygonInfo(context, Object.values(bbox.polygons), index);
|
||||
});
|
||||
}
|
||||
|
||||
if (bbox.rectangles.length > 0) {
|
||||
bbox.rectangles.forEach((item, i) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
let coordinates = {
|
||||
x: Math.round(
|
||||
(item.xyxy[0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y: Math.round(
|
||||
(item.xyxy[1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
x1: Math.round(
|
||||
(item.xyxy[2] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y1: Math.round(
|
||||
(item.xyxy[3] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
};
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "rgb(" + color.join(",") + ")";
|
||||
context.fillText(item.label || "", coordinates.x, coordinates.y - 10);
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
// context.strokeRect(
|
||||
// coordinates.x,
|
||||
// coordinates.y,
|
||||
// coordinates.x1 - coordinates.x,
|
||||
// coordinates.y1 - coordinates.y
|
||||
// );
|
||||
let lines = [];
|
||||
let lineWidth = (coordinates.x1 - coordinates.x) / 4;
|
||||
let lineHeight = (coordinates.y1 - coordinates.y) / 4;
|
||||
lines[0] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x + lineWidth,
|
||||
y1: coordinates.y,
|
||||
};
|
||||
lines[1] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x,
|
||||
y1: coordinates.y + lineHeight,
|
||||
};
|
||||
lines[2] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x1 - lineWidth,
|
||||
y1: coordinates.y,
|
||||
};
|
||||
lines[3] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y,
|
||||
x1: coordinates.x1,
|
||||
y1: coordinates.y + lineHeight,
|
||||
};
|
||||
lines[4] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x + lineWidth,
|
||||
y1: coordinates.y1,
|
||||
};
|
||||
lines[5] = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x,
|
||||
y1: coordinates.y1 - lineHeight,
|
||||
};
|
||||
lines[6] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x1 - lineWidth,
|
||||
y1: coordinates.y1,
|
||||
};
|
||||
lines[7] = {
|
||||
x: coordinates.x1,
|
||||
y: coordinates.y1,
|
||||
x1: coordinates.x1,
|
||||
y1: coordinates.y1 - lineHeight,
|
||||
};
|
||||
lines.forEach((item) => {
|
||||
ZQL_multivideo.drawLine(context, item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.values(bbox.lines).length > 0) {
|
||||
Object.values(bbox.lines).forEach((item, i) => {
|
||||
let color = JSON.parse(JSON.stringify(item.color)).reverse();
|
||||
let coordinates = {
|
||||
x: Math.round(
|
||||
(item.line[0][0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y: Math.round(
|
||||
(item.line[0][1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
x1: Math.round(
|
||||
(item.line[1][0] * ZQL_videosInfos[index].actualWidth) /
|
||||
ZQL_videosInfos[index].oriWidth
|
||||
),
|
||||
y1: Math.round(
|
||||
(item.line[1][1] * ZQL_videosInfos[index].actualHeight) /
|
||||
ZQL_videosInfos[index].oriHeight
|
||||
),
|
||||
};
|
||||
let context = ZQL_videosInfos[index].canvas.getContext("2d");
|
||||
context.font = "20px Arial bolder";
|
||||
context.fillStyle = "rgb(" + color.join(",") + ")";
|
||||
if (item.ext.direction) {
|
||||
context.fillText(item.name, (coordinates.x + coordinates.x1) / 2, (coordinates.y + coordinates.y1) / 2 + 20);
|
||||
}
|
||||
|
||||
context.strokeStyle = "rgb(" + color.join(",") + ")";
|
||||
context.lineWidth = 2;
|
||||
ZQL_multivideo.drawLine(context, coordinates);
|
||||
ZQL_multivideo.drawCountingInfo(context, Object.values(bbox.lines));
|
||||
});
|
||||
}
|
||||
},
|
||||
drawPolygons(points, context) {
|
||||
context.beginPath();
|
||||
context.moveTo(points[0][0], points[0][1]);
|
||||
|
||||
for (var i = 1; i < points.length; i++) {
|
||||
context.lineTo(points[i][0], points[i][1]);
|
||||
}
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
},
|
||||
drawLine(ctx, line) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(line.x, line.y);
|
||||
ctx.lineTo(line.x1, line.y1);
|
||||
ctx.stroke();
|
||||
},
|
||||
drawCountingInfo(context, lines) {
|
||||
lines.forEach((item, index) => {
|
||||
context.fillStyle = "rgb(255,0,0)";
|
||||
if (item.ext.direction.length == 2) {
|
||||
context.fillText(`[${item.name}] ${item.ext.action.count}: ${item.ext.result.count}`, 0, 20 * index + 20);
|
||||
} else {
|
||||
context.fillText(`[${item.name}] ${item.ext.action.increase}: ${item.ext.result.increase},${item.ext.action.decrease}: ${item.ext.result.decrease},${item.ext.action.delta}: ${item.ext.result.delta}`, 0, 20 * index + 20);
|
||||
}
|
||||
});
|
||||
},
|
||||
drawPolygonInfo(context, polygons, videoindex) {
|
||||
polygons.forEach((item, index) => {
|
||||
context.fillStyle =
|
||||
"rgb(" +
|
||||
JSON.parse(JSON.stringify(item.color)).reverse().join(",") +
|
||||
")";
|
||||
let leftPoint = item.polygon[0];
|
||||
for (let i = 1; i < item.polygon.length; i++) {
|
||||
if (item.polygon[i][0] < leftPoint[0]) {
|
||||
leftPoint = item.polygon[i];
|
||||
}
|
||||
}
|
||||
context.fillText(
|
||||
`${item.name}`,
|
||||
(leftPoint[0] * ZQL_videosInfos[videoindex].actualWidth) /
|
||||
ZQL_videosInfos[videoindex].oriWidth,
|
||||
(leftPoint[1] * ZQL_videosInfos[videoindex].actualHeight) /
|
||||
ZQL_videosInfos[videoindex].oriHeight + 20
|
||||
);
|
||||
if (item.ext.result) {
|
||||
context.fillStyle = "rgb(255,0,0)";
|
||||
context.fillText(`${item.name}: ${item.ext.result}`, 0, 20 * index + 20);
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyVideo(videonum) {
|
||||
for (let i = 0; i < videonum; i++) {
|
||||
ZQL_multivideo.destoryVideoByIndex(i);
|
||||
}
|
||||
},
|
||||
destoryVideoByIndex(index) {
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
if (ZQL_videosInfos[index]) {
|
||||
if (
|
||||
ZQL_videosInfos[index] &&
|
||||
ZQL_videosInfos[index].subscribeTimeout
|
||||
) {
|
||||
clearTimeout(ZQL_videosInfos[index].subscribeTimeout);
|
||||
ZQL_videosInfos[index].subscribeTimeout = null;
|
||||
}
|
||||
if (ZQL_videosInfos[index] && ZQL_videosInfos[index].replayTimer) {
|
||||
clearTimeout(ZQL_videosInfos[index].replayTimer);
|
||||
ZQL_videosInfos[index].replayTimer = null;
|
||||
}
|
||||
if (ZQL_videosInfos[index].refreshTimeInterval) {
|
||||
clearInterval(ZQL_videosInfos[index].refreshTimeInterval);
|
||||
ZQL_videosInfos[index].refreshTimeInterval = null;
|
||||
}
|
||||
let video = document.getElementById("video" + index);
|
||||
video && (video.srcObject = null);
|
||||
ZQL_videosInfos[index].srsrtc &&
|
||||
ZQL_videosInfos[index].srsrtc.destroy();
|
||||
ZQL_multivideo.clearCanvas(index);
|
||||
ZQL_videosInfos[index] = null;
|
||||
}
|
||||
},
|
||||
clearCanvas(index) {
|
||||
let canvas = document.getElementById("canvas" + index);
|
||||
if (canvas && canvas.getContext("2d")) {
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
|
||||
}
|
||||
},
|
||||
connectMqtt() {
|
||||
let mqttclient = mqtt.connect(`ws://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.websocket}/mqtt`);
|
||||
mqttclient.subscribe(
|
||||
ZQLGLOBAL.resultTopic,
|
||||
{ qos: 0 },
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.log("subscribe error:", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
mqttclient.subscribe(
|
||||
ZQLGLOBAL.streamCodeTopic,
|
||||
{ qos: 0 },
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.log("subscribe error:", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
mqttclient.on("message", (topic, payload) => {
|
||||
let msg = JSON.parse(payload.toString());
|
||||
if (msg.msg_type == "result") {
|
||||
let id = msg.data.source.id;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (ZQL_videosInfos[i]) {
|
||||
let alg =
|
||||
ZQL_videosInfos[i].alg && ZQL_videosInfos[i].alg.algname;
|
||||
|
||||
if (
|
||||
id == ZQL_playingSource[i] &&
|
||||
msg.data.alg.name == ZQL_videosInfos[i].alg
|
||||
) {
|
||||
ZQL_multivideo.setAlarms(msg.data.reserved_data, i);
|
||||
if (
|
||||
ZQL_videosInfos[i] &&
|
||||
ZQL_videosInfos[i].canvasTimeout
|
||||
) {
|
||||
clearTimeout(ZQL_videosInfos[i].canvasTimeout);
|
||||
}
|
||||
ZQL_videosInfos[i].canvasTimeout = setTimeout(() => {
|
||||
ZQL_multivideo.clearCanvas(i);
|
||||
}, 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg.msg_type == "stream_code") {
|
||||
let cameraId = msg.data.source_id;
|
||||
for (let i = 0; i < ZQL_playingSource.videoNum; i++) {
|
||||
if (cameraId == ZQL_playingSource[i] && ZQL_videosInfos[i]) {
|
||||
if (!ZQL_videosInfos[i].stream_code) {
|
||||
ZQL_videosInfos[i].stream_code = msg.data.stream_code;
|
||||
} else if (
|
||||
msg.data.stream_code != ZQL_videosInfos[i].stream_code
|
||||
) {
|
||||
ZQL_videosInfos[i].stream_code = msg.data.stream_code;
|
||||
ZQL_multivideo.handleRefresh(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ZQL_apis = {
|
||||
getSources: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.getSources}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
subscribeLive: (source_id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.subscribe}?source_id=${source_id}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
sysArgs: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.tunnel}`,
|
||||
success: function (tunnel) {
|
||||
if (tunnel.data.enable == false) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.sysArgs}`,
|
||||
success: function (res) {
|
||||
resolve(res.data.map.local)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
"srs_server": tunnel.data.srs_server,
|
||||
"srs_http_api": tunnel.data.srs_http_api,
|
||||
"srs_http_server": tunnel.data.srs_http_server,
|
||||
"websocket": tunnel.data.websocket
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
},
|
||||
detectStream: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}:${ZQLGLOBAL.srs_http_api}/api/v1/streams?start=0&count=10000`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
detectVideo: (device_id, stream) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.detect}?device_id=${device_id}&stream=${stream}&draw_size=1280`,
|
||||
success: function (res) {
|
||||
if (res.error == 0) {
|
||||
resolve({ status: 1 })
|
||||
} else {
|
||||
resolve({ status: 0 })
|
||||
}
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
gettoken: () => {
|
||||
var ak = ZQLGLOBAL.accessKey;
|
||||
var sk = ZQLGLOBAL.accessSecret;
|
||||
var timestamp = parseInt(new Date().getTime() / 1000);
|
||||
var nonce = ZQL_apis.generateRandomString(10);
|
||||
let signature = ZQL_apis.generateSignature(ak, sk, timestamp, nonce)
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.getToken}?signature=${signature}&ak=${ak}×tamp=${timestamp}&nonce=${nonce}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
generateSignature: (ak, sk, timestamp, nonce) => {
|
||||
var message = `${ak}:${timestamp}:${nonce}`;
|
||||
var hash = CryptoJS.HmacSHA256(message, sk);
|
||||
var signature = CryptoJS.enc.Hex.stringify(hash);
|
||||
return signature
|
||||
},
|
||||
generateRandomString(length) {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
// 设置文档结构
|
||||
ZQL_multivideo.setVideoEl();
|
||||
|
||||
// 设置事件监听
|
||||
// document.querySelector("#icon-oneviveo").addEventListener('click', () => {
|
||||
// playingSource.videoNum = 1;
|
||||
// setVideoEl();
|
||||
// })
|
||||
// document.querySelector("#icon-fourviveo").addEventListener('click', () => {
|
||||
// playingSource.videoNum = 4;
|
||||
// ZQL_multivideo.setVideoEl();
|
||||
// })
|
||||
|
||||
// 1. 获取摄像头列表
|
||||
Promise.all([ZQL_apis.getSources()]).then(res => {
|
||||
let cameras = res[0].data.map(item => {
|
||||
item.sourceId = item.id;
|
||||
item.title = item.desc;
|
||||
item.type = 'source';
|
||||
item.checked = false;
|
||||
ZQL_sources[item.id] = item
|
||||
return item
|
||||
});
|
||||
|
||||
layui.use(function () {
|
||||
var tree = layui.tree;
|
||||
var layer = layui.layer;
|
||||
tree.render({
|
||||
elem: '#ZQL_source_tree',
|
||||
data: cameras,
|
||||
// showCheckbox:true,
|
||||
onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩
|
||||
click: function (obj) {
|
||||
if (obj.data.sourceId) {
|
||||
let key = obj.data.sourceId;
|
||||
if (ZQL_sources[key].checked == false) {
|
||||
ZQL_sources[key].checked = true
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
ZQL_playingSource[0] = key;
|
||||
ZQL_multivideo.subscribeLive(key, 0);
|
||||
ZQL_multivideo.setAlgList(0);
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (!ZQL_playingSource[i]) {
|
||||
ZQL_playingSource[i] = key;
|
||||
ZQL_multivideo.subscribeLive(key, i);
|
||||
ZQL_multivideo.setAlgList(i)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZQL_sources[key].checked = false
|
||||
if (ZQL_playingSource.videoNum == 1) {
|
||||
ZQL_playingSource[0] = null;
|
||||
ZQL_multivideo.destoryVideoByIndex(0);
|
||||
ZQL_multivideo.clearAlgList(0);
|
||||
ZQL_multivideo.liveStopLoading(0);
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (ZQL_playingSource[i] == key) {
|
||||
ZQL_playingSource[i] = null;
|
||||
ZQL_multivideo.destoryVideoByIndex(i);
|
||||
ZQL_multivideo.clearAlgList(i)
|
||||
ZQL_multivideo.liveStopLoading(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
// 获取系统参数,连接mqtt,通过mqtt获取实时检测结果和视频流状态码
|
||||
ZQL_apis.sysArgs().then(res => {
|
||||
ZQLGLOBAL = Object.assign(ZQLGLOBAL, res)
|
||||
ZQL_multivideo.connectMqtt()
|
||||
}).catch(err => { })
|
||||
}
|
||||
5988
深度对接设备接口/public/crypto-js.js
Normal file
6
深度对接设备接口/public/jquery-1.10.2.min.js
vendored
Normal file
1
深度对接设备接口/public/jswebrtc.min.js
vendored
Normal file
9
深度对接设备接口/public/mpegts.js
Normal file
12
深度对接设备接口/public/mqtt.min.js
vendored
Normal file
126
深度对接设备接口/public/style.css
Normal file
@ -0,0 +1,126 @@
|
||||
/* Layui表格管理系统自定义样式 */
|
||||
|
||||
body {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.layui-card-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 搜索区域样式优化 */
|
||||
.layui-input-group {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.layui-input-group .layui-input {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.layui-input-suffix {
|
||||
background: #fff;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-left: none;
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.layui-table {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.layui-table thead tr {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.layui-table thead th {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.layui-btn-xs {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.layui-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.layui-col-md8,
|
||||
.layui-col-md4 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.layui-input-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.layui-input-suffix {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-top: none;
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 通知样式 */
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background: #5FB878;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background: #FF5722;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
61
深度对接设备接口/source.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>视频流管理</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/layui@2.8.18/dist/css/layui.css">
|
||||
<link rel="stylesheet" href="public/style.css">
|
||||
<script>
|
||||
let ZQLGLOBAL = {
|
||||
serverIp: '192.168.1.169',// 修改成您的盒子aiboxd的IP地址
|
||||
source: `:9091/ks/source`, // 视频源增删改查
|
||||
alg: `:9091/ks/alg`, // 算法列表
|
||||
attr: `:9089/ks/stream/attr`, // 检测视频是否在线
|
||||
group: `:9091/ks/group`, // 底库分组
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-container" style="padding: 20px;">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<h2>视频流管理</h2>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<!-- 搜索和操作区域 -->
|
||||
<div style="margin-bottom: 20px; display: flex;justify-content: space-between;">
|
||||
<button class="layui-btn layui-btn-primary" id="searchBtn">
|
||||
<i class="layui-icon layui-icon-search"></i> 查询
|
||||
</button>
|
||||
<button class="layui-btn layui-btn-normal" id="addBtn">
|
||||
<i class="layui-icon layui-icon-add-1"></i> 添加数据
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格操作按钮模板 -->
|
||||
<script type="text/html" id="operationBar" lay-filter="dataTable">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">
|
||||
<i class="layui-icon layui-icon-edit"></i> 编辑
|
||||
</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">
|
||||
<i class="layui-icon layui-icon-delete"></i> 删除
|
||||
</a>
|
||||
<input type="checkbox" name="open" lay-skin="switch" lay-filter="switchTest" title="启用|停用" value="{{= d.id }}" {{= d.status == 1 ? "checked" : "" }}>
|
||||
</script>
|
||||
|
||||
<!-- 引入Layui JS -->
|
||||
<script src="https://unpkg.com/layui@2.8.18/dist/layui.js"></script>
|
||||
<script src="public/jquery-1.10.2.min.js"></script>
|
||||
<script src="source.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
468
深度对接设备接口/source.js
Normal file
@ -0,0 +1,468 @@
|
||||
// Layui表格数据管理类
|
||||
class LayuiTableManager {
|
||||
constructor() {
|
||||
this.alg = {};
|
||||
this.data = [];
|
||||
this.isEditing = false;
|
||||
this.currentEditId = null;
|
||||
this.table = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.initLayui();
|
||||
this.bindEvents();
|
||||
await sourceApis.getAlgs().then(res => {
|
||||
res.data.forEach(alg => {
|
||||
this.alg[alg.name] = alg
|
||||
})
|
||||
})
|
||||
this.getTableData();
|
||||
}
|
||||
|
||||
// 初始化Layui
|
||||
initLayui() {
|
||||
layui.use(['table', 'layer', 'form'], () => {
|
||||
const table = layui.table;
|
||||
const layer = layui.layer;
|
||||
const form = layui.form;
|
||||
// 初始化表格
|
||||
this.table = table.render({
|
||||
elem: '#dataTable',
|
||||
data: this.data,
|
||||
cols: [[
|
||||
// { field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'desc', title: '描述', width: 150 },
|
||||
{ field: 'stream', title: '流地址', sort: true },
|
||||
{ field: 'alg_ch_names', title: '算法', },
|
||||
{ field: 'encoding', width: 100, title: '编码', },
|
||||
{ title: '操作', width: 250, toolbar: '#operationBar', fixed: 'right' }
|
||||
]],
|
||||
page: true,
|
||||
limit: 10,
|
||||
limits: [10, 20, 50],
|
||||
height: 'full-220',
|
||||
text: {
|
||||
none: '暂无数据'
|
||||
}
|
||||
});
|
||||
|
||||
// 监听工具条事件
|
||||
table.on('tool(dataTable)', (obj) => {
|
||||
const data = obj.data;
|
||||
if (obj.event === 'edit') {
|
||||
this.openEditModal(data, 'edit');
|
||||
} else if (obj.event === 'del') {
|
||||
this.deleteData(data.id);
|
||||
}
|
||||
});
|
||||
form.on('switch(switchTest)', (obj) =>{
|
||||
console.log(obj)
|
||||
let status;
|
||||
if(obj.elem.checked == true){
|
||||
status = 1
|
||||
}else{
|
||||
status = -1
|
||||
}
|
||||
sourceApis.editSource({
|
||||
id:obj.value,
|
||||
status:status
|
||||
}).then(res => {
|
||||
this.showNotification('数据更新成功!');
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getTableData() {
|
||||
sourceApis.getSources().then(res => {
|
||||
this.data = res.data.filter(item => item.type == 'stream').map(item => {
|
||||
item.alg_ch_names = Object.keys(item.alg).map(alg_name => this.alg[alg_name].ch_name).join(',')
|
||||
return item
|
||||
});
|
||||
if (this.table) {
|
||||
layui.use('table', () => {
|
||||
layui.table.reload('dataTable', {
|
||||
data: this.data
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvents() {
|
||||
// 添加按钮事件
|
||||
document.getElementById('addBtn').addEventListener('click', () => {
|
||||
this.openEditModal({
|
||||
desc: '',
|
||||
stream: '',
|
||||
alg: {}
|
||||
}, 'add');
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
document.getElementById('searchBtn').addEventListener('click', () => {
|
||||
this.getTableData();
|
||||
});
|
||||
}
|
||||
|
||||
// 打开模态框
|
||||
openEditModal(data, type) {
|
||||
layui.use(['layer', 'form'], () => {
|
||||
const layer = layui.layer;
|
||||
const form = layui.form;
|
||||
|
||||
let algEl = ``;
|
||||
for (let key in this.alg) {
|
||||
if (data.alg[key]) {
|
||||
algEl += `<input type="checkbox" name="${this.alg[key].name}" title="${this.alg[key].ch_name}" checked> `;
|
||||
} else {
|
||||
algEl += `<input type="checkbox" name="${this.alg[key].name}" title="${this.alg[key].ch_name}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: type == 'add' ? '添加数据' : '编辑数据',
|
||||
area: ['80vw', '80vh'],
|
||||
content: `
|
||||
<form class="layui-form" lay-filter="dataForm" style="padding: 20px;">
|
||||
<input type="hidden" name="id" value="${data.id}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">描述</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="desc" value="${data.desc}" required lay-verify="required" placeholder="请输入描述" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">流地址</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="stream" value="${data.stream}" required lay-verify="required" placeholder="请输入流地址" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item" >
|
||||
<label class="layui-form-label"></label>
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="detect">
|
||||
检测是否在线
|
||||
</button>
|
||||
<input type="hidden" name="draw_size" value="${data.draw_size}">
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">算法</label>
|
||||
<div class="layui-input-block" id="sel-algs-container">
|
||||
${algEl}
|
||||
</div>
|
||||
<input type="hidden" name="alg_ch_names" value="${data.alg_ch_names}">
|
||||
<span style="color:#999">注:本demo对选择的算法仅保存默认参数,如果参数配置中有必须绘制检测区域的参数,则检测区域默认设置为整张图片的坐标;如果参数配置中有必须绘制线的参数,则线默认设置为相对于图片从左到右垂直居中的横线。</span>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">算法参数</label>
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="getAlgParams">
|
||||
获取算法参数
|
||||
</button>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="saveData">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
success: () => {
|
||||
form.render();
|
||||
// 检测
|
||||
form.on('submit(detect)', (formData) => {
|
||||
sourceApis.getAttr(formData.field.stream).then(res => {
|
||||
data.draw_size = res.data.size;
|
||||
data.encoding = res.data.codec;
|
||||
})
|
||||
return false;
|
||||
});
|
||||
form.on('submit(getAlgParams)', (formData) => {
|
||||
if (!data.draw_size) {
|
||||
// 先检测
|
||||
this.showNotification('请先检测是否在线', 'error');
|
||||
return false;
|
||||
}
|
||||
// 算法参数含义可参考如下链接中的“3.前端配置文件”部分:https://github.com/AIDrive-Research/Custom-Algorithm/tree/main/02_CustomAlgorithm/03_PackageStructure
|
||||
let algEls = document.querySelectorAll("#sel-algs-container input");
|
||||
for (let i = 0; i < algEls.length; i++) {
|
||||
if (algEls[i].checked == true && !data.alg[algEls[i].name]) {
|
||||
((alg) => {
|
||||
sourceApis.getAlgJson(alg).then(res => {
|
||||
// 算法配置文件中的basicParams为需要保存到后台的参数
|
||||
data.alg[alg] = JSON.parse(JSON.stringify(res.basicParams));
|
||||
// 算法配置文件中的renderParams为算法参数的展示、配置规则
|
||||
if (res.renderParams.bbox) {
|
||||
// 判断是否必须有检测区域,如离岗、区域入侵等必须绘制检测区域
|
||||
let polygon = res.renderParams.bbox.polygons;
|
||||
if (polygon && polygon.exits == 'must') {
|
||||
data.alg[alg].bbox.polygons = [{
|
||||
id: `polygon_${new Date().getTime()}`,
|
||||
name: '',
|
||||
polygon: [[0, 0], [data.draw_size[0], 0], [data.draw_size[0], data.draw_size[1]], [0, data.draw_size[1]]] // 以整图坐标为例
|
||||
}]
|
||||
}
|
||||
// 判断是否必须有直线,如人员计数、车辆计数等必须绘制虚拟直线
|
||||
let line = res.renderParams.bbox.lines;
|
||||
if (line && line.exits == 'must') {
|
||||
data.alg[alg].bbox.lines = [{
|
||||
id: `line_${new Date().getTime()}`,
|
||||
name: '',
|
||||
line: [[0, data.draw_size[1] / 2], [data.draw_size[0], data.draw_size[1] / 2]],
|
||||
direction: 'd+', // d+表示从上到下,
|
||||
action: { count: "统计" }
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果算法类型为match_开头,说明需要配置底库分组
|
||||
if (data.alg[alg].alg_type.indexOf('match_') >= 0) {
|
||||
let lib_type = data.alg[alg].alg_type.replace('match_', '');
|
||||
// 获取对应算法的底库组
|
||||
sourceApis.getGrpup(lib_type).then(res => {
|
||||
if(res.data.length > 0){
|
||||
let groupId = res.data[0].id;
|
||||
data.alg[alg].reserved_args.group_id = groupId;
|
||||
} else {
|
||||
this.showNotification(`没有查询到${lib_type}的底库分组,请先添加分组`, 'error');
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})(algEls[i].name);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// 监听表单提交
|
||||
form.on('submit(saveData)', (formData) => {
|
||||
let alg = data.alg;
|
||||
let algEls = document.querySelectorAll("#sel-algs-container input");
|
||||
for (let i = 0; i < algEls.length; i++) {
|
||||
// checked == false,未勾选的算法
|
||||
if (algEls[i].checked == false && data.alg[algEls[i].name]){
|
||||
delete alg[algEls[i].name]; // 删除多余算法参数
|
||||
}
|
||||
}
|
||||
this.handleFormSubmit(formData.field, data);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 提交:添加/编辑
|
||||
handleFormSubmit(formData, data) {
|
||||
// 验证数据
|
||||
if (!this.validateData(formData)) {
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
type: 'stream', // 仅以视频流为例
|
||||
desc: formData.desc,
|
||||
stream: formData.stream,
|
||||
alg: data.alg,
|
||||
draw_size: data.draw_size,
|
||||
encoding: data.encoding,
|
||||
video_record: 0,
|
||||
ipv4: '',
|
||||
name: '',
|
||||
info: { rtsp_transport: "tcp", username: "", password: "" }
|
||||
}
|
||||
|
||||
if (!data.id) {
|
||||
sourceApis.addSource(params).then(res => {
|
||||
this.getTableData();
|
||||
this.showNotification('数据添加成功!');
|
||||
// 关闭模态框
|
||||
layui.use('layer', () => {
|
||||
layui.layer.closeAll();
|
||||
});
|
||||
})
|
||||
} else {
|
||||
params.id = data.id;
|
||||
sourceApis.editSource(params).then(res => {
|
||||
this.getTableData();
|
||||
this.showNotification('数据更新成功!');
|
||||
// 关闭模态框
|
||||
layui.use('layer', () => {
|
||||
layui.layer.closeAll();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 验证数据
|
||||
validateData(data) {
|
||||
if (!data.desc || data.desc.length < 2) {
|
||||
this.showNotification('描述不能重复', 'error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
deleteData(id) {
|
||||
layui.use('layer', () => {
|
||||
layui.layer.confirm('确定要删除这条数据吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
sourceApis.delSource({ id: id }).then(res => {
|
||||
this.getTableData();
|
||||
this.showNotification('数据删除成功!');
|
||||
layui.layer.close(index);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
showNotification(message, type = 'success') {
|
||||
layui.use('layer', () => {
|
||||
layui.layer.msg(message, {
|
||||
icon: type === 'success' ? 1 : 2,
|
||||
time: 2000
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 等待Layui加载完成后初始化
|
||||
layui.use(['table', 'layer', 'form'], () => {
|
||||
// 初始化表格管理器
|
||||
window.tableManager = new LayuiTableManager();
|
||||
});
|
||||
|
||||
const sourceApis = {
|
||||
getSources: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.source}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getAlgs: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.alg}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getAlgJson: (alg_name) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}/algsjson/${alg_name}.json`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getAttr: (stream) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.attr}?stream=${stream}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
addSource: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.source}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
editSource: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.source}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
delSource: (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
contentType: "application/json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.source}`,
|
||||
data: JSON.stringify(data),
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
getGrpup: (type) => {
|
||||
// 获取底库分组
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: `http://${ZQLGLOBAL.serverIp}${ZQLGLOBAL.group}?alg=${type}`,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
error: function (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||