支持选集下载

This commit is contained in:
mtvpls
2025-12-11 22:03:30 +08:00
parent f0e7a813c6
commit 4ff7c1caf3
2 changed files with 384 additions and 40 deletions
+87 -40
View File
@@ -40,6 +40,7 @@ import { SearchResult, DanmakuFilterConfig } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import EpisodeSelector from '@/components/EpisodeSelector';
import DownloadEpisodeSelector from '@/components/DownloadEpisodeSelector';
import PageLayout from '@/components/PageLayout';
import DoubanComments from '@/components/DoubanComments';
import DanmakuFilterSettings from '@/components/DanmakuFilterSettings';
@@ -500,6 +501,9 @@ function PlayPageClient() {
const [isEpisodeSelectorCollapsed, setIsEpisodeSelectorCollapsed] =
useState(false);
// 下载选集面板显示状态
const [showDownloadSelector, setShowDownloadSelector] = useState(false);
// 换源加载状态
const [isVideoLoading, setIsVideoLoading] = useState(true);
const [videoLoadingStage, setVideoLoadingStage] = useState<
@@ -754,6 +758,76 @@ function PlayPageClient() {
}
};
// 处理下载指定集数(支持批量下载)
const handleDownloadEpisode = async (episodeIndexes: number[]) => {
if (!detail || !detail.episodes || episodeIndexes.length === 0) {
if (artPlayerRef.current) {
artPlayerRef.current.notice.show = '无法获取视频地址';
}
return;
}
const tokenParam = proxyToken ? `&token=${encodeURIComponent(proxyToken)}` : '';
const origin = `${window.location.protocol}//${window.location.host}`;
let successCount = 0;
let failCount = 0;
// 批量处理下载
for (const episodeIndex of episodeIndexes) {
if (episodeIndex >= detail.episodes.length) {
failCount++;
continue;
}
const episodeUrl = detail.episodes[episodeIndex];
const proxyUrl = externalPlayerAdBlock
? `${origin}/api/proxy-m3u8?url=${encodeURIComponent(episodeUrl)}&source=${encodeURIComponent(currentSource)}${tokenParam}`
: episodeUrl;
const isM3u8 = episodeUrl.toLowerCase().includes('.m3u8') || episodeUrl.toLowerCase().includes('/m3u8/');
if (isM3u8) {
// M3U8格式 - 使用新的下载器,TS 格式
try {
const downloadTitle = `${videoTitle}_第${episodeIndex + 1}`;
await addDownloadTask(proxyUrl, downloadTitle, 'TS');
successCount++;
} catch (error) {
console.error(`添加下载任务失败 (第${episodeIndex + 1}集):`, error);
failCount++;
}
} else {
// 普通视频格式 - 直接下载
try {
const a = document.createElement('a');
a.href = proxyUrl;
a.download = `${videoTitle}_第${episodeIndex + 1}集.mp4`;
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
successCount++;
// 添加延迟避免浏览器阻止多个下载
await new Promise(resolve => setTimeout(resolve, 300));
} catch (error) {
console.error(`下载失败 (第${episodeIndex + 1}集):`, error);
failCount++;
}
}
}
// 显示结果通知
if (artPlayerRef.current) {
if (failCount === 0) {
artPlayerRef.current.notice.show = `已添加 ${successCount} 个下载任务!`;
} else if (successCount === 0) {
artPlayerRef.current.notice.show = '下载失败,请重试';
} else {
artPlayerRef.current.notice.show = `成功 ${successCount} 个,失败 ${failCount}`;
}
}
};
const ensureVideoSource = (video: HTMLVideoElement | null, url: string) => {
if (!video || !url) return;
const sources = Array.from(video.getElementsByTagName('source'));
@@ -3646,47 +3720,9 @@ function PlayPageClient() {
<div className='flex gap-1.5 lg:flex-wrap'>
{/* 下载按钮 */}
<button
onClick={async (e) => {
onClick={(e) => {
e.preventDefault();
// 获取正确的代理 URL - 使用浏览器实际访问的地址
const tokenParam = proxyToken ? `&token=${encodeURIComponent(proxyToken)}` : '';
const origin = `${window.location.protocol}//${window.location.host}`;
console.log('下载按钮 - origin:', origin);
console.log('下载按钮 - window.location:', window.location.href);
const proxyUrl = externalPlayerAdBlock
? `${origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}${tokenParam}`
: videoUrl;
console.log('下载按钮 - proxyUrl:', proxyUrl);
const isM3u8 = videoUrl.toLowerCase().includes('.m3u8') || videoUrl.toLowerCase().includes('/m3u8/');
if (isM3u8) {
// M3U8格式 - 使用新的下载器,TS 格式
try {
const downloadTitle = `${videoTitle}_第${currentEpisodeIndex + 1}`;
await addDownloadTask(proxyUrl, downloadTitle, 'TS');
if (artPlayerRef.current) {
artPlayerRef.current.notice.show = '已添加到下载队列!';
}
} catch (error) {
console.error('添加下载任务失败:', error);
if (artPlayerRef.current) {
artPlayerRef.current.notice.show = '添加下载失败,请重试';
}
}
} else {
// 普通视频格式 - 直接下载
const a = document.createElement('a');
a.href = proxyUrl;
a.download = `${videoTitle}_第${currentEpisodeIndex + 1}集.mp4`;
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
if (artPlayerRef.current) {
artPlayerRef.current.notice.show = '开始下载...';
}
}
setShowDownloadSelector(true);
}}
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-green-400 flex-shrink-0'
title='下载视频'
@@ -4126,6 +4162,17 @@ function PlayPageClient() {
{/* Toast通知 */}
{toast && <Toast {...toast} />}
{/* 下载选集面板 */}
<DownloadEpisodeSelector
isOpen={showDownloadSelector}
onClose={() => setShowDownloadSelector(false)}
totalEpisodes={totalEpisodes}
episodesTitles={detail?.episodes_titles || []}
videoTitle={videoTitle}
currentEpisodeIndex={currentEpisodeIndex}
onDownload={handleDownloadEpisode}
/>
{/* 弹幕过滤设置对话框 */}
<DanmakuFilterSettings
isOpen={showDanmakuFilterSettings}