1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-21 14:47:41 +01:00

source selection

This commit is contained in:
mrjvs 2023-10-17 23:26:53 +02:00
parent abec91a322
commit 8796d5b942
4 changed files with 156 additions and 7 deletions

View file

@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { Icons } from "@/components/Icon"; import { Icons } from "@/components/Icon";
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor"; import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
@ -6,7 +6,10 @@ import { Overlay } from "@/components/overlays/OverlayDisplay";
import { OverlayPage } from "@/components/overlays/OverlayPage"; import { OverlayPage } from "@/components/overlays/OverlayPage";
import { OverlayRouter } from "@/components/overlays/OverlayRouter"; import { OverlayRouter } from "@/components/overlays/OverlayRouter";
import { SettingsMenu } from "@/components/player/atoms/settings/SettingsMenu"; import { SettingsMenu } from "@/components/player/atoms/settings/SettingsMenu";
import { SourceSelectionView } from "@/components/player/atoms/settings/SourceSelectingView"; import {
EmbedSelectionView,
SourceSelectionView,
} from "@/components/player/atoms/settings/SourceSelectingView";
import { VideoPlayerButton } from "@/components/player/internals/Button"; import { VideoPlayerButton } from "@/components/player/internals/Button";
import { Context } from "@/components/player/internals/ContextUtils"; import { Context } from "@/components/player/internals/ContextUtils";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
@ -17,6 +20,19 @@ import { CaptionsView } from "./settings/CaptionsView";
import { QualityView } from "./settings/QualityView"; import { QualityView } from "./settings/QualityView";
function SettingsOverlay({ id }: { id: string }) { function SettingsOverlay({ id }: { id: string }) {
const [chosenSourceId, setChosenSourceId] = useState<string | null>(null);
const router = useOverlayRouter(id);
// reset source id when going to home or closing overlay
useEffect(() => {
if (!router.isRouterActive) {
setChosenSourceId(null);
}
if (router.route === "/") {
setChosenSourceId(null);
}
}, [router.isRouterActive, router.route]);
return ( return (
<Overlay id={id}> <Overlay id={id}>
<OverlayRouter id={id}> <OverlayRouter id={id}>
@ -40,7 +56,12 @@ function SettingsOverlay({ id }: { id: string }) {
</OverlayPage> </OverlayPage>
<OverlayPage id={id} path="/source" width={343} height={431}> <OverlayPage id={id} path="/source" width={343} height={431}>
<Context.Card> <Context.Card>
<SourceSelectionView id={id} /> <SourceSelectionView id={id} onChoose={setChosenSourceId} />
</Context.Card>
</OverlayPage>
<OverlayPage id={id} path="/source/embeds" width={343} height={431}>
<Context.Card>
<EmbedSelectionView id={id} sourceId={chosenSourceId} />
</Context.Card> </Context.Card>
</OverlayPage> </OverlayPage>
</OverlayRouter> </OverlayRouter>

View file

@ -1,12 +1,26 @@
import classNames from "classnames"; import classNames from "classnames";
import { useMemo } from "react"; import { ReactNode, useCallback, useEffect, useMemo, useRef } from "react";
import { useAsyncFn } from "react-use";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
import { Context } from "@/components/player/internals/ContextUtils"; import { Context } from "@/components/player/internals/ContextUtils";
import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { metaToScrapeMedia } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { providers } from "@/utils/providers"; import { providers } from "@/utils/providers";
export interface SourceSelectionViewProps {
id: string;
onChoose?: (id: string) => void;
}
export interface EmbedSelectionViewProps {
id: string;
sourceId: string | null;
}
export function SourceOption(props: { export function SourceOption(props: {
children: React.ReactNode; children: React.ReactNode;
selected?: boolean; selected?: boolean;
@ -32,7 +46,105 @@ export function SourceOption(props: {
); );
} }
export function SourceSelectionView({ id }: { id: string }) { export function EmbedOption(props: {
embedId: string;
url: string;
routerId: string;
}) {
const router = useOverlayRouter(props.routerId);
const meta = usePlayerStore((s) => s.meta);
const setSource = usePlayerStore((s) => s.setSource);
const progress = usePlayerStore((s) => s.progress.time);
const embedName = useMemo(() => {
if (!props.embedId) return "...";
const sourceMeta = providers.getMetadata(props.embedId);
return sourceMeta?.name ?? "...";
}, [props.embedId]);
const [request, run] = useAsyncFn(async () => {
const result = await providers.runEmbedScraper({
id: props.embedId,
url: props.url,
});
setSource(convertRunoutputToSource({ stream: result.stream }), progress);
router.close();
}, [props.embedId, meta, router]);
let content: ReactNode = null;
if (request.loading) content = <span>loading...</span>;
else if (request.error) content = <span>Failed to scrape</span>;
return (
<SourceOption onClick={run}>
<span className="flex flex-col">
<span>{embedName}</span>
{content}
</span>
</SourceOption>
);
}
export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
const router = useOverlayRouter(id);
const meta = usePlayerStore((s) => s.meta);
const setSource = usePlayerStore((s) => s.setSource);
const progress = usePlayerStore((s) => s.progress.time);
const sourceName = useMemo(() => {
if (!sourceId) return "...";
const sourceMeta = providers.getMetadata(sourceId);
return sourceMeta?.name ?? "...";
}, [sourceId]);
const [request, run] = useAsyncFn(async () => {
if (!sourceId || !meta) return null;
const scrapeMedia = metaToScrapeMedia(meta);
const result = await providers.runSourceScraper({
id: sourceId,
media: scrapeMedia,
});
if (result.stream) {
setSource(convertRunoutputToSource({ stream: result.stream }), progress);
router.close();
return null;
}
return result.embeds;
}, [sourceId, meta, router]);
const lastSourceId = useRef<string | null>(null);
useEffect(() => {
if (lastSourceId.current === sourceId) return;
lastSourceId.current = sourceId;
if (!sourceId) return;
run();
}, [run, sourceId]);
let content: ReactNode = null;
if (request.loading) content = <p>loading...</p>;
else if (request.error) content = <p>Failed to scrape</p>;
else if (request.value && request.value.length === 0)
content = <p>No embeds found</p>;
else if (request.value)
content = request.value.map((v) => (
<EmbedOption
key={v.embedId}
embedId={v.embedId}
url={v.url}
routerId={id}
/>
));
return (
<>
<Context.BackLink onClick={() => router.navigate("/source")}>
{sourceName}
</Context.BackLink>
<Context.Section>{content}</Context.Section>
</>
);
}
export function SourceSelectionView({
id,
onChoose,
}: SourceSelectionViewProps) {
const router = useOverlayRouter(id); const router = useOverlayRouter(id);
const metaType = usePlayerStore((s) => s.meta?.type); const metaType = usePlayerStore((s) => s.meta?.type);
const sources = useMemo(() => { const sources = useMemo(() => {
@ -49,7 +161,15 @@ export function SourceSelectionView({ id }: { id: string }) {
</Context.BackLink> </Context.BackLink>
<Context.Section> <Context.Section>
{sources.map((v) => ( {sources.map((v) => (
<SourceOption key={v.id}>{v.name}</SourceOption> <SourceOption
key={v.id}
onClick={() => {
onChoose?.(v.id);
router.navigate("/source/embeds");
}}
>
{v.name}
</SourceOption>
))} ))}
</Context.Section> </Context.Section>
</> </>

View file

@ -20,7 +20,9 @@ function isAllowedQuality(inp: string): inp is SourceQuality {
return allowedQualities.includes(inp); return allowedQualities.includes(inp);
} }
export function convertRunoutputToSource(out: RunOutput): SourceSliceSource { export function convertRunoutputToSource(out: {
stream: RunOutput["stream"];
}): SourceSliceSource {
if (out.stream.type === "hls") { if (out.stream.type === "hls") {
return { return {
type: "hls", type: "hls",

View file

@ -81,7 +81,12 @@ export function useInternalOverlayRouter(id: string) {
[id, setRoute, setTransition, setAnchorPoint] [id, setRoute, setTransition, setAnchorPoint]
); );
const activeRoute = routerActive
? joinPath(splitPath(route.slice(`/${id}`.length)))
: "/";
return { return {
activeRoute,
showBackwardsTransition, showBackwardsTransition,
isCurrentPage, isCurrentPage,
isOverlayActive, isOverlayActive,
@ -97,6 +102,7 @@ export function useOverlayRouter(id: string) {
const router = useInternalOverlayRouter(id); const router = useInternalOverlayRouter(id);
return { return {
id, id,
route: router.activeRoute,
isRouterActive: router.isOverlayActive(), isRouterActive: router.isOverlayActive(),
open: router.open, open: router.open,
close: router.close, close: router.close,