feat: background task for mp4 downloads

This commit is contained in:
Adrian Castro
2024-03-27 23:57:02 +01:00
parent 1c5a63f8f1
commit 57cd3e642b
5 changed files with 114 additions and 58 deletions

View File

@@ -1,8 +1,8 @@
import type { ExpoConfig } from "expo/config";
import withRemoveiOSNotificationEntitlement from "./config-plugins/withRemoveiOSNotificationEntitlement";
import withRNBackgroundDownloader from "./config-plugins/withRNBackgroundDownloader";
import { version } from "./package.json";
import withRemoveiOSNotificationEntitlement from "./src/plugins/withRemoveiOSNotificationEntitlement";
import withRNBackgroundDownloader from "./src/plugins/withRNBackgroundDownloader";
const defineConfig = (): ExpoConfig => ({
name: "movie-web",

View File

@@ -1,8 +1,15 @@
import type { DownloadTask } from "@kesha-antonov/react-native-background-downloader";
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";
import * as MediaLibrary from "expo-media-library";
import {
checkForExistingDownloads,
completeHandler,
download,
setConfig,
} from "@kesha-antonov/react-native-background-downloader";
import VideoManager from "@salihgun/react-native-video-processor";
import { useToastController } from "@tamagui/toast";
@@ -25,9 +32,15 @@ export interface DownloadItem {
asset?: Asset;
isHLS?: boolean;
media: ScrapeMedia;
downloadResumable?: FileSystem.DownloadResumable;
downloadTask?: DownloadTask;
}
// @ts-expect-error - types are not up to date
setConfig({
isLogsEnabled: false,
progressInterval: 250,
});
interface DownloadManagerContextType {
downloads: DownloadItem[];
startDownload: (
@@ -37,7 +50,7 @@ interface DownloadManagerContextType {
headers?: Record<string, string>,
) => Promise<Asset | void>;
removeDownload: (id: string) => void;
cancelDownload: (id: string) => Promise<void>;
cancelDownload: (id: string) => void;
}
const DownloadManagerContext = createContext<
@@ -85,11 +98,11 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
return cancellationFlags[downloadId] ?? false;
};
const cancelDownload = async (downloadId: string): Promise<void> => {
const cancelDownload = (downloadId: string) => {
setCancellationFlag(downloadId, true);
const downloadItem = downloads.find((d) => d.id === downloadId);
if (downloadItem?.downloadResumable) {
await downloadItem.downloadResumable.cancelAsync();
if (downloadItem?.downloadTask) {
downloadItem.downloadTask.stop();
}
toastController.show("Download cancelled", {
burntOptions: { preset: "done" },
@@ -98,6 +111,35 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
});
};
// const initializeDownloader = async () => {
// setConfig({ isLogsEnabled: true }); // Set any global configs here
// const existingTasks = await checkForExistingDownloads();
// existingTasks.forEach(task => {
// // Reattach event listeners to existing tasks
// processTask(task);
// });
// };
const _processTask = (task: DownloadTask) => {
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}`);
});
const downloadItem = downloads.find((d) => d.id === task.id);
if (downloadItem) {
updateDownloadItem(task.id, { downloadTask: task });
}
};
const startDownload = async (
url: string,
type: "mp4" | "hls",
@@ -143,34 +185,39 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
);
};
const downloadMP4 = async (
interface DownloadProgress {
bytesDownloaded: number;
bytesTotal: number;
}
const downloadMP4 = (
url: string,
downloadId: string,
headers: Record<string, string>,
) => {
): Promise<Asset> => {
return new Promise<Asset>((resolve, reject) => {
let lastBytesWritten = 0;
let lastTimestamp = Date.now();
const callback = (downloadProgress: FileSystem.DownloadProgressData) => {
const updateProgress = (downloadProgress: DownloadProgress) => {
const currentTime = Date.now();
const timeElapsed = (currentTime - lastTimestamp) / 1000;
if (timeElapsed === 0) return;
const bytesWritten = downloadProgress.totalBytesWritten;
const newBytes = bytesWritten - lastBytesWritten;
const newBytes = downloadProgress.bytesDownloaded - lastBytesWritten;
const speed = newBytes / timeElapsed / 1024;
const progress =
bytesWritten / downloadProgress.totalBytesExpectedToWrite;
downloadProgress.bytesDownloaded / downloadProgress.bytesTotal;
updateDownloadItem(downloadId, {
progress,
speed,
fileSize: downloadProgress.totalBytesExpectedToWrite,
downloaded: bytesWritten,
fileSize: downloadProgress.bytesTotal,
downloaded: downloadProgress.bytesDownloaded,
});
lastBytesWritten = bytesWritten;
lastBytesWritten = downloadProgress.bytesDownloaded;
lastTimestamp = currentTime;
};
@@ -179,30 +226,39 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
: null;
if (!fileUri) {
console.error("Cache directory is unavailable");
reject(new Error("Cache directory is unavailable"));
return;
}
const downloadResumable = FileSystem.createDownloadResumable(
const downloadTask = download({
id: downloadId,
url,
fileUri,
{ headers },
callback,
);
updateDownloadItem(downloadId, { downloadResumable });
try {
const result = await downloadResumable.downloadAsync();
if (result) {
console.log("Finished downloading to ", result.uri);
const asset = await saveFileToMediaLibraryAndDeleteOriginal(
result.uri,
downloadId,
);
return asset;
}
} catch (e) {
console.error(e);
destination: fileUri,
headers,
isNotificationVisible: true,
})
.begin(() => {
updateDownloadItem(downloadId, { downloadTask });
})
.progress(({ bytesDownloaded, bytesTotal }) => {
updateProgress({ bytesDownloaded, bytesTotal });
})
.done(() => {
saveFileToMediaLibraryAndDeleteOriginal(fileUri, downloadId)
.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}`));
});
});
};
const downloadHLS = async (

View File

@@ -17,7 +17,7 @@
"*.js",
".expo/types/**/*.ts",
"expo-env.d.ts",
"config-plugins/*",
"src/plugins/*",
],
"exclude": ["node_modules"],
}