feat hls加载,断网多次加载m3u8
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import Hls from 'hls.js';
|
import Hls from 'hls.js';
|
||||||
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;
|
||||||
@@ -17,7 +18,18 @@ 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: 20, // ts 分片加载最多重试20次
|
||||||
|
levelLoadingRetryDelay: 500,
|
||||||
|
fragLoadingMaxRetry: 20, // 片段加载重试
|
||||||
|
fragLoadingRetryDelay: 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, () => {
|
||||||
@@ -25,6 +37,8 @@ export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
|||||||
try {
|
try {
|
||||||
video.play().catch(() => {});
|
video.play().catch(() => {});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
// 加载成功,重置重试计数
|
||||||
|
// hlsRetryCount = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ★ 错误自动恢复:hls.js 拿到空 m3u8 或 ts 加载失败时自动重试
|
// ★ 错误自动恢复:hls.js 拿到空 m3u8 或 ts 加载失败时自动重试
|
||||||
@@ -32,8 +46,8 @@ export const initHls = (monitorVideoUrl, monitorVideoRef) => {
|
|||||||
if (data.fatal) {
|
if (data.fatal) {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
// 网络错误(m3u8 404 / ts 404)→ 重新开始加载
|
|
||||||
hls.startLoad();
|
hls.startLoad();
|
||||||
|
ElMessage.error('网络错误, 请检查网络或重试');
|
||||||
break;
|
break;
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
// 媒体解码错误 → 尝试恢复
|
// 媒体解码错误 → 尝试恢复
|
||||||
|
|||||||
@@ -158,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 Hls from 'hls.js';
|
import { initHls, destroyHls } from '@/utils/video';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { useVideosStore } from '@/store/modules/video';
|
import { useVideosStore } from '@/store/modules/video';
|
||||||
|
|
||||||
@@ -190,68 +190,9 @@ 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[]>([]);
|
||||||
@@ -419,7 +360,7 @@ const handleSee = async (row?: any) => {
|
|||||||
dialog1.visible = true;
|
dialog1.visible = true;
|
||||||
dialog1.title = '修改设备';
|
dialog1.title = '修改设备';
|
||||||
|
|
||||||
destroyHls();
|
destroyHls(monitorVideoRef);
|
||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
await videosStore.getVideos(row.id);
|
await videosStore.getVideos(row.id);
|
||||||
monitorVideoUrl.value = videosStore.hisUrl || '';
|
monitorVideoUrl.value = videosStore.hisUrl || '';
|
||||||
@@ -432,7 +373,7 @@ const handleSee = async (row?: any) => {
|
|||||||
|
|
||||||
// 等待 DOM 更新后初始化 Hls
|
// 等待 DOM 更新后初始化 Hls
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initHls();
|
initHls(monitorVideoUrl, monitorVideoRef);
|
||||||
// 等待视频加载完成后初始化 ResizeObserver,以便能正确获取视频尺寸
|
// 等待视频加载完成后初始化 ResizeObserver,以便能正确获取视频尺寸
|
||||||
initVideoResizeObserver();
|
initVideoResizeObserver();
|
||||||
|
|
||||||
@@ -565,7 +506,7 @@ const cleanupVisibilityChange = () => {
|
|||||||
|
|
||||||
const handleMonitorDialogClose = (done: () => void) => {
|
const handleMonitorDialogClose = (done: () => void) => {
|
||||||
// 卸载 Hls 与清理 video
|
// 卸载 Hls 与清理 video
|
||||||
destroyHls();
|
destroyHls(monitorVideoRef);
|
||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
destroyVideoResizeObserver();
|
destroyVideoResizeObserver();
|
||||||
resetMonitorDraw();
|
resetMonitorDraw();
|
||||||
@@ -710,7 +651,7 @@ const saveImage = async () => {
|
|||||||
|
|
||||||
const handleMonitorDialogOpen = () => {
|
const handleMonitorDialogOpen = () => {
|
||||||
// 初始化 Hls 与 video
|
// 初始化 Hls 与 video
|
||||||
initHls();
|
initHls(monitorVideoUrl, monitorVideoRef);
|
||||||
initVideoResizeObserver();
|
initVideoResizeObserver();
|
||||||
monitorDrawCollecting.value = false;
|
monitorDrawCollecting.value = false;
|
||||||
monitorDrawPoints.value = [];
|
monitorDrawPoints.value = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user