mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 12:23:24 +00:00
refactor: use expo filsesystem for downloads
This commit is contained in:
@@ -2,7 +2,6 @@ import type { ExpoConfig } from "expo/config";
|
||||
|
||||
import { version } from "./package.json";
|
||||
import withRemoveiOSNotificationEntitlement from "./src/plugins/withRemoveiOSNotificationEntitlement";
|
||||
import withRNBackgroundDownloader from "./src/plugins/withRNBackgroundDownloader";
|
||||
|
||||
const defineConfig = (): ExpoConfig => ({
|
||||
name: "movie-web",
|
||||
@@ -48,7 +47,6 @@ const defineConfig = (): ExpoConfig => ({
|
||||
plugins: [
|
||||
"expo-router",
|
||||
[withRemoveiOSNotificationEntitlement as unknown as string],
|
||||
[withRNBackgroundDownloader as unknown as string],
|
||||
[
|
||||
"expo-screen-orientation",
|
||||
{
|
||||
|
@@ -19,7 +19,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/metro-config": "^0.17.3",
|
||||
"@kesha-antonov/react-native-background-downloader": "^3.1.2",
|
||||
"@movie-web/api": "*",
|
||||
"@movie-web/colors": "*",
|
||||
"@movie-web/provider-utils": "*",
|
||||
|
@@ -1,16 +1,10 @@
|
||||
import type { DownloadTask } from "@kesha-antonov/react-native-background-downloader";
|
||||
import type { DownloadProgressData } from "expo-file-system";
|
||||
import type { Asset } from "expo-media-library";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import * as MediaLibrary from "expo-media-library";
|
||||
import * as Network from "expo-network";
|
||||
import { NetworkStateType } from "expo-network";
|
||||
import {
|
||||
checkForExistingDownloads,
|
||||
completeHandler,
|
||||
download,
|
||||
setConfig,
|
||||
} from "@kesha-antonov/react-native-background-downloader";
|
||||
import VideoManager from "@salihgun/react-native-video-processor";
|
||||
|
||||
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
||||
@@ -22,11 +16,6 @@ import {
|
||||
useNetworkSettingsStore,
|
||||
} from "~/stores/settings";
|
||||
|
||||
interface DownloadProgress {
|
||||
bytesDownloaded: number;
|
||||
bytesTotal: number;
|
||||
}
|
||||
|
||||
export interface Download {
|
||||
id: string;
|
||||
progress: number;
|
||||
@@ -44,7 +33,7 @@ export interface Download {
|
||||
| "importing";
|
||||
localPath?: string;
|
||||
media: ScrapeMedia;
|
||||
downloadTask?: DownloadTask;
|
||||
downloadTask?: FileSystem.DownloadResumable;
|
||||
}
|
||||
|
||||
export interface DownloadContent {
|
||||
@@ -52,12 +41,6 @@ export interface DownloadContent {
|
||||
downloads: Download[];
|
||||
}
|
||||
|
||||
// @ts-expect-error - types are not up to date
|
||||
setConfig({
|
||||
isLogsEnabled: false,
|
||||
progressInterval: 250,
|
||||
});
|
||||
|
||||
export const useDownloadManager = () => {
|
||||
const cancellationFlags = useState<Record<string, boolean>>({})[0];
|
||||
|
||||
@@ -76,10 +59,10 @@ export const useDownloadManager = () => {
|
||||
[cancellationFlags],
|
||||
);
|
||||
|
||||
const cancelDownload = (download: Download) => {
|
||||
const cancelDownload = async (download: Download) => {
|
||||
setCancellationFlag(download.id, true);
|
||||
if (download?.downloadTask) {
|
||||
download.downloadTask.stop();
|
||||
await download.downloadTask.cancelAsync();
|
||||
}
|
||||
showToast("Download cancelled", {
|
||||
burntOptions: { preset: "done" },
|
||||
@@ -141,112 +124,97 @@ export const useDownloadManager = () => {
|
||||
[setDownloads],
|
||||
);
|
||||
|
||||
const saveFileToMediaLibraryAndDeleteOriginal = useCallback(
|
||||
async (fileUri: string, download: Download): Promise<Asset | void> => {
|
||||
console.log(
|
||||
"Saving file to media library and deleting original",
|
||||
fileUri,
|
||||
);
|
||||
try {
|
||||
updateDownloadItem(download.id, { status: "importing" });
|
||||
const saveFileToMediaLibraryAndDeleteOriginal = async (
|
||||
fileUri: string,
|
||||
download: Download,
|
||||
): Promise<Asset | void> => {
|
||||
console.log("Saving file to media library and deleting original", fileUri);
|
||||
try {
|
||||
updateDownloadItem(download.id, { status: "importing" });
|
||||
|
||||
const asset = await MediaLibrary.createAssetAsync(fileUri);
|
||||
const { localUri } = await MediaLibrary.getAssetInfoAsync(asset);
|
||||
await FileSystem.deleteAsync(fileUri);
|
||||
const asset = await MediaLibrary.createAssetAsync(fileUri);
|
||||
const { localUri } = await MediaLibrary.getAssetInfoAsync(asset);
|
||||
await FileSystem.deleteAsync(fileUri);
|
||||
|
||||
updateDownloadItem(download.id, {
|
||||
status: "finished",
|
||||
localPath: localUri,
|
||||
});
|
||||
console.log("File saved to media library and original deleted");
|
||||
showToast("Download finished", {
|
||||
burntOptions: { preset: "done" },
|
||||
});
|
||||
return asset;
|
||||
} catch (error) {
|
||||
console.error("Error saving file to media library:", error);
|
||||
showToast("Download failed", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
}
|
||||
},
|
||||
[updateDownloadItem, showToast],
|
||||
);
|
||||
|
||||
const downloadMP4 = useCallback(
|
||||
(
|
||||
url: string,
|
||||
downloadItem: Download,
|
||||
headers: Record<string, string>,
|
||||
): Promise<Asset> => {
|
||||
return new Promise<Asset>((resolve, reject) => {
|
||||
let lastBytesWritten = 0;
|
||||
let lastTimestamp = Date.now();
|
||||
|
||||
const updateProgress = (downloadProgress: DownloadProgress) => {
|
||||
const currentTime = Date.now();
|
||||
const timeElapsed = (currentTime - lastTimestamp) / 1000;
|
||||
|
||||
if (timeElapsed === 0) return;
|
||||
|
||||
const newBytes = downloadProgress.bytesDownloaded - lastBytesWritten;
|
||||
const speed = newBytes / timeElapsed / 1024 / 1024;
|
||||
const progress =
|
||||
downloadProgress.bytesDownloaded / downloadProgress.bytesTotal;
|
||||
|
||||
updateDownloadItem(downloadItem.id, {
|
||||
progress,
|
||||
speed,
|
||||
fileSize: downloadProgress.bytesTotal,
|
||||
downloaded: downloadProgress.bytesDownloaded,
|
||||
});
|
||||
|
||||
lastBytesWritten = downloadProgress.bytesDownloaded;
|
||||
lastTimestamp = currentTime;
|
||||
};
|
||||
|
||||
const fileUri =
|
||||
FileSystem.cacheDirectory + "movie-web"
|
||||
? FileSystem.cacheDirectory + "movie-web" + url.split("/").pop()
|
||||
: null;
|
||||
if (!fileUri) {
|
||||
console.error("Cache directory is unavailable");
|
||||
reject(new Error("Cache directory is unavailable"));
|
||||
return;
|
||||
}
|
||||
|
||||
const downloadTask = download({
|
||||
id: downloadItem.id,
|
||||
url,
|
||||
destination: fileUri,
|
||||
headers,
|
||||
isNotificationVisible: true,
|
||||
})
|
||||
.begin(() => {
|
||||
updateDownloadItem(downloadItem.id, { downloadTask });
|
||||
})
|
||||
.progress(({ bytesDownloaded, bytesTotal }) => {
|
||||
updateProgress({ bytesDownloaded, bytesTotal });
|
||||
})
|
||||
.done(() => {
|
||||
saveFileToMediaLibraryAndDeleteOriginal(fileUri, downloadItem)
|
||||
.then((asset) => {
|
||||
if (asset) {
|
||||
resolve(asset);
|
||||
} else {
|
||||
reject(new Error("No asset returned"));
|
||||
}
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
})
|
||||
.error(({ error, errorCode }) => {
|
||||
console.error(`Download error: ${errorCode} - ${error}`);
|
||||
reject(new Error(`Download error: ${errorCode} - ${error}`));
|
||||
});
|
||||
updateDownloadItem(download.id, {
|
||||
status: "finished",
|
||||
localPath: localUri,
|
||||
});
|
||||
},
|
||||
[saveFileToMediaLibraryAndDeleteOriginal, updateDownloadItem],
|
||||
);
|
||||
console.log("File saved to media library and original deleted");
|
||||
showToast("Download finished", {
|
||||
burntOptions: { preset: "done" },
|
||||
});
|
||||
return asset;
|
||||
} catch (error) {
|
||||
console.error("Error saving file to media library:", error);
|
||||
showToast("Download failed", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const downloadMP4 = async (
|
||||
url: string,
|
||||
downloadItem: Download,
|
||||
headers: Record<string, string>,
|
||||
): Promise<Asset | void> => {
|
||||
let lastBytesWritten = 0;
|
||||
let lastTimestamp = Date.now();
|
||||
|
||||
const updateProgress = (downloadProgress: DownloadProgressData) => {
|
||||
const currentTime = Date.now();
|
||||
const timeElapsed = (currentTime - lastTimestamp) / 1000;
|
||||
|
||||
if (timeElapsed === 0) return;
|
||||
|
||||
const newBytes = downloadProgress.totalBytesWritten - lastBytesWritten;
|
||||
const speed = newBytes / timeElapsed / 1024 / 1024;
|
||||
const progress =
|
||||
downloadProgress.totalBytesWritten /
|
||||
downloadProgress.totalBytesExpectedToWrite;
|
||||
|
||||
updateDownloadItem(downloadItem.id, {
|
||||
progress,
|
||||
speed,
|
||||
fileSize: downloadProgress.totalBytesExpectedToWrite,
|
||||
downloaded: downloadProgress.totalBytesWritten,
|
||||
});
|
||||
|
||||
lastBytesWritten = downloadProgress.totalBytesWritten;
|
||||
lastTimestamp = currentTime;
|
||||
};
|
||||
|
||||
const fileUri =
|
||||
FileSystem.cacheDirectory + "movie-web"
|
||||
? FileSystem.cacheDirectory + "movie-web" + url.split("/").pop()
|
||||
: null;
|
||||
if (!fileUri) {
|
||||
console.error("Cache directory is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const downloadResumable = FileSystem.createDownloadResumable(
|
||||
url,
|
||||
fileUri,
|
||||
{
|
||||
headers,
|
||||
},
|
||||
updateProgress,
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await downloadResumable.downloadAsync();
|
||||
if (result) {
|
||||
console.log("Finished downloading to ", result.uri);
|
||||
return saveFileToMediaLibraryAndDeleteOriginal(
|
||||
result.uri,
|
||||
downloadItem,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupDownload = useCallback(
|
||||
async (segmentDir: string, download: Download) => {
|
||||
@@ -256,201 +224,184 @@ export const useDownloadManager = () => {
|
||||
[removeDownload],
|
||||
);
|
||||
|
||||
const downloadHLS = useCallback(
|
||||
async (
|
||||
url: string,
|
||||
download: Download,
|
||||
headers: Record<string, string>,
|
||||
) => {
|
||||
const segments = await extractSegmentsFromHLS(url, headers);
|
||||
const downloadHLS = async (
|
||||
url: string,
|
||||
download: Download,
|
||||
headers: Record<string, string>,
|
||||
) => {
|
||||
const segments = await extractSegmentsFromHLS(url, headers);
|
||||
|
||||
if (!segments || segments.length === 0) {
|
||||
return removeDownload(download);
|
||||
if (!segments || segments.length === 0) {
|
||||
return removeDownload(download);
|
||||
}
|
||||
|
||||
const totalSegments = segments.length;
|
||||
let segmentsDownloaded = 0;
|
||||
|
||||
const segmentDir = FileSystem.cacheDirectory + "movie-web/segments/";
|
||||
await ensureDirExists(segmentDir);
|
||||
|
||||
const updateProgress = () => {
|
||||
const progress = segmentsDownloaded / totalSegments;
|
||||
updateDownloadItem(download.id, {
|
||||
progress,
|
||||
downloaded: segmentsDownloaded,
|
||||
fileSize: totalSegments,
|
||||
});
|
||||
};
|
||||
|
||||
const localSegmentPaths = [];
|
||||
|
||||
for (const [index, segment] of segments.entries()) {
|
||||
if (getCancellationFlag(download.id)) {
|
||||
await cleanupDownload(segmentDir, download);
|
||||
return;
|
||||
}
|
||||
|
||||
const totalSegments = segments.length;
|
||||
let segmentsDownloaded = 0;
|
||||
const segmentFile = `${segmentDir}${index}.ts`;
|
||||
localSegmentPaths.push(segmentFile);
|
||||
|
||||
const segmentDir = FileSystem.cacheDirectory + "movie-web/segments/";
|
||||
await ensureDirExists(segmentDir);
|
||||
try {
|
||||
await downloadSegment(segment, segmentFile, headers);
|
||||
|
||||
const updateProgress = () => {
|
||||
const progress = segmentsDownloaded / totalSegments;
|
||||
updateDownloadItem(download.id, {
|
||||
progress,
|
||||
downloaded: segmentsDownloaded,
|
||||
fileSize: totalSegments,
|
||||
});
|
||||
};
|
||||
|
||||
const localSegmentPaths = [];
|
||||
|
||||
for (const [index, segment] of segments.entries()) {
|
||||
if (getCancellationFlag(download.id)) {
|
||||
await cleanupDownload(segmentDir, download);
|
||||
return;
|
||||
}
|
||||
|
||||
const segmentFile = `${segmentDir}${index}.ts`;
|
||||
localSegmentPaths.push(segmentFile);
|
||||
|
||||
try {
|
||||
await downloadSegment(download.id, segment, segmentFile, headers);
|
||||
|
||||
if (getCancellationFlag(download.id)) {
|
||||
await cleanupDownload(segmentDir, download);
|
||||
return;
|
||||
}
|
||||
|
||||
segmentsDownloaded++;
|
||||
updateProgress();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (getCancellationFlag(download.id)) {
|
||||
await cleanupDownload(segmentDir, download);
|
||||
return;
|
||||
}
|
||||
segmentsDownloaded++;
|
||||
updateProgress();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (getCancellationFlag(download.id)) {
|
||||
await cleanupDownload(segmentDir, download);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getCancellationFlag(download.id)) {
|
||||
return removeDownload(download);
|
||||
}
|
||||
if (getCancellationFlag(download.id)) {
|
||||
return removeDownload(download);
|
||||
}
|
||||
|
||||
updateDownloadItem(download.id, { status: "merging" });
|
||||
const uri = await VideoManager.mergeVideos(
|
||||
localSegmentPaths,
|
||||
`${FileSystem.cacheDirectory}movie-web/output.mp4`,
|
||||
);
|
||||
const asset = await saveFileToMediaLibraryAndDeleteOriginal(
|
||||
uri,
|
||||
download,
|
||||
);
|
||||
return asset;
|
||||
},
|
||||
[
|
||||
cleanupDownload,
|
||||
getCancellationFlag,
|
||||
removeDownload,
|
||||
saveFileToMediaLibraryAndDeleteOriginal,
|
||||
updateDownloadItem,
|
||||
],
|
||||
);
|
||||
updateDownloadItem(download.id, { status: "merging" });
|
||||
const uri = await VideoManager.mergeVideos(
|
||||
localSegmentPaths,
|
||||
`${FileSystem.cacheDirectory}movie-web/output.mp4`,
|
||||
);
|
||||
const asset = await saveFileToMediaLibraryAndDeleteOriginal(uri, download);
|
||||
return asset;
|
||||
};
|
||||
|
||||
const startDownload = useCallback(
|
||||
async (
|
||||
url: string,
|
||||
type: "mp4" | "hls",
|
||||
media: ScrapeMedia,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Asset | void> => {
|
||||
const { allowMobileData } = useNetworkSettingsStore.getState();
|
||||
const startDownload = async (
|
||||
url: string,
|
||||
type: "mp4" | "hls",
|
||||
media: ScrapeMedia,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Asset | void> => {
|
||||
const { allowMobileData } = useNetworkSettingsStore.getState();
|
||||
|
||||
const { type: networkType } = await Network.getNetworkStateAsync();
|
||||
const { type: networkType } = await Network.getNetworkStateAsync();
|
||||
|
||||
if (networkType === NetworkStateType.CELLULAR && !allowMobileData) {
|
||||
showToast("Mobile data downloads are disabled", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (networkType === NetworkStateType.CELLULAR && !allowMobileData) {
|
||||
showToast("Mobile data downloads are disabled", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||
if (status !== MediaLibrary.PermissionStatus.GRANTED) {
|
||||
showToast("Permission denied", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||
if (status !== MediaLibrary.PermissionStatus.GRANTED) {
|
||||
showToast("Permission denied", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const existingDownload = downloads.find(
|
||||
(d) => d.media.tmdbId === media.tmdbId,
|
||||
const existingDownload = downloads.find(
|
||||
(d) => d.media.tmdbId === media.tmdbId,
|
||||
);
|
||||
|
||||
if (existingDownload && media.type === "movie") {
|
||||
showToast("Download already exists", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingDownload && media.type === "show") {
|
||||
const existingEpisode = existingDownload.downloads.find(
|
||||
(d) =>
|
||||
d.media.type === "show" &&
|
||||
d.media.episode.tmdbId === media.episode.tmdbId,
|
||||
);
|
||||
|
||||
if (existingDownload && media.type === "movie") {
|
||||
if (existingEpisode) {
|
||||
showToast("Download already exists", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
showToast("Download started", {
|
||||
burntOptions: { preset: "none" },
|
||||
});
|
||||
|
||||
if (existingDownload && media.type === "show") {
|
||||
const existingEpisode = existingDownload.downloads.find(
|
||||
(d) =>
|
||||
d.media.type === "show" &&
|
||||
d.media.episode.tmdbId === media.episode.tmdbId,
|
||||
const newDownload: Download = {
|
||||
id: `download-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
fileSize: 0,
|
||||
downloaded: 0,
|
||||
type,
|
||||
url,
|
||||
status: "downloading",
|
||||
media,
|
||||
};
|
||||
|
||||
if (existingDownload) {
|
||||
existingDownload.downloads.push(newDownload);
|
||||
setDownloads((prev) => {
|
||||
return prev.map((d) =>
|
||||
d.media.tmdbId === media.tmdbId ? existingDownload : d,
|
||||
);
|
||||
|
||||
if (existingEpisode) {
|
||||
showToast("Download already exists", {
|
||||
burntOptions: { preset: "error" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
showToast("Download started", {
|
||||
burntOptions: { preset: "none" },
|
||||
});
|
||||
} else {
|
||||
setDownloads((prev) => {
|
||||
return [...prev, { media, downloads: [newDownload] }];
|
||||
});
|
||||
}
|
||||
|
||||
const newDownload: Download = {
|
||||
id: `download-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
fileSize: 0,
|
||||
downloaded: 0,
|
||||
type,
|
||||
url,
|
||||
status: "downloading",
|
||||
media,
|
||||
};
|
||||
|
||||
if (existingDownload) {
|
||||
existingDownload.downloads.push(newDownload);
|
||||
setDownloads((prev) => {
|
||||
return prev.map((d) =>
|
||||
d.media.tmdbId === media.tmdbId ? existingDownload : d,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setDownloads((prev) => {
|
||||
return [...prev, { media, downloads: [newDownload] }];
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "mp4") {
|
||||
const asset = await downloadMP4(url, newDownload, headers ?? {});
|
||||
return asset;
|
||||
} else if (type === "hls") {
|
||||
const asset = await downloadHLS(url, newDownload, headers ?? {});
|
||||
return asset;
|
||||
}
|
||||
},
|
||||
[downloadHLS, downloadMP4, downloads, setDownloads, showToast],
|
||||
);
|
||||
if (type === "mp4") {
|
||||
const asset = await downloadMP4(url, newDownload, headers ?? {});
|
||||
return asset;
|
||||
} else if (type === "hls") {
|
||||
const asset = await downloadHLS(url, newDownload, headers ?? {});
|
||||
return asset;
|
||||
}
|
||||
};
|
||||
|
||||
const downloadSegment = async (
|
||||
downloadId: string,
|
||||
segmentUrl: string,
|
||||
segmentFile: string,
|
||||
headers: Record<string, string>,
|
||||
) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const task = download({
|
||||
id: `${downloadId}-${segmentUrl.split("/").pop()}`,
|
||||
url: segmentUrl,
|
||||
destination: segmentFile,
|
||||
headers: headers,
|
||||
});
|
||||
const downloadResumable = FileSystem.createDownloadResumable(
|
||||
segmentUrl,
|
||||
segmentFile,
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
|
||||
task
|
||||
.done(() => {
|
||||
resolve();
|
||||
})
|
||||
.error((error) => {
|
||||
console.error(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const result = await downloadResumable.downloadAsync();
|
||||
if (result) {
|
||||
console.log("Finished downloading to ", result.uri);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
async function ensureDirExists(dir: string) {
|
||||
@@ -458,27 +409,6 @@ export const useDownloadManager = () => {
|
||||
await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const checkRunningTasks = async () => {
|
||||
const existingTasks = await checkForExistingDownloads();
|
||||
existingTasks.forEach((task) => {
|
||||
task
|
||||
.progress(({ bytesDownloaded, bytesTotal }) => {
|
||||
const progress = bytesDownloaded / bytesTotal;
|
||||
updateDownloadItem(task.id, { progress });
|
||||
})
|
||||
.done(() => {
|
||||
completeHandler(task.id);
|
||||
})
|
||||
.error(({ error, errorCode }) => {
|
||||
console.error(`Download error: ${errorCode} - ${error}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
void checkRunningTasks();
|
||||
}, [updateDownloadItem]);
|
||||
|
||||
return {
|
||||
startDownload,
|
||||
removeDownload,
|
||||
|
@@ -1,48 +0,0 @@
|
||||
const { withAppDelegate } = require("@expo/config-plugins");
|
||||
|
||||
function withRNBackgroundDownloader(expoConfig) {
|
||||
return withAppDelegate(expoConfig, async (appDelegateConfig) => {
|
||||
const { modResults: appDelegate } = appDelegateConfig;
|
||||
const appDelegateLines = appDelegate.contents.split("\n");
|
||||
|
||||
// Define the code to be added to AppDelegate.mm
|
||||
const backgroundDownloaderImport =
|
||||
"#import <RNBackgroundDownloader.h> // Required by react-native-background-downloader. Generated by expoPlugins/withRNBackgroundDownloader.js";
|
||||
const backgroundDownloaderDelegate = `\n// Delegate method required by react-native-background-downloader. Generated by expoPlugins/withRNBackgroundDownloader.js
|
||||
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
|
||||
{
|
||||
[RNBackgroundDownloader setCompletionHandlerWithIdentifier:identifier completionHandler:completionHandler];
|
||||
}`;
|
||||
|
||||
// Find the index of the AppDelegate import statement
|
||||
const importIndex = appDelegateLines.findIndex((line) =>
|
||||
/^#import "AppDelegate.h"/.test(line),
|
||||
);
|
||||
|
||||
// Find the index of the last line before the @end statement
|
||||
const endStatementIndex = appDelegateLines.findIndex((line) =>
|
||||
/@end/.test(line),
|
||||
);
|
||||
|
||||
// Insert the import statement if it's not already present
|
||||
if (!appDelegate.contents.includes(backgroundDownloaderImport)) {
|
||||
appDelegateLines.splice(importIndex + 1, 0, backgroundDownloaderImport);
|
||||
}
|
||||
|
||||
// Insert the delegate method above the @end statement
|
||||
if (!appDelegate.contents.includes(backgroundDownloaderDelegate)) {
|
||||
appDelegateLines.splice(
|
||||
endStatementIndex,
|
||||
0,
|
||||
backgroundDownloaderDelegate,
|
||||
);
|
||||
}
|
||||
|
||||
// Update the contents of the AppDelegate file
|
||||
appDelegate.contents = appDelegateLines.join("\n");
|
||||
|
||||
return appDelegateConfig;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = withRNBackgroundDownloader;
|
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -29,9 +29,6 @@ importers:
|
||||
'@expo/metro-config':
|
||||
specifier: ^0.17.3
|
||||
version: 0.17.3(@react-native/babel-preset@0.73.21)
|
||||
'@kesha-antonov/react-native-background-downloader':
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(react-native@0.73.6)
|
||||
'@movie-web/api':
|
||||
specifier: '*'
|
||||
version: link:../../packages/api
|
||||
@@ -2842,14 +2839,6 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@kesha-antonov/react-native-background-downloader@3.1.2(react-native@0.73.6):
|
||||
resolution: {integrity: sha512-xBs1DyGOdGCSI7mfE7cT7V1Ecv6G99A4zh08o0q7/DXOqttX4tymiTQNuWQxW2dMe7clOHGjzeW1BLYwofSYNw==}
|
||||
peerDependencies:
|
||||
react-native: '>=0.57.0'
|
||||
dependencies:
|
||||
react-native: 0.73.6(@babel/core@7.23.9)(@babel/preset-env@7.23.9)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@motionone/animation@10.17.0:
|
||||
resolution: {integrity: sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg==}
|
||||
dependencies:
|
||||
|
Reference in New Issue
Block a user