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)}`
);
}}
/>
);
}