diff --git a/apps/expo/src/components/player/ScraperProcess.tsx b/apps/expo/src/components/player/ScraperProcess.tsx
index c9a5efc..92f4a83 100644
--- a/apps/expo/src/components/player/ScraperProcess.tsx
+++ b/apps/expo/src/components/player/ScraperProcess.tsx
@@ -1,8 +1,14 @@
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { useRouter } from "expo-router";
-import type { HlsBasedStream, RunnerEvent } from "@movie-web/provider-utils";
+import type {
+ DiscoverEmbedsEvent,
+ HlsBasedStream,
+ InitEvent,
+ RunnerEvent,
+ UpdateEvent,
+} from "@movie-web/provider-utils";
import {
extractTracksFromHLS,
getVideoStream,
@@ -20,6 +26,12 @@ interface ScraperProcessProps {
data: ItemData;
}
+enum ScrapeStatus {
+ LOADING = "loading",
+ SUCCESS = "success",
+ ERROR = "error",
+}
+
export const ScraperProcess = ({ data }: ScraperProcessProps) => {
const router = useRouter();
@@ -33,11 +45,47 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
const setMeta = usePlayerStore((state) => state.setMeta);
const [checkedSource, setCheckedSource] = useState("");
- const handleEvent = (event: RunnerEvent) => {
+ 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)) {
+ switch (event.status) {
+ case ScrapeStatus.SUCCESS:
+ setScrapeStatus({ status: ScrapeStatus.SUCCESS, progress: 100 });
+ break;
+ case ScrapeStatus.ERROR as string:
+ setScrapeStatus({ status: ScrapeStatus.ERROR, progress: 0 });
+ break;
+ case ScrapeStatus.LOADING as string:
+ }
+ 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 () => {
@@ -84,8 +132,8 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
media: scrapeMedia,
events: {
// init: handleEvent,
- // update: handleEvent,
- // discoverEmbeds: handleEvent,
+ update: handleEvent,
+ discoverEmbeds: handleEvent,
start: handleEvent,
},
});
@@ -94,7 +142,7 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
if (streamResult.stream.type === "hls") {
const tracks = await extractTracksFromHLS(
- streamResult.stream.playlist, // multiple tracks example: "https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
+ streamResult.stream.playlist,
{
...streamResult.stream.preferredHeaders,
...streamResult.stream.headers,
@@ -154,6 +202,7 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
meta?.season?.number,
meta?.episode?.number,
setAudioTracks,
+ handleEvent,
]);
return (
@@ -164,6 +213,7 @@ export const ScraperProcess = ({ data }: ScraperProcessProps) => {
Checking {checkedSource}
+ {/* */}
diff --git a/apps/expo/src/components/player/StatusCircle.tsx b/apps/expo/src/components/player/StatusCircle.tsx
new file mode 100644
index 0000000..eb76e9a
--- /dev/null
+++ b/apps/expo/src/components/player/StatusCircle.tsx
@@ -0,0 +1,80 @@
+import React from "react";
+import { StyleSheet, View } from "react-native";
+import Animated, {
+ Easing,
+ useAnimatedProps,
+ useSharedValue,
+ withTiming,
+} from "react-native-reanimated";
+import { Circle, Svg } from "react-native-svg";
+import { AntDesign } from "@expo/vector-icons";
+
+const AnimatedCircle = Animated.createAnimatedComponent(Circle);
+
+export const StatusCircle = ({
+ type,
+ percentage = 0,
+}: {
+ type: string;
+ percentage: number;
+}) => {
+ const radius = 25;
+ const strokeWidth = 5;
+ const circleCircumference = 2 * Math.PI * radius;
+
+ const strokeDashoffset = useSharedValue(circleCircumference);
+
+ React.useEffect(() => {
+ strokeDashoffset.value = withTiming(
+ circleCircumference - (circleCircumference * percentage) / 100,
+ {
+ duration: 500,
+ easing: Easing.linear,
+ },
+ );
+ }, [circleCircumference, percentage, strokeDashoffset]);
+
+ const animatedProps = useAnimatedProps(() => ({
+ strokeDashoffset: strokeDashoffset.value,
+ }));
+
+ const renderIcon = () => {
+ switch (type) {
+ case "success":
+ return ;
+ case "error":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {renderIcon()}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ justifyContent: "center",
+ alignItems: "center",
+ position: "relative",
+ },
+});