Enhance SeriesContent and useMetadata hooks for improved image handling and cast fetching

This update modifies the SeriesContent component to check if the episode still_path is a full URL before attempting to fetch it from TMDB, improving image loading efficiency. Additionally, the useMetadata hook has been enhanced to include caching for cast data, better error handling, and improved logging for debugging purposes. The logic for handling TMDB and IMDb IDs has been streamlined, ensuring a more robust data fetching process.
This commit is contained in:
tapframe 2025-06-30 15:55:41 +05:30
parent 4b64dc64ee
commit 8cbda5c902
2 changed files with 94 additions and 62 deletions

View file

@ -250,8 +250,13 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const renderVerticalEpisodeCard = (episode: Episode) => { const renderVerticalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER; let episodeImage = EPISODE_PLACEHOLDER;
if (episode.still_path) { if (episode.still_path) {
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); // Check if still_path is already a full URL
if (tmdbUrl) episodeImage = tmdbUrl; if (episode.still_path.startsWith('http')) {
episodeImage = episode.still_path;
} else {
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500');
if (tmdbUrl) episodeImage = tmdbUrl;
}
} else if (metadata?.poster) { } else if (metadata?.poster) {
episodeImage = metadata.poster; episodeImage = metadata.poster;
} }
@ -369,8 +374,13 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const renderHorizontalEpisodeCard = (episode: Episode) => { const renderHorizontalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER; let episodeImage = EPISODE_PLACEHOLDER;
if (episode.still_path) { if (episode.still_path) {
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); // Check if still_path is already a full URL
if (tmdbUrl) episodeImage = tmdbUrl; if (episode.still_path.startsWith('http')) {
episodeImage = episode.still_path;
} else {
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500');
if (tmdbUrl) episodeImage = tmdbUrl;
}
} else if (metadata?.poster) { } else if (metadata?.poster) {
episodeImage = metadata.poster; episodeImage = metadata.poster;
} }

View file

