forked from Ninjalama/streamyfin_mirror
WIP
This commit is contained in:
@@ -69,10 +69,15 @@ const page: React.FC = () => {
|
||||
seriesId: item?.Id!,
|
||||
userId: user?.Id!,
|
||||
enableUserData: true,
|
||||
fields: ["MediaSources", "MediaStreams", "Overview"],
|
||||
// Note: Including trick play is necessary to enable trick play downloads
|
||||
fields: ["MediaSources", "MediaStreams", "Overview", "Trickplay"],
|
||||
});
|
||||
return res?.data.Items || [];
|
||||
},
|
||||
select: (data) =>
|
||||
[...(data || [])].sort(
|
||||
(a, b) => (a.ParentIndexNumber ?? 0) - (b.ParentIndexNumber ?? 0) || (a.IndexNumber ?? 0) - (b.IndexNumber ?? 0)
|
||||
),
|
||||
staleTime: 60,
|
||||
enabled: !!api && !!user?.Id && !!item?.Id,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { t } from "i18next";
|
||||
import { useAtom } from "jotai";
|
||||
import type React from "react";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Alert, Platform, View, type ViewProps } from "react-native";
|
||||
import { Alert, Platform, Switch, View, type ViewProps } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
@@ -55,6 +55,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
const [user] = useAtom(userAtom);
|
||||
const [queue, setQueue] = useAtom(queueAtom);
|
||||
const [settings] = useSettings();
|
||||
const [downloadUnwatchedOnly, setDownloadUnwatchedOnly] = useState(false);
|
||||
|
||||
const { processes, startBackgroundDownload, downloadedFiles } = useDownload();
|
||||
|
||||
@@ -96,6 +97,13 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
[items, downloadedFiles],
|
||||
);
|
||||
|
||||
const itemsToDownload = useMemo(() => {
|
||||
if (downloadUnwatchedOnly) {
|
||||
return itemsNotDownloaded.filter((item) => !item.UserData?.Played);
|
||||
}
|
||||
return itemsNotDownloaded;
|
||||
}, [itemsNotDownloaded, downloadUnwatchedOnly]);
|
||||
|
||||
const allItemsDownloaded = useMemo(() => {
|
||||
if (items.length === 0) return false;
|
||||
return itemsNotDownloaded.length === 0;
|
||||
@@ -138,30 +146,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const acceptDownloadOptions = useCallback(() => {
|
||||
if (userCanDownload === true) {
|
||||
if (itemsNotDownloaded.some((i) => !i.Id)) {
|
||||
throw new Error("No item id");
|
||||
}
|
||||
closeModal();
|
||||
|
||||
initiateDownload(...itemsNotDownloaded);
|
||||
} else {
|
||||
toast.error(
|
||||
t("home.downloads.toasts.you_are_not_allowed_to_download_files"),
|
||||
);
|
||||
}
|
||||
}, [
|
||||
queue,
|
||||
setQueue,
|
||||
itemsNotDownloaded,
|
||||
userCanDownload,
|
||||
maxBitrate,
|
||||
selectedMediaSource,
|
||||
selectedAudioStream,
|
||||
selectedSubtitleStream,
|
||||
]);
|
||||
|
||||
const initiateDownload = useCallback(
|
||||
async (...items: BaseItemDto[]) => {
|
||||
if (
|
||||
@@ -174,18 +158,15 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
"DownloadItem ~ initiateDownload: No api or user or item",
|
||||
);
|
||||
}
|
||||
let mediaSource = selectedMediaSource;
|
||||
let audioIndex: number | undefined = selectedAudioStream;
|
||||
let subtitleIndex: number | undefined = selectedSubtitleStream;
|
||||
|
||||
for (const item of items) {
|
||||
if (itemsNotDownloaded.length > 1) {
|
||||
const defaults = getDefaultPlaySettings(item, settings!);
|
||||
mediaSource = defaults.mediaSource;
|
||||
audioIndex = defaults.audioIndex;
|
||||
subtitleIndex = defaults.subtitleIndex;
|
||||
}
|
||||
|
||||
const downloadDetailsPromises = items.map(async (item) => {
|
||||
const { mediaSource, audioIndex, subtitleIndex } =
|
||||
itemsNotDownloaded.length > 1
|
||||
? getDefaultPlaySettings(item, settings!)
|
||||
: {
|
||||
mediaSource: selectedMediaSource,
|
||||
audioIndex: selectedAudioStream,
|
||||
subtitleIndex: selectedSubtitleStream,
|
||||
};
|
||||
const res = await getStreamUrl({
|
||||
api,
|
||||
item,
|
||||
@@ -198,7 +179,10 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
deviceProfile: download,
|
||||
download: true,
|
||||
});
|
||||
|
||||
return { res, item };
|
||||
});
|
||||
const downloadDetails = await Promise.all(downloadDetailsPromises);
|
||||
for (const { res, item } of downloadDetails) {
|
||||
if (!res) {
|
||||
Alert.alert(
|
||||
t("home.downloads.something_went_wrong"),
|
||||
@@ -206,11 +190,16 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { mediaSource: source, url } = res;
|
||||
|
||||
if (!url || !source) throw new Error("No url");
|
||||
|
||||
if (!url || !source) {
|
||||
console.error(`Could not get download URL for ${item.Name}`);
|
||||
toast.error(
|
||||
t("Could not get download URL for {{itemName}}", {
|
||||
itemName: item.Name,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
await startBackgroundDownload(url, item, source, maxBitrate);
|
||||
}
|
||||
},
|
||||
@@ -227,6 +216,26 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
],
|
||||
);
|
||||
|
||||
const acceptDownloadOptions = useCallback(() => {
|
||||
if (userCanDownload === true) {
|
||||
if (itemsToDownload.some((i) => !i.Id)) {
|
||||
throw new Error("No item id");
|
||||
}
|
||||
closeModal();
|
||||
|
||||
initiateDownload(...itemsToDownload);
|
||||
} else {
|
||||
toast.error(
|
||||
t("home.downloads.toasts.you_are_not_allowed_to_download_files"),
|
||||
);
|
||||
}
|
||||
}, [
|
||||
closeModal,
|
||||
initiateDownload,
|
||||
itemsToDownload,
|
||||
userCanDownload,
|
||||
]);
|
||||
|
||||
const renderBackdrop = useCallback(
|
||||
(props: BottomSheetBackdropProps) => (
|
||||
<BottomSheetBackdrop
|
||||
@@ -316,7 +325,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
<Text className='text-neutral-300'>
|
||||
{subtitle ||
|
||||
t("item_card.download.download_x_item", {
|
||||
item_count: itemsNotDownloaded.length,
|
||||
item_count: itemsToDownload.length,
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -326,6 +335,17 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
onChange={setMaxBitrate}
|
||||
selected={maxBitrate}
|
||||
/>
|
||||
{itemsNotDownloaded.length > 1 && (
|
||||
<View className='flex flex-row items-center justify-between w-full py-2'>
|
||||
<Text>
|
||||
{t("item_card.download.download_unwatched_only")}
|
||||
</Text>
|
||||
<Switch
|
||||
onValueChange={setDownloadUnwatchedOnly}
|
||||
value={downloadUnwatchedOnly}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{itemsNotDownloaded.length === 1 && (
|
||||
<>
|
||||
<MediaSourceSelector
|
||||
@@ -350,6 +370,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
className='mt-auto'
|
||||
onPress={acceptDownloadOptions}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const SubtitleTrackSelector: React.FC<Props> = ({
|
||||
selected,
|
||||
...props
|
||||
}) => {
|
||||
if (Platform.isTV) return null;
|
||||
const { t } = useTranslation();
|
||||
const subtitleStreams = useMemo(() => {
|
||||
return source?.MediaStreams?.filter((x) => x.Type === "Subtitle");
|
||||
}, [source]);
|
||||
@@ -28,9 +28,7 @@ export const SubtitleTrackSelector: React.FC<Props> = ({
|
||||
[subtitleStreams, selected],
|
||||
);
|
||||
|
||||
if (subtitleStreams?.length === 0) return null;
|
||||
|
||||
const { t } = useTranslation();
|
||||
if (Platform.isTV || subtitleStreams?.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View
|
||||
|
||||
@@ -65,24 +65,23 @@ interface DownloadCardProps extends TouchableOpacityProps {
|
||||
const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
const { startDownload, removeProcess } = useDownload();
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const cancelJobMutation = useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
if (!process) throw new Error("No active download");
|
||||
const tasks = await BackGroundDownloader.checkForExistingDownloads();
|
||||
|
||||
try {
|
||||
const tasks = await BackGroundDownloader.checkForExistingDownloads();
|
||||
for (const task of tasks) {
|
||||
if (task.id === id) {
|
||||
task.stop();
|
||||
}
|
||||
for (const task of tasks) {
|
||||
if (task.id === id) {
|
||||
await task.stop();
|
||||
}
|
||||
} finally {
|
||||
await removeProcess(id);
|
||||
}
|
||||
removeProcess(id);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t("home.downloads.toasts.download_cancelled"));
|
||||
queryClient.invalidateQueries({ queryKey: ["downloads"] });
|
||||
},
|
||||
onError: (e) => {
|
||||
console.error(e);
|
||||
|
||||
@@ -86,7 +86,8 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
userId: user.Id,
|
||||
seasonId: selectedSeasonId,
|
||||
enableUserData: true,
|
||||
fields: ["MediaSources", "MediaStreams", "Overview"],
|
||||
// Note: Including trick play is necessary to enable trick play downloads
|
||||
fields: ["MediaSources", "MediaStreams", "Overview", "Trickplay"],
|
||||
});
|
||||
|
||||
if (res.data.TotalRecordCount === 0)
|
||||
@@ -97,6 +98,10 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
|
||||
|
||||
return res.data.Items;
|
||||
},
|
||||
select: (data) =>
|
||||
[...(data || [])].sort(
|
||||
(a, b) => (a.IndexNumber ?? 0) - (b.IndexNumber ?? 0),
|
||||
),
|
||||
enabled: !!api && !!user?.Id && !!item.Id && !!selectedSeasonId,
|
||||
});
|
||||
|
||||
|
||||
@@ -527,9 +527,8 @@ export const Controls: FC<Props> = ({
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{`${time.hours > 0 ? `${time.hours}:` : ""}${time.minutes < 10 ? `0${time.minutes}` : time.minutes}:${
|
||||
time.seconds < 10 ? `0${time.seconds}` : time.seconds
|
||||
}`}
|
||||
{`${time.hours > 0 ? `${time.hours}:` : ""}${time.minutes < 10 ? `0${time.minutes}` : time.minutes}:${time.seconds < 10 ? `0${time.seconds}` : time.seconds
|
||||
}`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -644,10 +643,9 @@ export const Controls: FC<Props> = ({
|
||||
color='white'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{/* )} */}
|
||||
<TouchableOpacity
|
||||
onPress={onClose}
|
||||
className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'
|
||||
className='aspect-square flex flex-col l items-center justify-center p-2'
|
||||
>
|
||||
<Ionicons name='close' size={24} color='white' />
|
||||
</TouchableOpacity>
|
||||
@@ -822,19 +820,19 @@ export const Controls: FC<Props> = ({
|
||||
/>
|
||||
{(settings.maxAutoPlayEpisodeCount.value === -1 ||
|
||||
settings.autoPlayEpisodeCount <
|
||||
settings.maxAutoPlayEpisodeCount.value) && (
|
||||
<NextEpisodeCountDownButton
|
||||
show={
|
||||
!nextItem
|
||||
? false
|
||||
: isVlc
|
||||
? remainingTime < 10000
|
||||
: remainingTime < 10
|
||||
}
|
||||
onFinish={handleNextEpisodeAutoPlay}
|
||||
onPress={handleNextEpisodeManual}
|
||||
/>
|
||||
)}
|
||||
settings.maxAutoPlayEpisodeCount.value) && (
|
||||
<NextEpisodeCountDownButton
|
||||
show={
|
||||
!nextItem
|
||||
? false
|
||||
: isVlc
|
||||
? remainingTime < 10000
|
||||
: remainingTime < 10
|
||||
}
|
||||
onFinish={handleNextEpisodeAutoPlay}
|
||||
onPress={handleNextEpisodeManual}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useGlobalSearchParams } from "expo-router";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import ContinueWatchingPoster from "@/components/ContinueWatchingPoster";
|
||||
import {
|
||||
HorizontalScroll,
|
||||
@@ -183,7 +184,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
<SafeAreaView
|
||||
style={{
|
||||
position: "absolute",
|
||||
backgroundColor: "black",
|
||||
@@ -211,10 +212,8 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
|
||||
/>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
onPress={async () => {
|
||||
close();
|
||||
}}
|
||||
className='aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2'
|
||||
onPress={close}
|
||||
className='aspect-square flex flex-col l items-center justify-center p-2'
|
||||
>
|
||||
<Ionicons name='close' size={24} color='white' />
|
||||
</TouchableOpacity>
|
||||
@@ -228,9 +227,8 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
|
||||
<View
|
||||
key={_item.Id}
|
||||
style={{}}
|
||||
className={`flex flex-col w-44 ${
|
||||
item.Id !== _item.Id ? "opacity-75" : ""
|
||||
}`}
|
||||
className={`flex flex-col w-44 ${item.Id !== _item.Id ? "opacity-75" : ""
|
||||
}`}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
@@ -260,11 +258,6 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
|
||||
{runtimeTicksToSeconds(_item.RunTimeTicks)}
|
||||
</Text>
|
||||
</View>
|
||||
{!isOffline && (
|
||||
<View className='self-start mt-2'>
|
||||
<DownloadSingleItem item={_item} />
|
||||
</View>
|
||||
)}
|
||||
<Text numberOfLines={5} className='text-xs text-neutral-500 shrink'>
|
||||
{_item.Overview}
|
||||
</Text>
|
||||
@@ -274,6 +267,6 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
|
||||
estimatedItemSize={200}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,23 +13,18 @@ export const useItemQuery = (itemId: string, isOffline: boolean) => {
|
||||
queryKey: ["item", itemId],
|
||||
queryFn: async () => {
|
||||
if (isOffline) {
|
||||
const downloadedItem = downloadedFiles?.find(
|
||||
(item) => item.item.Id === itemId,
|
||||
);
|
||||
const downloadedItem = downloadedFiles?.find((item) => item.item.Id === itemId);
|
||||
if (downloadedItem) return downloadedItem.item;
|
||||
return null;
|
||||
}
|
||||
if (!api || !user || !itemId) return null;
|
||||
const res = await getUserLibraryApi(api).getItem({
|
||||
itemId: itemId,
|
||||
userId: user?.Id,
|
||||
});
|
||||
const res = await getUserLibraryApi(api).getItem({ itemId: itemId, userId: user?.Id });
|
||||
return res.data;
|
||||
},
|
||||
staleTime: 0,
|
||||
refetchOnMount: true,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
networkMode: isOffline ? "online" : "always",
|
||||
networkMode: "always",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -124,6 +124,8 @@ export const usePlaybackManager = () => {
|
||||
UserData: {
|
||||
...localItem.item.UserData,
|
||||
Played: true,
|
||||
PlaybackPositionTicks: 0,
|
||||
PlayedPercentage: 0,
|
||||
LastPlayedDate: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
@@ -169,6 +171,7 @@ export const usePlaybackManager = () => {
|
||||
...localItem.item.UserData,
|
||||
Played: false,
|
||||
PlaybackPositionTicks: 0,
|
||||
PlayedPercentage: 0,
|
||||
LastPlayedDate: new Date().toISOString(), // Keep track of when it was marked unplayed
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,13 +9,13 @@ import * as FileSystem from "expo-file-system";
|
||||
import * as Notifications from "expo-notifications";
|
||||
import { router } from "expo-router";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import type React from "react";
|
||||
import {
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AppState, type AppStateStatus } from "react-native";
|
||||
@@ -76,9 +76,6 @@ const calculateSpeed = (
|
||||
|
||||
export const processesAtom = atom<JobStatus[]>([]);
|
||||
const DOWNLOADS_DATABASE_KEY = "downloads.v2.json";
|
||||
function onAppStateChange(status: AppStateStatus) {
|
||||
focusManager.setFocused(status === "active");
|
||||
}
|
||||
|
||||
const DownloadContext = createContext<ReturnType<
|
||||
typeof useDownloadProvider
|
||||
@@ -108,12 +105,11 @@ function useDownloadProvider() {
|
||||
});
|
||||
|
||||
const currentProcesses = [...processes, ...missingProcesses];
|
||||
|
||||
const updatedProcesses = currentProcesses.map((p) => {
|
||||
// fallback. Doesn't really work for transcodes as they may be a lot smaller.
|
||||
// We make an wild guess by comparing bitrates
|
||||
const task = tasks.find((s) => s.id === p.id);
|
||||
if (task) {
|
||||
if (task && p.status === "downloading") {
|
||||
const estimatedSize = calculateEstimatedSize(p);
|
||||
let progress = p.progress;
|
||||
if (estimatedSize > 0) {
|
||||
@@ -168,9 +164,9 @@ function useDownloadProvider() {
|
||||
prev.map((p) =>
|
||||
p.id === processId
|
||||
? {
|
||||
...p,
|
||||
...newStatus,
|
||||
}
|
||||
...p,
|
||||
...newStatus,
|
||||
}
|
||||
: p,
|
||||
),
|
||||
);
|
||||
@@ -205,18 +201,9 @@ function useDownloadProvider() {
|
||||
networkMode: "always",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = AppState.addEventListener("change", onAppStateChange);
|
||||
|
||||
return () => subscription.remove();
|
||||
}, []);
|
||||
|
||||
const removeProcess = useCallback(
|
||||
async (id: string) => {
|
||||
setProcesses((prev) => prev.filter((process) => process.id !== id));
|
||||
},
|
||||
[setProcesses],
|
||||
);
|
||||
const removeProcess = useCallback((id: string) => {
|
||||
setProcesses((prev) => prev.filter((process) => process.id !== id));
|
||||
}, [setProcesses]);
|
||||
|
||||
const APP_CACHE_DOWNLOAD_DIRECTORY = `${FileSystem.cacheDirectory}${Application.applicationId}/Downloads/`;
|
||||
|
||||
@@ -309,7 +296,7 @@ function useDownloadProvider() {
|
||||
});
|
||||
|
||||
BackGroundDownloader?.setConfig({
|
||||
isLogsEnabled: true,
|
||||
isLogsEnabled: false,
|
||||
progressInterval: 500,
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
@@ -331,11 +318,10 @@ function useDownloadProvider() {
|
||||
})
|
||||
.progress((data) => {
|
||||
const percent = (data.bytesDownloaded / data.bytesTotal) * 100;
|
||||
console.log("Progress:", percent);
|
||||
updateProcess(process.id, {
|
||||
speed: undefined,
|
||||
status: "downloading",
|
||||
progress: 50,
|
||||
progress: percent,
|
||||
});
|
||||
})
|
||||
.done(async () => {
|
||||
@@ -409,11 +395,19 @@ function useDownloadProvider() {
|
||||
);
|
||||
BackGroundDownloader.completeHandler(process.id);
|
||||
removeProcess(process.id);
|
||||
const itemName =
|
||||
process.item.Type === "Episode" &&
|
||||
process.item.SeriesName &&
|
||||
process.item.ParentIndexNumber != null &&
|
||||
process.item.IndexNumber != null
|
||||
? `${process.item.SeriesName} - S${String(process.item.ParentIndexNumber).padStart(2, "0")}E${String(process.item.IndexNumber).padStart(2, "0")} - ${process.item.Name}`
|
||||
: process.item.Name;
|
||||
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: t("home.downloads.toasts.download_completed"),
|
||||
body: t("home.downloads.toasts.download_completed_for_item", {
|
||||
item: process.item.Name,
|
||||
item: itemName,
|
||||
}),
|
||||
data: {
|
||||
url: `/items/${process.item.Id}`,
|
||||
@@ -436,10 +430,10 @@ function useDownloadProvider() {
|
||||
);
|
||||
|
||||
const manageDownloadQueue = useCallback(() => {
|
||||
const activeDownloads = processes.filter(
|
||||
(p) => p.status === "downloading",
|
||||
).length;
|
||||
const activeDownloads = processes.filter((p) => p.status === "downloading").length;
|
||||
const concurrentLimit = settings?.remuxConcurrentLimit || 1;
|
||||
console.log("processes", processes.map((p) => p.status));
|
||||
|
||||
if (activeDownloads < concurrentLimit) {
|
||||
const queuedDownload = processes.find((p) => p.status === "queued");
|
||||
if (queuedDownload) {
|
||||
@@ -582,7 +576,7 @@ function useDownloadProvider() {
|
||||
const deleteItems = async (items: BaseItemDto[]) => {
|
||||
for (const item of items) {
|
||||
if (item.Id && (item.Type === "Movie" || item.Type === "Episode")) {
|
||||
deleteFile(item.Id, item.Type);
|
||||
await deleteFile(item.Id, item.Type);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -671,7 +665,6 @@ function useDownloadProvider() {
|
||||
deleteFile,
|
||||
deleteItems,
|
||||
removeProcess,
|
||||
setProcesses,
|
||||
startDownload,
|
||||
deleteFileByType,
|
||||
getDownloadedItemSize,
|
||||
|
||||
@@ -408,6 +408,7 @@
|
||||
"download_episode": "Download Episode",
|
||||
"download_movie": "Download Movie",
|
||||
"download_x_item": "Download {{item_count}} items",
|
||||
"download_unwatched_only": "Unwatched Only",
|
||||
"download_button": "Download",
|
||||
"using_optimized_server": "Using optimized server",
|
||||
"using_default_method": "Using default method"
|
||||
|
||||
Reference in New Issue
Block a user