feat: play downloads

This commit is contained in:
Adrian Castro
2024-03-21 14:14:30 +01:00
parent 13143a2664
commit 21b574ee87
8 changed files with 79 additions and 23 deletions

View File

@@ -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 (
<ScreenLayout title="Downloads">
<ScrollView>
{downloads.map((item) => (
<DownloadItem key={item.id} {...item} onLongPress={removeDownload} />
<DownloadItem
key={item.id}
{...item}
onPress={() => handlePress(item.asset)}
onLongPress={removeDownload}
/>
))}
</ScrollView>
</ScreenLayout>

View File

@@ -61,11 +61,9 @@ export default function RootLayout() {
}
return (
<DownloadManagerProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<RootLayoutNav />
</GestureHandlerRootView>
</DownloadManagerProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<RootLayoutNav />
</GestureHandlerRootView>
);
}
@@ -108,11 +106,13 @@ function RootLayoutNav() {
<QueryClientProvider client={queryClient}>
<TamaguiProvider config={tamaguiConfig} defaultTheme="main">
<ToastProvider>
<ThemeProvider value={DarkTheme}>
<Theme name={themeStore}>
<ScreenStacks />
</Theme>
</ThemeProvider>
<DownloadManagerProvider>
<ThemeProvider value={DarkTheme}>
<Theme name={themeStore}>
<ScreenStacks />
</Theme>
</ThemeProvider>
</DownloadManagerProvider>
<ToastViewport />
</ToastProvider>
</TamaguiProvider>

View File

@@ -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 <ScraperProcess data={data} />;
if (asset) {
return <VideoPlayer />;
}
if (playerStatus === PlayerStatus.READY) return <VideoPlayer />;
if (playerStatus === PlayerStatus.SCRAPING) {
return <ScraperProcess data={data} />;
}
if (playerStatus === PlayerStatus.READY) {
return <VideoPlayer />;
}
}

View File

@@ -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<DownloadItemProps> = ({
isFinished,
onLongPress,
statusText,
asset,
onPress,
}) => {
const percentage = progress * 100;
const formattedFileSize = formatBytes(fileSize);
@@ -64,7 +69,11 @@ export const DownloadItem: React.FC<DownloadItemProps> = ({
};
return (
<TouchableOpacity onLongPress={() => onLongPress(id)} activeOpacity={0.7}>
<TouchableOpacity
onPress={() => onPress(asset)}
onLongPress={() => onLongPress(id)}
activeOpacity={0.7}
>
<View marginBottom={16} borderRadius={8} borderColor="white" padding={16}>
<Text marginBottom={4} fontSize={16}>
{filename}

View File

@@ -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<ContextMenuOnPressNativeEvent>,
) => {
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 (

View File

@@ -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,

View File

@@ -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");

View File

@@ -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<VideoSlice> = (set) => ({
videoRef: null,
status: null,
meta: null,
asset: null,
setVideoRef: (ref) => {
set({ videoRef: ref });
@@ -81,8 +85,13 @@ export const createVideoSlice: MakeSlice<VideoSlice> = (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;
});