+
+
diff --git a/apps/web-antd/src/views/screen/monitor/map/Map.vue b/apps/web-antd/src/views/screen/monitor/map/Map.vue
new file mode 100644
index 00000000..a6c3478d
--- /dev/null
+++ b/apps/web-antd/src/views/screen/monitor/map/Map.vue
@@ -0,0 +1,131 @@
+
+
+
- 在线数量
+
+
+ 4
+ 7
+ 3
+ 个
南川区综合服务中心数智管理平台监控大屏
-
-
+ {{ weekDay }}
- 晴
- 40℃
-
-
-
+
+ 
- 退出
+
+
+
+
+
+
+
+
+ 监控编号 | +监控位置 | +监控范围 | +监控情况 | +
---|---|---|---|
0625142512 | +东区大门 | +右侧 | +正常 | +
0625342512 | +东区大门 | +左侧 | +正常 | +
0625142513 | +东区大门 | +正前方 | +正常 | +
0625142912 | +东区大门 | +马路 | +异常 | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+ 已处理
+ 8
+ 19.1
+
+
+ 未处理
+ 12
+ 19.1
+
+
+
+ 今日报警
+ 12
+ 19.1
+
+
完成率
+ 66%
+ 19.1
+
-
+
-
+
-
-
-
-
-
- 在线数量
-
-
- 4
- 7
- 3
- 个
-
-
-
-
-
-
-
- 监控编号 | -监控位置 | -监控范围 | -监控情况 | -
---|---|---|---|
0625142512 | -东区大门 | -右侧 | -正常 | -
0625342512 | -东区大门 | -左侧 | -正常 | -
0625142513 | -东区大门 | -正前方 | -正常 | -
0625142912 | -东区大门 | -马路 | -异常 | -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 
-
-
- 
-
-
-
-
- 
-
-
- 
-
-
-
-
- 
-
-
- 
-
+
-
-
-
-
-
-
-
-
-
- 已处理
- 8
- 19.1
-
-
- 未处理
- 12
- 19.1
-
-
-
-
- 今日报警
- 12
- 19.1
-
-
- 完成率
- 66%
- 19.1
-
-
-
-
-
-
-
- 报警时间 | -报警位置 | -报警内容 | -处理情况 | -
---|---|---|---|
06-25 14:25:12 | -东区大门 | -门禁异常 | -已处理 | -
06-25 14:25:12 | -东区大门 | -门禁异常 | -已处理 | -
06-25 14:25:12 | -东区大门 | -门禁异常 | -已处理 | -
06-25 14:25:12 | -东区大门 | -门禁异常 | -未处理 | -
-
-
-
-
-
-
-
-
-
- 
-
- 设备总数
- 650
-
-
-
-
-
- 
-
- 设备在线数
- 632
-
-
-
-
-
- 
-
- 设备离线数
- 18
-
+
+
+
+
+
+ 报警时间 | +报警位置 | +报警内容 | +处理情况 | +
---|---|---|---|
06-25 14:25:12 | +东区大门 | +门禁异常 | +已处理 | +
06-25 14:25:12 | +东区大门 | +门禁异常 | +已处理 | +
06-25 14:25:12 | +东区大门 | +门禁异常 | +已处理 | +
06-25 14:25:12 | +东区大门 | +门禁异常 | +未处理 | +
+
+
+
+
+
+
+
+
+ 
+
+ 设备总数
+ 650
+
+
+
+
+
+ 
+
+ 设备在线数
+ 632
+
+
+
+
+
+ 
+
+ 设备离线数
+ 18
+
+
+
+
+
diff --git a/apps/web-antd/src/views/screen/monitor/map/constants.js b/apps/web-antd/src/views/screen/monitor/map/constants.js
new file mode 100644
index 00000000..cca3ec5a
--- /dev/null
+++ b/apps/web-antd/src/views/screen/monitor/map/constants.js
@@ -0,0 +1,16 @@
+import camera from "#/assets/map/camear.png";
+
+export const MapDefaultData = {
+ center: [107.089,29.1714],
+ zoom: 21,
+ icons: {camera}
+};
+
+
+export const icons = {
+ camera: new BMap.Icon(MapDefaultData.icons.camera, new BMap.Size(23, 34), {
+ offset: new BMap.Size(12, 30),
+ textColor: '#fff',
+ zIndex: "100"
+ })
+}
diff --git a/apps/web-antd/src/views/screen/monitor/map/map.scss b/apps/web-antd/src/views/screen/monitor/map/map.scss
new file mode 100644
index 00000000..828858fa
--- /dev/null
+++ b/apps/web-antd/src/views/screen/monitor/map/map.scss
@@ -0,0 +1,89 @@
+.vmap {
+ position: relative;
+ width: 100%;
+ height: 100%;
+
+ .BMap_pop > div {
+ background: transparent !important;
+ border: 0 !important;
+ }
+
+ .BMap_pop > div:nth-child(n) {
+ display: none
+ }
+
+ .BMap_pop > div:nth-child(9) {
+ display: block
+ }
+
+ .BMap_pop > img {
+ width: 0 !important;
+ height: 0 !important;
+ }
+
+ #map {
+ width: 100%;
+ height: 100%;
+
+ .anchorBL {
+ display: none !important;
+ }
+
+ .close {
+ position: absolute;
+ right: 12px;
+ top: 5px;
+ font-size: 18px;
+ cursor: pointer;
+ border: 1px solid #409eff;
+ color: #409eff;
+ padding: 2px 5px;
+ text-align: center;
+ z-index: 100;
+ }
+
+ .wrap-title {
+ margin-top: 16px;
+ font-size: 16px;
+ color: white;
+ position: absolute;
+ height: 30px;
+ width: 100%;
+ line-height: 30px;
+ left: 20px;
+ font-weight: bold;
+ }
+
+ .video-wrap {
+ width: 530px !important;
+ height: 360px;
+ background: url("/src/assets/map/video-bg.png") no-repeat;
+ background-size: 100% 100%;
+
+ .wrap-content {
+ padding: 15px 10px 10px 10px;
+ height: 75%;
+ display: flex;
+
+ .content-left {
+ height: 100%;
+ width: 100%;
+ }
+
+ img {
+ width: 100%;
+ height: 111%;
+ }
+
+ .content-right {
+ margin-left: 15px;
+ padding-top: 10px;
+ }
+
+ }
+
+
+ }
+ }
+
+}
diff --git a/apps/web-antd/src/views/sis/acAdmin/dp-tree.vue b/apps/web-antd/src/views/sis/acAdmin/dp-tree.vue
index 790828d6..5ac53c85 100644
--- a/apps/web-antd/src/views/sis/acAdmin/dp-tree.vue
+++ b/apps/web-antd/src/views/sis/acAdmin/dp-tree.vue
@@ -9,14 +9,7 @@ defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
-const emit = defineEmits<{
- checked: [];
- /**
- * 点击节点的事件
- */
- reload: [];
- select: [];
-}>();
+const emit = defineEmits(['checked', 'reload', 'select']);
const searchValue = defineModel('searchValue', {
type: String,
@@ -39,6 +32,7 @@ async function loadChannelTree() {
function handleNode(nodes: any[], level: number) {
nodes.forEach((node) => {
+ node.key = node.id;
if (node.level < level) {
node.disabled = true;
}
@@ -107,7 +101,13 @@ function checkNodeData() {
const checkData: any = {};
+/**
+ * 树选中时间
+ * @param _keys 当前选中的节点key
+ * @param nodes 当前选中的节点
+ */
function onTreeCheck(_keys: any, nodes: any) {
+ // nodes 为当前当前选中的节点
const { checked, checkedNodes } = nodes;
// 找到需要播放的视频节点
checkedNodes.forEach((node: any) => {
@@ -119,8 +119,7 @@ function onTreeCheck(_keys: any, nodes: any) {
delete checkData[id];
}
});
- const data = toRaw(checkedNodes);
- emit('checked', checked, data);
+ emit('checked', _keys, nodes);
}
onMounted(loadChannelTree);
diff --git a/apps/web-antd/src/views/sis/acAdmin/index.vue b/apps/web-antd/src/views/sis/acAdmin/index.vue
index 2274bee2..dd871237 100644
--- a/apps/web-antd/src/views/sis/acAdmin/index.vue
+++ b/apps/web-antd/src/views/sis/acAdmin/index.vue
@@ -1,7 +1,7 @@
-
+
import DpTree from './dp-tree.vue';
import { Page } from '@vben/common-ui';
-import { ref } from 'vue';
+import { onMounted, onUnmounted, ref, toRaw } from 'vue';
import mpegts from 'mpegts.js';
-import { addStreamProxy } from '#/api/sis/stream';
+import { addFFmpegStreamProxy, addStreamProxy } from '#/api/sis/stream';
import { message } from 'ant-design-vue';
+import { checkHEVCSupport } from '#/utils/video';
+import type { AddStreamProxyResult } from '#/api/sis/stream/model';
/**
* 屏幕播放器数量
*/
const selected = 'selected';
-const playerNum = ref(4);
+const playerNum = ref(1);
/**
* 屏幕播放器样式
*/
const playerStyle = ref({
- width: '50%',
- height: '50%',
+ width: '100%',
+ height: '100%',
});
const currentSelectPlayerIndex = ref(-1);
@@ -61,28 +63,112 @@ const setItemRef = (el: any) => {
}
};
-function onNodeChecked(checked, nodes: any[]) {
+/**
+ * 处理带有子节点的数据
+ * @param node
+ * @param newNode
+ */
+function handleParentNoe(node: any, newNode: any[] = []) {
+ node.forEach((item: any) => {
+ if (item.level === 6) {
+ newNode.push(toRaw(item.data));
+ }
+ if (item.children && item.children.length >= 1) {
+ handleParentNoe(item.children, newNode);
+ }
+ });
+}
+
+/**
+ * 节点选中时间处理
+ * @param _val 选中节点id
+ * @param checked 是否选中
+ * @param node 节点数据
+ */
+function onNodeChecked(
+ _val: any,
+ { checked, node }: { checked: boolean; node: any },
+) {
+ // 此次操作需要新增或者删除节点
+ let checkNode: any = [];
+ handleParentNoe([node], checkNode);
+ // 新增
if (checked) {
- console.log(nodes);
- nodes.forEach((node: any) => {
- const { data, level } = node;
- // 只播放视频节点
- if (level == 6) {
- const index =
- currentSelectPlayerIndex.value === -1
- ? 0
- : currentSelectPlayerIndex.value;
- doPlayer(data, index);
+ /**
+ * 如果当前页面有选择播放未知,并且播放视频只有一个,则播放到制定位置
+ */
+ if (currentSelectPlayerIndex.value !== -1 && checkNode.length == 1) {
+ doPlayer(checkNode[0], currentSelectPlayerIndex.value - 1);
+ }
+ // 批量播放 currentSelectPlayerIndex 将不再生效
+ else {
+ // 如果此次播放数量小于当前播能播放
+ const freeArr: number[] = []; // 空闲播放器数量
+ for (let i = 0; i < playerNum.value; i++) {
+ const playerData = playerList[i];
+ if (!playerData) {
+ freeArr.push(i);
+ }
+ }
+ // 要播放的视频数量,小于等于空闲播放器数量,则填充空闲即可
+ if (checkNode.length <= freeArr.length) {
+ for (let j = 0; j < checkNode.length; j++) {
+ doPlayer(checkNode[j], freeArr[j]);
+ }
+ }
+ // 直接覆盖原有的播放视频
+ else {
+ for (let i = 0; i < playerNum.value; i++) {
+ doPlayer(checkNode[i], i);
+ }
+ }
+ }
+ }
+ // 删除
+ else {
+ checkNode.forEach((item: any) => {
+ for (let i = 0; i < playerNum.value; i++) {
+ const player = playerList[i];
+ if (player && player.data.id === item.id) {
+ closePlayer(i);
+ }
}
});
- } else {
-
}
}
// 播放器数据, 每一个位置代表页面上行的一个矩形
const playerList: any[] = [];
+function streamProxy(nodeData: any, cb: Function) {
+ let params = {};
+ if (nodeData.nvrIp) {
+ params = {
+ videoIp: nodeData.nvrIp,
+ videoPort: nodeData.nvrPort,
+ factoryNo: nodeData.nvrFactoryNo,
+ account: nodeData.nvrAccount,
+ pwd: nodeData.nvrPwd,
+ channelId: nodeData.nvrChannelNo,
+ };
+ } else {
+ params = {
+ videoIp: nodeData.deviceIp,
+ videoPort: nodeData.devicePort,
+ factoryNo: nodeData.factoryNo,
+ account: nodeData.deviceAccount,
+ pwd: nodeData.devicePwd,
+ channelId: nodeData.channelNo,
+ };
+
+ if (isSupportH265) {
+ addStreamProxy(params).then((res) => cb(res));
+ } else {
+ addFFmpegStreamProxy(params).then((res) => cb(res));
+ }
+ }
+}
+
/**
* 开始播放视频流
* @param nodeData 播放的节点数据
@@ -91,16 +177,7 @@ const playerList: any[] = [];
function doPlayer(nodeData: any, index: number = 0) {
console.log('index=', index);
if (mpegts.isSupported()) {
- const params = {
- videoIp: nodeData.deviceIp,
- videoPort: 554,
- factoryNo: nodeData.factoryNo,
- account: nodeData.deviceAccount,
- pwd: nodeData.devicePwd,
- channelId: nodeData.channelNo ? nodeData.channelNo : 101,
- };
- // }
- addStreamProxy(params).then((res) => {
+ streamProxy(nodeData, (res: AddStreamProxyResult) => {
const url = res.wsFlv;
// 将url 绑定到 nodeData
nodeData.url = url;
@@ -129,6 +206,7 @@ function doPlayer(nodeData: any, index: number = 0) {
playerList[index] = {
player,
data: nodeData,
+ el: videoElement,
};
} else {
console.log('视频播放元素获取异常');
@@ -139,54 +217,6 @@ function doPlayer(nodeData: any, index: number = 0) {
}
}
-function changeElPlayer(playerInfo: any, index: number) {
- const playerData = playerInfo.data;
- const oldPlayer = playerInfo.player;
- if (oldPlayer) {
- closePlayVieo(oldPlayer);
- }
- const videoConfig = {
- type: 'flv',
- url: playerData.url,
- isLive: true,
- hasAudio: false,
- hasVideo: true,
- enableWorker: true, // 启用分离的线程进行转码
- enableStashBuffer: false, // 关闭IO隐藏缓冲区
- stashInitialSize: 256, // 减少首帧显示等待时长
- };
- const playerConfig = {
- enableErrorRecover: true, // 启用错误恢复
- autoCleanupMaxBackwardDuration: 30,
- autoCleanupMinBackwardDuration: 10,
- };
- const player = mpegts.createPlayer(videoConfig, playerConfig);
- const videoElement = itemRefs.value[index];
- if (videoElement) {
- player.attachMediaElement(videoElement);
- player.load();
- player.play();
- playerList[index] = {
- player,
- data: playerData,
- };
- } else {
- console.log('视频播放元素获取异常');
- }
-}
-
-function closePlayVieo(plInfo: any) {
- if (plInfo) {
- try {
- plInfo.pause(); // 暂停
- plInfo.unload(); // 卸载
- plInfo.destroy(); // 销毁
- } catch (e) {
- console.log('播放器关闭失败,e=', e);
- }
- }
-}
-
function closePlayer(index: number) {
// 如果播放器存在,尝试关闭
const pData = playerList[index];
@@ -202,6 +232,33 @@ function closePlayer(index: number) {
}
}
}
+
+function catchUp() {
+ playerList.forEach((playerData) => {
+ if (playerData) {
+ const { player, el } = playerData;
+ const end = player.buffered.end(player.buffered.length - 1);
+ const diff = end - el.currentTime;
+ if (diff > 2) {
+ // 如果延迟超过2秒
+ el.currentTime = end - 0.5; // 跳转到接近直播点
+ }
+ }
+ });
+}
+
+let isSupportH265 = false;
+onMounted(() => {
+ // 检测浏览器是否支持h265
+ isSupportH265 = checkHEVCSupport();
+ setInterval(catchUp, 10000);
+});
+
+onUnmounted(() => {
+ for (let i = 0; i < playerList.length; i++) {
+ closePlayer(i);
+ }
+});