diff --git a/app/api/proxy/route.ts b/app/api/proxy/route.ts new file mode 100644 index 0000000..b8a9939 --- /dev/null +++ b/app/api/proxy/route.ts @@ -0,0 +1,86 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + const url = request.nextUrl.searchParams.get('url'); + + if (!url) { + return new NextResponse('Missing URL parameter', { status: 400 }); + } + + try { + // Beijing IP address to simulate request from China + const chinaIP = '202.108.22.5'; + + const response = await fetch(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'X-Forwarded-For': chinaIP, + 'Client-IP': chinaIP, + 'Referer': new URL(url).origin, + }, + }); + + const contentType = response.headers.get('Content-Type'); + + // Handle m3u8 playlists: rewrite URLs to go through proxy + if (contentType && (contentType.includes('application/vnd.apple.mpegurl') || contentType.includes('application/x-mpegurl') || url.endsWith('.m3u8'))) { + const text = await response.text(); + const baseUrl = new URL(url); + + const modifiedText = text.split('\n').map(line => { + // Skip comments and empty lines + if (line.trim().startsWith('#') || !line.trim()) { + return line; + } + + // Resolve relative URLs + try { + const absoluteUrl = new URL(line.trim(), baseUrl).toString(); + // Wrap in proxy + return `${request.nextUrl.origin}/api/proxy?url=${encodeURIComponent(absoluteUrl)}`; + } catch (e) { + return line; + } + }).join('\n'); + + return new NextResponse(modifiedText, { + status: response.status, + statusText: response.statusText, + headers: { + 'Content-Type': contentType, + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }); + } + + // For non-m3u8 content (segments, mp4, etc.), stream directly + const newResponse = new NextResponse(response.body, { + status: response.status, + statusText: response.statusText, + headers: new Headers(response.headers), + }); + + // Add CORS headers to allow playback + newResponse.headers.set('Access-Control-Allow-Origin', '*'); + newResponse.headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS'); + newResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + return newResponse; + } catch (error) { + console.error('Proxy error:', error); + return new NextResponse('Proxy failed', { status: 500 }); + } +} + +export async function OPTIONS(request: NextRequest) { + return new NextResponse(null, { + status: 204, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }); +} diff --git a/components/player/CustomVideoPlayer.tsx b/components/player/CustomVideoPlayer.tsx index 3434483..d9fb4df 100644 --- a/components/player/CustomVideoPlayer.tsx +++ b/components/player/CustomVideoPlayer.tsx @@ -10,6 +10,7 @@ interface CustomVideoPlayerProps { onError?: (error: string) => void; onTimeUpdate?: (currentTime: number, duration: number) => void; initialTime?: number; + shouldAutoPlay?: boolean; } /** @@ -19,8 +20,8 @@ interface CustomVideoPlayerProps { */ export function CustomVideoPlayer(props: CustomVideoPlayerProps) { const isMobile = useIsMobile(); - - return isMobile - ? + + return isMobile + ? : ; } diff --git a/components/player/DesktopVideoPlayer.tsx b/components/player/DesktopVideoPlayer.tsx index e58bc8b..b016105 100644 --- a/components/player/DesktopVideoPlayer.tsx +++ b/components/player/DesktopVideoPlayer.tsx @@ -11,6 +11,7 @@ interface DesktopVideoPlayerProps { onError?: (error: string) => void; onTimeUpdate?: (currentTime: number, duration: number) => void; initialTime?: number; + shouldAutoPlay?: boolean; } export function DesktopVideoPlayer({ @@ -18,7 +19,8 @@ export function DesktopVideoPlayer({ poster, onError, onTimeUpdate, - initialTime = 0 + initialTime = 0, + shouldAutoPlay = false }: DesktopVideoPlayerProps) { const { refs, state } = useDesktopPlayerState(); const { @@ -35,6 +37,7 @@ export function DesktopVideoPlayer({ const logic = useDesktopPlayerLogic({ src, initialTime, + shouldAutoPlay, onError, onTimeUpdate, refs, diff --git a/components/player/MobileVideoPlayer.tsx b/components/player/MobileVideoPlayer.tsx index f8a76c8..54b5b01 100644 --- a/components/player/MobileVideoPlayer.tsx +++ b/components/player/MobileVideoPlayer.tsx @@ -15,6 +15,7 @@ interface MobileVideoPlayerProps { onError?: (error: string) => void; onTimeUpdate?: (currentTime: number, duration: number) => void; initialTime?: number; + shouldAutoPlay?: boolean; } export function MobileVideoPlayer({ @@ -22,7 +23,8 @@ export function MobileVideoPlayer({ poster, onError, onTimeUpdate, - initialTime = 0 + initialTime = 0, + shouldAutoPlay = false }: MobileVideoPlayerProps) { const { refs, state } = useMobilePlayerState(); const { @@ -48,6 +50,7 @@ export function MobileVideoPlayer({ const logic = useMobilePlayerLogic({ src, initialTime, + shouldAutoPlay, onError, onTimeUpdate, refs, diff --git a/components/player/VideoPlayer.tsx b/components/player/VideoPlayer.tsx index 3ba4aa2..2533090 100644 --- a/components/player/VideoPlayer.tsx +++ b/components/player/VideoPlayer.tsx @@ -17,6 +17,8 @@ interface VideoPlayerProps { export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoPlayerProps) { const [videoError, setVideoError] = useState(''); + const [useProxy, setUseProxy] = useState(false); + const [shouldAutoPlay, setShouldAutoPlay] = useState(false); // Use reactive hook to subscribe to history updates // This ensures the component re-renders when history is hydrated from localStorage const viewingHistory = useHistoryStore(state => state.viewingHistory); @@ -66,9 +68,23 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP // Handle video errors const handleVideoError = (error: string) => { console.error('Video playback error:', error); + + // Auto-retry with proxy if not already using it + if (!useProxy) { + console.log('Attempting to retry with proxy...'); + setUseProxy(true); + setShouldAutoPlay(true); // Force autoplay after proxy retry + setVideoError(''); + return; + } + setVideoError(error); }; + const finalPlayUrl = useProxy + ? `/api/proxy?url=${encodeURIComponent(playUrl)}` + : playUrl; + if (!playUrl) { return ( @@ -96,14 +112,6 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP

播放失败

{videoError}

-