feat: add resolution and speed info

This commit is contained in:
shinya
2025-07-10 23:33:47 +08:00
parent 3fba53069a
commit a858a51876
5 changed files with 585 additions and 47 deletions

View File

@@ -1,3 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
import Hls from 'hls.js';
export function cleanHtmlTags(text: string): string {
if (!text) return '';
return text
@@ -8,3 +12,153 @@ export function cleanHtmlTags(text: string): string {
.replace(/ /g, ' ') // 将   替换为空格
.trim(); // 去掉首尾空格
}
/**
* 从m3u8地址获取视频质量等级和网络信息
* @param m3u8Url m3u8播放列表的URL
* @returns Promise<{quality: string, loadSpeed: string, pingTime: number}> 视频质量等级和网络信息
*/
export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{
quality: string; // 如720p、1080p等
loadSpeed: string; // 自动转换为KB/s或MB/s
pingTime: number; // 网络延迟(毫秒)
}> {
try {
// 直接使用m3u8 URL作为视频源避免CORS问题
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.muted = true;
video.preload = 'metadata';
// 测量网络延迟ping时间 - 使用m3u8 URL而不是ts文件
const pingStart = performance.now();
let pingTime = 0;
// 测量ping时间使用m3u8 URL
fetch(m3u8Url, { method: 'HEAD', mode: 'no-cors' })
.then(() => {
pingTime = performance.now() - pingStart;
})
.catch(() => {
pingTime = performance.now() - pingStart; // 记录到失败为止的时间
});
// 固定使用hls.js加载
const hls = new Hls();
// 设置超时处理
const timeout = setTimeout(() => {
hls.destroy();
video.remove();
reject(new Error('Timeout loading video metadata'));
}, 4000);
video.onerror = () => {
clearTimeout(timeout);
hls.destroy();
video.remove();
reject(new Error('Failed to load video metadata'));
};
let actualLoadSpeed = '未知';
let hasSpeedCalculated = false;
let hasMetadataLoaded = false;
let fragmentStartTime = 0;
// 检查是否可以返回结果
const checkAndResolve = () => {
if (
hasMetadataLoaded &&
(hasSpeedCalculated || actualLoadSpeed !== '未知')
) {
const width = video.videoWidth;
if (width && width > 0) {
clearTimeout(timeout);
hls.destroy();
video.remove();
// 根据视频宽度判断视频质量等级,使用经典分辨率的宽度作为分割点
const quality =
width >= 3840
? '4K' // 4K: 3840x2160
: width >= 2560
? '2K' // 2K: 2560x1440
: width >= 1920
? '1080p' // 1080p: 1920x1080
: width >= 1280
? '720p' // 720p: 1280x720
: width >= 854
? '480p'
: 'SD'; // 480p: 854x480
resolve({
quality,
loadSpeed: actualLoadSpeed,
pingTime: Math.round(pingTime),
});
}
}
};
// 监听片段加载开始
hls.on(Hls.Events.FRAG_LOADING, () => {
fragmentStartTime = performance.now();
});
// 监听片段加载完成,只需首个分片即可计算速度
hls.on(Hls.Events.FRAG_LOADED, (event: any, data: any) => {
if (
fragmentStartTime > 0 &&
data &&
data.payload &&
!hasSpeedCalculated
) {
const loadTime = performance.now() - fragmentStartTime;
const size = data.payload.byteLength || 0;
if (loadTime > 0 && size > 0) {
const speedKBps = size / 1024 / (loadTime / 1000);
// 立即计算速度,无需等待更多分片
const avgSpeedKBps = speedKBps;
if (avgSpeedKBps >= 1024) {
actualLoadSpeed = `${(avgSpeedKBps / 1024).toFixed(1)} MB/s`;
} else {
actualLoadSpeed = `${avgSpeedKBps.toFixed(1)} KB/s`;
}
hasSpeedCalculated = true;
checkAndResolve(); // 尝试返回结果
}
}
});
hls.loadSource(m3u8Url);
hls.attachMedia(video);
// 监听hls.js错误
hls.on(Hls.Events.ERROR, (event: any, data: any) => {
console.error('HLS错误:', data);
if (data.fatal) {
clearTimeout(timeout);
hls.destroy();
video.remove();
reject(new Error(`HLS播放失败: ${data.type}`));
}
});
// 监听视频元数据加载完成
video.onloadedmetadata = () => {
hasMetadataLoaded = true;
checkAndResolve(); // 尝试返回结果
};
});
} catch (error) {
throw new Error(
`Error getting video resolution: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}