mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 14:53:24 +00:00
add scraper screen
This commit is contained in:
@@ -1,145 +1,47 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ActivityIndicator, View } from "react-native";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { SafeAreaView, View } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { useRouter } from "expo-router";
|
||||
|
||||
import type {
|
||||
DiscoverEmbedsEvent,
|
||||
HlsBasedStream,
|
||||
InitEvent,
|
||||
RunnerEvent,
|
||||
UpdateEvent,
|
||||
} from "@movie-web/provider-utils";
|
||||
import {
|
||||
extractTracksFromHLS,
|
||||
getVideoStream,
|
||||
transformSearchResultToScrapeMedia,
|
||||
} from "@movie-web/provider-utils";
|
||||
import { fetchMediaDetails, fetchSeasonDetails } from "@movie-web/tmdb";
|
||||
import type { HlsBasedStream } from "@movie-web/provider-utils";
|
||||
import { extractTracksFromHLS } from "@movie-web/provider-utils";
|
||||
|
||||
import type { ItemData } from "../item/item";
|
||||
import type { AudioTrack } from "./AudioTrackSelector";
|
||||
import { useMeta } from "~/hooks/player/useMeta";
|
||||
import { useScrape } from "~/hooks/player/useSourceScrape";
|
||||
import { constructFullUrl } from "~/lib/url";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { PlayerStatus } from "~/stores/player/slices/interface";
|
||||
import { convertMetaToScrapeMedia } from "~/stores/player/slices/video";
|
||||
import { usePlayerStore } from "~/stores/player/store";
|
||||
import { Text } from "../ui/Text";
|
||||
import { ScrapeCard, ScrapeItem } from "./ScrapeCard";
|
||||
|
||||
interface ScraperProcessProps {
|
||||
data: ItemData;
|
||||
}
|
||||
|
||||
enum ScrapeStatus {
|
||||
LOADING = "loading",
|
||||
SUCCESS = "success",
|
||||
ERROR = "error",
|
||||
}
|
||||
|
||||
export const ScraperProcess = ({ data }: ScraperProcessProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const meta = usePlayerStore((state) => state.meta);
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
|
||||
const { convertMovieIdToMeta } = useMeta();
|
||||
const { startScraping, sourceOrder, sources, currentSource } = useScrape();
|
||||
|
||||
const setStream = usePlayerStore((state) => state.setCurrentStream);
|
||||
const setSeasonData = usePlayerStore((state) => state.setSeasonData);
|
||||
const setHlsTracks = usePlayerStore((state) => state.setHlsTracks);
|
||||
const setAudioTracks = usePlayerStore((state) => state.setAudioTracks);
|
||||
const setPlayerStatus = usePlayerStore((state) => state.setPlayerStatus);
|
||||
const setSourceId = usePlayerStore((state) => state.setSourceId);
|
||||
const setMeta = usePlayerStore((state) => state.setMeta);
|
||||
const [checkedSource, setCheckedSource] = useState("");
|
||||
|
||||
function isInitEvent(event: RunnerEvent): event is InitEvent {
|
||||
return (event as InitEvent).sourceIds !== undefined;
|
||||
}
|
||||
|
||||
function isUpdateEvent(event: RunnerEvent): event is UpdateEvent {
|
||||
return (event as UpdateEvent).percentage !== undefined;
|
||||
}
|
||||
|
||||
function isDiscoverEmbedsEvent(
|
||||
event: RunnerEvent,
|
||||
): event is DiscoverEmbedsEvent {
|
||||
return (event as DiscoverEmbedsEvent).sourceId !== undefined;
|
||||
}
|
||||
|
||||
const handleEvent = useCallback((event: RunnerEvent) => {
|
||||
if (typeof event === "string") {
|
||||
setCheckedSource(event);
|
||||
setScrapeStatus({ status: ScrapeStatus.LOADING, progress: 10 });
|
||||
} else if (isUpdateEvent(event)) {
|
||||
console.log(event.status);
|
||||
switch (event.status) {
|
||||
case "success":
|
||||
setScrapeStatus({ status: ScrapeStatus.SUCCESS, progress: 100 });
|
||||
break;
|
||||
case "failure":
|
||||
setScrapeStatus({ status: ScrapeStatus.ERROR, progress: 0 });
|
||||
break;
|
||||
case "pending":
|
||||
case "notfound":
|
||||
}
|
||||
setCheckedSource(event.id);
|
||||
} else if (isInitEvent(event) || isDiscoverEmbedsEvent(event)) {
|
||||
setScrapeStatus((prevStatus) => ({
|
||||
status: ScrapeStatus.LOADING,
|
||||
progress: Math.min(prevStatus.progress + 20, 95),
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [_scrapeStatus, setScrapeStatus] = useState({
|
||||
status: ScrapeStatus.LOADING,
|
||||
progress: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (!data) return router.back();
|
||||
const media = await fetchMediaDetails(data.id, data.type);
|
||||
if (!media) return router.back();
|
||||
const scrapeMedia = transformSearchResultToScrapeMedia(
|
||||
media.type,
|
||||
media.result,
|
||||
meta?.season?.number,
|
||||
meta?.episode?.number,
|
||||
);
|
||||
let seasonData = null;
|
||||
if (scrapeMedia.type === "show") {
|
||||
seasonData = await fetchSeasonDetails(
|
||||
scrapeMedia.tmdbId,
|
||||
scrapeMedia.season.number,
|
||||
);
|
||||
}
|
||||
const meta = await convertMovieIdToMeta(data.id, data.type);
|
||||
if (!meta) return;
|
||||
const streamResult = await startScraping(convertMetaToScrapeMedia(meta));
|
||||
|
||||
setMeta({
|
||||
...scrapeMedia,
|
||||
poster: media.result.poster_path,
|
||||
...("season" in scrapeMedia
|
||||
? {
|
||||
season: {
|
||||
number: scrapeMedia.season.number,
|
||||
tmdbId: scrapeMedia.tmdbId,
|
||||
},
|
||||
episode: {
|
||||
number: scrapeMedia.episode.number,
|
||||
tmdbId: scrapeMedia.episode.tmdbId,
|
||||
},
|
||||
episodes:
|
||||
seasonData?.episodes.map((e) => ({
|
||||
tmdbId: e.id.toString(),
|
||||
number: e.episode_number,
|
||||
name: e.name,
|
||||
})) ?? [],
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
const streamResult = await getVideoStream({
|
||||
media: scrapeMedia,
|
||||
events: {
|
||||
init: handleEvent,
|
||||
update: handleEvent,
|
||||
discoverEmbeds: handleEvent,
|
||||
start: handleEvent,
|
||||
},
|
||||
});
|
||||
if (!streamResult) return router.back();
|
||||
setStream(streamResult.stream);
|
||||
|
||||
@@ -184,31 +86,82 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
|
||||
};
|
||||
void fetchData();
|
||||
}, [
|
||||
convertMovieIdToMeta,
|
||||
data,
|
||||
router,
|
||||
setAudioTracks,
|
||||
setHlsTracks,
|
||||
setSeasonData,
|
||||
setStream,
|
||||
setPlayerStatus,
|
||||
setSourceId,
|
||||
setMeta,
|
||||
meta?.season?.number,
|
||||
meta?.episode?.number,
|
||||
setAudioTracks,
|
||||
handleEvent,
|
||||
setStream,
|
||||
startScraping,
|
||||
]);
|
||||
|
||||
let currentProviderIndex = sourceOrder.findIndex(
|
||||
(s) => s.id === currentSource || s.children.includes(currentSource ?? ""),
|
||||
);
|
||||
if (currentProviderIndex === -1) {
|
||||
currentProviderIndex = sourceOrder.length - 1;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
scrollViewRef.current?.scrollTo({
|
||||
y: currentProviderIndex * 80,
|
||||
animated: true,
|
||||
});
|
||||
}, [currentProviderIndex]);
|
||||
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<View className="flex-1 items-center justify-center bg-black">
|
||||
<View className="flex flex-col items-center">
|
||||
<Text className="mb-4 text-2xl text-white">
|
||||
Checking {checkedSource}
|
||||
</Text>
|
||||
<ActivityIndicator size="large" color="#0000ff" />
|
||||
{/* <StatusCircle type={scrapeStatus.status} percentage={scrapeStatus.progress} /> */}
|
||||
</View>
|
||||
<SafeAreaView className="flex h-full flex-1 flex-col">
|
||||
<View className="flex-1 items-center justify-center bg-background-main">
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
contentContainerClassName="items-center flex flex-col py-16"
|
||||
>
|
||||
{sourceOrder.map((order) => {
|
||||
const source = sources[order.id];
|
||||
if (!source) return null;
|
||||
const distance = Math.abs(
|
||||
sourceOrder.findIndex((o) => o.id === order.id) -
|
||||
currentProviderIndex,
|
||||
);
|
||||
return (
|
||||
<View
|
||||
key={order.id}
|
||||
style={{ opacity: Math.max(0, 1 - distance * 0.3) }}
|
||||
>
|
||||
<ScrapeCard
|
||||
id={order.id}
|
||||
name={source.name}
|
||||
status={source.status}
|
||||
hasChildren={order.children.length > 0}
|
||||
percentage={source.percentage}
|
||||
>
|
||||
<View
|
||||
className={cn({
|
||||
"mt-8 space-y-6": order.children.length > 0,
|
||||
})}
|
||||
>
|
||||
{order.children.map((embedId) => {
|
||||
const embed = sources[embedId];
|
||||
if (!embed) return null;
|
||||
return (
|
||||
<ScrapeItem
|
||||
id={embedId}
|
||||
name={embed.name}
|
||||
status={embed.status}
|
||||
percentage={embed.percentage}
|
||||
key={embedId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrapeCard>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user