From 88922f8f5b43ab637eeb32fec2d7c7b6c55e55a1 Mon Sep 17 00:00:00 2001 From: Joe <1712833832@qq.com> Date: Fri, 26 Jan 2024 13:35:39 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=BF=AE=E6=94=B9=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JiHeExpressway/components/Video/Video.vue | 9 +- .../components/Video/videoStream.js | 167 +++++++++++++++--- .../components/BroadcastReleases.vue | 2 +- 3 files changed, 151 insertions(+), 27 deletions(-) diff --git a/ruoyi-ui/src/views/JiHeExpressway/components/Video/Video.vue b/ruoyi-ui/src/views/JiHeExpressway/components/Video/Video.vue index 0fd018b6..18782370 100644 --- a/ruoyi-ui/src/views/JiHeExpressway/components/Video/Video.vue +++ b/ruoyi-ui/src/views/JiHeExpressway/components/Video/Video.vue @@ -3,7 +3,7 @@ diff --git a/ruoyi-ui/src/views/JiHeExpressway/components/Video/videoStream.js b/ruoyi-ui/src/views/JiHeExpressway/components/Video/videoStream.js index 82e499ad..046b7adc 100644 --- a/ruoyi-ui/src/views/JiHeExpressway/components/Video/videoStream.js +++ b/ruoyi-ui/src/views/JiHeExpressway/components/Video/videoStream.js @@ -1,10 +1,26 @@ import flvJs from "flv.js"; +import mpegTsJs from "mpegts.js"; +import { Message } from "element-ui"; // mpegts.js import { getCameraStream, getNearCamera, } from "@screen/pages/Home/components/RoadAndEvents/utils/httpList.js"; +/** + * 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 容器 @@ -42,6 +58,34 @@ export async function openVideoStream(container, { camId, url } = {}) { return player; } +async function getUrl({ camId, url, pileNum } = {}) { + // return testFlvUrl; + if (url) return url; + + if (pileNum) { + const { code, data } = await getNearCamera(pileNum).catch(() => ({})); + if (code != 200 || !data?.length) { + Message.warning("未获取到附近的相机信息"); + return; + } + + camId = data[0].camId; + } + + if (camId) { + const { code, data } = await getCameraStream(camId).catch(() => ({})); + if (code != 200) { + Message.warning("未获取到当前相机的播放地址"); + return; + } + + url = data.liveUrl; + } + + if (!url) return Promise.reject("获取 url 失败!"); + + return url; +} export class HttpLivePlayer { /** * @type { flvJs.Player } @@ -60,33 +104,37 @@ export class HttpLivePlayer { constructor(container, options) { this.container = container; + if (!flvJs.getFeatureList().mseLiveFlvPlayback) + return Message.error("浏览器不支持播放 flv 视频流"); - this.getUrl(options).then(() => { + this.initLiveVideo(); + getUrl(options).then((url) => { + this.url = url; this.initLiveVideo(); }); } - async getUrl({ camId, url, pileNum } = {}) { - this.url = url; + // async getUrl({ camId, url, pileNum } = {}) { + // this.url = url; - if (pileNum) { - const { code, data } = await getNearCamera(pileNum).catch(() => ({})); - if (code != 200 || !data?.length) return; + // if (pileNum) { + // const { code, data } = await getNearCamera(pileNum).catch(() => ({})); + // if (code != 200 || !data?.length) return; - camId = data[0].camId; - } + // camId = data[0].camId; + // } - if (camId) { - const { code, data } = await getCameraStream(camId).catch(() => ({})); - if (code != 200) return; + // if (camId) { + // const { code, data } = await getCameraStream(camId).catch(() => ({})); + // if (code != 200) return; - url = data.liveUrl; - } + // url = data.liveUrl; + // } - if (!url) return Promise.reject("获取 url 失败!"); + // if (!url) return Promise.reject("获取 url 失败!"); - this.url = url; - } + // this.url = url; + // } destroy() { if (!this.player) return; @@ -117,10 +165,11 @@ export class HttpLivePlayer { }, { autoCleanupSourceBuffer: true, - // enableWorker: false, //不启用分离线程 - // enableStashBuffer: true, //关闭IO隐藏缓冲区 + enableWorker: true, // 启用分离的线程进行转换 + enableStashBuffer: false, // 关闭IO隐藏缓冲区 如果您需要实时(最小延迟)来进行实时流播放,则设置为false + stashInitialSize: 128, isLive: true, - lazyLoad: false, + lazyLoad: true, } ); @@ -129,13 +178,35 @@ export class HttpLivePlayer { this.player.load(); this.player.play(); + this.container.addEventListener("progress", () => { + let end = player.buffered.end(0); //获取当前buffered值(缓冲区末尾) + let delta = end - 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("errorType", errorType); console.log("errorDetail", errorDetail); console.log("errorInfo", errorInfo); - // 视频出错后销毁重建 + this.player.pause(); + this.player.unload(); + this.player.detachMediaElement(); this.destroy(); + this.player = null; + this.initLiveVideo(); }); @@ -145,9 +216,61 @@ export class HttpLivePlayer { this.lastDecodedFrames = res.decodedFrames; } else { this.lastDecodedFrames = 0; - this.destroy(); - 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 + ); + + 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: true, + } + ); + + 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; +} diff --git a/ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Broadcast/components/BroadcastReleases.vue b/ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Broadcast/components/BroadcastReleases.vue index f4d8a74c..56b381c6 100644 --- a/ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Broadcast/components/BroadcastReleases.vue +++ b/ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Broadcast/components/BroadcastReleases.vue @@ -67,7 +67,7 @@ export default { deviceId: String, pileNum: String, currentId: [String, Number], - otherConfig: Object + otherConfig: String }, data() { return {