Compare commits
1 Commits
5ef955f1fc
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5144bc9a7d |
@@ -5,10 +5,6 @@
|
|||||||
"ComputedRef": true,
|
"ComputedRef": true,
|
||||||
"DirectiveBinding": true,
|
"DirectiveBinding": true,
|
||||||
"EffectScope": true,
|
"EffectScope": true,
|
||||||
"ElLoading": true,
|
|
||||||
"ElMessage": true,
|
|
||||||
"ElMessageBox": true,
|
|
||||||
"ElNotification": true,
|
|
||||||
"ExtractDefaultPropTypes": true,
|
"ExtractDefaultPropTypes": true,
|
||||||
"ExtractPropTypes": true,
|
"ExtractPropTypes": true,
|
||||||
"ExtractPublicPropTypes": true,
|
"ExtractPublicPropTypes": true,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
v-model:visible="popoverVisible"
|
v-model:visible="popoverVisible"
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-badge :value="infoData" :max="9999">
|
<el-badge :value="infoData" :max="999">
|
||||||
<div class="right-menu-item hover-effect" @click="goToWarningList">
|
<div class="right-menu-item hover-effect" @click="goToWarningList">
|
||||||
<el-icon><BellFilled /></el-icon>
|
<el-icon><BellFilled /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import Hls from 'hls.js';
|
import Hls from 'hls.js';
|
||||||
import { log } from 'node:console';
|
|
||||||
let hlsInstance: Hls | null = null;
|
let hlsInstance: Hls | null = null;
|
||||||
// let hlsRetryCount = 0;
|
|
||||||
|
|
||||||
export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
||||||
const url = monitorVideoUrl.value;
|
const url = monitorVideoUrl.value;
|
||||||
@@ -19,16 +17,7 @@ export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Hls.isSupported()) {
|
if (Hls.isSupported()) {
|
||||||
const hls = new Hls({
|
const hls = new Hls();
|
||||||
// ★ ZLM 注册流需要时间,配置自动重试
|
|
||||||
manifestLoadingMaxRetry: 5, // m3u8 加载最多重试20次
|
|
||||||
manifestLoadingRetryDelay: 500, // 每次重试间隔500ms
|
|
||||||
manifestLoadingTimeOut: 5000, // m3u8 请求超时5秒
|
|
||||||
levelLoadingMaxRetry: 2, // ts 分片加载最多重试20次
|
|
||||||
levelLoadingRetryDelay: 500,
|
|
||||||
lowLatencyMode: false, // 关掉低延迟模式,更稳定
|
|
||||||
startLevel: -1 // 自动选择码率
|
|
||||||
});
|
|
||||||
hls.loadSource(url);
|
hls.loadSource(url);
|
||||||
hls.attachMedia(video);
|
hls.attachMedia(video);
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
@@ -36,8 +25,6 @@ export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
|||||||
try {
|
try {
|
||||||
video.play().catch(() => {});
|
video.play().catch(() => {});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
// 加载成功,重置重试计数
|
|
||||||
// hlsRetryCount = 0;
|
|
||||||
});
|
});
|
||||||
hlsInstance = hls;
|
hlsInstance = hls;
|
||||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
@@ -69,9 +56,7 @@ export const destroyHls = (monitorVideoRef) => {
|
|||||||
video.removeAttribute('src');
|
video.removeAttribute('src');
|
||||||
// 对某些浏览器,需要调用 load() 来重置 media element
|
// 对某些浏览器,需要调用 load() 来重置 media element
|
||||||
if (typeof video.load === 'function') video.load();
|
if (typeof video.load === 'function') video.load();
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
console.log('Error in destroyHls:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<el-table-column label="所属设备" align="center" prop="deviceName" min-width="120" show-overflow-tooltip />
|
<el-table-column label="所属设备" align="center" prop="deviceName" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="报警区域" align="center" prop="monitoringArea" min-width="120" show-overflow-tooltip />
|
<el-table-column label="报警区域" align="center" prop="monitoringArea" min-width="120" show-overflow-tooltip />
|
||||||
<!-- pay content -->
|
<!-- pay content -->
|
||||||
<!-- <el-table-column label="报警图片" align="center" prop="alarmImage" min-width="120">
|
<el-table-column label="报警图片" align="center" prop="alarmImage" min-width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="scope.row.alarmImage"
|
v-if="scope.row.alarmImage"
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
/>
|
/>
|
||||||
<span v-else>暂无图片</span>
|
<span v-else>暂无图片</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column> -->
|
</el-table-column>
|
||||||
<el-table-column label="处理状态" align="center" prop="status" width="100">
|
<el-table-column label="处理状态" align="center" prop="status" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.status === 0" type="warning">未处理</el-tag>
|
<el-tag v-if="scope.row.status === 0" type="warning">未处理</el-tag>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="设备型号" align="center" prop="model" />
|
<el-table-column label="设备型号" align="center" prop="model" />
|
||||||
<el-table-column label="固件版本" align="center" prop="version" />
|
<el-table-column label="固件版本" align="center" prop="id" />
|
||||||
<el-table-column label="是否绘制区域" align="center" prop="isDrawArea">
|
<el-table-column label="是否绘制区域" align="center" prop="isDrawArea">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.isDrawArea === 1 || scope.row.isDrawArea === '1'">是</el-tag>
|
<el-tag v-if="scope.row.isDrawArea === 1 || scope.row.isDrawArea === '1'">是</el-tag>
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
@click="handleActivate(scope.row)"
|
@click="handleActivate(scope.row)"
|
||||||
>唤醒设备</el-button
|
>唤醒设备</el-button
|
||||||
>
|
>
|
||||||
<el-button :disabled="scope.row.status !== 2 && scope.row.status !== '2'" type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -134,7 +134,6 @@
|
|||||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||||
<span class="truncate">{{ videoName }}-{{ monitoringArea }}监控</span>
|
<span class="truncate">{{ videoName }}-{{ monitoringArea }}监控</span>
|
||||||
<div>
|
<div>
|
||||||
<el-button type="primary" class="shrink-0" @click="handleRefresh">刷新</el-button>
|
|
||||||
<el-button v-if="!seeSee" type="primary" class="shrink-0" @click="drawImage()">绘制区域</el-button>
|
<el-button v-if="!seeSee" type="primary" class="shrink-0" @click="drawImage()">绘制区域</el-button>
|
||||||
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="clearImage()">清空区域</el-button>
|
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="clearImage()">清空区域</el-button>
|
||||||
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="saveImage()">保存区域</el-button>
|
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="saveImage()">保存区域</el-button>
|
||||||
@@ -159,7 +158,7 @@ import { EquipmentList, DeviceInfoForm, AlarmAreaData } from '@/api/equipmentLis
|
|||||||
import { listDemo, getDemo, delDemo, addDemo, updateDemo } from '@/api/demo/demo';
|
import { listDemo, getDemo, delDemo, addDemo, updateDemo } from '@/api/demo/demo';
|
||||||
import { DemoVO, DemoQuery, DemoForm } from '@/api/demo/demo/types';
|
import { DemoVO, DemoQuery, DemoForm } from '@/api/demo/demo/types';
|
||||||
import { log } from 'node:console';
|
import { log } from 'node:console';
|
||||||
import { initHls, destroyHls } from '@/utils/video';
|
import Hls from 'hls.js';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { useVideosStore } from '@/store/modules/video';
|
import { useVideosStore } from '@/store/modules/video';
|
||||||
|
|
||||||
@@ -191,9 +190,68 @@ const dialog1 = reactive<DialogOption>({
|
|||||||
|
|
||||||
const monitorVideoUrl = ref('');
|
const monitorVideoUrl = ref('');
|
||||||
const monitorVideoRef = ref<HTMLVideoElement>();
|
const monitorVideoRef = ref<HTMLVideoElement>();
|
||||||
|
let hlsInstance: Hls | null = null;
|
||||||
let visibilityHandler: (() => void) | null = null;
|
let visibilityHandler: (() => void) | null = null;
|
||||||
const monitoringArea = ref<string>('');
|
const monitoringArea = ref<string>('');
|
||||||
|
|
||||||
|
const initHls = () => {
|
||||||
|
const url = monitorVideoUrl.value;
|
||||||
|
const video = monitorVideoRef.value;
|
||||||
|
if (!url || !video) return;
|
||||||
|
|
||||||
|
// 销毁已有实例
|
||||||
|
if (hlsInstance) {
|
||||||
|
try {
|
||||||
|
hlsInstance.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
hlsInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls();
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
// 自动播放(若浏览器允许)
|
||||||
|
try {
|
||||||
|
video.play().catch(() => {});
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
hlsInstance = hls;
|
||||||
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
// Safari/原生支持 HLS
|
||||||
|
video.src = url;
|
||||||
|
video.addEventListener('loadedmetadata', () => {
|
||||||
|
try {
|
||||||
|
video.play().catch(() => {});
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
proxy?.$modal.msgWarning('当前浏览器不支持 HLS 播放');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const destroyHls = () => {
|
||||||
|
try {
|
||||||
|
if (hlsInstance) {
|
||||||
|
hlsInstance.destroy();
|
||||||
|
hlsInstance = null;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
const video = monitorVideoRef.value;
|
||||||
|
if (video) {
|
||||||
|
// 清理原生 src,避免残留
|
||||||
|
try {
|
||||||
|
video.pause();
|
||||||
|
video.removeAttribute('src');
|
||||||
|
// 对某些浏览器,需要调用 load() 来重置 media element
|
||||||
|
if (typeof video.load === 'function') video.load();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
type MonitorPoint = { x: number; y: number };
|
type MonitorPoint = { x: number; y: number };
|
||||||
const monitorDrawCollecting = ref(false);
|
const monitorDrawCollecting = ref(false);
|
||||||
const monitorDrawPoints = ref<MonitorPoint[]>([]);
|
const monitorDrawPoints = ref<MonitorPoint[]>([]);
|
||||||
@@ -361,7 +419,7 @@ const handleSee = async (row?: any) => {
|
|||||||
dialog1.visible = true;
|
dialog1.visible = true;
|
||||||
dialog1.title = '修改设备';
|
dialog1.title = '修改设备';
|
||||||
|
|
||||||
destroyHls(monitorVideoRef);
|
destroyHls();
|
||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
await videosStore.getVideos(row.id);
|
await videosStore.getVideos(row.id);
|
||||||
monitorVideoUrl.value = videosStore.hisUrl || '';
|
monitorVideoUrl.value = videosStore.hisUrl || '';
|
||||||
@@ -374,7 +432,7 @@ const handleSee = async (row?: any) => {
|
|||||||
|
|
||||||
// 等待 DOM 更新后初始化 Hls
|
// 等待 DOM 更新后初始化 Hls
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initHls(monitorVideoUrl, monitorVideoRef);
|
initHls();
|
||||||
// 等待视频加载完成后初始化 ResizeObserver,以便能正确获取视频尺寸
|
// 等待视频加载完成后初始化 ResizeObserver,以便能正确获取视频尺寸
|
||||||
initVideoResizeObserver();
|
initVideoResizeObserver();
|
||||||
|
|
||||||
@@ -507,7 +565,7 @@ const cleanupVisibilityChange = () => {
|
|||||||
|
|
||||||
const handleMonitorDialogClose = (done: () => void) => {
|
const handleMonitorDialogClose = (done: () => void) => {
|
||||||
// 卸载 Hls 与清理 video
|
// 卸载 Hls 与清理 video
|
||||||
destroyHls(monitorVideoRef);
|
destroyHls();
|
||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
destroyVideoResizeObserver();
|
destroyVideoResizeObserver();
|
||||||
resetMonitorDraw();
|
resetMonitorDraw();
|
||||||
@@ -652,7 +710,7 @@ const saveImage = async () => {
|
|||||||
|
|
||||||
const handleMonitorDialogOpen = () => {
|
const handleMonitorDialogOpen = () => {
|
||||||
// 初始化 Hls 与 video
|
// 初始化 Hls 与 video
|
||||||
initHls(monitorVideoUrl, monitorVideoRef);
|
initHls();
|
||||||
initVideoResizeObserver();
|
initVideoResizeObserver();
|
||||||
monitorDrawCollecting.value = false;
|
monitorDrawCollecting.value = false;
|
||||||
monitorDrawPoints.value = [];
|
monitorDrawPoints.value = [];
|
||||||
@@ -689,19 +747,6 @@ watch(
|
|||||||
videoName.value = newValue === 1 ? '绘制区域' : '实时监控';
|
videoName.value = newValue === 1 ? '绘制区域' : '实时监控';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/** 刷新按钮操作 */
|
|
||||||
const handleRefresh = async () => {
|
|
||||||
if (!form.value.id) return;
|
|
||||||
destroyHls(monitorVideoRef);
|
|
||||||
monitorVideoUrl.value = '';
|
|
||||||
await videosStore.getVideos(form.value.id);
|
|
||||||
monitorVideoUrl.value = videosStore.hisUrl || '';
|
|
||||||
if (monitorVideoUrl.value) {
|
|
||||||
await nextTick();
|
|
||||||
initHls(monitorVideoUrl, monitorVideoRef);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="flex justify-end mb8">
|
<el-row :gutter="10" class="flex justify-end mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- <el-col :span="1.5">
|
<!-- <el-col :span="1.5">
|
||||||
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
|
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="flex justify-end mb8">
|
<el-row :gutter="10" class="flex justify-end mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- <el-col :span="1.5">
|
<!-- <el-col :span="1.5">
|
||||||
<el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
|
<el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
|
||||||
@@ -123,10 +123,10 @@
|
|||||||
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
|
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<!-- <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top"> -->
|
<!-- <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top"> -->
|
||||||
<el-button type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
|
<el-button v-hasPermi="['system:user:edit']" type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
|
||||||
<!-- </el-tooltip>
|
<!-- </el-tooltip>
|
||||||
<el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top"> -->
|
<el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top"> -->
|
||||||
<el-button type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button v-hasPermi="['system:user:remove']" type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
<!-- </el-tooltip> -->
|
<!-- </el-tooltip> -->
|
||||||
|
|
||||||
<!-- <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
|
<!-- <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
|
||||||
|
|||||||
Reference in New Issue
Block a user