import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { writeToLog } from "@/utils/log"; import Ionicons from "@expo/vector-icons/Ionicons"; import AsyncStorage from "@react-native-async-storage/async-storage"; import * as FileSystem from "expo-file-system"; import { FFmpegKit, FFmpegKitConfig } from "ffmpeg-kit-react-native"; import { atom, useAtom } from "jotai"; import { useCallback, useEffect, useState } from "react"; import { TouchableOpacity, View } from "react-native"; import ProgressCircle from "./ProgressCircle"; import { router } from "expo-router"; import { getPlaybackInfo, useDownloadMedia } from "@/utils/jellyfin"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { ProcessItem, runningProcesses } from "@/utils/atoms/downloads"; type DownloadProps = { item: BaseItemDto; url: string; }; // const useRemuxHlsToMp4 = (inputUrl: string, item: BaseItemDto) => { // if (!item.Id || !item.Name) { // writeToLog("ERROR", "useRemuxHlsToMp4 ~ missing arguments", { // item, // inputUrl, // }); // throw new Error("Item must have an Id and Name"); // } // const [session, setSession] = useAtom(runningProcesses); // const output = `${FileSystem.documentDirectory}${item.Id}.mp4`; // const command = `-y -fflags +genpts -i ${inputUrl} -c copy -max_muxing_queue_size 9999 ${output}`; // const startRemuxing = useCallback(async () => { // if (!item.Id || !item.Name) { // writeToLog( // "ERROR", // "useRemuxHlsToMp4 ~ startRemuxing ~ missing arguments", // { // item, // inputUrl, // } // ); // throw new Error("Item must have an Id and Name"); // } // writeToLog( // "INFO", // `useRemuxHlsToMp4 ~ startRemuxing for item ${item.Id} with url ${inputUrl}`, // { // item, // inputUrl, // } // ); // try { // setSession({ // item, // progress: 0, // }); // FFmpegKitConfig.enableStatisticsCallback((statistics) => { // let percentage = 0; // const videoLength = // (item.MediaSources?.[0].RunTimeTicks || 0) / 10000000; // In seconds // const fps = item.MediaStreams?.[0].RealFrameRate || 25; // const totalFrames = videoLength * fps; // const processedFrames = statistics.getVideoFrameNumber(); // if (totalFrames > 0) { // percentage = Math.floor((processedFrames / totalFrames) * 100); // } // setSession((prev) => { // return prev?.item.Id === item.Id! // ? { ...prev, progress: percentage } // : prev; // }); // }); // await FFmpegKit.executeAsync(command, async (session) => { // const returnCode = await session.getReturnCode(); // if (returnCode.isValueSuccess()) { // const currentFiles: BaseItemDto[] = JSON.parse( // (await AsyncStorage.getItem("downloaded_files")) || "[]" // ); // const otherItems = currentFiles.filter((i) => i.Id !== item.Id); // await AsyncStorage.setItem( // "downloaded_files", // JSON.stringify([...otherItems, item]) // ); // writeToLog( // "INFO", // `useRemuxHlsToMp4 ~ remuxing completed successfully for item: ${item.Name}`, // { // item, // inputUrl, // } // ); // setSession(null); // } else if (returnCode.isValueError()) { // console.error("Failed to remux:"); // writeToLog( // "ERROR", // `useRemuxHlsToMp4 ~ remuxing failed for item: ${item.Name}`, // { // item, // inputUrl, // } // ); // setSession(null); // } else if (returnCode.isValueCancel()) { // console.log("Remuxing was cancelled"); // writeToLog( // "INFO", // `useRemuxHlsToMp4 ~ remuxing was canceled for item: ${item.Name}`, // { // item, // inputUrl, // } // ); // setSession(null); // } // }); // } catch (error) { // console.error("Failed to remux:", error); // writeToLog( // "ERROR", // `useRemuxHlsToMp4 ~ remuxing failed for item: ${item.Name}`, // { // item, // inputUrl, // } // ); // } // }, [inputUrl, output, item, command]); // const cancelRemuxing = useCallback(async () => { // FFmpegKit.cancel(); // setSession(null); // console.log("Remuxing cancelled"); // }, []); // return { session, startRemuxing, cancelRemuxing }; // }; export const DownloadItem: React.FC = ({ url, item }) => { // const { session, startRemuxing, cancelRemuxing } = useRemuxHlsToMp4( // url, // item // ); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const [process] = useAtom(runningProcesses); const { downloadMedia, isDownloading, error } = useDownloadMedia(api); const downloadFile = useCallback(async () => { const playbackInfo = await getPlaybackInfo(api, item.Id, user?.Id); const source = playbackInfo?.MediaSources?.[0]; if (source?.SupportsDirectPlay && item.CanDownload) { downloadMedia(item); } else { console.log("file not supported"); } }, [item, user]); const [downloaded, setDownloaded] = useState(false); const [key, setKey] = useState(""); useEffect(() => { (async () => { const data: BaseItemDto[] = JSON.parse( (await AsyncStorage.getItem("downloaded_files")) || "[]" ); if (data.find((d) => d.Id === item.Id)) setDownloaded(true); })(); }, [key]); if (process && process.item.Id !== item.Id!) { return ( {}} style={{ opacity: 0.5 }}> ); } return ( {process ? ( { // cancelRemuxing(); }} className="-rotate-45" > ) : downloaded ? ( { router.push( `/(auth)/player/offline/page?url=${item.Id}.mp4&itemId=${item.Id}` ); }} > ) : ( { downloadFile(); }} > )} ); };