转存播放补充元信息

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 更新)
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'
const [currentSource, setCurrentSource] = useState(searchParams.get('source') || '');
@@ -579,6 +586,11 @@ function PlayPageClient() {
const [fileName] = useState(searchParams.get('fileName') || ''); // 小雅源:用户点击的文件名
const isDirectPlay = currentSource === 'directplay';
useEffect(() => {
setQuarkTempTMDBMeta(null);
setPendingQuarkTempTMDBData(null);
}, [currentSource, currentId]);
// 解析 source 参数以获取 embyKey(仅用于 API 调用)
const parseSourceForApi = (source: string): { source: string; embyKey?: string } => {
if (source.startsWith('emby_')) {
@@ -1187,14 +1199,18 @@ function PlayPageClient() {
const detCacheAge = Date.now() - detTimestamp;
const detCacheMaxAge = 24 * 60 * 60 * 1000; // 1天
if (detCacheAge < detCacheMaxAge && data && data.backdrop) {
console.log('使用缓存的TMDB详情数据');
setTmdbBackdrop(processImageUrl(data.backdrop));
if (detCacheAge < detCacheMaxAge && data) {
if (data.backdrop) {
setTmdbBackdrop(processImageUrl(data.backdrop));
} else {
setTmdbBackdrop(null);
}
// 如果没有豆瓣ID,使用TMDb数据补充
if (!videoDoubanId || videoDoubanId === 0) {
populateDoubanFieldsFromTMDB(data);
}
populatePlayMetadataFromTMDB(data);
return;
}
} catch (e) {
@@ -1215,7 +1231,6 @@ function PlayPageClient() {
const response = await fetch(url);
if (!response.ok) {
console.log('获取TMDB详情失败');
setTmdbBackdrop(null);
return;
}
@@ -1224,45 +1239,94 @@ function PlayPageClient() {
if (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 {
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) {
console.error('获取TMDB背景图失败:', error);
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数据填充豆瓣字段
const populateDoubanFieldsFromTMDB = (tmdbData: any) => {
// 设置评分
@@ -1296,6 +1360,45 @@ function PlayPageClient() {
fetchTMDBBackdrop();
}, [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('');
@@ -8872,12 +8975,12 @@ function PlayPageClient() {
</span>
)}
{/* 优先使用 doubanYear,如果没有则使用 detail.year 或 videoYear */}
{(doubanYear || detail?.year || videoYear) && (
<span>{doubanYear || detail?.year || videoYear}</span>
{(doubanYear || quarkTempTMDBMeta?.year || detail?.year || videoYear) && (
<span>{doubanYear || quarkTempTMDBMeta?.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 === '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}
>
@@ -8897,7 +9000,7 @@ function PlayPageClient() {
{detail?.type_name && <span>{detail.type_name}</span>}
</div>
{/* 剧情简介 */}
{(doubanCardSubtitle || correctedDesc || detail?.desc) && (
{(doubanCardSubtitle || quarkTempTMDBMeta?.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' }}
@@ -8908,7 +9011,7 @@ function PlayPageClient() {
{doubanCardSubtitle}
</div>
)}
{correctedDesc || detail?.desc}
{quarkTempTMDBMeta?.desc || correctedDesc || detail?.desc}
</div>
)}
</div>
+1 -1
View File
@@ -940,7 +940,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
{/* 源名称和集数信息 - 垂直居中 */}
<div className='flex items-center justify-between'>
<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-gray-500/60'
}`}>
+2 -2
View File
@@ -1043,7 +1043,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
>
<span
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={{
WebkitUserSelect: 'none',
@@ -1367,7 +1367,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
{config.showSourceName && source_name && !cmsData && (
<span
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={{
WebkitUserSelect: 'none',