From 258b07780e9232eff0b8146da70816910b26ad9d Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sat, 14 Feb 2026 23:50:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=95=B0=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserMenu.tsx | 77 +++++++++++++++++++++++++++ src/contexts/DownloadContext.tsx | 89 ++++++++++++++++++++++++++++---- 2 files changed, 157 insertions(+), 9 deletions(-) diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index d1fe92b..c576caf 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -124,6 +124,7 @@ export const UserMenu: React.FC = () => { const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false); const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false); const [exactSearch, setExactSearch] = useState(true); + const [maxConcurrentDownloads, setMaxConcurrentDownloads] = useState(6); // 邮件通知设置 const [userEmail, setUserEmail] = useState(''); @@ -479,6 +480,12 @@ export const UserMenu: React.FC = () => { if (savedExactSearch !== null) { setExactSearch(savedExactSearch === 'true'); } + + // 加载最大同时下载限制设置 + const savedMaxConcurrentDownloads = localStorage.getItem('maxConcurrentDownloads'); + if (savedMaxConcurrentDownloads !== null) { + setMaxConcurrentDownloads(Number(savedMaxConcurrentDownloads)); + } } }, []); @@ -820,6 +827,13 @@ export const UserMenu: React.FC = () => { } }; + const handleMaxConcurrentDownloadsChange = (value: number) => { + setMaxConcurrentDownloads(value); + if (typeof window !== 'undefined') { + localStorage.setItem('maxConcurrentDownloads', String(value)); + } + }; + const handleFluidSearchToggle = (value: boolean) => { setFluidSearch(value); if (typeof window !== 'undefined') { @@ -1937,6 +1951,69 @@ export const UserMenu: React.FC = () => { + + {/* 分割线 */} +
+ + {/* 最大同时下载限制 */} +
+
+

+ 最大同时下载限制 +

+

+ 控制播放页面下载时的同时下载数量 +

+
+
+ + 同时下载数量 + + + {maxConcurrentDownloads}个 + +
+
+ handleMaxConcurrentDownloadsChange(Number(e.target.value))} + className='flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700' + style={{ + background: `linear-gradient(to right, #10b981 0%, #10b981 ${((maxConcurrentDownloads - 1) / (10 - 1)) * 100}%, #e5e7eb ${((maxConcurrentDownloads - 1) / (10 - 1)) * 100}%, #e5e7eb 100%)` + }} + /> +
+
+ + + + +
+
)} diff --git a/src/contexts/DownloadContext.tsx b/src/contexts/DownloadContext.tsx index b0bb1ab..93d8db4 100644 --- a/src/contexts/DownloadContext.tsx +++ b/src/contexts/DownloadContext.tsx @@ -21,28 +21,85 @@ interface DownloadContextType { const DownloadContext = createContext(undefined); export function DownloadProvider({ children }: { children: React.ReactNode }) { + const [tasks, setTasks] = useState([]); + const [showDownloadPanel, setShowDownloadPanel] = useState(false); + const [startingTaskIds, setStartingTaskIds] = useState>(new Set()); + + // 自动启动下一个等待的任务 + const startNextPendingTask = useCallback((currentDownloader: M3U8Downloader) => { + // 从localStorage读取最大同时下载限制,默认6个 + const maxConcurrentDownloads = typeof window !== 'undefined' + ? Number(localStorage.getItem('maxConcurrentDownloads') || 6) + : 6; + + const allTasks = currentDownloader.getAllTasks(); + const downloadingCount = allTasks.filter(t => t.status === 'downloading').length; + + // 如果当前下载数量小于限制,启动下一个ready任务 + if (downloadingCount < maxConcurrentDownloads) { + const readyTask = allTasks.find(t => t.status === 'ready'); + if (readyTask) { + currentDownloader.startTask(readyTask.id); + setTasks(currentDownloader.getAllTasks()); + } + } + }, []); + const [downloader] = useState(() => new M3U8Downloader({ onProgress: (task) => { setTasks(downloader.getAllTasks()); }, onComplete: (task) => { setTasks(downloader.getAllTasks()); + // 任务完成后,尝试启动下一个等待的任务 + startNextPendingTask(downloader); }, onError: (task, error) => { console.error('下载错误:', error); setTasks(downloader.getAllTasks()); + // 任务出错后,尝试启动下一个等待的任务 + startNextPendingTask(downloader); }, })); - const [tasks, setTasks] = useState([]); - const [showDownloadPanel, setShowDownloadPanel] = useState(false); - const addDownloadTask = useCallback(async (url: string, title: string, type: 'TS' | 'MP4' = 'TS') => { try { const taskId = await downloader.createTask(url, title, type); setTasks(downloader.getAllTasks()); - await downloader.startTask(taskId); - setTasks(downloader.getAllTasks()); + + // 从localStorage读取最大同时下载限制,默认6个 + const maxConcurrentDownloads = typeof window !== 'undefined' + ? Number(localStorage.getItem('maxConcurrentDownloads') || 6) + : 6; + + // 检查当前正在下载的任务数量(包括正在启动的) + setStartingTaskIds(prev => { + const allTasks = downloader.getAllTasks(); + const currentDownloadingCount = allTasks.filter(t => t.status === 'downloading').length; + const totalActiveCount = currentDownloadingCount + prev.size; + + // 如果未超过限制,标记为正在启动并启动任务 + if (totalActiveCount < maxConcurrentDownloads) { + const newSet = new Set(prev); + newSet.add(taskId); + + // 异步启动任务 + downloader.startTask(taskId).then(() => { + setTasks(downloader.getAllTasks()); + // 启动完成后,从正在启动列表中移除 + setStartingTaskIds(current => { + const updated = new Set(current); + updated.delete(taskId); + return updated; + }); + }); + + return newSet; + } + + // 否则任务保持ready状态,等待其他任务完成后自动启动 + return prev; + }); } catch (error) { console.error('添加下载任务失败:', error); throw error; @@ -50,19 +107,33 @@ export function DownloadProvider({ children }: { children: React.ReactNode }) { }, [downloader]); const startTask = useCallback((taskId: string) => { - downloader.startTask(taskId); - setTasks(downloader.getAllTasks()); + // 从localStorage读取最大同时下载限制,默认6个 + const maxConcurrentDownloads = typeof window !== 'undefined' + ? Number(localStorage.getItem('maxConcurrentDownloads') || 6) + : 6; + + const currentDownloadingCount = downloader.getAllTasks().filter(t => t.status === 'downloading').length; + + // 如果未超过限制,启动任务 + if (currentDownloadingCount < maxConcurrentDownloads) { + downloader.startTask(taskId); + setTasks(downloader.getAllTasks()); + } }, [downloader]); const pauseTask = useCallback((taskId: string) => { downloader.pauseTask(taskId); setTasks(downloader.getAllTasks()); - }, [downloader]); + // 暂停任务后,尝试启动下一个等待的任务 + startNextPendingTask(downloader); + }, [downloader, startNextPendingTask]); const cancelTask = useCallback((taskId: string) => { downloader.cancelTask(taskId); setTasks(downloader.getAllTasks()); - }, [downloader]); + // 取消任务后,尝试启动下一个等待的任务 + startNextPendingTask(downloader); + }, [downloader, startNextPendingTask]); const retryFailedSegments = useCallback((taskId: string) => { downloader.retryFailedSegments(taskId);