|
|
|
<template>
|
|
|
|
|
|
|
|
<div id="videoWrapper">
|
|
|
|
<canvas ref="videoCanvas" id="videoCanvas" :width="canvasWidth" :height="canvasHeight">
|
|
|
|
</canvas>
|
|
|
|
<video ref="videoPlayer" id="videoPlayer" disablePictureInPicture autoplay muted
|
|
|
|
@loadedmetadata="changeVideoCanvasSize" @mouseover="mouseOverVideo" @mouseout="mouseOutVideo"
|
|
|
|
@mousedown="mouseDownVideo">
|
|
|
|
</video>
|
|
|
|
<div v-for="item in labelList" class="labels" :key="item.id"
|
|
|
|
:style="`top:${canvasHeight * item.canvasTopRatio}px;left:${canvasWidth * item.canvasLeftRatio}px`">
|
|
|
|
<div class="labels-item" @click="test(item.id)" @contextmenu.prevent="mouseRightFn">
|
|
|
|
<!-- <div class="labels-item" v-if="item.inFlag"> -->
|
|
|
|
<img src="@/assets/images/dialog.png" width="80" height="50" alt="">
|
|
|
|
<span>{{ item.name }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="labels" v-if="addLabel.isAddLabel"
|
|
|
|
:style="`position: fixed;transform: translate(-50%,-100%);top:${addLabelTop}px;left:${addLabelLeft}px`">
|
|
|
|
<div class="labels-item">
|
|
|
|
<img src="@/assets/images/dialog.png" width="80" height="50" alt="">
|
|
|
|
<span>1</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomList">
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">标签标志</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">截图</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">录像</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">3D缩放</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">设备标记</div>
|
|
|
|
</div>
|
|
|
|
<div class="buttomItem">
|
|
|
|
<div class="buttomName">工具箱</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<CameraLeftMenu></CameraLeftMenu>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang='ts'>
|
|
|
|
////@ts-nocheck
|
|
|
|
import { ref, h, onMounted, getCurrentInstance, ComponentInternalInstance, onUnmounted, watch } from "vue";
|
|
|
|
import * as markLabelApi from '@/axios/cameraMark/markLabelApi';
|
|
|
|
import * as markSearchApi from '@/axios/core/markSearchApi';
|
|
|
|
import { useStore } from '@/store/index';
|
|
|
|
import { apiUrl } from "@/axios";
|
|
|
|
import { storeToRefs } from 'pinia';
|
|
|
|
import Msg from "@/utils/message";
|
|
|
|
import * as cameraApi from '@/axios/cameraBase/cameraApi';
|
|
|
|
import CameraLeftMenu from '@/views/page/aside/cameraLeftMenu.vue'
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
let piniaStore = useStore();
|
|
|
|
let player = <HTMLVideoElement>document.querySelector('#videoPlayer')
|
|
|
|
let canvas = <HTMLCanvasElement>document.querySelector('#videoCanvas')
|
|
|
|
let canvasWidth = ref(0)
|
|
|
|
let canvasHeight = ref(0)
|
|
|
|
let curSelectKey = storeToRefs(piniaStore).curSelectKey
|
|
|
|
let cameraMap = storeToRefs(piniaStore).cameraMap
|
|
|
|
let webRtcServer: any = ref()
|
|
|
|
let isActiveChoose = ref(false)
|
|
|
|
let labelList = ref<any[]>([])
|
|
|
|
let addLabel = storeToRefs(piniaStore).addLabel
|
|
|
|
let addLabelLeft = ref(0);
|
|
|
|
let addLabelTop = ref(0);
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
{
|
|
|
|
player = <HTMLVideoElement>document.querySelector('#videoPlayer');
|
|
|
|
canvas = <HTMLCanvasElement>document.querySelector('#videoCanvas');
|
|
|
|
loadVideoPlayer();
|
|
|
|
loadVideoCanvas();
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watch(curSelectKey, (newVal, oldVal) => {
|
|
|
|
switchCamera(newVal)
|
|
|
|
})
|
|
|
|
watch(addLabel, (newVal, oldVal) => {
|
|
|
|
console.log(newVal);
|
|
|
|
if (newVal.isAddLabel) {
|
|
|
|
isActiveChoose.value = true
|
|
|
|
document.body.onmousemove = (e) => {
|
|
|
|
addLabelTop.value = e.clientY
|
|
|
|
addLabelLeft.value = e.clientX
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
//测试删除标签
|
|
|
|
function test(id: number) {
|
|
|
|
|
|
|
|
markLabelApi.Delete({ id }).then((res: any) => {
|
|
|
|
console.log(res.data);
|
|
|
|
if (res.data.code == 200) {
|
|
|
|
let msg = res.data.data == true ? "删除成功" : "删除失败"
|
|
|
|
if (res.data.data) {
|
|
|
|
labelList.value = labelList.value.filter((element: any) => {
|
|
|
|
return element.id != id
|
|
|
|
});
|
|
|
|
Msg.success("删除成功")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Msg.error("删除失败")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function mouseRightFn(e: any) {
|
|
|
|
console.log(1111111111, e);
|
|
|
|
|
|
|
|
}
|
|
|
|
function loadVideoPlayer() {
|
|
|
|
let elmId = 'videoPlayer';
|
|
|
|
let url = apiUrl.WebRtcUrl;
|
|
|
|
webRtcServer.value = new WebRtcStreamer(elmId, url);
|
|
|
|
}
|
|
|
|
function loadVideoCanvas() {
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
changeVideoCanvasSize();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function changeVideoCanvasSize() {
|
|
|
|
canvasWidth.value = player.clientWidth;
|
|
|
|
canvasHeight.value = player.clientHeight;
|
|
|
|
}
|
|
|
|
function mouseOverVideo() {
|
|
|
|
// console.log("移入");
|
|
|
|
|
|
|
|
if (!isActiveChoose.value) return;
|
|
|
|
player.classList.add('activeChoose');
|
|
|
|
}
|
|
|
|
function mouseOutVideo() {
|
|
|
|
// console.log("移除");
|
|
|
|
|
|
|
|
if (!isActiveChoose) return;
|
|
|
|
player.classList.remove('activeChoose');
|
|
|
|
}
|
|
|
|
function mouseDownVideo(e: MouseEvent) {
|
|
|
|
console.log('mouseDownVideo');
|
|
|
|
|
|
|
|
if (!isActiveChoose.value) return;
|
|
|
|
let cameraId = cameraMap.value.get(curSelectKey.value).id;
|
|
|
|
let name = "1";
|
|
|
|
let videoWidth = player.videoWidth | 1920;
|
|
|
|
let videoHeight = player.videoHeight | 1080;
|
|
|
|
let canvasWidth = player.clientWidth;
|
|
|
|
let canvasHeight = player.clientHeight;
|
|
|
|
let canvasLeft = e.offsetX;
|
|
|
|
let canvasTop = e.offsetY;
|
|
|
|
let canvasLeftRatio = canvasLeft / canvasWidth;
|
|
|
|
let canvasTopRatio = canvasTop / canvasHeight;
|
|
|
|
let entity = {
|
|
|
|
CbCameraId: cameraId,
|
|
|
|
Name: name,
|
|
|
|
VideoWidth: videoWidth,
|
|
|
|
VideoHeight: videoHeight,
|
|
|
|
CanvasLeftRatio: canvasLeftRatio,
|
|
|
|
CanvasTopRatio: canvasTopRatio
|
|
|
|
}
|
|
|
|
markLabelApi.AddReturnId({
|
|
|
|
'entity': entity
|
|
|
|
}).then((res: any) => {
|
|
|
|
let markLabelId: number = res.data.data;
|
|
|
|
console.log(markLabelId);
|
|
|
|
|
|
|
|
if (markLabelId <= 0) return;
|
|
|
|
markSearchApi.ActivateMarkLabel({
|
|
|
|
'cameraId': cameraId,
|
|
|
|
'markLabelId': markLabelId
|
|
|
|
}).then((res: any) => {
|
|
|
|
|
|
|
|
let ret: boolean = res.data.data;
|
|
|
|
console.log(ret, 'ret');
|
|
|
|
if (ret) {
|
|
|
|
let obj = {
|
|
|
|
cbCameraId: cameraId,
|
|
|
|
videoWidth: videoWidth,
|
|
|
|
videoHeight: videoHeight,
|
|
|
|
canvasLeftRatio: canvasLeftRatio,
|
|
|
|
canvasTopRatio: canvasTopRatio,
|
|
|
|
id: markLabelId,
|
|
|
|
isDelete: false,
|
|
|
|
inFlag: true,
|
|
|
|
name: name,
|
|
|
|
}
|
|
|
|
labelList.value.push(obj)
|
|
|
|
console.log(obj, 'obj111111111111111');
|
|
|
|
isActiveChoose.value = false
|
|
|
|
piniaStore.updateIsAddLabel({ labelType: "", isAddLabel: false })
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
|
|
|
function activateChoose() {
|
|
|
|
if (curSelectKey.value.length == 0) return;
|
|
|
|
isActiveChoose.value = true;
|
|
|
|
}
|
|
|
|
function deactivateChoose() {
|
|
|
|
if (curSelectKey.value.length == 0) return;
|
|
|
|
isActiveChoose.value = false;
|
|
|
|
}
|
|
|
|
function switchCamera(cameraId: string) {
|
|
|
|
console.log('camera switch.',cameraId,typeof cameraId);
|
|
|
|
|
|
|
|
// step1, get camera obj.
|
|
|
|
console.log('get camera obj.',cameraMap.value);
|
|
|
|
let cameraObj = cameraMap.value.get(cameraId);
|
|
|
|
console.log(cameraObj,'cameraObj');
|
|
|
|
|
|
|
|
if (!cameraObj) {
|
|
|
|
console.log('camera obj not found.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// object to proxy, ?
|
|
|
|
console.log(cameraObj, '111111');
|
|
|
|
|
|
|
|
// step2, get camera rtsp url.
|
|
|
|
console.log('get camera rtsp url.');
|
|
|
|
let rtspUrl = getRtspUrl(cameraObj);
|
|
|
|
|
|
|
|
// step3, connect webrtc-steamer.
|
|
|
|
console.log('connect webrtc-steamer.');
|
|
|
|
webRtcServer.value.disconnect();
|
|
|
|
webRtcServer.value.connect(rtspUrl);
|
|
|
|
|
|
|
|
// step4, active camera searcher.
|
|
|
|
markSearchApi.IsExistsSearcher({
|
|
|
|
'cameraId': cameraObj.id
|
|
|
|
}).then((res: any) => {
|
|
|
|
let flag: boolean = res.data.data;
|
|
|
|
if (!flag) {
|
|
|
|
console.log('not exist searcher.');
|
|
|
|
markSearchApi.ActiveSearcher({
|
|
|
|
'cameraId': cameraObj.id
|
|
|
|
}).then((res: any) => {
|
|
|
|
let flag: boolean = res.data.data;
|
|
|
|
console.log('activate searcher : ', flag);
|
|
|
|
if (flag) {
|
|
|
|
console.log('load camera labels');
|
|
|
|
getLabel(cameraObj.id)
|
|
|
|
loadMarkLabelsByLoop(cameraObj);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
console.log('load camera labels');
|
|
|
|
getLabel(cameraObj.id)
|
|
|
|
loadMarkLabelsByLoop(cameraObj);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// step5, load camera labels.
|
|
|
|
}
|
|
|
|
function getRtspUrl(cameraObj: any): string {
|
|
|
|
// 模拟 onvif
|
|
|
|
// return `rtsp://admin:hk123456@192.168.1.65:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1`;
|
|
|
|
return `rtsp://${cameraObj.userName}:${cameraObj.password}@${cameraObj.ip}:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1`;
|
|
|
|
}
|
|
|
|
function getLabel(cbCameraId: string | number) {
|
|
|
|
markLabelApi.GetList({ cbCameraId }).then((res: any) => {
|
|
|
|
|
|
|
|
if (res.data.code == 200) {
|
|
|
|
console.log(res, 'res');
|
|
|
|
labelList.value = res.data.data
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
function loadMarkLabelsByLoop(cameraObj: any, ms: number = 1000) {
|
|
|
|
if (curSelectKey.value !== cameraObj.id) {
|
|
|
|
console.log('load camera labels end.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
loadMarkLabels(cameraObj);
|
|
|
|
setTimeout(() => {
|
|
|
|
loadMarkLabelsByLoop(cameraObj, ms);
|
|
|
|
}, ms);
|
|
|
|
}
|
|
|
|
function loadMarkLabels(cameraObj: any) {
|
|
|
|
markSearchApi.GetMarkLabelCalcResultList({
|
|
|
|
'cameraId': cameraObj.id
|
|
|
|
}).then((res: any) => {
|
|
|
|
let list: Array<any> = res.data.data;
|
|
|
|
console.log(list);
|
|
|
|
|
|
|
|
list.forEach((element: any) => {
|
|
|
|
labelList.value.forEach((item: any, index: number) => {
|
|
|
|
if (element.id == item.id) {
|
|
|
|
labelList.value[index].canvasLeftRatio = element.canvasLeftRatio
|
|
|
|
labelList.value[index].canvasTopRatio = element.canvasTopRatio
|
|
|
|
labelList.value[index].inFlag = element.inFlag
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
drawMarkLabels(list);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function drawMarkLabels(markLabels: Array<any>) {
|
|
|
|
let ctx = canvas.getContext('2d');
|
|
|
|
if (!ctx) return;
|
|
|
|
ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
|
|
|
|
for (let markLabel of markLabels) {
|
|
|
|
if (!markLabel.inFlag) continue;
|
|
|
|
let x = Math.round(canvasWidth.value * markLabel.canvasLeftRatio);
|
|
|
|
let y = Math.round(canvasHeight.value * markLabel.canvasTopRatio);
|
|
|
|
x = canvasWidth.value * markLabel.canvasLeftRatio;
|
|
|
|
y = canvasHeight.value * markLabel.canvasTopRatio;
|
|
|
|
// console.log(markLabel);
|
|
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(x, y, 5, 0, 2 * Math.PI);
|
|
|
|
ctx.fillStyle = "red";
|
|
|
|
ctx.fill();
|
|
|
|
ctx.closePath();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
{
|
|
|
|
console.log('beforeUnmount.');
|
|
|
|
// 释放 webRtcServer
|
|
|
|
if (!webRtcServer.value) return;
|
|
|
|
console.log(123);
|
|
|
|
webRtcServer.value.disconnect();
|
|
|
|
webRtcServer.value = null;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang='less'>
|
|
|
|
#videoWrapper {
|
|
|
|
height: 100vh;
|
|
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
#videoCanvas {
|
|
|
|
position: absolute;
|
|
|
|
}
|
|
|
|
|
|
|
|
#videoPlayer {
|
|
|
|
height: 100%;
|
|
|
|
width: 100%;
|
|
|
|
object-fit: fill;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.labels {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
transform: translate(-50%, -100%);
|
|
|
|
z-index: 2;
|
|
|
|
|
|
|
|
.labels-item {
|
|
|
|
position: relative;
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
span {
|
|
|
|
position: absolute;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.buttomList {
|
|
|
|
position: absolute;
|
|
|
|
right: 1%;
|
|
|
|
top: 15%;
|
|
|
|
width: 80px;
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes rounte {
|
|
|
|
from {
|
|
|
|
transform: rotate(0deg);
|
|
|
|
}
|
|
|
|
|
|
|
|
to {
|
|
|
|
transform: rotate(360deg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttomItem {
|
|
|
|
position: relative;
|
|
|
|
width: 80px;
|
|
|
|
height: 80px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttomItem::before {
|
|
|
|
content: '';
|
|
|
|
background: url(@/assets/images/buttonBG01.png);
|
|
|
|
background-size: 100% 100%;
|
|
|
|
position: absolute;
|
|
|
|
animation: rounte 5s linear infinite;
|
|
|
|
top: 5%;
|
|
|
|
bottom: 5%;
|
|
|
|
left: 5%;
|
|
|
|
right: 5%;
|
|
|
|
z-index: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttomItem:hover::before {
|
|
|
|
background: url(@/assets/images/buttonBG02.png);
|
|
|
|
background-size: 100% 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttomName {
|
|
|
|
position: absolute;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
text-align: center;
|
|
|
|
color: #fff;
|
|
|
|
line-height: 3vw;
|
|
|
|
width: 100%;
|
|
|
|
z-index: 2;
|
|
|
|
font-size: 0.8em;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
</style>
|