@ -184,20 +184,23 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}; };
const loadCast = async () => { const loadCast = async () => {
if (!metadata || !metadata.id) return; logger.log('[loadCast] Starting cast fetch for:', id);
setLoadingCast(true); setLoadingCast(true);
try { try {
// Check cache first
const cachedCast = cacheService.getCast(id, type);
if (cachedCast) {
logger.log('[loadCast] Using cached cast data');
setCast(cachedCast);
setLoadingCast(false);
return;
}
// Handle TMDB IDs // Handle TMDB IDs
let metadataId = id;
let metadataType = type;
if (id.startsWith('tmdb:')) { if (id.startsWith('tmdb:')) {
const extractedTmdbId = id.split(':')[1]; const tmdbId = id.split(':')[1];
logger.log('[loadCast] Using extracted TMDB ID:', extractedTmdbId); logger.log('[loadCast] Using TMDB ID directly:', tmdbId);
const castData = await tmdbService.getCredits(parseInt(tmdbId), type);
// For TMDB IDs, we'll use the TMDB API directly
const castData = await tmdbService.getCredits(parseInt(extractedTmdbId), type);
if (castData && castData.cast) { if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({ const formattedCast = castData.cast.map((actor: any) => ({
id: actor.id, id: actor.id,
@ -205,49 +208,41 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
character: actor.character, character: actor.character,
profile_path: actor.profile_path profile_path: actor.profile_path
})); }));
logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`);
setCast(formattedCast); setCast(formattedCast);
cacheService.setCast(id, type, formattedCast);
setLoadingCast(false); setLoadingCast(false);
return formattedCast; return;
} }
setLoadingCast(false);
return [];
}
// Continue with the existing logic for non-TMDB IDs
const cachedCast = cacheService.getCast(id, type);
if (cachedCast) {
setCast(cachedCast);
setLoadingCast(false);
return;
} }
// Load cast in parallel with a fallback to empty array // Handle IMDb IDs or convert to TMDB ID
const castLoadingPromise = loadWithFallback(async () => { let tmdbId;
const tmdbId = await withTimeout( if (id.startsWith('tt')) {
tmdbService.findTMDBIdByIMDB(id), logger.log('[loadCast] Converting IMDb ID to TMDB ID');
API_TIMEOUT tmdbId = await tmdbService.findTMDBIdByIMDB(id);
); }
if (tmdbId) {
const castData = await withTimeout(
tmdbService.getCredits(tmdbId, type),
API_TIMEOUT,
{ cast: [], crew: [] }
);
if (castData.cast && castData.cast.length > 0) {
setCast(castData.cast);
cacheService.setCast(id, type, castData.cast);
return castData.cast;
}
}
return [];
}, []);
await castLoadingPromise; if (tmdbId) {
logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId);
const castData = await tmdbService.getCredits(tmdbId, type);
if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({
id: actor.id,
name: actor.name,
character: actor.character,
profile_path: actor.profile_path
}));
logger.log(`[loadCast] Found ${formattedCast.length} cast members`);
setCast(formattedCast);
cacheService.setCast(id, type, formattedCast);
}
} else {
logger.warn('[loadCast] Could not find TMDB ID for cast fetch');
}
} catch (error) { } catch (error) {
console.error('Failed to load cast:', error); logger.error('[loadCast] Failed to load cast:', error);
setCast([]); // Don't clear existing cast data on error
} finally { } finally {
setLoadingCast(false); setLoadingCast(false);
} }
@ -462,13 +457,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setMetadata(content.value); setMetadata(content.value);
// Update cache // Update cache
cacheService.setMetadata(id, type, content.value); cacheService.setMetadata(id, type, content.value);
if (type === 'series') {
// Load series data after the enhanced metadata is processed
setTimeout(() => {
loadSeriesData().catch(console.error);
}, 100);
}
} else { } else {
throw new Error('Content not found'); throw new Error('Content not found');
} }
@ -499,7 +487,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const groupedAddonEpisodes: GroupedEpisodes = {}; const groupedAddonEpisodes: GroupedEpisodes = {};
addonVideos.forEach((video: any) => { addonVideos.forEach((video: any) => {
const seasonNumber = video.season || 1; const seasonNumber = video.season;
if (!seasonNumber || seasonNumber < 1) {
return; // Skip season 0, which often contains extras
}
const episodeNumber = video.episode || video.number || 1; const episodeNumber = video.episode || video.number || 1;
if (!groupedAddonEpisodes[seasonNumber]) { if (!groupedAddonEpisodes[seasonNumber]) {
@ -514,7 +505,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
season_number: seasonNumber, season_number: seasonNumber,
episode_number: episodeNumber, episode_number: episodeNumber,
air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '', air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '',
still_path: video.thumbnail ? video.thumbnail.replace('https://image.tmdb.org/t/p/w500', '') : null, still_path: video.thumbnail,
vote_average: parseFloat(video.rating) || 0, vote_average: parseFloat(video.rating) || 0,
runtime: undefined, runtime: undefined,
episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`, episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`,
@ -531,6 +522,32 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}); });
logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`); logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`);
// Fetch season posters from TMDB
try {
const tmdbIdToUse = tmdbId || (id.startsWith('tt') ? await tmdbService.findTMDBIdByIMDB(id) : null);
if (tmdbIdToUse) {
if (!tmdbId) setTmdbId(tmdbIdToUse);
const showDetails = await tmdbService.getTVShowDetails(tmdbIdToUse);
if (showDetails?.seasons) {
Object.keys(groupedAddonEpisodes).forEach(seasonStr => {
const seasonNum = parseInt(seasonStr, 10);
const seasonInfo = showDetails.seasons.find(s => s.season_number === seasonNum);
const seasonPosterPath = seasonInfo?.poster_path;
if (seasonPosterPath) {
groupedAddonEpisodes[seasonNum] = groupedAddonEpisodes[seasonNum].map(ep => ({
...ep,
season_poster_path: seasonPosterPath,
}));
}
});
logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
}
}
} catch (error) {
logger.error('Failed to fetch TMDB season posters for addon episodes:', error);
}
setGroupedEpisodes(groupedAddonEpisodes); setGroupedEpisodes(groupedAddonEpisodes);
// Set the first available season // Set the first available season
@ -561,11 +578,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
]); ]);
const transformedEpisodes: GroupedEpisodes = {}; const transformedEpisodes: GroupedEpisodes = {};
Object.entries(allEpisodes).forEach(([season, episodes]) => { Object.entries(allEpisodes).forEach(([seasonStr, episodes]) => {
const seasonInfo = showDetails?.seasons?.find(s => s.season_number === parseInt(season)); const seasonNum = parseInt(seasonStr, 10);
if (seasonNum < 1) {
return; // Skip season 0, which often contains extras
}
const seasonInfo = showDetails?.seasons?.find(s => s.season_number === seasonNum);
const seasonPosterPath = seasonInfo?.poster_path; const seasonPosterPath = seasonInfo?.poster_path;
transformedEpisodes[parseInt(season)] = episodes.map(episode => ({ transformedEpisodes[seasonNum] = episodes.map(episode => ({
...episode, ...episode,
episodeString: `S${episode.season_number.toString().padStart(2, '0')}E${episode.episode_number.toString().padStart(2, '0')}`, episodeString: `S${episode.season_number.toString().padStart(2, '0')}E${episode.episode_number.toString().padStart(2, '0')}`,
season_poster_path: seasonPosterPath || null season_poster_path: seasonPosterPath || null