From 5d8c3daba040cfdb57872c3f0e91c7f20f2f2fe0 Mon Sep 17 00:00:00 2001 From: kuekhaoyang Date: Wed, 18 Mar 2026 13:06:58 +0800 Subject: [PATCH] feat: add full source resolution probing and display in player UI --- README.md | 1 + app/player/page.tsx | 8 +++++ components/player/EpisodeList.tsx | 52 +++++++++++++++++-------------- package-lock.json | 4 +-- package.json | 2 +- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 2fe16b8..165e3b4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ - **实时延迟监测**:可选实时显示各源的网络延迟 - **清晰度标签**:自动解析并显示视频清晰度(4K/蓝光/1080P/720P/HD 等),方便快速分辨源质量 - **实际分辨率检测**:播放视频时自动检测并显示实际视频分辨率(如 1920x1080),不依赖源标签,显示真实清晰度。分辨率标签 5 秒后自动隐藏,鼠标移动时重新显示 +- **全源分辨率探测**:播放器源列表中所有源均自动通过 m3u8 清单探测实际分辨率,显示精确的分辨率标签(1080P/720P/4K 等),不仅限于当前播放的源 - **繁体中文搜索**:自动将繁体中文转换为简体中文进行搜索,确保繁体输入也能搜到结果 - **源过滤**:支持按源和类型筛选搜索结果,源标签支持按类型分组显示,智能合并同名分类标签,展开/折叠状态持久化记忆 - **多级标签**:搜索结果和播放器中显示源名称和内容类型双重标签 diff --git a/app/player/page.tsx b/app/player/page.tsx index ee17e3b..44eadbf 100644 --- a/app/player/page.tsx +++ b/app/player/page.tsx @@ -10,6 +10,7 @@ import { PlayerError } from '@/components/player/PlayerError'; import { SourceInfo } from '@/components/player/EpisodeList'; import type { VideoSource } from '@/lib/types'; import type { VideoResolutionInfo } from '@/components/player/hooks/useVideoResolution'; +import { useResolutionProbe } from '@/lib/hooks/useResolutionProbe'; import { useVideoPlayer } from '@/lib/hooks/useVideoPlayer'; import { useHistory } from '@/lib/store/history-store'; import { FavoritesSidebar } from '@/components/favorites/FavoritesSidebar'; @@ -269,6 +270,12 @@ function PlayerContent() { // Track detected video resolution from the player const [detectedResolution, setDetectedResolution] = useState(null); + // Probe resolution for all grouped sources (not just the playing one) + const probeList = useMemo(() => { + return groupedSources.map(s => ({ id: s.id, source: s.source })); + }, [groupedSources]); + const { resolutions: sourceResolutions } = useResolutionProbe(probeList); + // Add initial history entry when video data is loaded useEffect(() => { if (videoData && playUrl && videoId) { @@ -431,6 +438,7 @@ function PlayerContent() { sources={groupedSources.length > 0 ? groupedSources : undefined} currentSource={currentSourceId || source || ''} currentResolution={detectedResolution} + sourceResolutions={sourceResolutions} onSourceChange={(newSource) => { const params = new URLSearchParams(); params.set('id', String(newSource.id)); diff --git a/components/player/EpisodeList.tsx b/components/player/EpisodeList.tsx index 16d9815..8dab0ce 100644 --- a/components/player/EpisodeList.tsx +++ b/components/player/EpisodeList.tsx @@ -11,6 +11,7 @@ import { useKeyboardNavigation } from '@/lib/hooks/useKeyboardNavigation'; import { settingsStore } from '@/lib/store/settings-store'; import { extractQualityLabel } from '@/lib/utils/video'; import type { VideoResolutionInfo } from './hooks/useVideoResolution'; +import type { ResolutionInfo } from '@/lib/hooks/useResolutionProbe'; interface Episode { name?: string; @@ -39,6 +40,8 @@ interface EpisodeListProps { onSourceChange?: (source: SourceInfo) => void; // Actual detected resolution for the current source currentResolution?: VideoResolutionInfo | null; + // Probed resolutions for all sources (key: "source:id") + sourceResolutions?: Record; } export function EpisodeList({ @@ -51,6 +54,7 @@ export function EpisodeList({ currentSource, onSourceChange, currentResolution, + sourceResolutions, }: EpisodeListProps) { const listRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); @@ -63,6 +67,22 @@ export function EpisodeList({ const showSourceSelector = sources && sources.length > 1 && onSourceChange; + // Helper: get best resolution badge for a source + const getResBadge = useCallback((source: SourceInfo, isCurrent: boolean) => { + // For current source, prefer actual detected resolution from video element + if (isCurrent && currentResolution) { + return { label: currentResolution.label, color: currentResolution.color }; + } + // Check probed resolution from m3u8 manifest + const probeKey = `${source.source}:${source.id}`; + const probed = sourceResolutions?.[probeKey]; + if (probed) { + return { label: probed.label, color: probed.color }; + } + // Fall back to quality label parsed from remarks + return extractQualityLabel(source.remarks) || null; + }, [currentResolution, sourceResolutions]); + // Current source info const currentSourceInfo = useMemo(() => { if (!sources || !currentSource) return null; @@ -331,18 +351,10 @@ export function EpisodeList({
{source.sourceName || source.source} {(() => { - // For the current source, prefer actual detected resolution - if (isCurrent && currentResolution) { - return ( - - {currentResolution.label} - - ); - } - const qb = extractQualityLabel(source.remarks); - return qb ? ( - - {qb.label} + const badge = getResBadge(source, isCurrent); + return badge ? ( + + {badge.label} ) : null; })()} @@ -419,18 +431,10 @@ export function EpisodeList({
{source.sourceName || source.source} {(() => { - // For the current source, prefer actual detected resolution - if (isCurrent && currentResolution) { - return ( - - {currentResolution.label} - - ); - } - const qb = extractQualityLabel(source.remarks); - return qb ? ( - - {qb.label} + const badge = getResBadge(source, isCurrent); + return badge ? ( + + {badge.label} ) : null; })()} diff --git a/package-lock.json b/package-lock.json index ef667ab..701bffc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kvideo", - "version": "4.8.0", + "version": "4.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "kvideo", - "version": "4.8.0", + "version": "4.8.1", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", diff --git a/package.json b/package.json index 367fad5..328edf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kvideo", - "version": "4.8.0", + "version": "4.8.1", "private": true, "scripts": { "dev": "next dev --port ${PORT:-3000}",