视频播放同时支持h265和h264播放

This commit is contained in:
15683799673 2025-08-10 19:35:45 +08:00
parent c3f43f6e71
commit 489ec29184
5 changed files with 104 additions and 91 deletions

View File

@ -12,9 +12,10 @@ export function addStreamProxy(params?: any) {
params, params,
); );
} }
export function addFFmpegStreamProxy(params?: any) { export function addFFmpegStreamProxy(params?: any) {
return requestClient.post<AddStreamProxyResult>( return requestClient.post<AddStreamProxyResult>(
'sis/stream/realtime/addFfmpeg', 'sis/stream/FFmpeg/proxy',
params, params,
); );
} }

View File

@ -0,0 +1,21 @@
/**
* h265
*/
export function checkHEVCSupport() {
const video = document.createElement('video');
const h265Support = {
hevc: false,
hvc1: false,
};
// 测试不同的HEVC MIME类型
if (video.canPlayType) {
h265Support.hevc =
video.canPlayType('video/mp4; codecs="hev1.1.6.L93.B0"') !== '';
h265Support.hvc1 =
video.canPlayType('video/mp4; codecs="hvc1.1.6.L93.B0"') !== '';
}
const result = h265Support.hevc || h265Support.hvc1;
console.log('当前浏览器是否支持h265:' + result);
return result;
}

View File

@ -27,10 +27,12 @@
<script setup lang="ts"> <script setup lang="ts">
import DpTree from './dp-tree.vue'; import DpTree from './dp-tree.vue';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { onMounted, ref, toRaw } from 'vue'; import { onMounted, onUnmounted, ref, toRaw } from 'vue';
import mpegts from 'mpegts.js'; 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 { message } from 'ant-design-vue';
import { checkHEVCSupport } from '#/utils/video';
import type { AddStreamProxyResult } from '#/api/sis/stream/model';
/** /**
* 屏幕播放器数量 * 屏幕播放器数量
@ -74,7 +76,7 @@ function handleParentNoe(node: any, newNode: any[] = []) {
if (item.children && item.children.length >= 1) { if (item.children && item.children.length >= 1) {
handleParentNoe(item.children, newNode); handleParentNoe(item.children, newNode);
} }
}) });
} }
/** /**
@ -138,6 +140,35 @@ function onNodeChecked(
// , // ,
const playerList: any[] = []; 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 播放的节点数据 * @param nodeData 播放的节点数据
@ -146,16 +177,7 @@ const playerList: any[] = [];
function doPlayer(nodeData: any, index: number = 0) { function doPlayer(nodeData: any, index: number = 0) {
console.log('index=', index); console.log('index=', index);
if (mpegts.isSupported()) { if (mpegts.isSupported()) {
const params = { streamProxy(nodeData, (res: AddStreamProxyResult) => {
videoIp: nodeData.deviceIp,
videoPort: 554,
factoryNo: nodeData.factoryNo,
account: nodeData.deviceAccount,
pwd: nodeData.devicePwd,
channelId: nodeData.channelNo ? nodeData.channelNo : 101,
};
// }
addStreamProxy(params).then((res) => {
const url = res.wsFlv; const url = res.wsFlv;
// url nodeData // url nodeData
nodeData.url = url; nodeData.url = url;
@ -195,55 +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,
el: videoElement,
};
} 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) { function closePlayer(index: number) {
// //
const pData = playerList[index]; const pData = playerList[index];
@ -274,9 +247,18 @@ function catchUp() {
}); });
} }
let isSupportH265 = false;
onMounted(() => { onMounted(() => {
// h265
isSupportH265 = checkHEVCSupport();
setInterval(catchUp, 10000); setInterval(catchUp, 10000);
}); });
onUnmounted(() => {
for (let i = 0; i < playerList.length; i++) {
closePlayer(i);
}
});
</script> </script>
<style> <style>
.player { .player {

View File

@ -2,7 +2,7 @@
<Page class="h-full w-full"> <Page class="h-full w-full">
<!-- 设备分组区域 --> <!-- 设备分组区域 -->
<div class="flex h-full gap-[8px]"> <div class="flex h-full gap-[8px]">
<ChannelTree class="h-[83vh] w-[300px]" @check="onNodeChecked" /> <ChannelTree class="h-full w-[300px]" @check="onNodeChecked" />
<!-- 设备分组区域 --> <!-- 设备分组区域 -->
<div class="bg-background flex-1"> <div class="bg-background flex-1">
@ -47,13 +47,15 @@ import { Page } from '@vben/common-ui';
import ChannelTree from './channel-tree.vue'; import ChannelTree from './channel-tree.vue';
import mpegts from 'mpegts.js'; import mpegts from 'mpegts.js';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { addStreamProxy } from '#/api/sis/stream'; import { addFFmpegStreamProxy, addStreamProxy } from '#/api/sis/stream';
import { import {
Svg16FrameIcon, Svg16FrameIcon,
Svg1FrameIcon, Svg1FrameIcon,
Svg4FrameIcon, Svg4FrameIcon,
Svg9FrameIcon, Svg9FrameIcon,
} from '@vben/icons'; } from '@vben/icons';
import { checkHEVCSupport } from '#/utils/video';
import type { AddStreamProxyResult } from '#/api/sis/stream/model';
const selected = 'selected'; const selected = 'selected';
@ -265,6 +267,35 @@ function changeElPlayer(playerInfo: any, index: number) {
} }
} }
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 播放的节点数据 * @param nodeData 播放的节点数据
@ -273,27 +304,7 @@ function changeElPlayer(playerInfo: any, index: number) {
function doPlayer(nodeData: any, index: number = 0) { function doPlayer(nodeData: any, index: number = 0) {
console.log('index=', index); console.log('index=', index);
if (mpegts.isSupported()) { if (mpegts.isSupported()) {
let params = {}; streamProxy(nodeData, (res: AddStreamProxyResult) => {
// 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,
};
// }
addStreamProxy(params).then((res) => {
const url = res.wsFlv; const url = res.wsFlv;
// url nodeData // url nodeData
nodeData.url = url; nodeData.url = url;
@ -360,8 +371,10 @@ function closePlayer(index: number) {
} }
} }
let isSupportH265 = false;
onMounted(() => { onMounted(() => {
// // h265
isSupportH265 = checkHEVCSupport();
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@ -27,11 +27,7 @@ export default defineConfig(async () => {
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
// mock代理目标地址 // mock代理目标地址
// target: 'http://192.168.43.169:8080',
// target: 'https://by.missmoc.top/api/',
target: 'http://127.0.0.1:8080', target: 'http://127.0.0.1:8080',
// target: 'http://192.168.0.106:8080',
// target: 'http://47.109.37.87:3010',
ws: true, ws: true,
}, },
}, },