diff --git a/app.json b/app.json index f173d0d3..3fef0c7c 100644 --- a/app.json +++ b/app.json @@ -19,7 +19,7 @@ "infoPlist": { "NSCameraUsageDescription": "The app needs access to your camera to scan barcodes.", "NSMicrophoneUsageDescription": "The app needs access to your microphone.", - "UIBackgroundModes": ["audio"], + "UIBackgroundModes": ["audio", "fetch"], "NSLocalNetworkUsageDescription": "The app needs access to your local network to connect to your Jellyfin server.", "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index 86fb08b5..a75ca44c 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -2,7 +2,10 @@ import { Button } from "@/components/Button"; import { Text } from "@/components/common/Text"; import { ListItem } from "@/components/ListItem"; import { SettingToggles } from "@/components/settings/SettingToggles"; -import { useDownload } from "@/providers/DownloadProvider"; +import { + registerBackgroundFetchAsync, + useDownload, +} from "@/providers/DownloadProvider"; import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; import { clearLogs, readFromLog } from "@/utils/log"; import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api"; @@ -66,6 +69,13 @@ export default function settings() { }} > + {/* */} Information diff --git a/app/_layout.tsx b/app/_layout.tsx index 1ed76bf2..abbfbd9c 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,28 +1,27 @@ -import { FullScreenVideoPlayer } from "@/components/FullScreenVideoPlayer"; +import { DownloadProvider } from "@/providers/DownloadProvider"; import { JellyfinProvider } from "@/providers/JellyfinProvider"; import { JobQueueProvider } from "@/providers/JobQueueProvider"; import { PlaybackProvider } from "@/providers/PlaybackProvider"; +import { orientationAtom } from "@/utils/atoms/orientation"; import { useSettings } from "@/utils/atoms/settings"; import { ActionSheetProvider } from "@expo/react-native-action-sheet"; import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; +import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader"; import { DarkTheme, ThemeProvider } from "@react-navigation/native"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useFonts } from "expo-font"; import { useKeepAwake } from "expo-keep-awake"; -import { Stack, useRouter } from "expo-router"; +import * as Linking from "expo-linking"; +import { Stack } from "expo-router"; import * as ScreenOrientation from "expo-screen-orientation"; import * as SplashScreen from "expo-splash-screen"; import { StatusBar } from "expo-status-bar"; import { Provider as JotaiProvider, useAtom } from "jotai"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; +import { AppState } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import "react-native-reanimated"; -import * as Linking from "expo-linking"; -import { orientationAtom } from "@/utils/atoms/orientation"; import { Toaster } from "sonner-native"; -import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader"; -import { AppState } from "react-native"; -import { DownloadProvider } from "@/providers/DownloadProvider"; SplashScreen.preventAutoHideAsync(); diff --git a/bun.lockb b/bun.lockb index 1f870f83..0438c6be 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/settings/SettingToggles.tsx b/components/settings/SettingToggles.tsx index b0b0b557..7646a7b6 100644 --- a/components/settings/SettingToggles.tsx +++ b/components/settings/SettingToggles.tsx @@ -36,8 +36,6 @@ export const SettingToggles: React.FC = ({ ...props }) => { const [marlinUrl, setMarlinUrl] = useState(""); const [optimizedVersionsServerUrl, setOptimizedVersionsServerUrl] = useState(""); - const [optimizedVersionsAuthHeader, setOptimizedVersionsAuthHeader] = - useState(""); const queryClient = useQueryClient(); @@ -571,65 +569,6 @@ export const SettingToggles: React.FC = ({ ...props }) => { )} - - - - - Optimized versions auth header - - - The auth header for the optimized versions server. - - - - setOptimizedVersionsAuthHeader(text)} - className="w-full" - /> - - - - {settings.optimizedVersionsAuthHeader && ( - - - {settings.optimizedVersionsAuthHeader} - - - )} - - diff --git a/package.json b/package.json index 8a7f82da..3d1a93f9 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/uuid": "^10.0.0", "axios": "^1.7.7", "expo": "~51.0.34", + "expo-background-fetch": "~12.0.1", "expo-blur": "~13.0.2", "expo-build-properties": "~0.12.5", "expo-constants": "~16.0.2", @@ -50,6 +51,7 @@ "expo-splash-screen": "~0.27.6", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.7", + "expo-task-manager": "~11.8.2", "expo-updates": "~0.25.25", "expo-web-browser": "~13.0.3", "ffmpeg-kit-react-native": "^6.0.2", diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 67ca0aa8..10d94da3 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -26,6 +26,8 @@ import React, { } from "react"; import { toast } from "sonner-native"; import { apiAtom } from "./JellyfinProvider"; +import * as BackgroundFetch from "expo-background-fetch"; +import * as TaskManager from "expo-task-manager"; export type ProcessItem = { id: string; @@ -42,8 +44,33 @@ export type ProcessItem = { | "queued"; }; +export const BACKGROUND_FETCH_TASK = "background-fetch"; + +TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { + const now = Date.now(); + + console.log( + `Got background fetch call at date: ${new Date(now).toISOString()}` + ); + + // Be sure to return the successful result type! + return BackgroundFetch.BackgroundFetchResult.NewData; +}); + const STORAGE_KEY = "runningProcesses"; +export async function registerBackgroundFetchAsync() { + return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, { + minimumInterval: 60 * 15, // 1 minutes + stopOnTerminate: false, // android only, + startOnBoot: true, // android only + }); +} + +export async function unregisterBackgroundFetchAsync() { + return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK); +} + const DownloadContext = createContext | null>(null); @@ -66,6 +93,9 @@ function useDownloadProvider() { }); useEffect(() => { + // Check background task status + checkStatusAsync(); + // Load initial processes state from AsyncStorage const loadInitialProcesses = async () => { const storedProcesses = await readProcesses(); @@ -74,6 +104,40 @@ function useDownloadProvider() { loadInitialProcesses(); }, []); + /******************** + * Background task + *******************/ + const [isRegistered, setIsRegistered] = useState(false); + const [status, setStatus] = + useState(null); + + const checkStatusAsync = async () => { + const status = await BackgroundFetch.getStatusAsync(); + const isRegistered = await TaskManager.isTaskRegisteredAsync( + BACKGROUND_FETCH_TASK + ); + setStatus(status); + setIsRegistered(isRegistered); + + console.log("Background fetch status:", status); + console.log("Background fetch task registered:", isRegistered); + }; + + const toggleFetchTask = async () => { + if (isRegistered) { + console.log("Unregistering background fetch task"); + await unregisterBackgroundFetchAsync(); + } else { + console.log("Registering background fetch task"); + await registerBackgroundFetchAsync(); + } + + checkStatusAsync(); + }; + /********************** + ********************** + *********************/ + const clearProcesses = useCallback(async () => { await AsyncStorage.removeItem(STORAGE_KEY); setProcesses([]); @@ -129,7 +193,7 @@ function useDownloadProvider() { }) .begin(() => { toast.info(`Download started for ${process.item.Name}`); - updateProcess(process.id, { state: "downloading" }); + updateProcess(process.id, { state: "downloading", progress: 0 }); }) .progress((data) => { const percent = (data.bytesDownloaded / data.bytesTotal) * 100; diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index b5e687a1..bf03502d 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -73,7 +73,6 @@ export type Settings = { forwardSkipTime: number; rewindSkipTime: number; optimizedVersionsServerUrl?: string | null; - optimizedVersionsAuthHeader?: string | null; downloadMethod?: "optimized" | "remux"; }; /** @@ -110,7 +109,6 @@ const loadSettings = async (): Promise => { forwardSkipTime: 30, rewindSkipTime: 10, optimizedVersionsServerUrl: null, - optimizedVersionsAuthHeader: null, downloadMethod: "remux", };