feat: add full source resolution probing and display in player UI

This commit is contained in:
kuekhaoyang
2026-03-18 13:06:58 +08:00
parent 064a27c8b6
commit 5d8c3daba0
5 changed files with 40 additions and 27 deletions
+1
View File
@@ -62,6 +62,7 @@
- **实时延迟监测**:可选实时显示各源的网络延迟
- **清晰度标签**:自动解析并显示视频清晰度(4K/蓝光/1080P/720P/HD 等),方便快速分辨源质量
- **实际分辨率检测**:播放视频时自动检测并显示实际视频分辨率(如 1920x1080),不依赖源标签,显示真实清晰度。分辨率标签 5 秒后自动隐藏,鼠标移动时重新显示
- **全源分辨率探测**:播放器源列表中所有源均自动通过 m3u8 清单探测实际分辨率,显示精确的分辨率标签(1080P/720P/4K 等),不仅限于当前播放的源
- **繁体中文搜索**:自动将繁体中文转换为简体中文进行搜索,确保繁体输入也能搜到结果
- **源过滤**:支持按源和类型筛选搜索结果,源标签支持按类型分组显示,智能合并同名分类标签,展开/折叠状态持久化记忆
- **多级标签**:搜索结果和播放器中显示源名称和内容类型双重标签
+8
View File
@@ -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<VideoResolutionInfo | null>(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));
+28 -24
View File
@@ -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<string, ResolutionInfo | null>;
}
export function EpisodeList({
@@ -51,6 +54,7 @@ export function EpisodeList({
currentSource,
onSourceChange,
currentResolution,
sourceResolutions,
}: EpisodeListProps) {
const listRef = useRef<HTMLDivElement>(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({
<div className="font-medium text-sm truncate flex items-center gap-1.5">
{source.sourceName || source.source}
{(() => {
// For the current source, prefer actual detected resolution
if (isCurrent && currentResolution) {
return (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${currentResolution.color}`}>
{currentResolution.label}
</span>
);
}
const qb = extractQualityLabel(source.remarks);
return qb ? (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${qb.color}`}>
{qb.label}
const badge = getResBadge(source, isCurrent);
return badge ? (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${badge.color}`}>
{badge.label}
</span>
) : null;
})()}
@@ -419,18 +431,10 @@ export function EpisodeList({
<div className="font-medium text-sm truncate flex items-center gap-1.5">
{source.sourceName || source.source}
{(() => {
// For the current source, prefer actual detected resolution
if (isCurrent && currentResolution) {
return (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${currentResolution.color}`}>
{currentResolution.label}
</span>
);
}
const qb = extractQualityLabel(source.remarks);
return qb ? (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${qb.color}`}>
{qb.label}
const badge = getResBadge(source, isCurrent);
return badge ? (
<span className={`inline-flex items-center px-1 py-0 rounded text-[9px] font-bold text-white ${badge.color}`}>
{badge.label}
</span>
) : null;
})()}
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "4.8.0",
"version": "4.8.1",
"private": true,
"scripts": {
"dev": "next dev --port ${PORT:-3000}",