转存播放补充元信息

This commit is contained in:
mtvpls
2026-04-07 21:17:18 +08:00
parent 4b62d7d7af
commit 80ae3e8c21
3 changed files with 145 additions and 42 deletions
+142 -39
View File
@@ -572,6 +572,13 @@ function PlayPageClient() {
// 纠错后的描述信息(用于显示,不触发 detail 更新) // 纠错后的描述信息(用于显示,不触发 detail 更新)
const [correctedDesc, setCorrectedDesc] = useState<string>(''); const [correctedDesc, setCorrectedDesc] = useState<string>('');
const [quarkTempTMDBMeta, setQuarkTempTMDBMeta] = useState<{
desc?: string;
poster?: string;
year?: string;
tmdbId?: number;
} | null>(null);
const [pendingQuarkTempTMDBData, setPendingQuarkTempTMDBData] = useState<any | null>(null);
// 当前源和ID - source 直接存储完整格式(如 'emby_wumei' 或 'emby' // 当前源和ID - source 直接存储完整格式(如 'emby_wumei' 或 'emby'
const [currentSource, setCurrentSource] = useState(searchParams.get('source') || ''); const [currentSource, setCurrentSource] = useState(searchParams.get('source') || '');
@@ -579,6 +586,11 @@ function PlayPageClient() {
const [fileName] = useState(searchParams.get('fileName') || ''); // 小雅源:用户点击的文件名 const [fileName] = useState(searchParams.get('fileName') || ''); // 小雅源:用户点击的文件名
const isDirectPlay = currentSource === 'directplay'; const isDirectPlay = currentSource === 'directplay';
useEffect(() => {
setQuarkTempTMDBMeta(null);
setPendingQuarkTempTMDBData(null);
}, [currentSource, currentId]);
// 解析 source 参数以获取 embyKey(仅用于 API 调用) // 解析 source 参数以获取 embyKey(仅用于 API 调用)
const parseSourceForApi = (source: string): { source: string; embyKey?: string } => { const parseSourceForApi = (source: string): { source: string; embyKey?: string } => {
if (source.startsWith('emby_')) { if (source.startsWith('emby_')) {
@@ -1187,14 +1199,18 @@ function PlayPageClient() {
const detCacheAge = Date.now() - detTimestamp; const detCacheAge = Date.now() - detTimestamp;
const detCacheMaxAge = 24 * 60 * 60 * 1000; // 1天 const detCacheMaxAge = 24 * 60 * 60 * 1000; // 1天
if (detCacheAge < detCacheMaxAge && data && data.backdrop) { if (detCacheAge < detCacheMaxAge && data) {
console.log('使用缓存的TMDB详情数据'); if (data.backdrop) {
setTmdbBackdrop(processImageUrl(data.backdrop)); setTmdbBackdrop(processImageUrl(data.backdrop));
} else {
setTmdbBackdrop(null);
}
// 如果没有豆瓣ID,使用TMDb数据补充 // 如果没有豆瓣ID,使用TMDb数据补充
if (!videoDoubanId || videoDoubanId === 0) { if (!videoDoubanId || videoDoubanId === 0) {
populateDoubanFieldsFromTMDB(data); populateDoubanFieldsFromTMDB(data);
} }
populatePlayMetadataFromTMDB(data);
return; return;
} }
} catch (e) { } catch (e) {
@@ -1215,7 +1231,6 @@ function PlayPageClient() {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
console.log('获取TMDB详情失败');
setTmdbBackdrop(null); setTmdbBackdrop(null);
return; return;
} }
@@ -1224,45 +1239,94 @@ function PlayPageClient() {
if (result.backdrop) { if (result.backdrop) {
setTmdbBackdrop(processImageUrl(result.backdrop)); setTmdbBackdrop(processImageUrl(result.backdrop));
// 如果没有豆瓣ID,使用TMDb数据补充
if (!videoDoubanId || videoDoubanId === 0) {
populateDoubanFieldsFromTMDB(result);
}
// 保存title到tmdbId的映射到localStorage1个月)
if (result.tmdbId) {
try {
localStorage.setItem(
mappingCacheKey,
JSON.stringify({
tmdbId: result.tmdbId,
timestamp: Date.now(),
})
);
// 保存TMDB详情数据到localStorage1天)
const detailsCacheKey = `tmdb_details_${result.tmdbId}`;
localStorage.setItem(
detailsCacheKey,
JSON.stringify({
data: result,
timestamp: Date.now(),
})
);
} catch (e) {
console.error('保存缓存失败:', e);
}
}
} else { } else {
setTmdbBackdrop(null); setTmdbBackdrop(null);
} }
// 如果没有豆瓣ID,使用TMDb数据补充
if (!videoDoubanId || videoDoubanId === 0) {
populateDoubanFieldsFromTMDB(result);
}
populatePlayMetadataFromTMDB(result);
// 保存title到tmdbId的映射到localStorage1个月)
if (result.tmdbId) {
try {
localStorage.setItem(
mappingCacheKey,
JSON.stringify({
tmdbId: result.tmdbId,
timestamp: Date.now(),
})
);
// 保存TMDB详情数据到localStorage1天)
const detailsCacheKey = `tmdb_details_${result.tmdbId}`;
localStorage.setItem(
detailsCacheKey,
JSON.stringify({
data: result,
timestamp: Date.now(),
})
);
} catch (e) {
console.error('保存缓存失败:', e);
}
}
} catch (error) { } catch (error) {
console.error('获取TMDB背景图失败:', error); console.error('获取TMDB背景图失败:', error);
setTmdbBackdrop(null); setTmdbBackdrop(null);
} }
}; };
const populatePlayMetadataFromTMDB = (tmdbData: any) => {
const currentDetail = detailRef.current;
if (!currentDetail || currentDetail.source !== 'quark-temp') {
setPendingQuarkTempTMDBData(tmdbData);
return;
}
const tmdbYear = tmdbData.releaseDate?.split('-')[0] || '';
const shouldReplaceDesc = !currentDetail.desc || currentDetail.desc.startsWith('临时播放目录:');
const resolvedTmdbId = typeof tmdbData.tmdbId === 'string'
? Number(String(tmdbData.tmdbId).split(':')[1] || 0)
: tmdbData.tmdbId;
setQuarkTempTMDBMeta({
desc: shouldReplaceDesc ? (tmdbData.overview || currentDetail.desc) : currentDetail.desc,
poster: currentDetail.poster || tmdbData.poster || '',
year: currentDetail.year || tmdbYear,
tmdbId: currentDetail.tmdb_id || resolvedTmdbId,
});
setDetail((prev) => {
if (!prev || prev.source !== 'quark-temp') {
return prev;
}
return {
...prev,
poster: prev.poster || tmdbData.poster || '',
year: prev.year || tmdbYear,
desc: shouldReplaceDesc ? (tmdbData.overview || prev.desc) : prev.desc,
tmdb_id: prev.tmdb_id || resolvedTmdbId,
};
});
if (tmdbData.overview && (!correctedDesc || currentDetail.desc?.startsWith('临时播放目录:'))) {
setCorrectedDesc(tmdbData.overview);
}
if (tmdbData.poster && !currentDetail.poster) {
setVideoCover(processImageUrl(tmdbData.poster));
}
if (tmdbYear && !currentDetail.year) {
setVideoYear(tmdbYear);
}
};
// 辅助函数:使用TMDb数据填充豆瓣字段 // 辅助函数:使用TMDb数据填充豆瓣字段
const populateDoubanFieldsFromTMDB = (tmdbData: any) => { const populateDoubanFieldsFromTMDB = (tmdbData: any) => {
// 设置评分 // 设置评分
@@ -1296,6 +1360,45 @@ function PlayPageClient() {
fetchTMDBBackdrop(); fetchTMDBBackdrop();
}, [videoTitle, videoDoubanId, isDirectPlay]); }, [videoTitle, videoDoubanId, isDirectPlay]);
useEffect(() => {
if (
pendingQuarkTempTMDBData &&
detail?.source === 'quark-temp'
) {
const pending = pendingQuarkTempTMDBData;
setPendingQuarkTempTMDBData(null);
const tmdbYear = pending.releaseDate?.split('-')[0] || '';
const shouldReplaceDesc = !detail.desc || detail.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,
});
setDetail((prev) => prev && prev.source === 'quark-temp' ? {
...prev,
poster: prev.poster || pending.poster || '',
year: prev.year || tmdbYear,
desc: shouldReplaceDesc ? (pending.overview || prev.desc) : prev.desc,
tmdb_id: prev.tmdb_id || resolvedTmdbId,
} : prev);
if (pending.poster && !detail.poster) {
setVideoCover(processImageUrl(pending.poster));
}
if (tmdbYear && !detail.year) {
setVideoYear(tmdbYear);
}
if (pending.overview) {
setCorrectedDesc(pending.overview);
}
}
}, [pendingQuarkTempTMDBData, detail]);
// 视频播放地址 // 视频播放地址
const [videoUrl, setVideoUrl] = useState(''); const [videoUrl, setVideoUrl] = useState('');
@@ -8872,12 +8975,12 @@ function PlayPageClient() {
</span> </span>
)} )}
{/* 优先使用 doubanYear,如果没有则使用 detail.year 或 videoYear */} {/* 优先使用 doubanYear,如果没有则使用 detail.year 或 videoYear */}
{(doubanYear || detail?.year || videoYear) && ( {(doubanYear || quarkTempTMDBMeta?.year || detail?.year || videoYear) && (
<span>{doubanYear || detail?.year || videoYear}</span> <span>{doubanYear || quarkTempTMDBMeta?.year || detail?.year || videoYear}</span>
)} )}
{detail?.source_name && ( {detail?.source_name && (
<span <span
className={`relative group cursor-pointer border px-2 py-[1px] rounded ${detail.source === 'xiaoya' ? 'border-blue-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' : detail.source === 'quark-temp' ? 'border-purple-500' : detail.source === 'openlist' || detail.source === 'emby' || detail.source?.startsWith('emby_') ? 'border-yellow-500' : 'border-gray-500/60'
}`} }`}
onClick={fetchCurrentSourceVideoInfo} onClick={fetchCurrentSourceVideoInfo}
> >
@@ -8897,7 +9000,7 @@ function PlayPageClient() {
{detail?.type_name && <span>{detail.type_name}</span>} {detail?.type_name && <span>{detail.type_name}</span>}
</div> </div>
{/* 剧情简介 */} {/* 剧情简介 */}
{(doubanCardSubtitle || correctedDesc || detail?.desc) && ( {(doubanCardSubtitle || quarkTempTMDBMeta?.desc || correctedDesc || detail?.desc) && (
<div <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' : ''}`} 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' }} style={{ whiteSpace: 'pre-line' }}
@@ -8908,7 +9011,7 @@ function PlayPageClient() {
{doubanCardSubtitle} {doubanCardSubtitle}
</div> </div>
)} )}
{correctedDesc || detail?.desc} {quarkTempTMDBMeta?.desc || correctedDesc || detail?.desc}
</div> </div>
)} )}
</div> </div>
+1 -1
View File
@@ -940,7 +940,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
{/* 源名称和集数信息 - 垂直居中 */} {/* 源名称和集数信息 - 垂直居中 */}
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<span className={`text-xs px-2 py-1 border rounded text-gray-700 dark:text-gray-300 ${ <span className={`text-xs px-2 py-1 border rounded text-gray-700 dark:text-gray-300 ${
source.source === 'xiaoya' ? 'border-blue-500' : source.source === 'openlist' || source.source === 'emby' || source.source?.startsWith('emby_') source.source === 'xiaoya' ? 'border-blue-500' : source.source === 'quark-temp' ? 'border-purple-500' : source.source === 'openlist' || source.source === 'emby' || source.source?.startsWith('emby_')
? 'border-yellow-500' ? 'border-yellow-500'
: 'border-gray-500/60' : 'border-gray-500/60'
}`}> }`}>
+2 -2
View File
@@ -1043,7 +1043,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
> >
<span <span
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/60 ${ className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/60 ${
actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : origin === 'live' ? 'border-red-500' : 'border-white/60' actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'quark-temp' ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : origin === 'live' ? 'border-red-500' : 'border-white/60'
}`} }`}
style={{ style={{
WebkitUserSelect: 'none', WebkitUserSelect: 'none',
@@ -1367,7 +1367,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
{config.showSourceName && source_name && !cmsData && ( {config.showSourceName && source_name && !cmsData && (
<span <span
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/30 backdrop-blur-sm ${ className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/30 backdrop-blur-sm ${
actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : 'border-white/60' actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'quark-temp' ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : 'border-white/60'
}`} }`}
style={{ style={{
WebkitUserSelect: 'none', WebkitUserSelect: 'none',