'use client'; import { useEffect, useState, useCallback, useRef } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { getTMDBImageUrl, getGenreNames, type TMDBItem } from '@/lib/tmdb.client'; import { ChevronLeft, ChevronRight, Play } from 'lucide-react'; interface BannerCarouselProps { autoPlayInterval?: number; // 自动播放间隔(毫秒) } // 扩展TMDBItem类型以支持TX数据源的额外字段 interface BannerItem extends TMDBItem { subtitle?: string; // TX数据源的子标题 tags?: string[]; // TX数据源的标签 } export default function BannerCarousel({ autoPlayInterval = 5000 }: BannerCarouselProps) { const router = useRouter(); const [items, setItems] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isPaused, setIsPaused] = useState(false); const [skipNextAutoPlay, setSkipNextAutoPlay] = useState(false); // 跳过下一次自动播放 const [isYouTubeAccessible, setIsYouTubeAccessible] = useState(false); // YouTube连通性(默认false,检查后再决定) const [enableTrailers, setEnableTrailers] = useState(false); // 是否启用预告片(默认关闭) const touchStartX = useRef(0); const touchEndX = useRef(0); const isManualChange = useRef(false); // 标记是否为手动切换 // LocalStorage 缓存配置 const LOCALSTORAGE_DURATION = 24 * 60 * 60 * 1000; // 1天 // 根据数据源获取缓存key const getLocalStorageKey = (source: string) => { return `banner_trending_cache_${source}`; }; // 跳转到播放页面 const handlePlay = (title: string) => { router.push(`/play?title=${encodeURIComponent(title)}`); }; // 获取图片URL(处理TX完整URL和TMDB路径) const getImageUrl = (path: string | null) => { if (!path) return ''; // 如果是完整URL(TX数据源),直接返回 if (path.startsWith('http://') || path.startsWith('https://')) { return path; } // 否则使用TMDB的URL拼接 return getTMDBImageUrl(path, 'original'); }; // 读取本地设置 useEffect(() => { const setting = localStorage.getItem('enableTrailers'); if (setting !== null) { setEnableTrailers(setting === 'true'); } }, []); // 检测YouTube连通性 - 仅在启用预告片时检测 useEffect(() => { // 如果未启用预告片,不进行检测 if (!enableTrailers) { setIsYouTubeAccessible(false); return; } const checkYouTubeAccess = () => { const img = document.createElement('img'); const timeout = setTimeout(() => { img.src = ''; setIsYouTubeAccessible(false); }, 3000); img.onload = () => { clearTimeout(timeout); setIsYouTubeAccessible(true); }; img.onerror = () => { clearTimeout(timeout); setIsYouTubeAccessible(false); }; // 添加随机查询参数避免缓存 img.src = `https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg?t=${Date.now()}`; }; checkYouTubeAccess(); }, [enableTrailers]); // 获取热门内容 useEffect(() => { const fetchTrending = async () => { try { // 先尝试从所有可能的数据源缓存中读取 const sources = ['TMDB', 'TX']; let cachedData = null; let validSource = null; for (const source of sources) { const cacheKey = getLocalStorageKey(source); const cached = localStorage.getItem(cacheKey); if (cached) { try { const { data, timestamp } = JSON.parse(cached); const now = Date.now(); // 如果缓存未过期,使用缓存数据 if (now - timestamp < LOCALSTORAGE_DURATION) { cachedData = data; validSource = source; break; } } catch (e) { console.error('解析缓存数据失败:', e); } } } // 如果有有效的缓存,直接使用,不请求API if (cachedData) { setItems(cachedData); setIsLoading(false); return; } // 没有缓存或缓存过期,从 API 获取数据 const response = await fetch('/api/tmdb/trending'); const result = await response.json(); if (result.code === 200 && result.list.length > 0) { const dataSource = result.source || 'TMDB'; // 获取数据源标识 const cacheKey = getLocalStorageKey(dataSource); setItems(result.list); // 保存到 localStorage(使用数据源特定的key) try { localStorage.setItem(cacheKey, JSON.stringify({ data: result.list, timestamp: Date.now() })); } catch (e) { // localStorage 可能已满,忽略错误 console.error('保存到 localStorage 失败:', e); } } } catch (error) { console.error('获取热门内容失败:', error); } finally { setIsLoading(false); } }; fetchTrending(); }, []); // 自动播放 useEffect(() => { if (!items.length || isPaused) return; const timer = setInterval(() => { // 如果设置了跳过标志,跳过这一次自动播放 if (skipNextAutoPlay) { setSkipNextAutoPlay(false); return; } setCurrentIndex((prev) => (prev + 1) % items.length); }, autoPlayInterval); return () => clearInterval(timer); }, [items.length, isPaused, autoPlayInterval, skipNextAutoPlay]); const goToPrevious = useCallback(() => { isManualChange.current = true; setSkipNextAutoPlay(true); setCurrentIndex((prev) => (prev - 1 + items.length) % items.length); setTimeout(() => { isManualChange.current = false; }, 100); }, [items.length]); const goToNext = useCallback(() => { isManualChange.current = true; setSkipNextAutoPlay(true); setCurrentIndex((prev) => (prev + 1) % items.length); setTimeout(() => { isManualChange.current = false; }, 100); }, [items.length]); const goToSlide = useCallback((index: number) => { isManualChange.current = true; setSkipNextAutoPlay(true); setCurrentIndex(index); setTimeout(() => { isManualChange.current = false; }, 100); }, []); // 触摸事件处理 const handleTouchStart = (e: React.TouchEvent) => { // 防止在手动切换过程中触发 if (isManualChange.current) return; touchStartX.current = e.touches[0].clientX; touchEndX.current = 0; // 重置结束位置 }; const handleTouchMove = (e: React.TouchEvent) => { // 防止在手动切换过程中触发 if (isManualChange.current) return; touchEndX.current = e.touches[0].clientX; }; const handleTouchEnd = () => { // 防止在手动切换过程中触发 if (isManualChange.current) return; if (!touchStartX.current) return; // 如果有滑动,则执行滑动逻辑 if (touchEndX.current !== 0) { const distance = touchStartX.current - touchEndX.current; const minSwipeDistance = 50; // 最小滑动距离 if (Math.abs(distance) > minSwipeDistance) { if (distance > 0) { // 向左滑动,显示下一张 goToNext(); } else { // 向右滑动,显示上一张 goToPrevious(); } } } // 重置 touchStartX.current = 0; touchEndX.current = 0; }; if (isLoading) { return (
); } if (!items.length) { return null; } const currentItem = items[currentIndex]; return (
setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} onClick={() => { // 移动端点击整个轮播图跳转 if (window.innerWidth < 768) { handlePlay(currentItem.title); } }} > {/* 背景图片或视频 */}
{items.map((item, index) => (
{item.video_key && isYouTubeAccessible && enableTrailers ? ( /* 显示YouTube视频 */