Refactor FeaturedContent and RatingsSection components to enhance logging and error handling

This update removes unnecessary logging statements from the FeaturedContent and RatingsSection components, streamlining the code for better readability. Additionally, it improves error handling by ensuring fallback logos are set appropriately when fetching fails. The useMetadataAssets hook is also optimized for logo and banner fetching, incorporating clearer conditions for asset retrieval based on user preferences. Overall, these changes enhance the maintainability and performance of the components.
This commit is contained in:
tapframe 2025-05-04 01:03:38 +05:30
parent b8484e432f
commit 190c1a7371
4 changed files with 230 additions and 195 deletions

View file

@ -91,22 +91,18 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
try {
const isValid = await isValidMetahubLogo(url);
if (!isValid) {
console.warn(`[FeaturedContent] Metahub logo validation failed: ${url}`);
return false;
}
} catch (validationError) {
// If validation fails, still try to load the image
console.warn(`[FeaturedContent] Logo validation error, will try to load anyway: ${url}`, validationError);
}
}
// Always attempt to prefetch the image regardless of format validation
await ExpoImage.prefetch(url);
imageCache[url] = true;
console.log(`[FeaturedContent] Successfully preloaded image: ${url}`);
return true;
} catch (error) {
console.error('[FeaturedContent] Error preloading image:', error);
return false;
}
};
@ -146,8 +142,6 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
}
}
logger.log(`[FeaturedContent] Fetching logo with preference: ${logoPreference}, language: ${preferredLanguage}, ID: ${contentId}`);
// Extract IMDB ID if available
let imdbId = null;
if (featuredContent.id.startsWith('tt')) {
@ -172,13 +166,12 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
logger.log(`[FeaturedContent] Using Metahub logo: ${metahubUrl}`);
setLogoUrl(metahubUrl);
logoFetchInProgress.current = false;
return; // Exit if Metahub logo was found
}
} catch (error) {
logger.warn(`[FeaturedContent] Failed to fetch Metahub logo:`, error);
// Removed logger.warn
}
// Fall back to TMDB if Metahub fails and we have a TMDB ID
@ -189,14 +182,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
logger.log(`[FeaturedContent] Using fallback TMDB logo (${preferredLanguage}): ${logoUrl}`);
setLogoUrl(logoUrl);
} else if (currentLogo) {
// If TMDB fails too, use existing logo if any
setLogoUrl(currentLogo);
}
} catch (error) {
logger.error('[FeaturedContent] Error fetching TMDB logo:', error);
// Removed logger.error
if (currentLogo) setLogoUrl(currentLogo);
}
} else if (currentLogo) {
@ -212,13 +204,12 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
logger.log(`[FeaturedContent] Using TMDB logo (${preferredLanguage}): ${logoUrl}`);
setLogoUrl(logoUrl);
logoFetchInProgress.current = false;
return; // Exit if TMDB logo was found
}
} catch (error) {
logger.error('[FeaturedContent] Error fetching TMDB logo:', error);
// Removed logger.error
}
} else if (imdbId) {
// If we have IMDB ID but no TMDB ID, try to find TMDB ID
@ -231,14 +222,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
const logoUrl = await tmdbService.getContentLogo(tmdbType, foundTmdbId.toString(), preferredLanguage);
if (logoUrl) {
logger.log(`[FeaturedContent] Using TMDB logo (${preferredLanguage}) via IMDB lookup: ${logoUrl}`);
setLogoUrl(logoUrl);
logoFetchInProgress.current = false;
return; // Exit if TMDB logo was found
}
}
} catch (error) {
logger.error('[FeaturedContent] Error finding TMDB ID from IMDB:', error);
// Removed logger.error
}
}
@ -249,14 +239,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
logger.log(`[FeaturedContent] Using fallback Metahub logo: ${metahubUrl}`);
setLogoUrl(metahubUrl);
} else if (currentLogo) {
// If Metahub fails too, use existing logo if any
setLogoUrl(currentLogo);
}
} catch (error) {
logger.warn(`[FeaturedContent] Failed to fetch fallback Metahub logo:`, error);
// Removed logger.warn
if (currentLogo) setLogoUrl(currentLogo);
}
} else if (currentLogo) {
@ -265,10 +254,10 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
}
}
} catch (error) {
logger.error('[FeaturedContent] Error fetching logo:', error);
if (featuredContent?.logo) setLogoUrl(featuredContent.logo);
// Removed logger.error
// Optionally set a fallback logo or handle the error state
setLogoUrl(featuredContent.logo ?? null); // Fallback to initial logo or null
} finally {
// Clear fetch in progress flag
logoFetchInProgress.current = false;
}
};

View file

