diff --git a/app/api/detail/route.ts b/app/api/detail/route.ts index 1b9b551..3c9c3fd 100644 --- a/app/api/detail/route.ts +++ b/app/api/detail/route.ts @@ -28,7 +28,7 @@ async function handleDetailRequest(id: string | null, source: string | null, met } const sourceConfig = getSourceById(source); - + if (!sourceConfig) { return NextResponse.json( { error: 'Invalid source ID' }, @@ -39,18 +39,18 @@ async function handleDetailRequest(id: string | null, source: string | null, met // Fetch video detail without validation (already validated during search) try { const videoDetail = await getVideoDetail(id, sourceConfig); - + // Skip validation - videos are already checked during search // Just return the episodes as-is - console.log(`[${method}] Fetching video details for ${id} from ${sourceConfig.name}`); - + + return NextResponse.json({ success: true, data: videoDetail, }); } catch (error) { console.error('Detail API error:', error); - + return NextResponse.json( { success: false, @@ -70,7 +70,7 @@ export async function GET(request: NextRequest) { return await handleDetailRequest(id, source, 'GET'); } catch (error) { console.error('Detail API error:', error); - + return NextResponse.json( { success: false, @@ -90,7 +90,7 @@ export async function POST(request: NextRequest) { return await handleDetailRequest(id, source, 'POST'); } catch (error) { console.error('Detail API error:', error); - + return NextResponse.json( { success: false, diff --git a/app/api/search-parallel/route.ts b/app/api/search-parallel/route.ts index 133a4c3..77cb22b 100644 --- a/app/api/search-parallel/route.ts +++ b/app/api/search-parallel/route.ts @@ -10,7 +10,7 @@ import { getSourceById } from '@/lib/api/video-sources'; export async function POST(request: NextRequest) { const encoder = new TextEncoder(); - + const stream = new ReadableStream({ async start(controller) { try { @@ -19,9 +19,9 @@ export async function POST(request: NextRequest) { // Validate input if (!query || typeof query !== 'string' || query.trim().length === 0) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'error', - message: 'Invalid query' + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + type: 'error', + message: 'Invalid query' })}\n\n`)); controller.close(); return; @@ -33,21 +33,21 @@ export async function POST(request: NextRequest) { .filter((source: any): source is NonNullable => source !== undefined); if (sources.length === 0) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'error', - message: 'No valid sources' + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + type: 'error', + message: 'No valid sources' })}\n\n`)); controller.close(); return; } // Send initial status - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'start', totalSources: sources.length })}\n\n`)); - console.log(`[Search Parallel] Starting search for "${query}" across ${sources.length} sources`); + // Track progress let completedSources = 0; @@ -57,22 +57,22 @@ export async function POST(request: NextRequest) { const searchPromises = sources.map(async (source: any) => { const startTime = performance.now(); // Track start time try { - console.log(`[Search Parallel] Searching source: ${source.id} (${getSourceDisplayName(source.id)})`); - + + // Search this source const result = await searchVideos(query.trim(), [source], page); const endTime = performance.now(); // Track end time const latency = Math.round(endTime - startTime); // Calculate latency in ms const videos = result[0]?.results || []; - + completedSources++; totalVideosFound += videos.length; - console.log(`[Search Parallel] Source ${source.id} completed in ${latency}ms: ${videos.length} videos found`); + // Stream videos immediately as they arrive WITH latency data if (videos.length > 0) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'videos', videos: videos.map((video: any) => ({ ...video, @@ -87,7 +87,7 @@ export async function POST(request: NextRequest) { } // Send progress update - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'progress', completedSources, totalSources: sources.length, @@ -100,8 +100,8 @@ export async function POST(request: NextRequest) { // Log error but continue with other sources console.error(`[Search Parallel] Source ${source.id} failed after ${latency}ms:`, error); completedSources++; - - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'progress', completedSources, totalSources: sources.length, @@ -113,10 +113,10 @@ export async function POST(request: NextRequest) { // Wait for all sources to complete await Promise.all(searchPromises); - console.log(`[Search Parallel] Search complete: ${totalVideosFound} total videos found from ${completedSources}/${sources.length} sources`); + // Send completion signal - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'complete', totalVideosFound, totalSources: sources.length @@ -126,7 +126,7 @@ export async function POST(request: NextRequest) { } catch (error) { console.error('Search error:', error); - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', message: error instanceof Error ? error.message : 'Unknown error' })}\n\n`)); diff --git a/app/page.tsx b/app/page.tsx index b03135f..1ebda39 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -24,7 +24,7 @@ function HomePage() { const searchParams = useSearchParams(); const { loadFromCache, saveToCache } = useSearchCache(); const hasLoadedCache = useRef(false); - + const [query, setQuery] = useState(''); const [hasSearched, setHasSearched] = useState(false); const [currentSortBy, setCurrentSortBy] = useState('default'); @@ -75,15 +75,15 @@ function HomePage() { const urlQuery = searchParams.get('q'); const cached = loadFromCache(); - + if (urlQuery) { setQuery(urlQuery); if (cached && cached.query === urlQuery && cached.results.length > 0) { - console.log('📦 Loading cached results for:', urlQuery, cached.results.length, 'videos'); + setHasSearched(true); loadCachedResults(cached.results, cached.availableSources); } else { - console.log('🔍 Auto-searching for URL query:', urlQuery); + setTimeout(() => handleSearch(urlQuery), 100); } } @@ -114,16 +114,16 @@ function HomePage() { transform: 'translate3d(0, 0, 0)' }}>
-
- KVideo @@ -133,7 +133,7 @@ function HomePage() {

视频聚合平台

- +
- + @@ -177,7 +177,7 @@ function HomePage() { resultsCount={results.length} availableSources={availableSources} /> - + {/* Source Badges - Clickable video source filtering */} {availableSources.length > 0 && ( )} - + {/* Type Badges - Auto-collected from search results */} {typeBadges.length > 0 && ( )} - + {/* Display filtered videos (both source and type filters applied) */}
diff --git a/lib/api/client.ts b/lib/api/client.ts index 8641269..5bae31d 100644 --- a/lib/api/client.ts +++ b/lib/api/client.ts @@ -54,7 +54,7 @@ async function withRetry( return await fn(); } catch (error) { lastError = error as Error; - + if (i < retries) { await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * (i + 1))); } @@ -73,7 +73,7 @@ async function searchVideosBySource( page: number = 1 ): Promise<{ results: VideoItem[]; source: string; responseTime: number }> { const startTime = Date.now(); - + const url = new URL(`${source.baseUrl}${source.searchPath}`); url.searchParams.set('ac', 'detail'); url.searchParams.set('wd', query); @@ -155,7 +155,7 @@ function parseEpisodes(playUrl: string): Episode[] { try { // Format: "Episode1$url1#Episode2$url2#..." const episodes = playUrl.split('#').filter(Boolean); - + return episodes.map((episode, index) => { const [name, url] = episode.split('$'); return { @@ -200,11 +200,7 @@ export async function getVideoDetail( const data: ApiDetailResponse = await response.json(); - console.log(`Video detail fetched from ${source.name}:`, { - id, - code: data.code, - hasData: !!data.list && data.list.length > 0 - }); + if (data.code !== 1 && data.code !== 0) { throw new Error(data.msg || 'Invalid API response'); @@ -215,15 +211,15 @@ export async function getVideoDetail( } const videoData = data.list[0]; - + // Parse episodes from vod_play_url const episodes = parseEpisodes(videoData.vod_play_url || ''); - console.log(`Parsed ${episodes.length} episodes for video ${id}`); + if (episodes.length > 0) { - console.log('First episode URL:', episodes[0].url); } + return { vod_id: videoData.vod_id, vod_name: videoData.vod_name, diff --git a/lib/hooks/useParallelSearch.ts b/lib/hooks/useParallelSearch.ts index 2fdc500..6e6be43 100644 --- a/lib/hooks/useParallelSearch.ts +++ b/lib/hooks/useParallelSearch.ts @@ -47,7 +47,7 @@ export function useParallelSearch( const [totalSources, setTotalSources] = useState(0); const [totalVideosFound, setTotalVideosFound] = useState(0); const currentQueryRef = useRef(''); - + const abortControllerRef = useRef(null); /** @@ -107,8 +107,8 @@ export function useParallelSearch( if (data.type === 'start') { setTotalSources(data.totalSources); - console.log(`[useParallelSearch] Started search for ${data.totalSources} sources`); - } + + } else if (data.type === 'videos') { const currentQuery = currentQueryRef.current; const newVideos: Video[] = data.videos.map((video: any) => ({ @@ -118,18 +118,18 @@ export function useParallelSearch( relevanceScore: calculateRelevanceScore(video, currentQuery), })); - console.log(`[useParallelSearch] Received ${newVideos.length} videos from source ${data.source}`); + // Optimized: Insert new videos in sorted position instead of re-sorting entire array setResults((prev) => { if (prev.length === 0) return newVideos; - + // Binary insert for better performance with combined sorting const combined = [...prev]; for (const video of newVideos) { const relevanceScore = video.relevanceScore || 0; const latency = video.latency || 99999; // Default high latency for sorting - + // Find insert position using binary search // Sort by: 1) relevance score (DESC), 2) latency (ASC) let left = 0; @@ -138,7 +138,7 @@ export function useParallelSearch( const mid = Math.floor((left + right) / 2); const midRelevance = combined[mid].relevanceScore || 0; const midLatency = combined[mid].latency || 99999; - + // Compare by relevance first if (midRelevance > relevanceScore) { left = mid + 1; @@ -165,17 +165,16 @@ export function useParallelSearch( name: newVideos[0]?.sourceName || data.source, }); } - } + } else if (data.type === 'progress') { setCompletedSources(data.completedSources); setTotalVideosFound(data.totalVideosFound); - } + } else if (data.type === 'complete') { setLoading(false); - - console.log(`[useParallelSearch] Search complete: ${data.totalVideosFound} videos found`); - console.log(`[useParallelSearch] Sources with videos: ${sourcesMap.size}`); - + + + // Update available sources with correct property names const sources = Array.from(sourcesMap.entries()).map(([id, info]) => ({ id: id, // Changed from sourceId to id @@ -184,20 +183,20 @@ export function useParallelSearch( })); setAvailableSources(sources); - console.log('[useParallelSearch] Available sources:', sources); + // Apply final sorting after all results are received setResults((currentResults) => { const sorted = sortVideos(currentResults, sortBy); - + // Cache results setTimeout(() => { onCacheUpdate(searchQuery, sorted, sources); }, 100); - + return sorted; }); - } + } else if (data.type === 'error') { console.error('Search error:', data.message); setLoading(false); @@ -209,7 +208,7 @@ export function useParallelSearch( } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { - console.log('Search aborted'); + } else { console.error('Search error:', error); } @@ -237,7 +236,7 @@ export function useParallelSearch( * Load cached results */ const loadCachedResults = useCallback((cachedResults: Video[], cachedSources: any[]) => { - console.log('[useParallelSearch] Loading cached results:', cachedResults.length, 'videos'); + setResults(cachedResults); setAvailableSources(cachedSources); setTotalVideosFound(cachedResults.length); diff --git a/lib/hooks/useSearchCache.ts b/lib/hooks/useSearchCache.ts index baf9692..831bbc1 100644 --- a/lib/hooks/useSearchCache.ts +++ b/lib/hooks/useSearchCache.ts @@ -22,10 +22,10 @@ export function useSearchCache() { availableSources: sources, timestamp: Date.now(), }; - + try { localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); - console.log('💾 Saved search to cache:', query, results.length, 'results'); + } catch (error) { console.error('Failed to save cache:', error); } @@ -35,15 +35,15 @@ export function useSearchCache() { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) return null; - + const cache: SearchCache = JSON.parse(cached); - + // Check if cache is still valid if (Date.now() - cache.timestamp > CACHE_DURATION) { localStorage.removeItem(CACHE_KEY); return null; } - + return cache; } catch (error) { console.error('Failed to load cache:', error); diff --git a/lib/hooks/useVideoPlayer.ts b/lib/hooks/useVideoPlayer.ts index 0963e8e..529f1b8 100644 --- a/lib/hooks/useVideoPlayer.ts +++ b/lib/hooks/useVideoPlayer.ts @@ -44,11 +44,11 @@ export function useVideoPlayer( try { setVideoError(''); setLoading(true); - + const response = await fetch(`/api/detail?id=${videoId}&source=${source}`); const data = await response.json(); - console.log('Video detail API response:', data); + if (!response.ok) { if (response.status === 404) { @@ -60,22 +60,17 @@ export function useVideoPlayer( } if (data.success && data.data) { - console.log('Video data received:', { - id: data.data.vod_id, - name: data.data.vod_name, - episodeCount: data.data.episodes?.length || 0, - firstEpisodeUrl: data.data.episodes?.[0]?.url - }); + setVideoData(data.data); setLoading(false); - + if (data.data.episodes && data.data.episodes.length > 0) { const episodeIndex = episodeParam ? parseInt(episodeParam, 10) : 0; const validIndex = (episodeIndex >= 0 && episodeIndex < data.data.episodes.length) ? episodeIndex : 0; - + const episodeUrl = data.data.episodes[validIndex].url; - console.log('Setting play URL for episode', validIndex, ':', episodeUrl); + setCurrentEpisode(validIndex); setPlayUrl(episodeUrl); } else { diff --git a/lib/utils/latency.ts b/lib/utils/latency.ts index 80337ba..9200708 100644 --- a/lib/utils/latency.ts +++ b/lib/utils/latency.ts @@ -50,14 +50,4 @@ export function formatLatency(latency: number): string { return `${latency}ms`; } -/** - * Get latency emoji indicator - * @param latency - Response time in milliseconds - * @returns Emoji representing speed - */ -export function getLatencyEmoji(latency: number): string { - if (latency < 500) return '⚡'; // Excellent - if (latency < 1000) return '✨'; // Good - if (latency < 2000) return '⏱️'; // Fair - return '🐌'; // Slow -} + diff --git a/package-lock.json b/package-lock.json index 8ff988f..250afce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@types/wcag-contrast": "^3.0.3", "eslint": "^9", "eslint-config-next": "16.0.3", "tailwindcss": "^4", @@ -1579,13 +1578,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/wcag-contrast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/wcag-contrast/-/wcag-contrast-3.0.3.tgz", - "integrity": "sha512-oprevfwJSLfpQK4KaWsRKJuNoebV76+xhmbXiWJGy+FkS34LpCgCMNIwRXWTb8xmmSxUE2ycFOYE7uyRVRm3LA==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz",