import flvJs from "flv.js"; import mpegTsJs from "mpegts.js"; import { Message } from "element-ui"; // mpegts.js import { getCameraStream, getNearCamera, getNearCameraNew, } from "@screen/pages/Home/components/RoadAndEvents/utils/httpList.js"; const ErrorTypesCn = { NetworkError: "网络错误", MediaError: "媒体错误", OtherError: "其他错误", }; /** * flv 视频测试 * https://bilibili.github.io/flv.js/demo/ * https://www.zngg.net/tool/detail/FlvPlayer * https://xqq.im/mpegts.js/demo/arib.html */ /** * flv 视频流 */ const testFlvUrl = "https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-720p.flv"; // "https://v17.dogevideo.com/vcloud/17/v/20190424/1556036075_818c4125ec9c8cbc7a7a8a7cc1601512/1057/e51d88e79732fb952ebdbb4a57aa628a.flv?vkey=D9EAC2&tkey=17062414799ec7c466dc&auth_key=1706255879-zQK6JYToEeRiUark-0-6e363fb7709e783e64efc919d2267bdc"; /** * * @param {HTMLElement} container 容器 * @param {{camId?: string; url?: string}?} options {camId: 相机ID; url: 直播地址} * @returns */ export async function openVideoStream(container, { camId, url } = {}) { console.log(camId,333) if (camId) { const { code, data } = await getCameraStream(camId).catch(() => ({})); if (code != 200) return; url = data.liveUrl; } if (!url) return; // console.log(flvJs.getFeatureList().mseLivePlayback); const player = flvJs.createPlayer({ type: "flv", url: url, isLive: true, hasVideo: true, hasAudio: true, }); player.attachMediaElement(container); player.load(); player.play(); player.on(flvJs.Events.ERROR, (errorType, errorDetails, errorInfo) => { console.error('video errorInfo', errorInfo); }); return player; } async function getUrl({ camId} = {}) { if(!camId){ return } const { code, data } = await getCameraStream(camId).catch(() => ({})); if (code != 200) { // Message.warning("未获取到当前相机的播放地址"); return; } let url = data.liveUrl; if (!url) { // Message.warning("未获取到当前相机的播放地址"); return Promise.reject("获取 url 失败"); } return url; } export class HttpLivePlayer { /** * @type { flvJs.Player } */ player; /** * @type { HTMLVideoElement } */ container; url; // 解码 帧 lastDecodedFrames; retryCount = 0; maxRetries = 3; // 最大重试次数 constructor(container, options) { this.container = container; if (!flvJs.getFeatureList().mseLiveFlvPlayback) return Message.error("浏览器不支持播放 flv 视频流"); getUrl(options).then((url) => { this.url = url; this.initLiveVideo(); }).catch(error => { console.error('获取URL失败:', error); this.destroy(); this.clearMediaElementErrors(); // 清除媒体元素的错误状态 // Message.error('无法获取视频流URL'); }); } destroy() { if (!this.player) return; this.player.pause(); this.player.unload(); this.player.detachMediaElement(); this.player.destroy(); this.player = null; } clearMediaElementErrors() { if (this.container.error) { console.error('Media element is in an error state:', this.container.error); this.container.error = null; // 清除错误状态 } } initLiveVideo() { this.destroy(); this.clearMediaElementErrors(); // 清除媒体元素的错误状态 this.player = null; this.lastDecodedFrames = null; this.retryCount = 0; if (!this.url) return; this.player = flvJs.createPlayer( { type: "flv", url: this.url, isLive: true, }, { autoCleanupSourceBuffer: true, // enableWorker: true, // 启用分离的线程进行转换 // enableStashBuffer: false, // 关闭IO隐藏缓冲区 如果您需要实时(最小延迟)来进行实时流播放,则设置为false // stashInitialSize: 128, isLive: true, lazyLoad: false, } ); this.player.attachMediaElement(this.container); this.player.load(); this.player.play(); // this.container.addEventListener("progress", () => { // let end = this.player.buffered.end(0); //获取当前buffered值(缓冲区末尾) // let delta = end - this.player.currentTime; //获取buffered与当前播放位置的差值 // // 延迟过大,通过跳帧的方式更新视频 // if (delta > 10 || delta < 0) { // this.player.currentTime = this.player.buffered.end(0) - 1; // return; // } // // 追帧 // if (delta > 1) { // this.container.playbackRate = 1.1; // } else { // this.container.playbackRate = 1; // } // }); this.player.on(flvJs.Events.ERROR, (errorType, errorDetail, errorInfo) => { console.log("video errorInfo", errorInfo); // Message.warning( // `视频流加载失败, ${ErrorTypesCn[errorType] || "其他错误"}` // ); if (this.retryCount < this.maxRetries) { this.retryCount++; this.initLiveVideo(); } else { Message.error(`视频流加载失败,已达到最大重试次数。${ErrorTypesCn[errorType] || "其他错误"}`); this.destroy(); } }); // 视频断流 this.player.on(flvJs.Events.STATISTICS_INFO, (res) => { if (this.lastDecodedFrames != res.decodedFrames) { this.lastDecodedFrames = res.decodedFrames; } else { this.lastDecodedFrames = 0; this.initLiveVideo(); // 重置解码帧计数器,防止假死 // this.destroy(); // this.initLiveVideo(); } }); } } /** * https://juejin.cn/post/6855577308271476743 * https://www.cnblogs.com/xiahj/p/flvExtend.html * 使用 mpegTsJs * @param {*} container * @param {*} options */ export async function openLiveVideo(container, options) { if (!mpegTsJs.getFeatureList().mseLivePlayback) return Message.error("浏览器不支持播放 flv 视频流"); const url = await getUrl(options).catch(() => {}); console.log( "%c [ url ]-212-「videoStream.js」", "font-size:15px; background:#6f87e8; color:#b3cbff;", url, options ); if (!url) return; const player = mpegTsJs.createPlayer( { type: "flv", url, isLive: true, }, { autoCleanupSourceBuffer: true, enableWorker: true, // 启用分离的线程进行转换 // enableStashBuffer: false, // 关闭IO隐藏缓冲区 如果您需要实时(最小延迟)来进行实时流播放,则设置为false // stashInitialSize: 128, isLive: true, lazyLoad: false, } ); player.attachMediaElement(container); container.addEventListener("play", () => { try { let end = player.buffered.end(0) - 1; player.currentTime = end; } catch (error) {} }); player.load(); player.play(); return player; }