import { useHistory, useParams } from "react-router-dom"; import { Helmet } from "react-helmet"; import { useEffect, useRef, useState } from "react"; import { MWStream } from "@/backend/helpers/streams"; import { SelectedMediaData, useScrape } from "@/hooks/useScrape"; import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; import { decodeJWId } from "@/backend/metadata/justwatch"; import { Loading } from "@/components/layout/Loading"; import { useLoading } from "@/hooks/useLoading"; import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types"; import { useGoBack } from "@/hooks/useGoBack"; import { IconPatch } from "@/components/buttons/IconPatch"; import { VideoPlayer } from "@/video/components/VideoPlayer"; import { MetaController } from "@/video/components/controllers/MetaController"; import { SourceController } from "@/video/components/controllers/SourceController"; import { Icons } from "@/components/Icon"; import { VideoPlayerHeader } from "@/video/components/parts/VideoPlayerHeader"; import { ProgressListenerController } from "@/video/components/controllers/ProgressListenerController"; import { VideoPlayerMeta } from "@/video/state/types"; import { SeriesController } from "@/video/components/controllers/SeriesController"; import { useWatchedItem } from "@/state/watched"; import { useTranslation } from "react-i18next"; import { MediaFetchErrorView } from "./MediaErrorView"; import { MediaScrapeLog } from "./MediaScrapeLog"; import { NotFoundMedia, NotFoundWrapper } from "../notfound/NotFoundView"; function MediaViewLoading(props: { onGoBack(): void }) { const { t } = useTranslation(); return (
{t("videoPlayer.loading")}

{t("videoPlayer.findingBestVideo")}

); } interface MediaViewScrapingProps { onStream(stream: MWStream): void; onGoBack(): void; meta: DetailedMeta; selected: SelectedMediaData; } function MediaViewScraping(props: MediaViewScrapingProps) { const { eventLog, stream, pending } = useScrape(props.meta, props.selected); const { t } = useTranslation(); useEffect(() => { if (stream) { props.onStream(stream); } }, [stream, props]); return (
{props.meta.meta.title}
{pending ? ( <>

{t("videoPlayer.findingBestVideo")}

) : ( <>

{t("videoPlayer.noVideos")}

)}
); } interface MediaViewPlayerProps { meta: DetailedMeta; stream: MWStream; selected: SelectedMediaData; onChangeStream: (sId: string, eId: string) => void; } export function MediaViewPlayer(props: MediaViewPlayerProps) { const goBack = useGoBack(); const { updateProgress, watchedItem } = useWatchedItem( props.meta, props.selected.episode ); const firstStartTime = useRef(watchedItem?.progress); useEffect(() => { firstStartTime.current = watchedItem?.progress; // only want it to change when stream changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.stream]); const metaProps: VideoPlayerMeta = { meta: props.meta, captions: [], }; let metaSeasonData: MWSeasonWithEpisodeMeta | undefined; if ( props.selected.type === MWMediaType.SERIES && props.meta.meta.type === MWMediaType.SERIES ) { metaProps.episode = { seasonId: props.selected.season, episodeId: props.selected.episode, }; metaProps.seasons = props.meta.meta.seasons; metaSeasonData = props.meta.meta.seasonData; } return (
d.seasonId && d.episodeId && props.onChangeStream?.(d.seasonId, d.episodeId) } />
); } export function MediaView() { const params = useParams<{ media: string; episode?: string; season?: string; }>(); const goBack = useGoBack(); const history = useHistory(); const [meta, setMeta] = useState(null); const [selected, setSelected] = useState(null); const [exec, loading, error] = useLoading( async (mediaParams: string, seasonId?: string) => { const data = decodeJWId(mediaParams); if (!data) return null; return getMetaFromId(data.type, data.id, seasonId); } ); // TODO get stream from someplace that actually gets updated const [stream, setStream] = useState(null); const lastSearchValue = useRef<(string | undefined)[] | null>(null); useEffect(() => { const newValue = [params.media, params.season, params.episode]; const lastVal = lastSearchValue.current; const isSame = lastVal?.[0] === newValue[0] && (lastVal?.[1] === newValue[1] || !lastVal?.[1]) && (lastVal?.[2] === newValue[2] || !lastVal?.[2]); lastSearchValue.current = newValue; if (isSame && lastVal !== null) return; setMeta(null); setStream(null); setSelected(null); exec(params.media, params.season).then((v) => { setMeta(v ?? null); setStream(null); if (v) { if (v.meta.type !== MWMediaType.SERIES) { setSelected({ type: v.meta.type, season: undefined, episode: undefined, }); } else { const season = params.season ?? v.meta.seasonData.id; const episode = params.episode ?? v.meta.seasonData.episodes[0].id; setSelected({ type: MWMediaType.SERIES, season, episode, }); if (season !== params.season || episode !== params.episode) history.replace( `/media/${encodeURIComponent(params.media)}/${encodeURIComponent( season )}/${encodeURIComponent(episode)}` ); } } else setSelected(null); }); }, [exec, history, params]); if (loading) return ; if (error) return ; if (!meta || !selected) return ( ); // scraping view will start scraping and return with onStream if (!stream) return ( ); // show stream once we have a stream return ( { history.replace( `/media/${encodeURIComponent(params.media)}/${encodeURIComponent( sId )}/${encodeURIComponent(eId)}` ); }} /> ); }