@ -2,9 +2,6 @@ import React, { useEffect, useState, useRef } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, Image, Animated } from 'react-native';
import { colors } from '../../styles/colors';
import { useMDBListRatings } from '../../hooks/useMDBListRatings';
import { logger } from '../../utils/logger';
import { MaterialIcons } from '@expo/vector-icons';
import { FontAwesome } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen';
@ -67,9 +64,7 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
try {
const enabled = await isMDBListEnabled();
setIsMDBEnabled(enabled);
logger.log('[RatingsSection] MDBList enabled:', enabled);
} catch (error) {
logger.error('[RatingsSection] Failed to check if MDBList is enabled:', error);
setIsMDBEnabled(true); // Default to enabled
}
};
@ -88,26 +83,21 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
setEnabledProviders(defaultSettings);
}
} catch (error) {
logger.error('[RatingsSection] Failed to load provider settings:', error);
}
};
useEffect(() => {
logger.log(`[RatingsSection] Mounted for ${type}:`, imdbId);
return () => {
logger.log(`[RatingsSection] Unmounted for ${type}:`, imdbId);
};
}, [imdbId, type]);
useEffect(() => {
if (error) {
logger.error('[RatingsSection] Error state:', error);
}
}, [error]);
useEffect(() => {
if (ratings) {
logger.log('[RatingsSection] Received ratings:', ratings);
}
}, [ratings]);
@ -124,12 +114,10 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
// If MDBList is disabled, don't show anything
if (!isMDBEnabled) {
logger.log('[RatingsSection] MDBList is disabled, not showing ratings');
return null;
}
if (loading) {
logger.log('[RatingsSection] Loading state');
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color={colors.primary} />
@ -138,12 +126,9 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
}
if (error || !ratings || Object.keys(ratings).length === 0) {
logger.log('[RatingsSection] No ratings to display');
return null;
}
logger.log('[RatingsSection] Rendering ratings:', Object.keys(ratings).length);
// Define the order and icons/colors for the ratings
const ratingConfig = {
imdb: {

View file

@ -61,47 +61,83 @@ export const useMetadataAssets = (
// Fetch logo immediately for TMDB content - with guard against recursive updates
useEffect(() => {
const logoPreference = settings.logoSourcePreference || 'metahub';
const currentLogoUrl = metadata?.logo;
let shouldFetchLogo = false;
// Determine if we need to fetch a new logo
if (!currentLogoUrl) {
logger.log(`[useMetadataAssets:Logo] Condition check: No current logo exists. Proceeding with fetch.`);
shouldFetchLogo = true;
} else {
const isCurrentLogoMetahub = isMetahubUrl(currentLogoUrl);
const isCurrentLogoTmdb = isTmdbUrl(currentLogoUrl);
if (logoPreference === 'tmdb' && !isCurrentLogoTmdb) {
logger.log(`[useMetadataAssets:Logo] Condition check: Preference is TMDB, but current logo is not TMDB (${currentLogoUrl}). Proceeding with fetch.`);
shouldFetchLogo = true;
} else if (logoPreference === 'metahub' && !isCurrentLogoMetahub) {
logger.log(`[useMetadataAssets:Logo] Condition check: Preference is Metahub, but current logo is not Metahub (${currentLogoUrl}). Proceeding with fetch.`);
shouldFetchLogo = true;
} else {
logger.log(`[useMetadataAssets:Logo] Condition check: Skipping fetch. Preference (${logoPreference}) matches existing logo source. Current logo: ${currentLogoUrl}`);
}
}
// Guard against infinite loops by checking if we're already fetching
if (metadata && !metadata.logo && !logoFetchInProgress.current) {
if (shouldFetchLogo && !logoFetchInProgress.current) {
logger.log(`[useMetadataAssets:Logo] Starting logo fetch. Current metadata logo: ${currentLogoUrl}`);
logoFetchInProgress.current = true;
const fetchLogo = async () => {
// Clear existing logo before fetching new one to avoid briefly showing wrong logo
// Only do this if we decided to fetch because of a mismatch or non-existence
if (shouldFetchLogo) {
logger.log(`[useMetadataAssets:Logo] Clearing existing logo in metadata state before fetch.`);
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
}
try {
// Get logo source preference from settings
const logoPreference = settings.logoSourcePreference || 'metahub';
// const logoPreference = settings.logoSourcePreference || 'metahub'; // Already defined above
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
logger.log(`[useMetadataAssets] Fetching logo with strict preference: ${logoPreference}`);
logger.log(`[useMetadataAssets:Logo] Fetching logo. Preference: ${logoPreference}, Language: ${preferredLanguage}, IMDB ID: ${imdbId}`);
if (logoPreference === 'metahub' && imdbId) {
// Metahub path - direct fetch without HEAD request for speed
logger.log(`[useMetadataAssets:Logo] Preference is Metahub. Attempting Metahub fetch for ${imdbId}.`);
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
try {
// Verify Metahub image exists to prevent showing broken images
logger.log(`[useMetadataAssets:Logo] Checking Metahub logo existence: ${metahubUrl}`);
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
// Update metadata with Metahub logo
setMetadata((prevMetadata: any) => ({
...prevMetadata!,
logo: metahubUrl
}));
logger.log(`[useMetadataAssets] Set Metahub logo: ${metahubUrl}`);
logger.log(`[useMetadataAssets:Logo] Metahub logo found. Updating metadata state.`);
setMetadata((prevMetadata: any) => {
logger.log(`[useMetadataAssets:Logo] setMetadata called with Metahub logo: ${metahubUrl}`);
return { ...prevMetadata!, logo: metahubUrl };
});
} else {
logger.warn(`[useMetadataAssets] Metahub logo not found for ${imdbId}`);
logger.warn(`[useMetadataAssets:Logo] Metahub logo HEAD request failed with status ${response.status} for ${imdbId}`);
}
} catch (error) {
logger.error(`[useMetadataAssets] Error checking Metahub logo:`, error);
logger.error(`[useMetadataAssets:Logo] Error checking Metahub logo:`, error);
}
} else if (logoPreference === 'tmdb') {
// TMDB path - optimized flow
logger.log(`[useMetadataAssets:Logo] Preference is TMDB. Attempting TMDB fetch.`);
let tmdbId: string | null = null;
let contentType = type === 'series' ? 'tv' : 'movie';
// Extract or find TMDB ID in one step
if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1];
logger.log(`[useMetadataAssets:Logo] Extracted TMDB ID from route ID: ${tmdbId}`);
} else if (imdbId) {
logger.log(`[useMetadataAssets:Logo] Attempting to find TMDB ID from IMDB ID: ${imdbId}`);
// Only look up TMDB ID if we don't already have it
try {
const tmdbService = TMDBService.getInstance();
@ -109,33 +145,46 @@ export const useMetadataAssets = (
if (foundId) {
tmdbId = String(foundId);
setFoundTmdbId(tmdbId); // Save for banner fetching
logger.log(`[useMetadataAssets:Logo] Found TMDB ID: ${tmdbId}`);
} else {
logger.warn(`[useMetadataAssets:Logo] Could not find TMDB ID for IMDB ID: ${imdbId}`);
}
} catch (error) {
logger.error(`[useMetadataAssets] Error finding TMDB ID:`, error);
logger.error(`[useMetadataAssets:Logo] Error finding TMDB ID:`, error);
}
} else {
logger.warn(`[useMetadataAssets:Logo] Cannot attempt TMDB fetch: No TMDB ID in route and no IMDB ID provided.`);
}
if (tmdbId) {
try {
// Direct fetch - avoid multiple service calls
logger.log(`[useMetadataAssets:Logo] Fetching TMDB logo for ${contentType} ID: ${tmdbId}, Language: ${preferredLanguage}`);
const tmdbService = TMDBService.getInstance();
const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage);
if (logoUrl) {
setMetadata((prevMetadata: any) => ({
...prevMetadata!,
logo: logoUrl
}));
logger.log(`[useMetadataAssets] Set TMDB logo: ${logoUrl}`);
logger.log(`[useMetadataAssets:Logo] TMDB logo found. Updating metadata state.`);
setMetadata((prevMetadata: any) => {
logger.log(`[useMetadataAssets:Logo] setMetadata called with TMDB logo: ${logoUrl}`);
return { ...prevMetadata!, logo: logoUrl };
});
} else {
logger.warn(`[useMetadataAssets:Logo] No TMDB logo found for ${contentType}/${tmdbId}.`);
}
} catch (error) {
logger.error(`[useMetadataAssets] Error fetching TMDB logo:`, error);
logger.error(`[useMetadataAssets:Logo] Error fetching TMDB logo:`, error);
}
} else {
logger.warn(`[useMetadataAssets:Logo] Skipping TMDB logo fetch as no TMDB ID was determined.`);
}
} else {
logger.log(`[useMetadataAssets:Logo] Preference not Metahub and no IMDB ID, or preference not TMDB. No logo fetched.`);
}
} catch (error) {
logger.error(`[useMetadataAssets] Error in fetchLogo:`, error);
logger.error(`[useMetadataAssets:Logo] Error in outer fetchLogo try block:`, error);
} finally {
logger.log(`[useMetadataAssets:Logo] Finished logo fetch attempt.`);
logoFetchInProgress.current = false;
}
};
@ -143,23 +192,38 @@ export const useMetadataAssets = (
// Execute fetch without awaiting
fetchLogo();
}
}, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference]);
// Add logging for when fetch is skipped due to already fetching
else if (shouldFetchLogo && logoFetchInProgress.current) {
logger.log(`[useMetadataAssets:Logo] Skipping logo fetch because logoFetchInProgress is true.`);
}
}, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference, settings.tmdbLanguagePreference]); // Added tmdbLanguagePreference dependency
// Fetch banner image based on logo source preference - optimized version
useEffect(() => {
// Skip if no metadata or already completed with the correct source
if (!metadata) return;
if (!metadata) {
logger.log(`[useMetadataAssets:Banner] Skipping banner fetch: No metadata.`);
return;
}
// Check if we need to refresh the banner based on source
const currentPreference = settings.logoSourcePreference || 'metahub';
logger.log(`[useMetadataAssets:Banner] Checking banner fetch. Preference: ${currentPreference}, Current Banner Source: ${bannerSource}, Forced Refresh Done: ${forcedBannerRefreshDone.current}`);
if (bannerSource === currentPreference && forcedBannerRefreshDone.current) {
logger.log(`[useMetadataAssets:Banner] Skipping fetch: Banner already loaded with correct source (${currentPreference}).`);
return; // Already have the correct source, no need to refresh
}
const fetchBanner = async () => {
logger.log(`[useMetadataAssets:Banner] Starting banner fetch.`);
setLoadingBanner(true);
setBannerImage(null); // Clear existing banner to prevent mixed sources
setBannerSource(null); // Clear source tracking
let finalBanner: string | null = null;
let bannerSourceType: 'tmdb' | 'metahub' | 'default' = 'default';
try {
// Extract all possible IDs at once
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
@ -173,20 +237,36 @@ export const useMetadataAssets = (
tmdbId = foundTmdbId;
} else if ((metadata as any).tmdbId) {
tmdbId = (metadata as any).tmdbId;
} else if (imdbId) {
// Last attempt: Look up TMDB ID if we haven't yet
logger.log(`[useMetadataAssets:Banner] Attempting TMDB ID lookup from IMDB ID: ${imdbId} for banner fetch.`);
try {
const tmdbService = TMDBService.getInstance();
const foundId = await tmdbService.findTMDBIdByIMDB(imdbId);
if (foundId) {
tmdbId = String(foundId);
logger.log(`[useMetadataAssets:Banner] Found TMDB ID: ${tmdbId}`);
} else {
logger.warn(`[useMetadataAssets:Banner] Could not find TMDB ID for IMDB ID: ${imdbId}`);
}
} catch (lookupError) {
logger.error(`[useMetadataAssets:Banner] Error looking up TMDB ID:`, lookupError);
}
}
logger.log(`[useMetadataAssets:Banner] Determined TMDB ID for banner fetch: ${tmdbId}`);
// Default fallback to use if nothing else works
let finalBanner: string | null = null;
let bannerSourceType: 'tmdb' | 'metahub' | 'default' = 'default';
if (currentPreference === 'tmdb' && tmdbId) {
// TMDB direct path
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
logger.log(`[useMetadataAssets:Banner] Preference is TMDB. Attempting TMDB banner fetch for ${contentType}/${tmdbId}.`);
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
try {
// Use TMDBService instead of direct fetch with hardcoded API key
const tmdbService = TMDBService.getInstance();
logger.log(`[useMetadataAssets] Fetching TMDB details for ${endpoint}/${tmdbId}`);
logger.log(`[useMetadataAssets:Banner] Fetching TMDB details for ${endpoint}/${tmdbId}`);
try {
// Get details with backdrop path using TMDBService
@ -196,111 +276,161 @@ export const useMetadataAssets = (
// Step 1: Get basic details
if (endpoint === 'movie') {
details = await tmdbService.getMovieDetails(tmdbId);
logger.log(`[useMetadataAssets:Banner] TMDB getMovieDetails result:`, details ? `Found backdrop: ${!!details.backdrop_path}, Found poster: ${!!details.poster_path}` : 'null');
// Step 2: Get images separately if details succeeded
if (details) {
try {
// Use getMovieImages to get image data - this returns a logo URL but we need more
await tmdbService.getMovieImages(tmdbId, preferredLanguage);
// We'll use the backdrop from the details
logger.log(`[useMetadataAssets] Got movie details for ${tmdbId}`);
} catch (imageError) {
logger.warn(`[useMetadataAssets] Could not get movie images: ${imageError}`);
}
}
} else {
// Step 2: Get images separately if details succeeded (This call might not be needed for banner)
// if (details) {
// try {
// await tmdbService.getMovieImages(tmdbId, preferredLanguage);
// logger.log(`[useMetadataAssets:Banner] Got movie images for ${tmdbId}`);
// } catch (imageError) {
// logger.warn(`[useMetadataAssets:Banner] Could not get movie images: ${imageError}`);
// }
//}
} else { // TV Show
details = await tmdbService.getTVShowDetails(Number(tmdbId));
// Step 2: Get images separately if details succeeded
if (details) {
try {
// Use getTvShowImages to get image data - this returns a logo URL but we need more
await tmdbService.getTvShowImages(tmdbId, preferredLanguage);
// We'll use the backdrop from the details
logger.log(`[useMetadataAssets] Got TV details for ${tmdbId}`);
} catch (imageError) {
logger.warn(`[useMetadataAssets] Could not get TV images: ${imageError}`);
}
}
logger.log(`[useMetadataAssets:Banner] TMDB getTVShowDetails result:`, details ? `Found backdrop: ${!!details.backdrop_path}, Found poster: ${!!details.poster_path}` : 'null');
// Step 2: Get images separately if details succeeded (This call might not be needed for banner)
// if (details) {
// try {
// await tmdbService.getTvShowImages(tmdbId, preferredLanguage);
// logger.log(`[useMetadataAssets:Banner] Got TV images for ${tmdbId}`);
// } catch (imageError) {
// logger.warn(`[useMetadataAssets:Banner] Could not get TV images: ${imageError}`);
// }
// }
}
// Check if we have a backdrop path from details
if (details && details.backdrop_path) {
finalBanner = tmdbService.getImageUrl(details.backdrop_path);
bannerSourceType = 'tmdb';
logger.log(`[useMetadataAssets] Using TMDB backdrop from details: ${finalBanner}`);
logger.log(`[useMetadataAssets:Banner] Using TMDB backdrop from details: ${finalBanner}`);
}
// If no backdrop, try poster as fallback
else if (details && details.poster_path) {
logger.warn(`[useMetadataAssets] No backdrop available, using poster as fallback`);
logger.warn(`[useMetadataAssets:Banner] No TMDB backdrop available, using poster as fallback.`);
finalBanner = tmdbService.getImageUrl(details.poster_path);
bannerSourceType = 'tmdb';
}
else {
logger.warn(`[useMetadataAssets] No backdrop or poster found for ${endpoint}/${tmdbId}`);
logger.warn(`[useMetadataAssets:Banner] No TMDB backdrop or poster found for ${endpoint}/${tmdbId}. TMDB path failed.`);
// Explicitly set finalBanner to null if TMDB fails
finalBanner = null;
}
} catch (innerErr) {
logger.error(`[useMetadataAssets] Error fetching TMDB details/images:`, innerErr);
}
} catch (err) {
logger.error(`[useMetadataAssets] TMDB service initialization error:`, err);
logger.error(`[useMetadataAssets:Banner] Error fetching TMDB details/images:`, innerErr);
finalBanner = null; // Ensure failure case nullifies banner
}
} catch (err) {
logger.error(`[useMetadataAssets:Banner] TMDB service initialization error:`, err);
finalBanner = null; // Ensure failure case nullifies banner
}
} else if (currentPreference === 'metahub' && imdbId) {
// Metahub path - verify it exists to prevent broken images
logger.log(`[useMetadataAssets:Banner] Preference is Metahub. Attempting Metahub banner fetch for ${imdbId}.`);
const metahubUrl = `https://images.metahub.space/background/medium/${imdbId}/img`;
try {
try {
logger.log(`[useMetadataAssets:Banner] Checking Metahub banner existence: ${metahubUrl}`);
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
finalBanner = metahubUrl;
bannerSourceType = 'metahub';
logger.log(`[useMetadataAssets] Using Metahub banner: ${finalBanner}`);
logger.log(`[useMetadataAssets:Banner] Metahub banner found: ${finalBanner}`);
} else {
logger.warn(`[useMetadataAssets] Metahub banner not found, using default`);
logger.warn(`[useMetadataAssets:Banner] Metahub banner HEAD request failed with status ${response.status}, using default.`);
finalBanner = null; // Ensure fallback if Metahub fails
}
} catch (error) {
logger.error(`[useMetadataAssets] Error checking Metahub banner:`, error);
logger.error(`[useMetadataAssets:Banner] Error checking Metahub banner:`, error);
finalBanner = null; // Ensure fallback if Metahub errors
}
} else {
// This case handles:
// 1. Preference is TMDB but no tmdbId could be found.
// 2. Preference is Metahub but no imdbId was provided.
logger.log(`[useMetadataAssets:Banner] Skipping direct fetch: Preference=${currentPreference}, tmdbId=${tmdbId}, imdbId=${imdbId}. Will rely on default/fallback.`);
finalBanner = null; // Explicitly nullify banner if preference conditions aren't met
}
// Fallback logic if preferred source failed or wasn't attempted
if (!finalBanner) {
logger.log(`[useMetadataAssets:Banner] Preferred source (${currentPreference}) did not yield a banner. Checking fallbacks.`);
// Fallback 1: Try the *other* source if the preferred one failed
if (currentPreference === 'tmdb' && imdbId) { // If preferred was TMDB, try Metahub
logger.log(`[useMetadataAssets:Banner] Fallback: Trying Metahub for ${imdbId}.`);
const metahubUrl = `https://images.metahub.space/background/medium/${imdbId}/img`;
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
finalBanner = metahubUrl;
bannerSourceType = 'metahub';
logger.log(`[useMetadataAssets:Banner] Fallback Metahub banner found: ${finalBanner}`);
} else {
logger.warn(`[useMetadataAssets:Banner] Fallback Metahub HEAD failed: ${response.status}`);
}
} catch (fallbackError) {
logger.error(`[useMetadataAssets:Banner] Fallback Metahub check error:`, fallbackError);
}
} else if (currentPreference === 'metahub' && tmdbId) { // If preferred was Metahub, try TMDB
logger.log(`[useMetadataAssets:Banner] Fallback: Trying TMDB for ${contentType}/${tmdbId}.`);
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
try {
const tmdbService = TMDBService.getInstance();
let details = endpoint === 'movie' ? await tmdbService.getMovieDetails(tmdbId) : await tmdbService.getTVShowDetails(Number(tmdbId));
if (details?.backdrop_path) {
finalBanner = tmdbService.getImageUrl(details.backdrop_path);
bannerSourceType = 'tmdb';
logger.log(`[useMetadataAssets:Banner] Fallback TMDB banner found (backdrop): ${finalBanner}`);
} else if (details?.poster_path) {
finalBanner = tmdbService.getImageUrl(details.poster_path);
bannerSourceType = 'tmdb';
logger.log(`[useMetadataAssets:Banner] Fallback TMDB banner found (poster): ${finalBanner}`);
} else {
logger.warn(`[useMetadataAssets:Banner] Fallback TMDB fetch found no backdrop or poster.`);
}
} catch (fallbackError) {
logger.error(`[useMetadataAssets:Banner] Fallback TMDB check error:`, fallbackError);
}
}
// Fallback 2: Use metadata banner/poster if other source also failed
if (!finalBanner) {
logger.log(`[useMetadataAssets:Banner] Fallback source also failed or not applicable. Using metadata.banner or metadata.poster.`);
finalBanner = metadata?.banner || metadata?.poster || null;
bannerSourceType = 'default';
if (finalBanner) {
logger.log(`[useMetadataAssets:Banner] Using default banner from metadata: ${finalBanner}`);
} else {
logger.warn(`[useMetadataAssets:Banner] No default banner found in metadata either.`);
}
}
}
// If no source-specific banner was found, use default
if (!finalBanner) {
finalBanner = metadata.banner || metadata.poster;
bannerSourceType = 'default';
logger.log(`[useMetadataAssets] Using default banner: ${finalBanner}`);
}
// Set banner image once at the end
setBannerImage(finalBanner);
setBannerSource(bannerSourceType);
} catch (error) {
logger.error(`[useMetadataAssets] Banner fetch error:`, error);
// Use default banner if error occurred
setBannerImage(metadata.banner || metadata.poster);
// Set the final state
logger.log(`[useMetadataAssets:Banner] Final decision: Setting banner to ${finalBanner} (Source: ${bannerSourceType})`);
setBannerImage(finalBanner);
setBannerSource(bannerSourceType); // Track the source of the final image
forcedBannerRefreshDone.current = true; // Mark this cycle as complete
} catch (error) {
logger.error(`[useMetadataAssets:Banner] Error in outer fetchBanner try block:`, error);
// Ensure fallback to default even on outer error
const defaultBanner = metadata?.banner || metadata?.poster || null;
setBannerImage(defaultBanner);
setBannerSource('default');
} finally {
setLoadingBanner(false);
forcedBannerRefreshDone.current = true;
logger.log(`[useMetadataAssets:Banner] Setting default banner due to outer error: ${defaultBanner}`);
} finally {
logger.log(`[useMetadataAssets:Banner] Finished banner fetch attempt.`);
setLoadingBanner(false);
}
};
fetchBanner();
}, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId, bannerSource]);
// Original reset forced refresh effect
useEffect(() => {
if (forcedBannerRefreshDone.current) {
logger.log(`[useMetadataAssets] Logo preference changed, resetting banner refresh flag`);
forcedBannerRefreshDone.current = false;
// Clear the banner image immediately to prevent showing the wrong source briefly
setBannerImage(null);
setBannerSource(null);
// This will trigger the banner fetch effect to run again
}
}, [settings.logoSourcePreference]);
}, [metadata, id, type, imdbId, settings.logoSourcePreference, settings.tmdbLanguagePreference, setMetadata, foundTmdbId, bannerSource]); // Added bannerSource dependency to re-evaluate if it changes unexpectedly
return {
bannerImage,

View file

@ -1,5 +1,4 @@
import axios from 'axios';
import { logger } from '../utils/logger';
import AsyncStorage from '@react-native-async-storage/async-storage';
// TMDB API configuration
@ -100,15 +99,12 @@ export class TMDBService {
if (this.useCustomKey && savedKey) {
this.apiKey = savedKey;
logger.log('Using custom TMDb API key');
} else {
this.apiKey = DEFAULT_API_KEY;
logger.log('Using default TMDb API key');
}
this.apiKeyLoaded = true;
} catch (error) {
logger.error('Failed to load TMDb API key from storage, using default:', error);
this.apiKey = DEFAULT_API_KEY;
this.apiKeyLoaded = true;
}
@ -157,7 +153,6 @@ export class TMDBService {
});
return response.data.results;
} catch (error) {
logger.error('Failed to search TV show:', error);
return [];
}
}
@ -175,7 +170,6 @@ export class TMDBService {
});
return response.data;
} catch (error) {
logger.error('Failed to get TV show details:', error);
return null;
}
}
@ -198,7 +192,6 @@ export class TMDBService {
);
return response.data;
} catch (error) {
logger.error('Failed to get episode external IDs:', error);
return null;
}
}
@ -234,7 +227,6 @@ export class TMDBService {
TMDBService.ratingCache.set(cacheKey, rating);
return rating;
} catch (error) {
logger.error('Failed to get IMDb rating:', error);
// Cache the failed result too to prevent repeated failed requests
TMDBService.ratingCache.set(cacheKey, null);
return null;
@ -289,7 +281,6 @@ export class TMDBService {
return season;
} catch (error) {
logger.error('Failed to get season details:', error);
return null;
}
}
@ -314,7 +305,6 @@ export class TMDBService {
);
return response.data;
} catch (error) {
logger.error('Failed to get episode details:', error);
return null;
}
}
@ -333,7 +323,6 @@ export class TMDBService {
const tmdbId = await this.findTMDBIdByIMDB(imdbId);
return tmdbId;
} catch (error) {
logger.error('Failed to extract TMDB ID from Stremio ID:', error);
return null;
}
}
@ -366,7 +355,6 @@ export class TMDBService {
return null;
} catch (error) {
logger.error('Failed to find TMDB ID by IMDB ID:', error);
return null;
}
}
@ -376,13 +364,11 @@ export class TMDBService {
*/
getImageUrl(path: string | null, size: 'original' | 'w500' | 'w300' | 'w185' | 'profile' = 'original'): string | null {
if (!path) {
logger.warn(`[TMDBService] Cannot construct image URL from null path`);
return null;
}
const baseImageUrl = 'https://image.tmdb.org/t/p/';
const fullUrl = `${baseImageUrl}${size}${path}`;
logger.log(`[TMDBService] Constructed image URL: ${fullUrl}`);
return fullUrl;
}
@ -411,7 +397,6 @@ export class TMDBService {
await Promise.all(seasonPromises);
return allEpisodes;
} catch (error) {
logger.error('Failed to get all episodes:', error);
return {};
}
}
@ -472,7 +457,6 @@ export class TMDBService {
crew: response.data.crew || []
};
} catch (error) {
logger.error('Failed to fetch credits:', error);
return { cast: [], crew: [] };
}
}
@ -487,7 +471,6 @@ export class TMDBService {
});
return response.data;
} catch (error) {
logger.error('Failed to fetch person details:', error);
return null;
}
}
@ -506,14 +489,12 @@ export class TMDBService {
);
return response.data;
} catch (error) {
logger.error('Failed to get show external IDs:', error);
return null;
}
}
async getRecommendations(type: 'movie' | 'tv', tmdbId: string): Promise<any[]> {
if (!this.apiKey) {
logger.error('TMDB API key not set');
return [];
}
try {
@ -523,7 +504,6 @@ export class TMDBService {
});
return response.data.results || [];
} catch (error) {
logger.error(`Error fetching TMDB ${type} recommendations for ID ${tmdbId}:`, error);
return [];
}
}
@ -541,7 +521,6 @@ export class TMDBService {
});
return response.data.results;
} catch (error) {
logger.error('Failed to search multi:', error);
return [];
}
}
@ -560,7 +539,6 @@ export class TMDBService {
});
return response.data;
} catch (error) {
logger.error('Failed to get movie details:', error);
return null;
}
}
@ -570,8 +548,6 @@ export class TMDBService {
*/
async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
try {
logger.log(`[TMDBService] Fetching movie images for TMDB ID: ${movieId}, preferred language: ${preferredLanguage}`);
const response = await axios.get(`${BASE_URL}/movie/${movieId}/images`, {
headers: await this.getHeaders(),
params: await this.getParams({
@ -580,7 +556,6 @@ export class TMDBService {
});
const images = response.data;
logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for movie ID ${movieId}`);
if (images && images.logos && images.logos.length > 0) {
// First prioritize preferred language SVG logos if not English
@ -591,7 +566,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredSvgLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for movie ID ${movieId}: ${preferredSvgLogo.file_path}`);
return this.getImageUrl(preferredSvgLogo.file_path);
}
@ -602,7 +576,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredPngLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for movie ID ${movieId}: ${preferredPngLogo.file_path}`);
return this.getImageUrl(preferredPngLogo.file_path);
}
@ -611,7 +584,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} logo for movie ID ${movieId}: ${preferredLogo.file_path}`);
return this.getImageUrl(preferredLogo.file_path);
}
}
@ -623,7 +595,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enSvgLogo) {
logger.log(`[TMDBService] Found English SVG logo for movie ID ${movieId}: ${enSvgLogo.file_path}`);
return this.getImageUrl(enSvgLogo.file_path);
}
@ -634,7 +605,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enPngLogo) {
logger.log(`[TMDBService] Found English PNG logo for movie ID ${movieId}: ${enPngLogo.file_path}`);
return this.getImageUrl(enPngLogo.file_path);
}
@ -643,7 +613,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enLogo) {
logger.log(`[TMDBService] Found English logo for movie ID ${movieId}: ${enLogo.file_path}`);
return this.getImageUrl(enLogo.file_path);
}
@ -652,7 +621,6 @@ export class TMDBService {
logo.file_path && logo.file_path.endsWith('.svg')
);
if (svgLogo) {
logger.log(`[TMDBService] Found SVG logo for movie ID ${movieId}: ${svgLogo.file_path}`);
return this.getImageUrl(svgLogo.file_path);
}
@ -661,20 +629,15 @@ export class TMDBService {
logo.file_path && logo.file_path.endsWith('.png')
);
if (pngLogo) {
logger.log(`[TMDBService] Found PNG logo for movie ID ${movieId}: ${pngLogo.file_path}`);
return this.getImageUrl(pngLogo.file_path);
}
// Last resort: any logo
logger.log(`[TMDBService] Using first available logo for movie ID ${movieId}: ${images.logos[0].file_path}`);
return this.getImageUrl(images.logos[0].file_path);
}
logger.warn(`[TMDBService] No logos found for movie ID ${movieId}`);
return null; // No logos found
} catch (error) {
// Log error but don't throw, just return null if fetching images fails
logger.error(`[TMDBService] Failed to get movie images for ID ${movieId}:`, error);
return null;
}
}
@ -684,8 +647,6 @@ export class TMDBService {
*/
async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
try {
logger.log(`[TMDBService] Fetching TV show images for TMDB ID: ${showId}, preferred language: ${preferredLanguage}`);
const response = await axios.get(`${BASE_URL}/tv/${showId}/images`, {
headers: await this.getHeaders(),
params: await this.getParams({
@ -694,7 +655,6 @@ export class TMDBService {
});
const images = response.data;
logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for TV show ID ${showId}`);
if (images && images.logos && images.logos.length > 0) {
// First prioritize preferred language SVG logos if not English
@ -705,7 +665,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredSvgLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for TV show ID ${showId}: ${preferredSvgLogo.file_path}`);
return this.getImageUrl(preferredSvgLogo.file_path);
}
@ -716,7 +675,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredPngLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for TV show ID ${showId}: ${preferredPngLogo.file_path}`);
return this.getImageUrl(preferredPngLogo.file_path);
}
@ -725,7 +683,6 @@ export class TMDBService {
logo.iso_639_1 === preferredLanguage
);
if (preferredLogo) {
logger.log(`[TMDBService] Found ${preferredLanguage} logo for TV show ID ${showId}: ${preferredLogo.file_path}`);
return this.getImageUrl(preferredLogo.file_path);
}
}
@ -737,7 +694,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enSvgLogo) {
logger.log(`[TMDBService] Found English SVG logo for TV show ID ${showId}: ${enSvgLogo.file_path}`);
return this.getImageUrl(enSvgLogo.file_path);
}
@ -748,7 +704,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enPngLogo) {
logger.log(`[TMDBService] Found English PNG logo for TV show ID ${showId}: ${enPngLogo.file_path}`);
return this.getImageUrl(enPngLogo.file_path);
}
@ -757,7 +712,6 @@ export class TMDBService {
logo.iso_639_1 === 'en'
);
if (enLogo) {
logger.log(`[TMDBService] Found English logo for TV show ID ${showId}: ${enLogo.file_path}`);
return this.getImageUrl(enLogo.file_path);
}
@ -766,7 +720,6 @@ export class TMDBService {
logo.file_path && logo.file_path.endsWith('.svg')
);
if (svgLogo) {
logger.log(`[TMDBService] Found SVG logo for TV show ID ${showId}: ${svgLogo.file_path}`);
return this.getImageUrl(svgLogo.file_path);
}
@ -775,20 +728,15 @@ export class TMDBService {
logo.file_path && logo.file_path.endsWith('.png')
);
if (pngLogo) {
logger.log(`[TMDBService] Found PNG logo for TV show ID ${showId}: ${pngLogo.file_path}`);
return this.getImageUrl(pngLogo.file_path);
}
// Last resort: any logo
logger.log(`[TMDBService] Using first available logo for TV show ID ${showId}: ${images.logos[0].file_path}`);
return this.getImageUrl(images.logos[0].file_path);
}
logger.warn(`[TMDBService] No logos found for TV show ID ${showId}`);
return null; // No logos found
} catch (error) {
// Log error but don't throw, just return null if fetching images fails
logger.error(`[TMDBService] Failed to get TV show images for ID ${showId}:`, error);
return null;
}
}
@ -798,21 +746,16 @@ export class TMDBService {
*/
async getContentLogo(type: 'movie' | 'tv', id: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
try {
logger.log(`[TMDBService] Getting content logo for ${type} with ID ${id}, preferred language: ${preferredLanguage}`);
const result = type === 'movie'
? await this.getMovieImages(id, preferredLanguage)
: await this.getTvShowImages(id, preferredLanguage);
if (result) {
logger.log(`[TMDBService] Successfully retrieved logo for ${type} ID ${id}: ${result}`);
} else {
logger.warn(`[TMDBService] No logo found for ${type} ID ${id}`);
}
return result;
} catch (error) {
logger.error(`[TMDBService] Failed to get content logo for ${type} ID ${id}:`, error);
return null;
}
}
@ -847,7 +790,6 @@ export class TMDBService {
}
return null;
} catch (error) {
logger.error('Error fetching certification:', error);
return null;
}
}
@ -883,7 +825,6 @@ export class TMDBService {
external_ids: externalIdsResponse.data
};
} catch (error) {
logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error);
return item;
}
})
@ -891,7 +832,6 @@ export class TMDBService {
return resultsWithExternalIds;
} catch (error) {
logger.error(`Failed to get trending ${type} content:`, error);
return [];
}
}
@ -928,7 +868,6 @@ export class TMDBService {
external_ids: externalIdsResponse.data
};
} catch (error) {
logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error);
return item;
}
})
@ -936,7 +875,6 @@ export class TMDBService {
return resultsWithExternalIds;
} catch (error) {
logger.error(`Failed to get popular ${type} content:`, error);
return [];
}
}
@ -976,7 +914,6 @@ export class TMDBService {
external_ids: externalIdsResponse.data
};
} catch (error) {
logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error);
return item;
}
})
@ -984,7 +921,6 @@ export class TMDBService {
return resultsWithExternalIds;
} catch (error) {
logger.error(`Failed to get upcoming ${type} content:`, error);
return [];
}
}
@ -1002,7 +938,6 @@ export class TMDBService {
});
return response.data.genres || [];
} catch (error) {
logger.error('Failed to fetch movie genres:', error);
return [];
}
}
@ -1020,7 +955,6 @@ export class TMDBService {
});
return response.data.genres || [];
} catch (error) {
logger.error('Failed to fetch TV genres:', error);
return [];
}
}
@ -1041,7 +975,6 @@ export class TMDBService {
const genre = genreList.find(g => g.name.toLowerCase() === genreName.toLowerCase());
if (!genre) {
logger.error(`Genre ${genreName} not found`);
return [];
}
@ -1075,7 +1008,6 @@ export class TMDBService {
external_ids: externalIdsResponse.data
};
} catch (error) {
logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error);
return item;
}
})
@ -1083,7 +1015,6 @@ export class TMDBService {
return resultsWithExternalIds;
} catch (error) {
logger.error(`Failed to discover ${type} by genre ${genreName}:`, error);
return [];
}
}