mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
Merge pull request #206 from movie-web/feature-small-features
Meta on window + show selected provider and embed
This commit is contained in:
commit
8844efa754
17 changed files with 3715 additions and 3588 deletions
|
@ -7,7 +7,6 @@
|
||||||
"@formkit/auto-animate": "^1.0.0-beta.5",
|
"@formkit/auto-animate": "^1.0.0-beta.5",
|
||||||
"@headlessui/react": "^1.5.0",
|
"@headlessui/react": "^1.5.0",
|
||||||
"@react-spring/web": "^9.7.1",
|
"@react-spring/web": "^9.7.1",
|
||||||
"@types/react-helmet": "^6.1.6",
|
|
||||||
"@use-gesture/react": "^10.2.24",
|
"@use-gesture/react": "^10.2.24",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"fscreen": "^1.2.0",
|
"fscreen": "^1.2.0",
|
||||||
|
@ -92,6 +91,7 @@
|
||||||
"vite-plugin-package-version": "^1.0.2",
|
"vite-plugin-package-version": "^1.0.2",
|
||||||
"vite-plugin-pwa": "^0.14.4",
|
"vite-plugin-pwa": "^0.14.4",
|
||||||
"vitest": "^0.28.5",
|
"vitest": "^0.28.5",
|
||||||
"workbox-window": "^6.5.4"
|
"workbox-window": "^6.5.4",
|
||||||
|
"@types/react-helmet": "^6.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ registerEmbedScraper({
|
||||||
async getStream() {
|
async getStream() {
|
||||||
// throw new Error("Oh well 2")
|
// throw new Error("Oh well 2")
|
||||||
return {
|
return {
|
||||||
|
embedId: "",
|
||||||
streamUrl: "",
|
streamUrl: "",
|
||||||
quality: MWStreamQuality.Q1080P,
|
quality: MWStreamQuality.Q1080P,
|
||||||
captions: [],
|
captions: [],
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { registerEmbedScraper } from "@/backend/helpers/register";
|
||||||
import {
|
import {
|
||||||
MWStreamQuality,
|
MWStreamQuality,
|
||||||
MWStreamType,
|
MWStreamType,
|
||||||
MWStream,
|
MWEmbedStream,
|
||||||
} from "@/backend/helpers/streams";
|
} from "@/backend/helpers/streams";
|
||||||
import { proxiedFetch } from "@/backend/helpers/fetch";
|
import { proxiedFetch } from "@/backend/helpers/fetch";
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ const URL_API = `${URL_BASE}/api`;
|
||||||
const URL_API_SOURCE = `${URL_API}/source`;
|
const URL_API_SOURCE = `${URL_API}/source`;
|
||||||
|
|
||||||
async function scrape(embed: string) {
|
async function scrape(embed: string) {
|
||||||
const sources: MWStream[] = [];
|
const sources: MWEmbedStream[] = [];
|
||||||
|
|
||||||
const embedID = embed.split("/").pop();
|
const embedID = embed.split("/").pop();
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ async function scrape(embed: string) {
|
||||||
|
|
||||||
for (const stream of streams) {
|
for (const stream of streams) {
|
||||||
sources.push({
|
sources.push({
|
||||||
|
embedId: "",
|
||||||
streamUrl: stream.file as string,
|
streamUrl: stream.file as string,
|
||||||
quality: stream.label as MWStreamQuality,
|
quality: stream.label as MWStreamQuality,
|
||||||
type: stream.type as MWStreamType,
|
type: stream.type as MWStreamType,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MWStream } from "./streams";
|
import { MWEmbedStream } from "./streams";
|
||||||
|
|
||||||
export enum MWEmbedType {
|
export enum MWEmbedType {
|
||||||
M4UFREE = "m4ufree",
|
M4UFREE = "m4ufree",
|
||||||
|
@ -23,5 +23,5 @@ export type MWEmbedScraper = {
|
||||||
rank: number;
|
rank: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
||||||
getStream(ctx: MWEmbedContext): Promise<MWStream>;
|
getStream(ctx: MWEmbedContext): Promise<MWEmbedStream>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,7 +43,13 @@ async function findBestEmbedStream(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
ctx: MWProviderRunContext
|
ctx: MWProviderRunContext
|
||||||
): Promise<MWStream | null> {
|
): Promise<MWStream | null> {
|
||||||
if (result.stream) return result.stream;
|
if (result.stream) {
|
||||||
|
return {
|
||||||
|
...result.stream,
|
||||||
|
providerId,
|
||||||
|
embedId: providerId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let embedNum = 0;
|
let embedNum = 0;
|
||||||
for (const embed of result.embeds) {
|
for (const embed of result.embeds) {
|
||||||
|
@ -89,6 +95,7 @@ async function findBestEmbedStream(
|
||||||
type: "embed",
|
type: "embed",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stream.providerId = providerId;
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,5 +28,11 @@ export type MWStream = {
|
||||||
streamUrl: string;
|
streamUrl: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
|
providerId?: string;
|
||||||
|
embedId?: string;
|
||||||
captions: MWCaption[];
|
captions: MWCaption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MWEmbedStream = MWStream & {
|
||||||
|
embedId: string;
|
||||||
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
useVideoPlayerDescriptor,
|
useVideoPlayerDescriptor,
|
||||||
VideoPlayerContextProvider,
|
VideoPlayerContextProvider,
|
||||||
} from "../state/hooks";
|
} from "../state/hooks";
|
||||||
|
import { MetaAction } from "./actions/MetaAction";
|
||||||
import { VideoElementInternal } from "./internal/VideoElementInternal";
|
import { VideoElementInternal } from "./internal/VideoElementInternal";
|
||||||
|
|
||||||
export interface VideoPlayerBaseProps {
|
export interface VideoPlayerBaseProps {
|
||||||
|
@ -44,6 +45,7 @@ function VideoPlayerBaseWithState(props: VideoPlayerBaseProps) {
|
||||||
: "",
|
: "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
|
<MetaAction />
|
||||||
<VideoElementInternal autoPlay={props.autoPlay} />
|
<VideoElementInternal autoPlay={props.autoPlay} />
|
||||||
<CastingInternal />
|
<CastingInternal />
|
||||||
<WrapperRegisterInternal wrapper={ref.current} />
|
<WrapperRegisterInternal wrapper={ref.current} />
|
||||||
|
|
59
src/video/components/actions/MetaAction.tsx
Normal file
59
src/video/components/actions/MetaAction.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { MWCaption } from "@/backend/helpers/streams";
|
||||||
|
import { DetailedMeta } from "@/backend/metadata/getmeta";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useMeta } from "@/video/state/logic/meta";
|
||||||
|
import { useProgress } from "@/video/state/logic/progress";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export type WindowMeta = {
|
||||||
|
meta: DetailedMeta;
|
||||||
|
captions: MWCaption[];
|
||||||
|
episode?: {
|
||||||
|
episodeId: string;
|
||||||
|
seasonId: string;
|
||||||
|
};
|
||||||
|
seasons?: {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
episodes?: { id: string; number: number; title: string }[];
|
||||||
|
}[];
|
||||||
|
progress: {
|
||||||
|
time: number;
|
||||||
|
duration: number;
|
||||||
|
};
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
meta?: Record<string, WindowMeta>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MetaAction() {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const meta = useMeta(descriptor);
|
||||||
|
const progress = useProgress(descriptor);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!window.meta) window.meta = {};
|
||||||
|
if (meta) {
|
||||||
|
window.meta[descriptor] = {
|
||||||
|
meta: meta.meta,
|
||||||
|
captions: meta.captions,
|
||||||
|
seasons: meta.seasons,
|
||||||
|
episode: meta.episode,
|
||||||
|
progress: {
|
||||||
|
time: progress.time,
|
||||||
|
duration: progress.duration,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (window.meta) delete window.meta[descriptor];
|
||||||
|
};
|
||||||
|
}, [meta, descriptor, progress]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ interface SourceControllerProps {
|
||||||
source: string;
|
source: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
|
providerId?: string;
|
||||||
|
embedId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SourceController(props: SourceControllerProps) {
|
export function SourceController(props: SourceControllerProps) {
|
||||||
|
|
|
@ -18,12 +18,14 @@ import { MWEmbed, MWEmbedType } from "@/backend/helpers/embed";
|
||||||
import { FloatingCardView } from "@/components/popout/FloatingCard";
|
import { FloatingCardView } from "@/components/popout/FloatingCard";
|
||||||
import { FloatingView } from "@/components/popout/FloatingView";
|
import { FloatingView } from "@/components/popout/FloatingView";
|
||||||
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
import { useFloatingRouter } from "@/hooks/useFloatingRouter";
|
||||||
|
import { useSource } from "@/video/state/logic/source";
|
||||||
import { PopoutListEntry } from "./PopoutUtils";
|
import { PopoutListEntry } from "./PopoutUtils";
|
||||||
|
|
||||||
interface EmbedEntryProps {
|
interface EmbedEntryProps {
|
||||||
name: string;
|
name: string;
|
||||||
type: MWEmbedType;
|
type: MWEmbedType;
|
||||||
url: string;
|
url: string;
|
||||||
|
active: boolean;
|
||||||
onSelect: (stream: MWStream) => void;
|
onSelect: (stream: MWStream) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +45,7 @@ export function EmbedEntry(props: EmbedEntryProps) {
|
||||||
isOnDarkBackground
|
isOnDarkBackground
|
||||||
loading={loading}
|
loading={loading}
|
||||||
errored={!!error}
|
errored={!!error}
|
||||||
|
active={props.active}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
scrapeEmbed();
|
scrapeEmbed();
|
||||||
}}
|
}}
|
||||||
|
@ -61,6 +64,9 @@ export function SourceSelectionPopout(props: {
|
||||||
const descriptor = useVideoPlayerDescriptor();
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
const controls = useControls(descriptor);
|
const controls = useControls(descriptor);
|
||||||
const meta = useMeta(descriptor);
|
const meta = useMeta(descriptor);
|
||||||
|
const { source } = useSource(descriptor);
|
||||||
|
const providerRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const providers = useMemo(
|
const providers = useMemo(
|
||||||
() =>
|
() =>
|
||||||
meta
|
meta
|
||||||
|
@ -96,6 +102,8 @@ export function SourceSelectionPopout(props: {
|
||||||
quality: stream.quality,
|
quality: stream.quality,
|
||||||
source: stream.streamUrl,
|
source: stream.streamUrl,
|
||||||
type: stream.type,
|
type: stream.type,
|
||||||
|
embedId: stream.embedId,
|
||||||
|
providerId: providerRef.current ?? undefined,
|
||||||
});
|
});
|
||||||
if (meta) {
|
if (meta) {
|
||||||
controls.setMeta({
|
controls.setMeta({
|
||||||
|
@ -106,7 +114,6 @@ export function SourceSelectionPopout(props: {
|
||||||
controls.closePopout();
|
controls.closePopout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerRef = useRef<string | null>(null);
|
|
||||||
const selectProvider = (providerId?: string) => {
|
const selectProvider = (providerId?: string) => {
|
||||||
if (!providerId) {
|
if (!providerId) {
|
||||||
providerRef.current = null;
|
providerRef.current = null;
|
||||||
|
@ -188,6 +195,7 @@ export function SourceSelectionPopout(props: {
|
||||||
{providers.map((v) => (
|
{providers.map((v) => (
|
||||||
<PopoutListEntry
|
<PopoutListEntry
|
||||||
key={v.id}
|
key={v.id}
|
||||||
|
active={v.id === source?.providerId}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
selectProvider(v.id);
|
selectProvider(v.id);
|
||||||
}}
|
}}
|
||||||
|
@ -234,6 +242,10 @@ export function SourceSelectionPopout(props: {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (scrapeResult.stream) selectSource(scrapeResult.stream);
|
if (scrapeResult.stream) selectSource(scrapeResult.stream);
|
||||||
}}
|
}}
|
||||||
|
active={
|
||||||
|
selectedProviderPopulated?.id === source?.providerId &&
|
||||||
|
selectedProviderPopulated?.id === source?.embedId
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Native source
|
Native source
|
||||||
</PopoutListEntry>
|
</PopoutListEntry>
|
||||||
|
@ -245,6 +257,7 @@ export function SourceSelectionPopout(props: {
|
||||||
name={v.displayName ?? ""}
|
name={v.displayName ?? ""}
|
||||||
key={v.url}
|
key={v.url}
|
||||||
url={v.url}
|
url={v.url}
|
||||||
|
active={false} // TODO add embed id extractor
|
||||||
onSelect={(stream) => {
|
onSelect={(stream) => {
|
||||||
selectSource(stream);
|
selectSource(stream);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -9,6 +9,8 @@ export type VideoSourceEvent = {
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
url: string;
|
url: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
|
providerId?: string;
|
||||||
|
embedId?: string;
|
||||||
caption: null | {
|
caption: null | {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
@ -133,6 +133,8 @@ export function createCastingStateProvider(
|
||||||
type: source.type,
|
type: source.type,
|
||||||
url: source.source,
|
url: source.source,
|
||||||
caption: null,
|
caption: null,
|
||||||
|
embedId: source.embedId,
|
||||||
|
providerId: source.providerId,
|
||||||
};
|
};
|
||||||
resetStateForSource(descriptor, state);
|
resetStateForSource(descriptor, state);
|
||||||
updateSource(descriptor, state);
|
updateSource(descriptor, state);
|
||||||
|
@ -224,6 +226,8 @@ export function createCastingStateProvider(
|
||||||
quality: state.source.quality,
|
quality: state.source.quality,
|
||||||
source: state.source.url,
|
source: state.source.url,
|
||||||
type: state.source.type,
|
type: state.source.type,
|
||||||
|
embedId: state.source.embedId,
|
||||||
|
providerId: state.source.providerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,6 +4,8 @@ type VideoPlayerSource = {
|
||||||
source: string;
|
source: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
|
providerId?: string;
|
||||||
|
embedId?: string;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
export type VideoPlayerStateController = {
|
export type VideoPlayerStateController = {
|
||||||
|
|
|
@ -189,6 +189,8 @@ export function createVideoStateProvider(
|
||||||
type: source.type,
|
type: source.type,
|
||||||
url: source.source,
|
url: source.source,
|
||||||
caption: null,
|
caption: null,
|
||||||
|
embedId: source.embedId,
|
||||||
|
providerId: source.providerId,
|
||||||
};
|
};
|
||||||
updateSource(descriptor, state);
|
updateSource(descriptor, state);
|
||||||
},
|
},
|
||||||
|
@ -334,6 +336,8 @@ export function createVideoStateProvider(
|
||||||
quality: state.source.quality,
|
quality: state.source.quality,
|
||||||
source: state.source.url,
|
source: state.source.url,
|
||||||
type: state.source.type,
|
type: state.source.type,
|
||||||
|
embedId: state.source.embedId,
|
||||||
|
providerId: state.source.providerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -58,6 +58,8 @@ export type VideoPlayerState = {
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
url: string;
|
url: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
|
providerId?: string;
|
||||||
|
embedId?: string;
|
||||||
caption: null | {
|
caption: null | {
|
||||||
url: string;
|
url: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -146,6 +146,8 @@ export function MediaViewPlayer(props: MediaViewPlayerProps) {
|
||||||
source={props.stream.streamUrl}
|
source={props.stream.streamUrl}
|
||||||
type={props.stream.type}
|
type={props.stream.type}
|
||||||
quality={props.stream.quality}
|
quality={props.stream.quality}
|
||||||
|
embedId={props.stream.embedId}
|
||||||
|
providerId={props.stream.providerId}
|
||||||
/>
|
/>
|
||||||
<ProgressListenerController
|
<ProgressListenerController
|
||||||
startAt={firstStartTime.current}
|
startAt={firstStartTime.current}
|
||||||
|
@ -181,6 +183,7 @@ export function MediaView() {
|
||||||
return getMetaFromId(data.type, data.id, seasonId);
|
return getMetaFromId(data.type, data.id, seasonId);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
// TODO get stream from someplace that actually gets updated
|
||||||
const [stream, setStream] = useState<MWStream | null>(null);
|
const [stream, setStream] = useState<MWStream | null>(null);
|
||||||
|
|
||||||
const lastSearchValue = useRef<(string | undefined)[] | null>(null);
|
const lastSearchValue = useRef<(string | undefined)[] | null>(null);
|
||||||
|
|
Loading…
Reference in a new issue