Refactor video source identifiers and enhance player navigation with improved button functionality

This commit is contained in:
kuekhaoyang
2025-11-16 21:40:29 +08:00
parent df8843aefb
commit ec052df078
3 changed files with 146 additions and 132 deletions
+89 -92
View File
@@ -2,6 +2,7 @@
import { useState, useRef } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
@@ -48,9 +49,9 @@ export default function Home() {
try {
// Get all enabled source IDs
const sourceIds = ['custom_0', 'custom_1', 'custom_2', 'custom_3', 'custom_4',
'custom_5', 'custom_6', 'custom_7', 'custom_8', 'custom_9',
'custom_10', 'custom_11', 'custom_12', 'custom_13', 'custom_14', 'custom_15'];
const sourceIds = ['dytt', 'ruyi', 'baofeng', 'tianya', 'feifan',
'sanliuling', 'wolong', 'jisu', 'mozhua', 'modu',
'zuida', 'yinghua', 'baiduyun', 'wujin', 'wangwang', 'ikun'];
// Use streaming API
const response = await fetch('/api/search-stream', {
@@ -180,41 +181,26 @@ export default function Home() {
const getSourceName = (sourceId: string): string => {
const sourceNames: Record<string, string> = {
'custom_0': '电影天堂',
'custom_1': '如意',
'custom_2': '暴风',
'custom_3': '天涯',
'custom_4': '非凡影视',
'custom_5': '360',
'custom_6': '卧龙',
'custom_7': '极速',
'custom_8': '魔爪',
'custom_9': '魔都',
'custom_10': '海外看',
'custom_11': '新浪',
'custom_12': '光速',
'custom_13': '红牛',
'custom_14': '樱花',
'custom_15': '飞速',
'dytt': '电影天堂',
'ruyi': '如意',
'baofeng': '暴风',
'tianya': '天涯',
'feifan': '非凡影视',
'sanliuling': '360',
'wolong': '卧龙',
'jisu': '极速',
'mozhua': '魔爪',
'modu': '魔都',
'zuida': '最大',
'yinghua': '樱花',
'baiduyun': '百度云',
'wujin': '无尽',
'wangwang': '旺旺',
'ikun': 'iKun',
};
return sourceNames[sourceId] || sourceId;
};
const handleVideoClick = (video: any) => {
// Abort ongoing search when user clicks a video
if (abortControllerRef.current && loading) {
abortControllerRef.current.abort();
setLoading(false);
}
const params = new URLSearchParams({
id: video.vod_id,
source: video.source,
title: video.vod_name,
});
router.push(`/player?${params.toString()}`);
};
return (
<div className="min-h-screen">
{/* Glass Navbar */}
@@ -295,7 +281,7 @@ export default function Home() {
</div>
{/* Results Section */}
{(results.length >= 10 || (!loading && results.length > 0)) && (
{(results.length >= 1 || (!loading && results.length > 0)) && (
<div className="animate-fade-in">
<div className="flex flex-col gap-4 mb-6">
<div className="flex items-center justify-between flex-wrap gap-3">
@@ -348,67 +334,78 @@ export default function Home() {
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4 md:gap-6">
{results.map((video, index) => (
<Card
key={`${video.vod_id}-${index}`}
onClick={() => handleVideoClick(video)}
className={`p-0 overflow-hidden group cursor-pointer ${video.isNew ? 'animate-scale-in' : ''}`}
>
{/* Poster */}
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden" style={{ borderRadius: 'var(--radius-2xl) var(--radius-2xl) 0 0' }}>
{video.vod_pic ? (
<img
src={video.vod_pic}
alt={video.vod_name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Icons.Film size={64} className="text-[var(--text-color-secondary)]" />
</div>
)}
{/* Source Badge - Top Left */}
{video.sourceName && (
<div className="absolute top-2 left-2 z-10">
<Badge variant="primary" className="text-xs backdrop-blur-md bg-[var(--accent-color)]/90">
{video.sourceName}
</Badge>
</div>
)}
{/* Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="absolute bottom-0 left-0 right-0 p-3">
{video.type_name && (
<Badge variant="secondary" className="text-xs mb-2">
{video.type_name}
</Badge>
)}
{video.vod_year && (
<div className="flex items-center gap-1 text-white/80 text-xs">
<Icons.Calendar size={12} />
<span>{video.vod_year}</span>
{results.map((video, index) => {
const videoUrl = `/player?${new URLSearchParams({
id: video.vod_id,
source: video.source,
title: video.vod_name,
}).toString()}`;
return (
<Link
key={`${video.vod_id}-${index}`}
href={videoUrl}
>
<Card
className={`p-0 overflow-hidden group cursor-pointer ${video.isNew ? 'animate-scale-in' : ''}`}
>
{/* Poster */}
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden" style={{ borderRadius: 'var(--radius-2xl) var(--radius-2xl) 0 0' }}>
{video.vod_pic ? (
<img
src={video.vod_pic}
alt={video.vod_name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Icons.Film size={64} className="text-[var(--text-color-secondary)]" />
</div>
)}
{/* Source Badge - Top Left */}
{video.sourceName && (
<div className="absolute top-2 left-2 z-10">
<Badge variant="primary" className="text-xs backdrop-blur-md bg-[var(--accent-color)]/90">
{video.sourceName}
</Badge>
</div>
)}
{/* Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="absolute bottom-0 left-0 right-0 p-3">
{video.type_name && (
<Badge variant="secondary" className="text-xs mb-2">
{video.type_name}
</Badge>
)}
{video.vod_year && (
<div className="flex items-center gap-1 text-white/80 text-xs">
<Icons.Calendar size={12} />
<span>{video.vod_year}</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Info */}
<div className="p-3">
<h4 className="font-semibold text-sm text-[var(--text-color)] line-clamp-2 group-hover:text-[var(--accent-color)] transition-colors">
{video.vod_name}
</h4>
{video.vod_remarks && (
<p className="text-xs text-[var(--text-color-secondary)] mt-1 line-clamp-1">
{video.vod_remarks}
</p>
)}
</div>
</Card>
))}
{/* Info */}
<div className="p-3">
<h4 className="font-semibold text-sm text-[var(--text-color)] line-clamp-2 group-hover:text-[var(--accent-color)] transition-colors">
{video.vod_name}
</h4>
{video.vod_remarks && (
<p className="text-xs text-[var(--text-color-secondary)] mt-1 line-clamp-1">
{video.vod_remarks}
</p>
)}
</div>
</Card>
</Link>
);
})}
</div>
</div>
)}
+41 -24
View File
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { Icons } from '@/components/ui/Icon';
import Image from 'next/image';
function PlayerContent() {
const searchParams = useSearchParams();
@@ -27,22 +28,22 @@ function PlayerContent() {
const getSourceName = (sourceId: string | null): string => {
if (!sourceId) return '';
const sourceNames: Record<string, string> = {
'custom_0': '电影天堂',
'custom_1': '如意',
'custom_2': '暴风',
'custom_3': '天涯',
'custom_4': '非凡影视',
'custom_5': '360',
'custom_6': '卧龙',
'custom_7': '极速',
'custom_8': '魔爪',
'custom_9': '魔都',
'custom_10': '海外看',
'custom_11': '新浪',
'custom_12': '光速',
'custom_13': '红牛',
'custom_14': '樱花',
'custom_15': '飞速',
'dytt': '电影天堂',
'ruyi': '如意',
'baofeng': '暴风',
'tianya': '天涯',
'feifan': '非凡影视',
'sanliuling': '360',
'wolong': '卧龙',
'jisu': '极速',
'mozhua': '魔爪',
'modu': '魔都',
'zuida': '最大',
'yinghua': '樱花',
'baiduyun': '百度云',
'wujin': '无尽',
'wangwang': '旺旺',
'ikun': 'iKun',
};
return sourceNames[sourceId] || sourceId;
};
@@ -154,14 +155,30 @@ function PlayerContent() {
<nav className="sticky top-4 z-50 mx-4 mt-4 mb-8">
<div className="max-w-7xl mx-auto bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[0_4px_12px_color-mix(in_srgb,var(--shadow-color)_40%,transparent)] px-6 py-4">
<div className="flex items-center justify-between">
<Button
variant="secondary"
onClick={() => router.back()}
className="flex items-center gap-2"
>
<Icons.ChevronLeft size={20} />
<span></span>
</Button>
<div className="flex items-center gap-3">
<Button
variant="secondary"
onClick={() => router.push('/')}
className="flex items-center gap-2 p-2 w-10 h-10"
title="返回首页"
>
<Image
src="/icon.png"
alt="KVideo"
width={24}
height={24}
className="object-contain"
/>
</Button>
<Button
variant="secondary"
onClick={() => router.back()}
className="flex items-center gap-2"
>
<Icons.ChevronLeft size={20} />
<span></span>
</Button>
</div>
<ThemeSwitcher />
</div>
</div>
+16 -16
View File
@@ -11,7 +11,7 @@ const HEALTH_CHECK_TIMEOUT = 5000;
// Default predefined video sources - Real Chinese video APIs
const DEFAULT_SOURCES: VideoSource[] = [
{
id: 'custom_0',
id: 'dytt',
name: '电影天堂',
baseUrl: 'http://caiji.dyttzyapi.com/api.php/provide/vod',
searchPath: '',
@@ -20,7 +20,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 1,
},
{
id: 'custom_1',
id: 'ruyi',
name: '如意',
baseUrl: 'https://cj.rycjapi.com/api.php/provide/vod',
searchPath: '',
@@ -29,7 +29,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 2,
},
{
id: 'custom_2',
id: 'baofeng',
name: '暴风',
baseUrl: 'https://bfzyapi.com/api.php/provide/vod',
searchPath: '',
@@ -38,7 +38,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 3,
},
{
id: 'custom_3',
id: 'tianya',
name: '天涯',
baseUrl: 'https://tyyszy.com/api.php/provide/vod',
searchPath: '',
@@ -47,7 +47,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 4,
},
{
id: 'custom_4',
id: 'feifan',
name: '非凡影视',
baseUrl: 'http://ffzy5.tv/api.php/provide/vod',
searchPath: '',
@@ -56,7 +56,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 5,
},
{
id: 'custom_5',
id: 'sanliuling',
name: '360',
baseUrl: 'https://360zy.com/api.php/provide/vod',
searchPath: '',
@@ -65,7 +65,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 6,
},
{
id: 'custom_6',
id: 'wolong',
name: '卧龙',
baseUrl: 'https://wolongzyw.com/api.php/provide/vod',
searchPath: '',
@@ -74,7 +74,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 7,
},
{
id: 'custom_7',
id: 'jisu',
name: '极速',
baseUrl: 'https://jszyapi.com/api.php/provide/vod',
searchPath: '',
@@ -83,7 +83,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 8,
},
{
id: 'custom_8',
id: 'mozhua',
name: '魔爪',
baseUrl: 'https://mozhuazy.com/api.php/provide/vod',
searchPath: '',
@@ -92,7 +92,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 9,
},
{
id: 'custom_9',
id: 'modu',
name: '魔都',
baseUrl: 'https://www.mdzyapi.com/api.php/provide/vod',
searchPath: '',
@@ -101,7 +101,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 10,
},
{
id: 'custom_10',
id: 'zuida',
name: '最大',
baseUrl: 'https://api.zuidapi.com/api.php/provide/vod',
searchPath: '',
@@ -110,7 +110,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 11,
},
{
id: 'custom_11',
id: 'yinghua',
name: '樱花',
baseUrl: 'https://m3u8.apiyhzy.com/api.php/provide/vod',
searchPath: '',
@@ -119,7 +119,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 12,
},
{
id: 'custom_12',
id: 'baiduyun',
name: '百度云',
baseUrl: 'https://api.apibdzy.com/api.php/provide/vod',
searchPath: '',
@@ -128,7 +128,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 13,
},
{
id: 'custom_13',
id: 'wujin',
name: '无尽',
baseUrl: 'https://api.wujinapi.me/api.php/provide/vod',
searchPath: '',
@@ -137,7 +137,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 14,
},
{
id: 'custom_14',
id: 'wangwang',
name: '旺旺',
baseUrl: 'https://wwzy.tv/api.php/provide/vod',
searchPath: '',
@@ -146,7 +146,7 @@ const DEFAULT_SOURCES: VideoSource[] = [
priority: 15,
},
{
id: 'custom_15',
id: 'ikun',
name: 'iKun',
baseUrl: 'https://ikunzyapi.com/api.php/provide/vod',
searchPath: '',