mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
source selection
This commit is contained in:
parent
abec91a322
commit
8796d5b942
4 changed files with 156 additions and 7 deletions
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
|
||||
|
@ -6,7 +6,10 @@ import { Overlay } from "@/components/overlays/OverlayDisplay";
|
|||
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
||||
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
|
||||
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 { Context } from "@/components/player/internals/ContextUtils";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
|
@ -17,6 +20,19 @@ import { CaptionsView } from "./settings/CaptionsView";
|
|||
import { QualityView } from "./settings/QualityView";
|
||||
|
||||
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 (
|
||||
<Overlay id={id}>
|
||||
<OverlayRouter id={id}>
|
||||
|
@ -40,7 +56,12 @@ function SettingsOverlay({ id }: { id: string }) {
|
|||
</OverlayPage>
|
||||
<OverlayPage id={id} path="/source" width={343} height={431}>
|
||||
<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>
|
||||
</OverlayPage>
|
||||
</OverlayRouter>
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
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 { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
|
||||
import { Context } from "@/components/player/internals/ContextUtils";
|
||||
import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { metaToScrapeMedia } from "@/stores/player/slices/source";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
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: {
|
||||
children: React.ReactNode;
|
||||
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 metaType = usePlayerStore((s) => s.meta?.type);
|
||||
const sources = useMemo(() => {
|
||||
|
@ -49,7 +161,15 @@ export function SourceSelectionView({ id }: { id: string }) {
|
|||
</Context.BackLink>
|
||||
<Context.Section>
|
||||
{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>
|
||||
</>
|
||||
|
|
|
@ -20,7 +20,9 @@ function isAllowedQuality(inp: string): inp is SourceQuality {
|
|||
return allowedQualities.includes(inp);
|
||||
}
|
||||
|
||||
export function convertRunoutputToSource(out: RunOutput): SourceSliceSource {
|
||||
export function convertRunoutputToSource(out: {
|
||||
stream: RunOutput["stream"];
|
||||
}): SourceSliceSource {
|
||||
if (out.stream.type === "hls") {
|
||||
return {
|
||||
type: "hls",
|
||||
|
|
|
@ -81,7 +81,12 @@ export function useInternalOverlayRouter(id: string) {
|
|||
[id, setRoute, setTransition, setAnchorPoint]
|
||||
);
|
||||
|
||||
const activeRoute = routerActive
|
||||
? joinPath(splitPath(route.slice(`/${id}`.length)))
|
||||
: "/";
|
||||
|
||||
return {
|
||||
activeRoute,
|
||||
showBackwardsTransition,
|
||||
isCurrentPage,
|
||||
isOverlayActive,
|
||||
|
@ -97,6 +102,7 @@ export function useOverlayRouter(id: string) {
|
|||
const router = useInternalOverlayRouter(id);
|
||||
return {
|
||||
id,
|
||||
route: router.activeRoute,
|
||||
isRouterActive: router.isOverlayActive(),
|
||||
open: router.open,
|
||||
close: router.close,
|
||||
|
|
Loading…
Reference in a new issue