新增下载任务数设置
This commit is contained in:
@@ -124,6 +124,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false);
|
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false);
|
||||||
const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false);
|
const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false);
|
||||||
const [exactSearch, setExactSearch] = useState(true);
|
const [exactSearch, setExactSearch] = useState(true);
|
||||||
|
const [maxConcurrentDownloads, setMaxConcurrentDownloads] = useState(6);
|
||||||
|
|
||||||
// 邮件通知设置
|
// 邮件通知设置
|
||||||
const [userEmail, setUserEmail] = useState('');
|
const [userEmail, setUserEmail] = useState('');
|
||||||
@@ -479,6 +480,12 @@ export const UserMenu: React.FC = () => {
|
|||||||
if (savedExactSearch !== null) {
|
if (savedExactSearch !== null) {
|
||||||
setExactSearch(savedExactSearch === 'true');
|
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) => {
|
const handleFluidSearchToggle = (value: boolean) => {
|
||||||
setFluidSearch(value);
|
setFluidSearch(value);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -1937,6 +1951,69 @@ export const UserMenu: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 分割线 */}
|
||||||
|
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||||
|
|
||||||
|
{/* 最大同时下载限制 */}
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<div>
|
||||||
|
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
|
最大同时下载限制
|
||||||
|
</h4>
|
||||||
|
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||||
|
控制播放页面下载时的同时下载数量
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<span className='text-xs text-gray-600 dark:text-gray-400'>
|
||||||
|
同时下载数量
|
||||||
|
</span>
|
||||||
|
<span className='text-xs font-medium text-gray-700 dark:text-gray-300'>
|
||||||
|
{maxConcurrentDownloads}个
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<input
|
||||||
|
type='range'
|
||||||
|
min='1'
|
||||||
|
max='10'
|
||||||
|
step='1'
|
||||||
|
value={maxConcurrentDownloads}
|
||||||
|
onChange={(e) => 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%)`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-between text-xs text-gray-500 dark:text-gray-400'>
|
||||||
|
<button
|
||||||
|
onClick={() => handleMaxConcurrentDownloadsChange(1)}
|
||||||
|
className={`px-2 py-0.5 rounded ${maxConcurrentDownloads === 1 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
1个
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleMaxConcurrentDownloadsChange(3)}
|
||||||
|
className={`px-2 py-0.5 rounded ${maxConcurrentDownloads === 3 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
3个
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleMaxConcurrentDownloadsChange(6)}
|
||||||
|
className={`px-2 py-0.5 rounded ${maxConcurrentDownloads === 6 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
6个
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleMaxConcurrentDownloadsChange(10)}
|
||||||
|
className={`px-2 py-0.5 rounded ${maxConcurrentDownloads === 10 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
10个
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,28 +21,85 @@ interface DownloadContextType {
|
|||||||
const DownloadContext = createContext<DownloadContextType | undefined>(undefined);
|
const DownloadContext = createContext<DownloadContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function DownloadProvider({ children }: { children: React.ReactNode }) {
|
export function DownloadProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [tasks, setTasks] = useState<M3U8DownloadTask[]>([]);
|
||||||
|
const [showDownloadPanel, setShowDownloadPanel] = useState(false);
|
||||||
|
const [startingTaskIds, setStartingTaskIds] = useState<Set<string>>(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({
|
const [downloader] = useState(() => new M3U8Downloader({
|
||||||
onProgress: (task) => {
|
onProgress: (task) => {
|
||||||
setTasks(downloader.getAllTasks());
|
setTasks(downloader.getAllTasks());
|
||||||
},
|
},
|
||||||
onComplete: (task) => {
|
onComplete: (task) => {
|
||||||
setTasks(downloader.getAllTasks());
|
setTasks(downloader.getAllTasks());
|
||||||
|
// 任务完成后,尝试启动下一个等待的任务
|
||||||
|
startNextPendingTask(downloader);
|
||||||
},
|
},
|
||||||
onError: (task, error) => {
|
onError: (task, error) => {
|
||||||
console.error('下载错误:', error);
|
console.error('下载错误:', error);
|
||||||
setTasks(downloader.getAllTasks());
|
setTasks(downloader.getAllTasks());
|
||||||
|
// 任务出错后,尝试启动下一个等待的任务
|
||||||
|
startNextPendingTask(downloader);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [tasks, setTasks] = useState<M3U8DownloadTask[]>([]);
|
|
||||||
const [showDownloadPanel, setShowDownloadPanel] = useState(false);
|
|
||||||
|
|
||||||
const addDownloadTask = useCallback(async (url: string, title: string, type: 'TS' | 'MP4' = 'TS') => {
|
const addDownloadTask = useCallback(async (url: string, title: string, type: 'TS' | 'MP4' = 'TS') => {
|
||||||
try {
|
try {
|
||||||
const taskId = await downloader.createTask(url, title, type);
|
const taskId = await downloader.createTask(url, title, type);
|
||||||
setTasks(downloader.getAllTasks());
|
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) {
|
} catch (error) {
|
||||||
console.error('添加下载任务失败:', error);
|
console.error('添加下载任务失败:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -50,19 +107,33 @@ export function DownloadProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}, [downloader]);
|
}, [downloader]);
|
||||||
|
|
||||||
const startTask = useCallback((taskId: string) => {
|
const startTask = useCallback((taskId: string) => {
|
||||||
downloader.startTask(taskId);
|
// 从localStorage读取最大同时下载限制,默认6个
|
||||||
setTasks(downloader.getAllTasks());
|
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]);
|
}, [downloader]);
|
||||||
|
|
||||||
const pauseTask = useCallback((taskId: string) => {
|
const pauseTask = useCallback((taskId: string) => {
|
||||||
downloader.pauseTask(taskId);
|
downloader.pauseTask(taskId);
|
||||||
setTasks(downloader.getAllTasks());
|
setTasks(downloader.getAllTasks());
|
||||||
}, [downloader]);
|
// 暂停任务后,尝试启动下一个等待的任务
|
||||||
|
startNextPendingTask(downloader);
|
||||||
|
}, [downloader, startNextPendingTask]);
|
||||||
|
|
||||||
const cancelTask = useCallback((taskId: string) => {
|
const cancelTask = useCallback((taskId: string) => {
|
||||||
downloader.cancelTask(taskId);
|
downloader.cancelTask(taskId);
|
||||||
setTasks(downloader.getAllTasks());
|
setTasks(downloader.getAllTasks());
|
||||||
}, [downloader]);
|
// 取消任务后,尝试启动下一个等待的任务
|
||||||
|
startNextPendingTask(downloader);
|
||||||
|
}, [downloader, startNextPendingTask]);
|
||||||
|
|
||||||
const retryFailedSegments = useCallback((taskId: string) => {
|
const retryFailedSegments = useCallback((taskId: string) => {
|
||||||
downloader.retryFailedSegments(taskId);
|
downloader.retryFailedSegments(taskId);
|
||||||
|
|||||||
Reference in New Issue
Block a user