mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
loading screen usescrape
This commit is contained in:
parent
cf83df64bb
commit
2f1058cb9c
7 changed files with 119 additions and 28 deletions
|
@ -37,12 +37,14 @@ registerProvider({
|
|||
rank: 69,
|
||||
type: [MWMediaType.MOVIE],
|
||||
|
||||
async scrape({ media: { imdbId } }) {
|
||||
async scrape({ progress, media: { imdbId } }) {
|
||||
progress(10);
|
||||
const streamRes = await fetch(
|
||||
`${
|
||||
conf().CORS_PROXY_URL
|
||||
}https://database.gdriveplayer.us/player.php?imdb=${imdbId}`
|
||||
).then((d) => d.text());
|
||||
progress(90);
|
||||
const page = new DOMParser().parseFromString(streamRes, "text/html");
|
||||
|
||||
const script: HTMLElement | undefined = Array.from(
|
||||
|
|
|
@ -22,6 +22,7 @@ export enum Icons {
|
|||
COMPRESS = "compress",
|
||||
VOLUME = "volume",
|
||||
VOLUME_X = "volume_x",
|
||||
X = "x",
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
|
@ -51,6 +52,7 @@ const iconList: Record<Icons, string> = {
|
|||
compress: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M160 64c0-17.7-14.3-32-32-32s-32 14.3-32 32v64H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c17.7 0 32-14.3 32-32V64zM32 320c-17.7 0-32 14.3-32 32s14.3 32 32 32H96v64c0 17.7 14.3 32 32 32s32-14.3 32-32V352c0-17.7-14.3-32-32-32H32zM352 64c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7 14.3 32 32 32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H352V64zM320 320c-17.7 0-32 14.3-32 32v96c0 17.7 14.3 32 32 32s32-14.3 32-32V384h64c17.7 0 32-14.3 32-32s-14.3-32-32-32H320z"/></svg>`,
|
||||
volume: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M533.6 32.5C598.5 85.3 640 165.8 640 256s-41.5 170.8-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"/></svg>`,
|
||||
volume_x: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>`,
|
||||
x: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/></svg>`,
|
||||
};
|
||||
|
||||
export const Icon = memo((props: IconProps) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import { DotList } from "@/components/text/DotList";
|
||||
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||
import { mediaTypeToJW } from "@/backend/metadata/justwatch";
|
||||
|
||||
export interface MediaCardProps {
|
||||
media: MWMediaMeta;
|
||||
|
@ -42,9 +43,9 @@ export function MediaCard(props: MediaCardProps) {
|
|||
if (!props.linkable) return <span>{content}</span>;
|
||||
return (
|
||||
<Link
|
||||
to={`/media/${encodeURIComponent(props.media.type)}-${encodeURIComponent(
|
||||
props.media.id
|
||||
)}`}
|
||||
to={`/media/${encodeURIComponent(
|
||||
mediaTypeToJW(props.media.type)
|
||||
)}-${encodeURIComponent(props.media.id)}`}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { MWStream } from "@/backend/helpers/streams";
|
|||
import { DetailedMeta } from "@/backend/metadata/getmeta";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface ScrapeEventLog {
|
||||
export interface ScrapeEventLog {
|
||||
type: "provider" | "embed";
|
||||
errored: boolean;
|
||||
percentage: number;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { BookmarkContextProvider } from "@/state/bookmark";
|
|||
import { WatchedContextProvider } from "@/state/watched";
|
||||
|
||||
import { NotFoundPage } from "@/views/notfound/NotFoundView";
|
||||
import { MediaView } from "@/views/MediaView";
|
||||
import { MediaView } from "@/views/media/MediaView";
|
||||
import { SearchView } from "@/views/search/SearchView";
|
||||
import { MWMediaType } from "@/backend/metadata/types";
|
||||
|
||||
|
|
78
src/views/media/MediaScrapeLog.tsx
Normal file
78
src/views/media/MediaScrapeLog.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { ScrapeEventLog } from "@/hooks/useScrape";
|
||||
|
||||
interface MediaScrapeLogProps {
|
||||
events: ScrapeEventLog[];
|
||||
}
|
||||
|
||||
interface MediaScrapePillProps {
|
||||
event: ScrapeEventLog;
|
||||
}
|
||||
|
||||
function MediaScrapePillSkeleton() {
|
||||
return <div className="h-9 w-[220px] rounded-full bg-slate-800 opacity-50" />;
|
||||
}
|
||||
|
||||
function MediaScrapePill({ event }: MediaScrapePillProps) {
|
||||
return (
|
||||
<div className="flex h-9 w-[220px] items-center rounded-full bg-slate-800 p-3 text-denim-700">
|
||||
<div className="mr-2">
|
||||
{!event.errored ? (
|
||||
<svg className="h-[18px] w-[18px] -rotate-90" viewBox="0 0 100 100">
|
||||
<circle
|
||||
className="fill-transparent stroke-denim-700 stroke-[15] transition-[stroke-dashoffset] duration-150"
|
||||
r="40"
|
||||
cx="50"
|
||||
cy="50"
|
||||
style={{
|
||||
strokeDasharray: `${2 * Math.PI * 40} ${2 * Math.PI * 40}`,
|
||||
strokeDashoffset: `${
|
||||
2 * Math.PI * 40 -
|
||||
(event.percentage / 100) * (2 * Math.PI * 40)
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<Icon icon={Icons.X} className="text-[0.85em] text-rose-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<p
|
||||
className={`overflow-hidden text-ellipsis whitespace-nowrap ${
|
||||
event.errored ? "text-rose-400" : ""
|
||||
}`}
|
||||
>
|
||||
{event.id}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaScrapeLog(props: MediaScrapeLogProps) {
|
||||
return (
|
||||
<div className="relative h-16 w-[400px] overflow-hidden">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="relative flex h-full w-[220px] items-center">
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 flex items-center gap-[16px] transition-transform duration-200"
|
||||
style={{
|
||||
transform: `translateX(${
|
||||
-1 * (220 + 16) * props.events.length
|
||||
}px)`,
|
||||
}}
|
||||
>
|
||||
<MediaScrapePillSkeleton />
|
||||
{props.events.map((v) => (
|
||||
<MediaScrapePill event={v} key={v.id} />
|
||||
))}
|
||||
<MediaScrapePillSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-y-0 left-0 w-40 bg-gradient-to-r from-denim-100 to-transparent" />
|
||||
<div className="absolute inset-y-0 right-0 w-40 bg-gradient-to-l from-denim-100 to-transparent" />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -7,9 +7,21 @@ import { VideoPlayerHeader } from "@/components/video/parts/VideoPlayerHeader";
|
|||
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
||||
import { JWMediaToMediaType } from "@/backend/metadata/justwatch";
|
||||
import { SourceControl } from "@/components/video/controls/SourceControl";
|
||||
import { Loading } from "@/components/layout/Loading";
|
||||
import { MediaScrapeLog } from "./MediaScrapeLog";
|
||||
|
||||
function MediaViewLoading() {
|
||||
return <p>Loading meta...</p>;
|
||||
function MediaViewLoading(props: { onGoBack(): void }) {
|
||||
return (
|
||||
<div className="relative flex h-screen items-center justify-center">
|
||||
<div className="absolute inset-x-0 top-0 p-6">
|
||||
<VideoPlayerHeader onClick={props.onGoBack} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Loading className="mb-4" />
|
||||
<p className="mb-8 text-denim-700">Finding the best video for you</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface MediaViewScrapingProps {
|
||||
|
@ -18,7 +30,7 @@ interface MediaViewScrapingProps {
|
|||
meta: DetailedMeta;
|
||||
}
|
||||
function MediaViewScraping(props: MediaViewScrapingProps) {
|
||||
const { eventLog, pending, stream } = useScrape(props.meta);
|
||||
const { eventLog, stream } = useScrape(props.meta);
|
||||
|
||||
useEffect(() => {
|
||||
if (stream) {
|
||||
|
@ -26,24 +38,21 @@ function MediaViewScraping(props: MediaViewScrapingProps) {
|
|||
}
|
||||
}, [stream, props]);
|
||||
|
||||
// TODO error screen if no streams found
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VideoPlayerHeader
|
||||
onClick={props.onGoBack}
|
||||
title={props.meta.meta.title}
|
||||
/>
|
||||
<p>pending: {pending.toString()}</p>
|
||||
<p>
|
||||
stream: {stream?.streamUrl} - {stream?.type} - {stream?.quality}
|
||||
</p>
|
||||
<hr />
|
||||
{eventLog.map((v) => (
|
||||
<div className="rounded-xl p-1 text-white">
|
||||
<p>
|
||||
{v.percentage}% - {v.type} - {v.errored ? "ERROR" : "pending"}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
<div className="relative flex h-screen items-center justify-center">
|
||||
<div className="absolute inset-x-0 top-0 py-6 px-8">
|
||||
<VideoPlayerHeader
|
||||
onClick={props.onGoBack}
|
||||
title={props.meta.meta.title}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Loading className="mb-4" />
|
||||
<p className="mb-8 text-denim-700">Finding the best video for you</p>
|
||||
<MediaScrapeLog events={eventLog} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -70,10 +79,9 @@ export function MediaView() {
|
|||
}, [setMeta, params]);
|
||||
|
||||
// TODO watched store
|
||||
// TODO scrape loading state
|
||||
// TODO error page with video header
|
||||
|
||||
if (!meta) return <MediaViewLoading />;
|
||||
if (!meta) return <MediaViewLoading onGoBack={goBack} />;
|
||||
if (!stream)
|
||||
return (
|
||||
<MediaViewScraping meta={meta} onGoBack={goBack} onStream={setStream} />
|
Loading…
Reference in a new issue