From af423c22e1ef59f4ffea48bd4697391a89d8abe6 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sun, 11 Jan 2026 20:45:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E9=80=9F=E5=A2=9E=E5=8A=A0=E7=A0=81?= =?UTF-8?q?=E7=8E=87=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 7 ++++--- src/components/EpisodeSelector.tsx | 6 ++++++ src/lib/utils.ts | 31 +++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index eb84220..2a13d21 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -1140,7 +1140,7 @@ function PlayPageClient() { // 保存优选时的测速结果,避免EpisodeSelector重复测速 const [precomputedVideoInfo, setPrecomputedVideoInfo] = useState< - Map + Map >(new Map()); // 折叠状态(仅在 lg 及以上屏幕有效) @@ -1238,7 +1238,7 @@ function PlayPageClient() { const batchSize = Math.ceil(sources.length / 2); const allResults: Array<{ source: SearchResult; - testResult: { quality: string; loadSpeed: string; pingTime: number }; + testResult: { quality: string; loadSpeed: string; pingTime: number; bitrate: string }; } | null> = []; for (let start = 0; start < sources.length; start += batchSize) { @@ -1278,6 +1278,7 @@ function PlayPageClient() { quality: string; loadSpeed: string; pingTime: number; + bitrate: string; hasError?: boolean; } >(); @@ -1294,7 +1295,7 @@ function PlayPageClient() { // 过滤出成功的结果用于优选计算 const successfulResults = allResults.filter(Boolean) as Array<{ source: SearchResult; - testResult: { quality: string; loadSpeed: string; pingTime: number }; + testResult: { quality: string; loadSpeed: string; pingTime: number; bitrate: string }; }>; setPrecomputedVideoInfo(newVideoInfoMap); diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx index 6d7f317..18b855d 100644 --- a/src/components/EpisodeSelector.tsx +++ b/src/components/EpisodeSelector.tsx @@ -21,6 +21,7 @@ interface VideoInfo { quality: string; loadSpeed: string; pingTime: number; + bitrate: string; // 视频码率 hasError?: boolean; // 添加错误状态标识 } @@ -898,6 +899,11 @@ const EpisodeSelector: React.FC = ({
{videoInfo.pingTime}ms
+ {videoInfo.bitrate && videoInfo.bitrate !== '未知' && ( +
+ {videoInfo.bitrate} +
+ )} ); } else { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 39d9ea3..a689017 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -135,12 +135,13 @@ export function processVideoUrl(originalUrl: string): string { /** * 从m3u8地址获取视频质量等级和网络信息 * @param m3u8Url m3u8播放列表的URL - * @returns Promise<{quality: string, loadSpeed: string, pingTime: number}> 视频质量等级和网络信息 + * @returns Promise<{quality: string, loadSpeed: string, pingTime: number, bitrate: string}> 视频质量等级和网络信息 */ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ quality: string; // 如720p、1080p等 loadSpeed: string; // 自动转换为KB/s或MB/s pingTime: number; // 网络延迟(毫秒) + bitrate: string; // 视频码率(如 "2.5 Mbps") }> { try { // 直接使用m3u8 URL作为视频源,避免CORS问题 @@ -182,6 +183,7 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ let actualLoadSpeed = '未知'; let hasSpeedCalculated = false; let hasMetadataLoaded = false; + let estimatedBitrate = 0; // 估算的码率(bps) let fragmentStartTime = 0; @@ -211,17 +213,32 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ ? '480p' : 'SD'; // 480p: 854x480 + // 格式化码率 + const bitrateStr = estimatedBitrate > 0 + ? estimatedBitrate >= 1000000 + ? `${(estimatedBitrate / 1000000).toFixed(1)} Mbps` + : `${Math.round(estimatedBitrate / 1000)} Kbps` + : '未知'; + resolve({ quality, loadSpeed: actualLoadSpeed, pingTime: Math.round(pingTime), + bitrate: bitrateStr, }); } else { // webkit 无法获取尺寸,直接返回 + const bitrateStr = estimatedBitrate > 0 + ? estimatedBitrate >= 1000000 + ? `${(estimatedBitrate / 1000000).toFixed(1)} Mbps` + : `${Math.round(estimatedBitrate / 1000)} Kbps` + : '未知'; + resolve({ quality: '未知', loadSpeed: actualLoadSpeed, pingTime: Math.round(pingTime), + bitrate: bitrateStr, }); } } @@ -255,6 +272,18 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{ actualLoadSpeed = `${avgSpeedKBps.toFixed(1)} KB/s`; } hasSpeedCalculated = true; + + // 从分片估算码率 + if (data.frag && data.frag.duration > 0) { + const fragmentDuration = data.frag.duration; // 分片时长(秒) + const fragmentSize = size; // 分片大小(字节) + + // 码率 = (分片大小 × 8 bits) / 分片时长 + estimatedBitrate = Math.round((fragmentSize * 8) / fragmentDuration); + + console.log(`[测速] 估算码率: ${(estimatedBitrate / 1000000).toFixed(2)} Mbps (分片: ${(fragmentSize / 1024 / 1024).toFixed(2)} MB, 时长: ${fragmentDuration.toFixed(1)}s)`); + } + checkAndResolve(); // 尝试返回结果 } }