diff --git a/apps/expo/src/app/(tabs)/downloads.tsx b/apps/expo/src/app/(tabs)/downloads.tsx
index 0cef369..2c126ef 100644
--- a/apps/expo/src/app/(tabs)/downloads.tsx
+++ b/apps/expo/src/app/(tabs)/downloads.tsx
@@ -1,18 +1,37 @@
+import type { Asset } from "expo-media-library";
import React from "react";
import { ScrollView } from "react-native-gesture-handler";
+import { useRouter } from "expo-router";
import { DownloadItem } from "~/components/DownloadItem";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
+import { usePlayerStore } from "~/stores/player/store";
const DownloadsScreen: React.FC = () => {
const { downloads, removeDownload } = useDownloadManager();
+ const resetVideo = usePlayerStore((state) => state.resetVideo);
+ const router = useRouter();
+
+ const handlePress = (asset?: Asset) => {
+ if (!asset) return;
+ resetVideo();
+ router.push({
+ pathname: "/videoPlayer",
+ params: { data: JSON.stringify(asset) },
+ });
+ };
return (
{downloads.map((item) => (
-
+ handlePress(item.asset)}
+ onLongPress={removeDownload}
+ />
))}
diff --git a/apps/expo/src/app/_layout.tsx b/apps/expo/src/app/_layout.tsx
index c9a1a6d..6c1029d 100644
--- a/apps/expo/src/app/_layout.tsx
+++ b/apps/expo/src/app/_layout.tsx
@@ -61,11 +61,9 @@ export default function RootLayout() {
}
return (
-
-
-
-
-
+
+
+
);
}
@@ -108,11 +106,13 @@ function RootLayoutNav() {
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/apps/expo/src/app/videoPlayer.tsx b/apps/expo/src/app/videoPlayer.tsx
index d19f0fd..618dd1d 100644
--- a/apps/expo/src/app/videoPlayer.tsx
+++ b/apps/expo/src/app/videoPlayer.tsx
@@ -9,6 +9,7 @@ import { usePlayerStore } from "~/stores/player/store";
export default function VideoPlayerWrapper() {
const playerStatus = usePlayerStore((state) => state.interface.playerStatus);
+ const asset = usePlayerStore((state) => state.asset);
const { presentFullscreenPlayer } = usePlayer();
const router = useRouter();
@@ -21,8 +22,15 @@ export default function VideoPlayerWrapper() {
void presentFullscreenPlayer();
- if (playerStatus === PlayerStatus.SCRAPING)
- return ;
+ if (asset) {
+ return ;
+ }
- if (playerStatus === PlayerStatus.READY) return ;
+ if (playerStatus === PlayerStatus.SCRAPING) {
+ return ;
+ }
+
+ if (playerStatus === PlayerStatus.READY) {
+ return ;
+ }
}
diff --git a/apps/expo/src/components/DownloadItem.tsx b/apps/expo/src/components/DownloadItem.tsx
index 93da300..d5bb084 100644
--- a/apps/expo/src/components/DownloadItem.tsx
+++ b/apps/expo/src/components/DownloadItem.tsx
@@ -1,3 +1,4 @@
+import type { Asset } from "expo-media-library";
import React from "react";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Progress, Spinner, Text, View } from "tamagui";
@@ -12,6 +13,8 @@ export interface DownloadItemProps {
isFinished: boolean;
onLongPress: (id: string) => void;
statusText?: string;
+ asset?: Asset;
+ onPress: (asset?: Asset) => void;
}
const formatBytes = (bytes: number, decimals = 2) => {
@@ -33,6 +36,8 @@ export const DownloadItem: React.FC = ({
isFinished,
onLongPress,
statusText,
+ asset,
+ onPress,
}) => {
const percentage = progress * 100;
const formattedFileSize = formatBytes(fileSize);
@@ -64,7 +69,11 @@ export const DownloadItem: React.FC = ({
};
return (
- onLongPress(id)} activeOpacity={0.7}>
+ onPress(asset)}
+ onLongPress={() => onLongPress(id)}
+ activeOpacity={0.7}
+ >
{filename}
diff --git a/apps/expo/src/components/item/item.tsx b/apps/expo/src/components/item/item.tsx
index 380f0c9..0b1cd53 100644
--- a/apps/expo/src/components/item/item.tsx
+++ b/apps/expo/src/components/item/item.tsx
@@ -5,7 +5,7 @@ import ContextMenu from "react-native-context-menu-view";
import { useRouter } from "expo-router";
import { Image, Text, View } from "tamagui";
-// import { useDownloadManager } from "~/hooks/DownloadManagerContext";
+import { useDownloadManager } from "~/hooks/DownloadManagerContext";
import { usePlayerStore } from "~/stores/player/store";
export interface ItemData {
@@ -19,7 +19,7 @@ export interface ItemData {
export default function Item({ data }: { data: ItemData }) {
const resetVideo = usePlayerStore((state) => state.resetVideo);
const router = useRouter();
- // const { startDownload } = useDownloadManager();
+ const { startDownload } = useDownloadManager();
const { title, type, year, posterUrl } = data;
@@ -41,10 +41,10 @@ export default function Item({ data }: { data: ItemData }) {
e: NativeSyntheticEvent,
) => {
console.log(e.nativeEvent.name);
- // startDownload(
- // "https://samplelib.com/lib/preview/mp4/sample-5s.mp4",
- // "mp4",
- // ).catch(console.error);
+ startDownload(
+ "https://samplelib.com/lib/preview/mp4/sample-5s.mp4",
+ "mp4",
+ ).catch(console.error);
};
return (
diff --git a/apps/expo/src/components/player/VideoPlayer.tsx b/apps/expo/src/components/player/VideoPlayer.tsx
index 2591fca..4662062 100644
--- a/apps/expo/src/components/player/VideoPlayer.tsx
+++ b/apps/expo/src/components/player/VideoPlayer.tsx
@@ -56,6 +56,7 @@ export const VideoPlayer = () => {
const stream = usePlayerStore((state) => state.interface.currentStream);
const selectedAudioTrack = useAudioTrackStore((state) => state.selectedTrack);
const videoRef = usePlayerStore((state) => state.videoRef);
+ const asset = usePlayerStore((state) => state.asset);
const setVideoRef = usePlayerStore((state) => state.setVideoRef);
const setStatus = usePlayerStore((state) => state.setStatus);
const setIsIdle = usePlayerStore((state) => state.setIsIdle);
@@ -167,6 +168,12 @@ export const VideoPlayer = () => {
useEffect(() => {
const initializePlayer = async () => {
+ if (asset) {
+ setVideoSrc(asset);
+ setIsLoading(false);
+ return;
+ }
+
if (!stream) {
await dismissFullscreenPlayer();
return router.back();
@@ -214,6 +221,7 @@ export const VideoPlayer = () => {
void synchronizePlayback();
};
}, [
+ asset,
dismissFullscreenPlayer,
hasStartedPlaying,
router,
diff --git a/apps/expo/src/hooks/DownloadManagerContext.tsx b/apps/expo/src/hooks/DownloadManagerContext.tsx
index fc7958a..9e02380 100644
--- a/apps/expo/src/hooks/DownloadManagerContext.tsx
+++ b/apps/expo/src/hooks/DownloadManagerContext.tsx
@@ -1,3 +1,4 @@
+import type { Asset } from "expo-media-library";
import type { ReactNode } from "react";
import React, { createContext, useContext, useEffect, useState } from "react";
import * as FileSystem from "expo-file-system";
@@ -17,6 +18,7 @@ export interface DownloadItem {
type: "mp4" | "hls";
isFinished: boolean;
statusText?: string;
+ asset?: Asset;
}
interface DownloadManagerContextType {
@@ -168,11 +170,12 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
throw new Error("MediaLibrary permission not granted");
}
- await MediaLibrary.saveToLibraryAsync(fileUri);
+ const asset = await MediaLibrary.createAssetAsync(fileUri);
await FileSystem.deleteAsync(fileUri);
updateDownloadItem(downloadId, {
statusText: undefined,
+ asset,
isFinished: true,
});
console.log("File saved to media library and original deleted");
diff --git a/apps/expo/src/stores/player/slices/video.ts b/apps/expo/src/stores/player/slices/video.ts
index 863f8a4..26a9516 100644
--- a/apps/expo/src/stores/player/slices/video.ts
+++ b/apps/expo/src/stores/player/slices/video.ts
@@ -1,4 +1,5 @@
import type { AVPlaybackStatus, Video } from "expo-av";
+import type { Asset } from "expo-media-library";
import type { ScrapeMedia } from "@movie-web/provider-utils";
@@ -31,10 +32,12 @@ export interface VideoSlice {
videoRef: Video | null;
status: AVPlaybackStatus | null;
meta: PlayerMeta | null;
+ asset: Asset | null;
setVideoRef(ref: Video | null): void;
setStatus(status: AVPlaybackStatus | null): void;
setMeta(meta: PlayerMeta | null): void;
+ setAsset(asset: Asset | null): void;
resetVideo(): void;
}
@@ -66,6 +69,7 @@ export const createVideoSlice: MakeSlice = (set) => ({
videoRef: null,
status: null,
meta: null,
+ asset: null,
setVideoRef: (ref) => {
set({ videoRef: ref });
@@ -81,8 +85,13 @@ export const createVideoSlice: MakeSlice = (set) => ({
s.meta = meta;
});
},
+ setAsset: (asset) => {
+ set((s) => {
+ s.asset = asset;
+ });
+ },
resetVideo() {
- set({ videoRef: null, status: null, meta: null });
+ set({ videoRef: null, status: null, meta: null, asset: null });
set((s) => {
s.interface.playerStatus = PlayerStatus.SCRAPING;
});