增加离线下载功能
This commit is contained in:
+107
-9
@@ -9,6 +9,7 @@ import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { usePlaySync } from '@/hooks/usePlaySync';
|
||||
import { getDoubanDetail } from '@/lib/douban.client';
|
||||
import { useDownload } from '@/contexts/DownloadContext';
|
||||
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||
|
||||
import {
|
||||
deleteFavorite,
|
||||
@@ -71,6 +72,15 @@ function PlayPageClient() {
|
||||
// 获取 Proxy M3U8 Token
|
||||
const proxyToken = typeof window !== 'undefined' ? process.env.NEXT_PUBLIC_PROXY_M3U8_TOKEN || '' : '';
|
||||
|
||||
// 获取用户认证信息
|
||||
const authInfo = typeof window !== 'undefined' ? getAuthInfoFromBrowserCookie() : null;
|
||||
|
||||
// 离线下载功能配置
|
||||
const enableOfflineDownload = typeof window !== 'undefined'
|
||||
? process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true'
|
||||
: false;
|
||||
const hasOfflinePermission = authInfo?.role === 'owner' || authInfo?.role === 'admin';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 状态变量(State)
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -739,8 +749,34 @@ function PlayPageClient() {
|
||||
return Math.round(score * 100) / 100; // 保留两位小数
|
||||
};
|
||||
|
||||
// 检查是否有本地下载的视频
|
||||
const checkLocalDownload = async (
|
||||
source: string,
|
||||
videoId: string,
|
||||
episodeIndex: number
|
||||
): Promise<boolean> => {
|
||||
if (!enableOfflineDownload || !hasOfflinePermission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/offline-download?action=check&source=${encodeURIComponent(source)}&videoId=${encodeURIComponent(videoId)}&episodeIndex=${episodeIndex}`
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data.downloaded || false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查本地下载失败:', error);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 更新视频地址
|
||||
const updateVideoUrl = (
|
||||
const updateVideoUrl = async (
|
||||
detailData: SearchResult | null,
|
||||
episodeIndex: number
|
||||
) => {
|
||||
@@ -752,14 +788,25 @@ function PlayPageClient() {
|
||||
setVideoUrl('');
|
||||
return;
|
||||
}
|
||||
const newUrl = detailData?.episodes[episodeIndex] || '';
|
||||
|
||||
let newUrl = detailData?.episodes[episodeIndex] || '';
|
||||
|
||||
// 检查是否有本地下载的文件
|
||||
const hasLocalFile = await checkLocalDownload(currentSource, currentId, episodeIndex);
|
||||
|
||||
if (hasLocalFile) {
|
||||
// 使用本地代理接口,URL以.m3u8结尾以便Artplayer自动识别
|
||||
newUrl = `/api/offline-download/local/${currentSource}/${currentId}/${episodeIndex}/playlist.m3u8`;
|
||||
console.log('使用本地下载文件播放:', newUrl);
|
||||
}
|
||||
|
||||
if (newUrl !== videoUrl) {
|
||||
setVideoUrl(newUrl);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理下载指定集数(支持批量下载)
|
||||
const handleDownloadEpisode = async (episodeIndexes: number[]) => {
|
||||
const handleDownloadEpisode = async (episodeIndexes: number[], offlineMode = false) => {
|
||||
if (!detail || !detail.episodes || episodeIndexes.length === 0) {
|
||||
if (artPlayerRef.current) {
|
||||
artPlayerRef.current.notice.show = '无法获取视频地址';
|
||||
@@ -781,12 +828,55 @@ function PlayPageClient() {
|
||||
}
|
||||
|
||||
const episodeUrl = detail.episodes[episodeIndex];
|
||||
const proxyUrl = externalPlayerAdBlock
|
||||
? `${origin}/api/proxy-m3u8?url=${encodeURIComponent(episodeUrl)}&source=${encodeURIComponent(currentSource)}${tokenParam}`
|
||||
: episodeUrl;
|
||||
|
||||
// 离线下载模式:无论是否开启去广告,都走非去广告逻辑
|
||||
const proxyUrl = offlineMode
|
||||
? episodeUrl // 离线下载不使用代理,直接使用原始URL
|
||||
: (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) {
|
||||
if (offlineMode && isM3u8) {
|
||||
// 离线下载模式 - 调用服务器API
|
||||
try {
|
||||
const downloadTitle = `${videoTitle}_第${episodeIndex + 1}集`;
|
||||
const response = await fetch('/api/offline-download', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
source: currentSource,
|
||||
videoId: currentId,
|
||||
episodeIndex,
|
||||
title: downloadTitle,
|
||||
m3u8Url: proxyUrl,
|
||||
metadata: detail ? {
|
||||
videoTitle: detail.title,
|
||||
cover: detail.poster,
|
||||
description: detail.desc,
|
||||
year: detail.year,
|
||||
rating: undefined, // SearchResult 没有 rating 字段
|
||||
totalEpisodes: detail.episodes?.length,
|
||||
} : undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
successCount++;
|
||||
} else {
|
||||
console.error(`离线下载任务创建失败 (第${episodeIndex + 1}集):`, data.error);
|
||||
failCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`离线下载任务创建失败 (第${episodeIndex + 1}集):`, error);
|
||||
failCount++;
|
||||
}
|
||||
} else if (isM3u8) {
|
||||
// M3U8格式 - 使用新的下载器,TS 格式
|
||||
try {
|
||||
const downloadTitle = `${videoTitle}_第${episodeIndex + 1}集`;
|
||||
@@ -819,7 +909,9 @@ function PlayPageClient() {
|
||||
// 显示结果通知
|
||||
if (artPlayerRef.current) {
|
||||
if (failCount === 0) {
|
||||
artPlayerRef.current.notice.show = `已添加 ${successCount} 个下载任务!`;
|
||||
artPlayerRef.current.notice.show = offlineMode
|
||||
? `已创建 ${successCount} 个离线下载任务!`
|
||||
: `已添加 ${successCount} 个下载任务!`;
|
||||
} else if (successCount === 0) {
|
||||
artPlayerRef.current.notice.show = '下载失败,请重试';
|
||||
} else {
|
||||
@@ -2692,6 +2784,10 @@ function PlayPageClient() {
|
||||
if (video.hls) {
|
||||
video.hls.destroy();
|
||||
}
|
||||
|
||||
// 每次创建HLS实例时,都读取最新的blockAdEnabled状态
|
||||
const shouldUseCustomLoader = blockAdEnabledRef.current;
|
||||
|
||||
const hls = new Hls({
|
||||
debug: false, // 关闭日志
|
||||
enableWorker: true, // WebWorker 解码,降低主线程压力
|
||||
@@ -2703,7 +2799,7 @@ function PlayPageClient() {
|
||||
maxBufferSize: 60 * 1000 * 1000, // 约 60MB,超出后触发清理
|
||||
|
||||
/* 自定义loader */
|
||||
loader: (blockAdEnabledRef.current
|
||||
loader: (shouldUseCustomLoader
|
||||
? CustomHlsJsLoader
|
||||
: Hls.DefaultConfig.loader) as any,
|
||||
});
|
||||
@@ -4171,6 +4267,8 @@ function PlayPageClient() {
|
||||
videoTitle={videoTitle}
|
||||
currentEpisodeIndex={currentEpisodeIndex}
|
||||
onDownload={handleDownloadEpisode}
|
||||
enableOfflineDownload={enableOfflineDownload}
|
||||
hasOfflinePermission={hasOfflinePermission}
|
||||
/>
|
||||
|
||||
{/* 弹幕过滤设置对话框 */}
|
||||
|
||||
Reference in New Issue
Block a user