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:
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 { 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>
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue