/* eslint-disable react/prop-types */
import { useEffect, useRef, useState, useMemo, useCallback } from "react";
import { useLocation, useParams, Link, useNavigate } from "react-router-dom";
import { useLanguage } from "@/src/context/LanguageContext";
import { useWatch } from "@/src/hooks/useWatch";
import BouncingLoader from "@/src/components/ui/bouncingloader/Bouncingloader";
import Episodelist from "@/src/components/episodelist/Episodelist";
import website_name from "@/src/config/website";
import Sidecard from "@/src/components/sidecard/Sidecard";
import {
faClosedCaptioning,
faMicrophone,
faPlay,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Servers from "@/src/components/servers/Servers";
import { Skeleton } from "@/src/components/ui/Skeleton/Skeleton";
import SidecardLoader from "@/src/components/Loader/Sidecard.loader";
import Watchcontrols from "@/src/components/watchcontrols/Watchcontrols";
import useWatchControl from "@/src/hooks/useWatchControl";
import Player from "@/src/components/player/Player";
import getSafeTitle from "@/src/utils/getSafetitle";
import { Helmet } from 'react-helmet-async';
import InfoTag from "@/src/components/ui/InfoTag/InfoTag";
import {
generateDescription,
generateKeywords,
generateCanonicalUrl,
generateOGImage,
generateAnimeStructuredData,
generateVideoStructuredData,
generateBreadcrumbStructuredData,
optimizeTitle,
} from '@/src/utils/seo.utils';
export default function Watch() {
const location = useLocation();
const navigate = useNavigate();
const { id: animeId } = useParams();
const queryParams = new URLSearchParams(location.search);
let initialEpisodeId = queryParams.get("ep");
const { language } = useLanguage();
const isFirstSet = useRef(true);
const [showNextEpisodeSchedule, setShowNextEpisodeSchedule] = useState(true);
const {
buffering,
streamInfo,
streamUrl,
animeInfo,
episodes,
nextEpisodeSchedule,
animeInfoLoading,
totalEpisodes,
isFullOverview,
intro,
outro,
subtitles,
thumbnail,
setIsFullOverview,
activeEpisodeNum,
episodeId,
setEpisodeId,
activeServerId,
setActiveServerId,
servers,
serverLoading,
activeServerType,
setActiveServerType,
activeServerName,
setActiveServerName,
seasons
} = useWatch(animeId, initialEpisodeId);
const {
autoPlay,
setAutoPlay,
autoSkipIntro,
setAutoSkipIntro,
autoNext,
setAutoNext,
} = useWatchControl();
const videoContainerRef = useRef(null);
const playerRef = useRef(null);
const episodesRef = useRef(null);
// Sync URL with episodeId
useEffect(() => {
if (!episodes?.length) return;
const currentEpNum = episodeId;
const isValidEpisode = episodes.some(ep => ep.id.split('ep=')[1] === currentEpNum);
if (!currentEpNum || !isValidEpisode) {
const fallbackId = episodes[0].id.match(/ep=(\d+)/)?.[1];
if (fallbackId && fallbackId !== currentEpNum) setEpisodeId(fallbackId);
return;
}
const newUrl = `/watch/${animeId}?ep=${currentEpNum}`;
if (isFirstSet.current) {
navigate(newUrl, { replace: true });
isFirstSet.current = false;
} else {
navigate(newUrl);
}
}, [episodeId, animeId, navigate, episodes, setEpisodeId]);
// Redirect if no episodes
useEffect(() => {
if (totalEpisodes === 0) navigate(`/${animeId}`);
}, [animeId, totalEpisodes, navigate]);
// Height adjustment logic
const adjustHeight = useCallback(() => {
if (window.innerWidth > 1200) {
if (playerRef.current && episodesRef.current) {
episodesRef.current.style.height = 'auto';
episodesRef.current.style.maxHeight = `${playerRef.current.offsetHeight}px`;
}
} else if (episodesRef.current) {
episodesRef.current.style.height = 'auto';
episodesRef.current.style.maxHeight = 'none';
}
}, []);
useEffect(() => {
const resizeObserver = new ResizeObserver(adjustHeight);
if (playerRef.current) resizeObserver.observe(playerRef.current);
window.addEventListener('resize', adjustHeight);
adjustHeight();
return () => {
resizeObserver.disconnect();
window.removeEventListener('resize', adjustHeight);
};
}, [adjustHeight, buffering, animeInfoLoading]);
const seoData = useMemo(() => {
if (!animeInfo) return null;
const safeT = getSafeTitle(animeInfo.title, language, animeInfo.japanese_title);
return {
safeTitle: safeT,
pageTitle: optimizeTitle(`Watch ${safeT} Episode ${activeEpisodeNum} Sub Dub Online Free`),
pageDescription: generateDescription(`Stream ${safeT} Episode ${activeEpisodeNum} in HD with English Sub and Dub. ${animeInfo.animeInfo?.Overview}`),
pageKeywords: `${generateKeywords(animeInfo)}, episode ${activeEpisodeNum}`,
canonicalUrl: generateCanonicalUrl(`/watch/${animeId}?ep=${episodeId}`),
ogImage: generateOGImage(animeInfo.poster),
structured: generateAnimeStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }),
videoStructured: generateVideoStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }, streamUrl),
breadcrumb: generateBreadcrumbStructuredData([
{ name: 'Home', url: '/' },
{ name: animeInfo.title, url: `/${animeId}` },
{ name: `Episode ${activeEpisodeNum}`, url: `/watch/${animeId}?ep=${episodeId}` }
])
};
}, [animeId, animeInfo, activeEpisodeNum, episodeId, language, streamUrl]);
const tags = useMemo(() => {
const info = animeInfo?.animeInfo?.tvInfo;
if (!info) return [];
return [
{ condition: info.rating, text: info.rating, bgColor: "#ffffff" },
{ condition: info.quality, text: info.quality, bgColor: "#FFBADE" },
{ condition: info.sub, text: info.sub, icon: faClosedCaptioning, bgColor: "#B0E3AF" },
{ condition: info.dub, text: info.dub, icon: faMicrophone, bgColor: "#B9E7FF" },
];
}, [animeInfo]);
return (
<>
{seoData && (
{!buffering && !streamInfo ? (
servers ? (
<>
Probably this server is down, try other servers
Either reload or try again after sometime
>
) : (
<>
Probably streaming server is down
Either reload or try again after sometime
>
)
) : null}
{season.season}