diff --git a/src/components/home/FeaturedContent.tsx b/src/components/home/FeaturedContent.tsx index 5da195d1..b155ae5d 100644 --- a/src/components/home/FeaturedContent.tsx +++ b/src/components/home/FeaturedContent.tsx @@ -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; } }; diff --git a/src/components/metadata/RatingsSection.tsx b/src/components/metadata/RatingsSection.tsx index 208cb8ba..a52076e1 100644 --- a/src/components/metadata/RatingsSection.tsx +++ b/src/components/metadata/RatingsSection.tsx @@ -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 = ({ 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 = ({ 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 = ({ 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 ( @@ -138,12 +126,9 @@ export const RatingsSection: React.FC = ({ 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: { diff --git a/src/hooks/useMetadataAssets.ts b/src/hooks/useMetadataAssets.ts index 8aaef104..0b44bf09 100644 --- a/src/hooks/useMetadataAssets.ts +++ b/src/hooks/useMetadataAssets.ts @@ -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, diff --git a/src/services/tmdbService.ts b/src/services/tmdbService.ts index 5fca12ed..405e4f6b 100644 --- a/src/services/tmdbService.ts +++ b/src/services/tmdbService.ts @@ -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 { 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 { 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 { 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 { 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 []; } }