You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
6.8 KiB
263 lines
6.8 KiB
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;
|
|
}
|
|
|