From 8cf6df683d82103a04dab9644efda632e812e91e Mon Sep 17 00:00:00 2001 From: lxj <15683799673@163.com> Date: Tue, 12 Aug 2025 15:47:15 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/sis/stream/index.ts | 11 ++ .../src/views/screen/monitor/map/Map.vue | 149 +++++++++++++++--- .../src/views/screen/monitor/map/map.scss | 5 + apps/web-antd/src/views/sis/acAdmin/index.vue | 2 +- apps/web-antd/src/views/sis/video/index.vue | 17 +- 5 files changed, 158 insertions(+), 26 deletions(-) diff --git a/apps/web-antd/src/api/sis/stream/index.ts b/apps/web-antd/src/api/sis/stream/index.ts index c233a1f5..87e18d94 100644 --- a/apps/web-antd/src/api/sis/stream/index.ts +++ b/apps/web-antd/src/api/sis/stream/index.ts @@ -19,3 +19,14 @@ export function addFFmpegStreamProxy(params?: any) { params, ); } + +export function addMediaStreamProxy(params?: any) { + return requestClient.post('sis/stream/proxy', params); +} + +export function addFFmpegMediaStreamProxy(params?: any) { + return requestClient.post( + 'sis/stream/ffmpeg/proxy', + params, + ); +} diff --git a/apps/web-antd/src/views/screen/monitor/map/Map.vue b/apps/web-antd/src/views/screen/monitor/map/Map.vue index a6c3478d..f56abc0f 100644 --- a/apps/web-antd/src/views/screen/monitor/map/Map.vue +++ b/apps/web-antd/src/views/screen/monitor/map/Map.vue @@ -8,13 +8,42 @@ import './map.scss'; import { icons, MapDefaultData } from './constants.js'; import { onMounted } from 'vue'; import { deviceManageList } from '#/api/sis/deviceManage/index.js'; +import { deviceChannelList } from '#/api/sis/deviceChannel/index.js'; +import mpegts from 'mpegts.js'; +import { message } from 'ant-design-vue'; +import { + addFFmpegMediaStreamProxy, + addMediaStreamProxy, +} from '#/api/sis/stream/index.js'; +import { checkHEVCSupport } from '#/utils/video.js'; // 地图全局对象 let map = null; - +let currentPlayer; +let videoElement; +let isSupportH265 = false; onMounted(() => { + // 检测浏览器是否支持h265 + isSupportH265 = checkHEVCSupport(); mapFn.intiMap(); // 加载设备信息 + loadCameraData(); + // 增加视频追帧操作 + setInterval(catchUp, 10000); +}); + +function catchUp() { + if (currentPlayer) { + const end = currentPlayer.buffered.end(player.buffered.length - 1); + const diff = end - videoElement.currentTime; + if (diff > 2) { + // 如果延迟超过2秒 + videoElement.currentTime = end - 0.5; // 跳转到接近直播点 + } + } +} + +function loadCameraData() { deviceManageList({ deviceType: 1 }).then(({ rows = [], total = 0 }) => { if (total > 0) { // 渲染设备点位信息 @@ -29,39 +58,111 @@ onMounted(() => { mapFn.renderOverLayersData(items, function (marker, data) { // 渲染设备点击事件 marker.addEventListener('click', () => { - console.log(data); // 查询当前设备的通道信息 - - //渲染设备播放列表 - const html = [ - '
', - `
${data.deviceName}
`, - `
X
`, - `
`, - `
`, - '', - `
`, - `
`, - `
`, - ]; - const infoWindow = new BMap.InfoWindow(html.join(''), { - width: 530, - height: 350, - enableCloseOnClick: false, - }); - map.openInfoWindow(infoWindow, marker.point); + const params = { + deviceIp: data.deviceIp, + pageNum: 1, + pageSize: 10, + }; + deviceChannelList({ deviceIp: data.deviceIp }).then( + ({ total, rows }) => { + console.log(data); + const deviceData = { ...data }; + if (total > 0) { + //渲染设备播放列表 + const deviceName = deviceData.deviceName; + const html = [ + '
', + `
${deviceName}
`, + `
X
`, + `
`, + `
`, + '', + `
`, + `
`, + `
`, + ]; + const infoWindow = new BMap.InfoWindow(html.join(''), { + width: 530, + height: 350, + enableCloseOnClick: false, + }); + map.openInfoWindow(infoWindow, marker.point); + const data = rows[0]; + const params = {}; + if (data.nvrIp) { + params.deviceIp = data.nvrIp; + params.channelNo = data.nvrChannelNo; + } else { + params.deviceIp = data.deviceIp; + params.channelNo = data.channelNo; + } + doPlayer(params); + } + }, + ); }); }); } }); -}); -// 当前页面播放的对象 -let currentPlayer = undefined; +} +/** + * 开始播放视频流 + * @param nodeData 播放的节点数据 + */ +function doPlayer(nodeData) { + if (mpegts.isSupported()) { + streamProxy(nodeData, (res) => { + const url = res.flv; + // 将url 绑定到 nodeData + nodeData.url = url; + closeVideo(currentPlayer); + const videoConfig = { + type: 'flv', + url: 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 = document.getElementById('video-player'); + if (videoElement) { + player.attachMediaElement(videoElement); + player.load(); + player.play(); + } else { + console.log('视频播放元素获取异常'); + } + }); + } else { + message.error('浏览器不支持播放'); + } +} + +function streamProxy(params, cb) { + if (isSupportH265) { + addMediaStreamProxy(params).then((res) => cb(res)); + } else { + // addMediaStreamProxy(params).then((res) => cb(res)); + addFFmpegMediaStreamProxy(params).then((res) => cb(res)); + } +} + +// 当前页面播放的对象 window.pageEvent = { closeInfoWindow() { map.closeInfoWindow(); closeVideo(currentPlayer); + currentPlayer = null; }, }; diff --git a/apps/web-antd/src/views/screen/monitor/map/map.scss b/apps/web-antd/src/views/screen/monitor/map/map.scss index 828858fa..1174a593 100644 --- a/apps/web-antd/src/views/screen/monitor/map/map.scss +++ b/apps/web-antd/src/views/screen/monitor/map/map.scss @@ -75,6 +75,11 @@ height: 111%; } + video { + width: 100%; + height: 100%; + } + .content-right { margin-left: 15px; padding-top: 10px; diff --git a/apps/web-antd/src/views/sis/acAdmin/index.vue b/apps/web-antd/src/views/sis/acAdmin/index.vue index dd871237..8d6febaf 100644 --- a/apps/web-antd/src/views/sis/acAdmin/index.vue +++ b/apps/web-antd/src/views/sis/acAdmin/index.vue @@ -178,7 +178,7 @@ function doPlayer(nodeData: any, index: number = 0) { console.log('index=', index); if (mpegts.isSupported()) { streamProxy(nodeData, (res: AddStreamProxyResult) => { - const url = res.wsFlv; + const url = res.flv; // 将url 绑定到 nodeData nodeData.url = url; closePlayer(index); diff --git a/apps/web-antd/src/views/sis/video/index.vue b/apps/web-antd/src/views/sis/video/index.vue index da6348c1..bda3df59 100644 --- a/apps/web-antd/src/views/sis/video/index.vue +++ b/apps/web-antd/src/views/sis/video/index.vue @@ -305,7 +305,7 @@ function doPlayer(nodeData: any, index: number = 0) { console.log('index=', index); if (mpegts.isSupported()) { streamProxy(nodeData, (res: AddStreamProxyResult) => { - const url = res.wsFlv; + const url = res.flv; // 将url 绑定到 nodeData nodeData.url = url; closePlayer(index); @@ -371,10 +371,25 @@ 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(() => {