'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter, usePathname, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import Image from 'next/image'; import VideoPlayer from '@/components/VideoPlayer'; import EpisodeList from '@/components/EpisodeList'; import { fetchEpisodeSources, fetchAnimeInfo } from '@/lib/api'; export default function WatchPage() { const { episodeId } = useParams(); const router = useRouter(); const pathname = usePathname(); const [videoSource, setVideoSource] = useState(null); const [anime, setAnime] = useState(null); const [currentEpisode, setCurrentEpisode] = useState(null); const [isDub, setIsDub] = useState(false); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [videoHeaders, setVideoHeaders] = useState({}); const [subtitles, setSubtitles] = useState([]); const [thumbnails, setThumbnails] = useState(null); const [animeId, setAnimeId] = useState(null); const [episodeData, setEpisodeData] = useState(null); const [isRetrying, setIsRetrying] = useState(false); const [currentPage, setCurrentPage] = useState(1); const episodesPerPage = 100; const [showFullSynopsis, setShowFullSynopsis] = useState(false); const [autoSkip, setAutoSkip] = useState(false); const [currentEpisodeId, setCurrentEpisodeId] = useState(episodeId); // Handle URL updates when currentEpisodeId changes useEffect(() => { if (currentEpisodeId && currentEpisodeId !== episodeId) { const newUrl = `/watch/${currentEpisodeId}`; window.history.pushState({ episodeId: currentEpisodeId }, '', newUrl); } }, [currentEpisodeId, episodeId]); // Listen for popstate (browser back/forward) events useEffect(() => { const handlePopState = (event) => { const path = window.location.pathname; const match = path.match(/\/watch\/(.+)$/); if (match) { const newEpisodeId = match[1]; setCurrentEpisodeId(newEpisodeId); } }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, []); // Extract animeId from the URL useEffect(() => { if (episodeId) { // Log the raw episodeId from the URL for debugging console.log('[Watch] Raw episodeId from URL:', episodeId); // The URL might contain query parameters for episode number const [baseId, queryParams] = episodeId.split('?'); console.log('[Watch] Base ID:', baseId); setAnimeId(baseId); setCurrentEpisodeId(episodeId); } }, [episodeId]); // Fetch episode sources first to ensure we have data even if anime info fails useEffect(() => { if (!currentEpisodeId || currentEpisodeId === 'undefined') { setError('Invalid episode ID'); setIsLoading(false); return; } const fetchVideoData = async () => { setIsLoading(true); setError(null); setVideoSource(null); try { console.log(`[Watch] Fetching video for episode ${currentEpisodeId} (dub: ${isDub})`); // Fetch the episode sources from the API const data = await fetchEpisodeSources(currentEpisodeId, isDub); console.log('[Watch] Episode API response:', data); setEpisodeData(data); if (!data || !data.sources || data.sources.length === 0) { throw new Error('No video sources available for this episode'); } // Extract headers if they exist in the response if (data.headers) { console.log('[Watch] Headers from API:', data.headers); setVideoHeaders(data.headers); } else { // Set default headers if none provided const defaultHeaders = { "Referer": "https://hianime.to/", "Origin": "https://hianime.to" }; console.log('[Watch] No headers provided from API, using defaults:', defaultHeaders); setVideoHeaders(defaultHeaders); } // Try to find the best source in order of preference // 1. HLS (m3u8) sources // 2. High quality MP4 sources const hlsSource = data.sources.find(src => src.isM3U8); const mp4Source = data.sources.find(src => !src.isM3U8); let selectedSource = null; if (hlsSource && hlsSource.url) { console.log('[Watch] Selected HLS source:', hlsSource.url); selectedSource = hlsSource.url; } else if (mp4Source && mp4Source.url) { console.log('[Watch] Selected MP4 source:', mp4Source.url); selectedSource = mp4Source.url; } else if (data.sources[0] && data.sources[0].url) { console.log('[Watch] Falling back to first available source:', data.sources[0].url); selectedSource = data.sources[0].url; } else { throw new Error('No valid video URLs found'); } setVideoSource(selectedSource); setIsLoading(false); } catch (error) { console.error('[Watch] Error fetching video sources:', error); setError(error.message || 'Failed to load video'); setIsLoading(false); // If this is the first try, attempt to retry once if (!isRetrying) { console.log('[Watch] First error, attempting retry...'); setIsRetrying(true); setTimeout(() => { console.log('[Watch] Executing retry...'); fetchVideoData(); }, 2000); } } }; fetchVideoData(); }, [currentEpisodeId, isDub, isRetrying]); // Fetch anime info using extracted animeId useEffect(() => { if (animeId) { const fetchAnimeDetails = async () => { try { setIsRetrying(true); console.log(`[Watch] Fetching anime info for ID: ${animeId}`); const animeData = await fetchAnimeInfo(animeId); if (animeData) { console.log('[Watch] Anime info received:', animeData.title); setAnime(animeData); // Find the current episode in the anime episode list if (animeData.episodes && animeData.episodes.length > 0) { console.log('[Watch] Episodes found:', animeData.episodes.length); // First try exact match let episode = animeData.episodes.find(ep => ep.id === episodeId); // If not found, try to find by checking if episodeId is contained in ep.id if (!episode && episodeId.includes('$episode$')) { const episodeIdPart = episodeId.split('$episode$')[1]; episode = animeData.episodes.find(ep => ep.id.includes(episodeIdPart)); } if (episode) { setCurrentEpisode(episode); console.log('[Watch] Current episode found:', episode.number); } else { console.warn('[Watch] Current episode not found in episode list. Looking for:', episodeId); console.log('[Watch] First few episodes:', animeData.episodes.slice(0, 3).map(ep => ep.id)); } } else { console.warn('[Watch] No episodes found in anime data or episodes array is empty'); } } else { console.error('[Watch] Failed to fetch anime info or received empty response'); } } catch (error) { console.error('[Watch] Error fetching anime info:', error); } finally { setIsRetrying(false); } }; fetchAnimeDetails(); } else { console.warn('[Watch] No animeId available to fetch anime details'); } }, [animeId, episodeId]); const handleDubToggle = () => { setIsDub(!isDub); }; const handleEpisodeClick = (newEpisodeId) => { if (newEpisodeId !== currentEpisodeId) { // Update the URL using history API const newUrl = `/watch/${newEpisodeId}`; window.history.pushState({ episodeId: newEpisodeId }, '', newUrl); // Update state to trigger video reload setCurrentEpisodeId(newEpisodeId); // Update current episode in state if (anime?.episodes) { const newEpisode = anime.episodes.find(ep => ep.id === newEpisodeId); if (newEpisode) { setCurrentEpisode(newEpisode); } } } }; const handleRetryAnimeInfo = () => { if (animeId) { setIsRetrying(true); fetchAnimeInfo(animeId) .then(data => { if (data) { setAnime(data); console.log('[Watch] Anime info retry succeeded:', data.title); } else { console.error('[Watch] Anime info retry failed: empty response'); } }) .catch(error => { console.error('[Watch] Anime info retry error:', error); }) .finally(() => { setIsRetrying(false); }); } }; const findAdjacentEpisodes = () => { if (!anime?.episodes || !currentEpisodeId) return { prev: null, next: null }; const currentIndex = anime.episodes.findIndex(ep => ep.id === currentEpisodeId); if (currentIndex === -1) return { prev: null, next: null }; return { prev: currentIndex > 0 ? anime.episodes[currentIndex - 1] : null, next: currentIndex < anime.episodes.length - 1 ? anime.episodes[currentIndex + 1] : null }; }; const { prev, next } = findAdjacentEpisodes(); return (
{/* Left Side - Video Player (70%) */}
{/* Video Player Container */}
{error ? (
Error: {error}

The video source couldn't be loaded. Please try again or check back later.

) : isLoading ? (
Loading video...
) : videoSource ? (
) : (
No video source available

Please try again or check back later.

)}
{/* Video Controls - Slimmer and without container background */}
{/* Audio and Playback Controls */}
{/* Playback Settings */}

Playback Settings

{/* Auto Skip Checkbox */} {(episodeData?.intro || episodeData?.outro) && ( )}
{/* Audio Toggle */}

Audio

{/* Episode Navigation */}
{anime?.episodes && ( <> )}
{/* Anime Info Section */} {anime && (
{/* Cover Image */}
{anime.title}
{/* Details */}

{anime.title}

{/* Status Bar */}
{anime.status} {anime.type} {anime.totalEpisodes} Episodes
{/* Synopsis Section */}

Synopsis

{anime.description}
{/* Genres */} {anime.genres && (
{anime.genres.map((genre, index) => ( {genre} ))}
)}
)}
{/* Right Side - Episode List (30%) */}
{anime?.episodes ? (
) : (
No episodes available
)}
); }