移动云盘在线播放

This commit is contained in:
mtvpls
2026-04-26 17:41:56 +08:00
parent d8d7e57eab
commit c877397682
16 changed files with 984 additions and 104 deletions
+57 -39
View File
@@ -8,8 +8,8 @@ import { Suspense, useEffect, useMemo, useRef, useState } from 'react';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import {
convertDanmakuFormat,
clearDanmakuCacheByTitle,
convertDanmakuFormat,
getDanmakuById,
getDanmakuFromCache,
getEpisodes,
@@ -54,12 +54,13 @@ import {
pruneLocalEpisodeProgressStorage,
saveLocalEpisodeProgress,
} from '@/lib/episode-progress';
import { getTMDBImageUrl } from '@/lib/tmdb.search';
import { isNetdiskSource, normalizeNetdiskSource } from '@/lib/netdisk/source';
import {
getRecommendationCache,
recommendationCacheKeys,
setRecommendationCache,
} from '@/lib/recommendations/cache';
import { getTMDBImageUrl } from '@/lib/tmdb.search';
import { DanmakuFilterConfig, EpisodeFilterConfig, SearchResult } from '@/lib/types';
import { base58Decode, getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import { useEnableAIComments } from '@/hooks/useEnableAIComments';
@@ -605,27 +606,28 @@ function PlayPageClient() {
// 纠错后的描述信息(用于显示,不触发 detail 更新)
const [correctedDesc, setCorrectedDesc] = useState<string>('');
const [quarkTempTMDBMeta, setQuarkTempTMDBMeta] = useState<{
const [netdiskTMDBMeta, setNetdiskTMDBMeta] = useState<{
desc?: string;
poster?: string;
year?: string;
tmdbId?: number;
} | null>(null);
const [pendingQuarkTempTMDBData, setPendingQuarkTempTMDBData] = useState<any | null>(null);
const [pendingNetdiskTMDBData, setPendingNetdiskTMDBData] = useState<any | null>(null);
// 当前源和ID - source 直接存储完整格式(如 'emby_wumei' 或 'emby'
const [currentSource, setCurrentSource] = useState(searchParams.get('source') || '');
const [currentSource, setCurrentSource] = useState(normalizeNetdiskSource(searchParams.get('source')) || '');
const [currentId, setCurrentId] = useState(searchParams.get('id') || '');
const [fileName] = useState(searchParams.get('fileName') || ''); // 小雅源:用户点击的文件名
const isDirectPlay = currentSource === 'directplay';
useEffect(() => {
setQuarkTempTMDBMeta(null);
setPendingQuarkTempTMDBData(null);
setNetdiskTMDBMeta(null);
setPendingNetdiskTMDBData(null);
}, [currentSource, currentId]);
// 解析 source 参数以获取 embyKey(仅用于 API 调用)
const parseSourceForApi = (source: string): { source: string; embyKey?: string } => {
source = normalizeNetdiskSource(source);
if (source.startsWith('emby_')) {
const key = source.substring(5);
return { source: 'emby', embyKey: key };
@@ -1300,19 +1302,22 @@ function PlayPageClient() {
const populatePlayMetadataFromTMDB = (tmdbData: any) => {
const currentDetail = detailRef.current;
if (!currentDetail || currentDetail.source !== 'quark-temp') {
setPendingQuarkTempTMDBData(tmdbData);
if (!currentDetail || !isNetdiskSource(currentDetail.source)) {
setPendingNetdiskTMDBData(tmdbData);
return;
}
const tmdbYear = tmdbData.releaseDate?.split('-')[0] || '';
const shouldReplaceDesc = !currentDetail.desc || currentDetail.desc.startsWith('临时播放目录:');
const shouldReplaceDesc =
!currentDetail.desc ||
currentDetail.desc.startsWith('临时播放目录:') ||
currentDetail.desc.startsWith('移动云盘分享:');
const resolvedTmdbId = typeof tmdbData.tmdbId === 'string'
? Number(String(tmdbData.tmdbId).split(':')[1] || 0)
: tmdbData.tmdbId;
setQuarkTempTMDBMeta({
setNetdiskTMDBMeta({
desc: shouldReplaceDesc ? (tmdbData.overview || currentDetail.desc) : currentDetail.desc,
poster: currentDetail.poster || tmdbData.poster || '',
year: currentDetail.year || tmdbYear,
@@ -1320,7 +1325,7 @@ function PlayPageClient() {
});
setDetail((prev) => {
if (!prev || prev.source !== 'quark-temp') {
if (!prev || !isNetdiskSource(prev.source)) {
return prev;
}
@@ -1381,25 +1386,32 @@ function PlayPageClient() {
useEffect(() => {
if (
pendingQuarkTempTMDBData &&
detail?.source === 'quark-temp'
pendingNetdiskTMDBData &&
isNetdiskSource(detail?.source)
) {
const pending = pendingQuarkTempTMDBData;
setPendingQuarkTempTMDBData(null);
const currentDetail = detail;
if (!currentDetail) {
return;
}
const pending = pendingNetdiskTMDBData;
setPendingNetdiskTMDBData(null);
const tmdbYear = pending.releaseDate?.split('-')[0] || '';
const shouldReplaceDesc = !detail.desc || detail.desc.startsWith('临时播放目录:');
const shouldReplaceDesc =
!currentDetail.desc ||
currentDetail.desc.startsWith('临时播放目录:') ||
currentDetail.desc.startsWith('移动云盘分享:');
const resolvedTmdbId = typeof pending.tmdbId === 'string'
? Number(String(pending.tmdbId).split(':')[1] || 0)
: pending.tmdbId;
setQuarkTempTMDBMeta({
desc: shouldReplaceDesc ? (pending.overview || detail.desc) : detail.desc,
poster: detail.poster || pending.poster || '',
year: detail.year || tmdbYear,
tmdbId: detail.tmdb_id || resolvedTmdbId,
setNetdiskTMDBMeta({
desc: shouldReplaceDesc ? (pending.overview || currentDetail.desc) : currentDetail.desc,
poster: currentDetail.poster || pending.poster || '',
year: currentDetail.year || tmdbYear,
tmdbId: currentDetail.tmdb_id || resolvedTmdbId,
});
setDetail((prev) => prev && prev.source === 'quark-temp' ? {
setDetail((prev) => prev && isNetdiskSource(prev.source) ? {
...prev,
poster: prev.poster || pending.poster || '',
year: prev.year || tmdbYear,
@@ -1407,17 +1419,17 @@ function PlayPageClient() {
tmdb_id: prev.tmdb_id || resolvedTmdbId,
} : prev);
if (pending.poster && !detail.poster) {
if (pending.poster && !currentDetail.poster) {
setVideoCover(processImageUrl(pending.poster));
}
if (tmdbYear && !detail.year) {
if (tmdbYear && !currentDetail.year) {
setVideoYear(tmdbYear);
}
if (pending.overview) {
setCorrectedDesc(pending.overview);
}
}
}, [pendingQuarkTempTMDBData, detail]);
}, [pendingNetdiskTMDBData, detail]);
// 视频播放地址
const [videoUrl, setVideoUrl] = useState('');
@@ -1535,7 +1547,7 @@ function PlayPageClient() {
!isM3u8LikeUrl(videoUrl) &&
(
detail.source === 'openlist' ||
detail.source === 'quark-temp' ||
isNetdiskSource(detail.source) ||
detail.source === 'xiaoya' ||
detail.source.startsWith('emby')
)
@@ -4165,7 +4177,7 @@ function PlayPageClient() {
// 监听 URL 参数变化,处理换源和换视频(用于房员跟随房主操作)
useEffect(() => {
const urlSource = searchParams.get('source');
const urlSource = normalizeNetdiskSource(searchParams.get('source'));
const urlId = searchParams.get('id');
// 只在URL参数存在且与当前状态不同时才处理
@@ -9639,12 +9651,12 @@ function PlayPageClient() {
</span>
)}
{/* 优先使用 doubanYear,如果没有则使用 detail.year 或 videoYear */}
{(doubanYear || quarkTempTMDBMeta?.year || detail?.year || videoYear) && (
<span>{doubanYear || quarkTempTMDBMeta?.year || detail?.year || videoYear}</span>
{(doubanYear || netdiskTMDBMeta?.year || detail?.year || videoYear) && (
<span>{doubanYear || netdiskTMDBMeta?.year || detail?.year || videoYear}</span>
)}
{detail?.source_name && (
<span
className={`relative group cursor-pointer border px-2 py-[1px] rounded ${detail.source === 'xiaoya' ? 'border-blue-500' : detail.source === 'quark-temp' ? 'border-purple-500' : detail.source === 'openlist' || detail.source === 'emby' || detail.source?.startsWith('emby_') ? 'border-yellow-500' : 'border-gray-500/60'
className={`relative group cursor-pointer border px-2 py-[1px] rounded ${detail.source === 'xiaoya' ? 'border-blue-500' : isNetdiskSource(detail.source) ? 'border-purple-500' : detail.source === 'openlist' || detail.source === 'emby' || detail.source?.startsWith('emby_') ? 'border-yellow-500' : 'border-gray-500/60'
}`}
onClick={fetchCurrentSourceVideoInfo}
>
@@ -9664,7 +9676,7 @@ function PlayPageClient() {
{detail?.type_name && <span>{detail.type_name}</span>}
</div>
{/* 剧情简介 */}
{(doubanCardSubtitle || quarkTempTMDBMeta?.desc || correctedDesc || detail?.desc) && (
{(doubanCardSubtitle || netdiskTMDBMeta?.desc || correctedDesc || detail?.desc) && (
<div
className={`mt-0 text-base leading-relaxed opacity-90 overflow-y-auto pr-2 flex-1 min-h-0 scrollbar-hide ${tmdbBackdrop ? 'text-white' : ''}`}
style={{ whiteSpace: 'pre-line' }}
@@ -9675,7 +9687,7 @@ function PlayPageClient() {
{doubanCardSubtitle}
</div>
)}
{quarkTempTMDBMeta?.desc || correctedDesc || detail?.desc}
{netdiskTMDBMeta?.desc || correctedDesc || detail?.desc}
</div>
)}
</div>
@@ -9720,9 +9732,15 @@ function PlayPageClient() {
)}
</>
) : (
<span className='text-gray-600 dark:text-gray-400'>
</span>
isNetdiskSource(detail?.source) ? (
<div className='flex flex-col items-center justify-center text-gray-500 dark:text-gray-400'>
<Cloud className='w-16 h-16 opacity-80' />
</div>
) : (
<span className='text-gray-600 dark:text-gray-400'>
</span>
)
)}
</div>
</div>
@@ -9929,7 +9947,7 @@ function PlayPageClient() {
// 特殊源使用 tmdb,其他使用 cms(通过 doubanId
// 如果有豆瓣ID且不为0,传入doubanId
detail.source === 'openlist' ||
detail.source === 'quark-temp' ||
isNetdiskSource(detail.source) ||
detail.source?.startsWith('emby') ||
detail.source === 'xiaoya'
? undefined
@@ -9940,7 +9958,7 @@ function PlayPageClient() {
tmdbId={
// 特殊源使用 tmdb
detail.source === 'openlist' ||
detail.source === 'quark-temp' ||
isNetdiskSource(detail.source) ||
detail.source?.startsWith('emby') ||
detail.source === 'xiaoya'
? detail.tmdb_id
@@ -9952,7 +9970,7 @@ function PlayPageClient() {
// 非特殊源使用 cms 数据
// 但如果有豆瓣ID且不为0,则不传入cmsData,优先使用豆瓣数据
detail.source !== 'openlist' &&
detail.source !== 'quark-temp' &&
!isNetdiskSource(detail.source) &&
!detail.source?.startsWith('emby') &&
detail.source !== 'xiaoya' &&
!(detail.douban_id && detail.douban_id !== 0)