mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
185 lines
6 KiB
TypeScript
185 lines
6 KiB
TypeScript
import { useParams } from "react-router-dom";
|
|
import { useCallback, useContext, useMemo, useState } from "react";
|
|
import { Icon, Icons } from "@/components/Icon";
|
|
import { getProviders } from "@/backend/helpers/register";
|
|
import { useLoading } from "@/hooks/useLoading";
|
|
import { DetailedMeta } from "@/backend/metadata/getmeta";
|
|
import { MWMediaType } from "@/backend/metadata/types";
|
|
import { MWProviderScrapeResult } from "@/backend/helpers/provider";
|
|
import { runProvider } from "@/backend/helpers/run";
|
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
|
import { Loading } from "@/components/layout/Loading";
|
|
import {
|
|
useVideoPlayerState,
|
|
VideoPlayerDispatchContext,
|
|
} from "../VideoContext";
|
|
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
|
|
import { VideoPopout } from "../parts/VideoPopout";
|
|
|
|
interface Props {
|
|
className?: string;
|
|
media?: DetailedMeta;
|
|
}
|
|
|
|
function PopoutSourceSelect(props: { media: DetailedMeta }) {
|
|
const dispatch = useContext(VideoPlayerDispatchContext);
|
|
const providers = useMemo(
|
|
() => getProviders().filter((v) => v.type.includes(props.media.meta.type)),
|
|
[props]
|
|
);
|
|
const { episode, season } = useParams<{ episode: string; season: string }>();
|
|
const [selected, setSelected] = useState<string | null>(null);
|
|
const selectedProvider = useMemo(
|
|
() => providers.find((v) => v.id === selected),
|
|
[selected, providers]
|
|
);
|
|
|
|
const [scrapeData, setScrapeData] = useState<MWProviderScrapeResult | null>(
|
|
null
|
|
);
|
|
const [scrapeProvider, loadingProvider, errorProvider] = useLoading(
|
|
async (providerId: string) => {
|
|
const theProvider = providers.find((v) => v.id === providerId);
|
|
if (!theProvider) throw new Error("Invalid provider");
|
|
return runProvider(theProvider, {
|
|
media: props.media,
|
|
progress: () => {},
|
|
type: props.media.meta.type,
|
|
episode: (props.media.meta.type === MWMediaType.SERIES
|
|
? episode
|
|
: undefined) as any,
|
|
season: (props.media.meta.type === MWMediaType.SERIES
|
|
? season
|
|
: undefined) as any,
|
|
});
|
|
}
|
|
);
|
|
|
|
// TODO add embed support
|
|
// TODO restore startAt when changing source
|
|
// TODO auto choose when only one option
|
|
// TODO close when selecting item
|
|
// TODO show currently selected provider
|
|
// TODO clear error state when switching
|
|
// const [scrapeEmbed, embedLoading, embedError] = useLoading(
|
|
// async (embed: MWEmbed) => {
|
|
// if (!embed.type) throw new Error("Invalid embed type");
|
|
// const theScraper = getEmbedScraperByType(embed.type);
|
|
// if (!theScraper) throw new Error("Invalid scraper");
|
|
// return runEmbedScraper(theScraper, {
|
|
// progress: () => {},
|
|
// url: embed.url,
|
|
// });
|
|
// }
|
|
// );
|
|
|
|
const selectProvider = useCallback(
|
|
(id: string) => {
|
|
scrapeProvider(id).then((v) => {
|
|
if (!v) throw new Error("No scrape result");
|
|
setScrapeData(v);
|
|
});
|
|
setSelected(id);
|
|
},
|
|
[setSelected, scrapeProvider]
|
|
);
|
|
|
|
if (!selectedProvider)
|
|
return (
|
|
<>
|
|
<div className="flex items-center space-x-3 border-b border-denim-500 p-4 font-bold text-white">
|
|
<span>Select video source</span>
|
|
</div>
|
|
<div className="overflow-y-auto p-4">
|
|
<div className="space-y-1">
|
|
{providers.map((e) => (
|
|
<div
|
|
className="text-denim-800 -mx-2 flex items-center space-x-1 rounded p-2 text-white hover:bg-denim-600"
|
|
onClick={() => selectProvider(e.id)}
|
|
key={e.id}
|
|
>
|
|
{e.displayName}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<div className="flex items-center space-x-3 border-b border-denim-500 p-4 font-bold text-white">
|
|
<button
|
|
className="-m-1.5 rounded p-1.5 hover:bg-denim-600"
|
|
onClick={() => setSelected(null)}
|
|
type="button"
|
|
>
|
|
<Icon icon={Icons.CHEVRON_LEFT} />
|
|
</button>
|
|
<span>{selectedProvider.displayName}</span>
|
|
</div>
|
|
<div className="overflow-y-auto p-4 text-white">
|
|
{loadingProvider ? (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<Loading />
|
|
</div>
|
|
) : errorProvider ? (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<div className="flex flex-col flex-wrap items-center text-slate-400">
|
|
<IconPatch
|
|
icon={Icons.EYE_SLASH}
|
|
className="text-xl text-bink-600"
|
|
/>
|
|
<p className="mt-6 w-full text-center">
|
|
Something went wrong loading streams.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : scrapeData ? (
|
|
<div>
|
|
{scrapeData.stream ? (
|
|
<div
|
|
className="text-denim-800 -mx-2 flex items-center space-x-1 rounded p-2 text-white hover:bg-denim-600"
|
|
onClick={() =>
|
|
scrapeData.stream &&
|
|
dispatch({
|
|
url: scrapeData.stream.streamUrl,
|
|
quality: scrapeData.stream.quality,
|
|
sourceType: scrapeData.stream.type,
|
|
type: "SET_SOURCE",
|
|
})
|
|
}
|
|
>
|
|
{selectedProvider.displayName}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function SourceSelectionControl(props: Props) {
|
|
const { videoState } = useVideoPlayerState();
|
|
|
|
if (!props.media) return null;
|
|
|
|
return (
|
|
<div className={props.className}>
|
|
<div className="relative">
|
|
<VideoPopout
|
|
id="source"
|
|
className="grid grid-rows-[auto,minmax(0,1fr)]"
|
|
>
|
|
<PopoutSourceSelect media={props.media} />
|
|
</VideoPopout>
|
|
<VideoPlayerIconButton
|
|
icon={Icons.FILE}
|
|
text="Video source"
|
|
onClick={() => videoState.openPopout("source")}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|