mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-15 22:42:10 +00:00
Enhance HeroSection and useMetadataAssets for improved ID handling and asset fetching
This update introduces robust ID parsing and conversion logic in the HeroSection component, allowing for seamless navigation based on TMDB and IMDb IDs. Additionally, the useMetadataAssets hook has been optimized to manage logo and banner fetching more effectively, incorporating source tracking to prevent mixing assets from different sources. The changes improve error handling and logging for better debugging and user experience. The LogoSourceSettings component has also been updated to clarify the source selection process for logos and backgrounds.
This commit is contained in:
parent
10cbf077d6
commit
b8484e432f
4 changed files with 236 additions and 386 deletions
|
|
@ -16,6 +16,7 @@ import Animated, {
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { colors } from '../../styles/colors';
|
import { colors } from '../../styles/colors';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
import { TMDBService } from '../../services/tmdbService';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
|
|
@ -110,7 +111,48 @@ const ActionButtons = React.memo(({
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.iconButton]}
|
style={[styles.iconButton]}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
navigation.navigate('ShowRatings', { showId: id.split(':')[1] });
|
let finalTmdbId: number | null = null;
|
||||||
|
|
||||||
|
if (id && id.startsWith('tmdb:')) {
|
||||||
|
const numericPart = id.split(':')[1];
|
||||||
|
const parsedId = parseInt(numericPart, 10);
|
||||||
|
if (!isNaN(parsedId)) {
|
||||||
|
finalTmdbId = parsedId;
|
||||||
|
} else {
|
||||||
|
logger.error(`[HeroSection] Failed to parse TMDB ID from: ${id}`);
|
||||||
|
}
|
||||||
|
} else if (id && id.startsWith('tt')) {
|
||||||
|
// It's an IMDb ID, convert it
|
||||||
|
logger.log(`[HeroSection] Detected IMDb ID: ${id}, attempting conversion to TMDB ID.`);
|
||||||
|
try {
|
||||||
|
const tmdbService = TMDBService.getInstance();
|
||||||
|
const convertedId = await tmdbService.findTMDBIdByIMDB(id);
|
||||||
|
if (convertedId) {
|
||||||
|
finalTmdbId = convertedId;
|
||||||
|
logger.log(`[HeroSection] Successfully converted IMDb ID ${id} to TMDB ID: ${finalTmdbId}`);
|
||||||
|
} else {
|
||||||
|
logger.error(`[HeroSection] Could not convert IMDb ID ${id} to TMDB ID.`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[HeroSection] Error converting IMDb ID ${id}:`, error);
|
||||||
|
}
|
||||||
|
} else if (id) {
|
||||||
|
// Assume it might be a raw TMDB ID (numeric string)
|
||||||
|
const parsedId = parseInt(id, 10);
|
||||||
|
if (!isNaN(parsedId)) {
|
||||||
|
finalTmdbId = parsedId;
|
||||||
|
} else {
|
||||||
|
logger.error(`[HeroSection] Unrecognized ID format or invalid numeric ID: ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate if we have a valid TMDB ID
|
||||||
|
if (finalTmdbId !== null) {
|
||||||
|
navigation.navigate('ShowRatings', { showId: finalTmdbId });
|
||||||
|
} else {
|
||||||
|
logger.error(`[HeroSection] Could not navigate to ShowRatings, failed to obtain a valid TMDB ID from original id: ${id}`);
|
||||||
|
// Optionally show an error message to the user here
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="assessment" size={24} color="#fff" />
|
<MaterialIcons name="assessment" size={24} color="#fff" />
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ export const useMetadataAssets = (
|
||||||
const [loadingBanner, setLoadingBanner] = useState<boolean>(false);
|
const [loadingBanner, setLoadingBanner] = useState<boolean>(false);
|
||||||
const forcedBannerRefreshDone = useRef<boolean>(false);
|
const forcedBannerRefreshDone = useRef<boolean>(false);
|
||||||
|
|
||||||
|
// Add source tracking to prevent mixing sources
|
||||||
|
const [bannerSource, setBannerSource] = useState<'tmdb' | 'metahub' | 'default' | null>(null);
|
||||||
|
|
||||||
// State for logo loading
|
// State for logo loading
|
||||||
const [logoLoadError, setLogoLoadError] = useState(false);
|
const [logoLoadError, setLogoLoadError] = useState(false);
|
||||||
const logoFetchInProgress = useRef<boolean>(false);
|
const logoFetchInProgress = useRef<boolean>(false);
|
||||||
|
|
@ -26,475 +29,275 @@ export const useMetadataAssets = (
|
||||||
// For TMDB ID tracking
|
// For TMDB ID tracking
|
||||||
const [foundTmdbId, setFoundTmdbId] = useState<string | null>(null);
|
const [foundTmdbId, setFoundTmdbId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Effect to force-refresh the logo when it doesn't match the preference
|
// Force reset when preference changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metadata?.logo && !forcedLogoRefreshDone.current) {
|
// Reset all cached data when preference changes
|
||||||
|
setBannerImage(null);
|
||||||
|
setBannerSource(null);
|
||||||
|
forcedBannerRefreshDone.current = false;
|
||||||
|
forcedLogoRefreshDone.current = false;
|
||||||
|
logoRefreshCounter.current = 0;
|
||||||
|
|
||||||
|
// Force logo refresh on preference change
|
||||||
|
if (metadata?.logo) {
|
||||||
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
||||||
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
||||||
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
||||||
|
|
||||||
// Check if logo source doesn't match preference
|
// Always clear logo on preference change to force proper refresh
|
||||||
if ((preferenceIsMetahub && !currentLogoIsMetahub) ||
|
|
||||||
(!preferenceIsMetahub && !currentLogoIsTmdb)) {
|
|
||||||
logger.log(`[useMetadataAssets] Initial load: Logo source doesn't match preference. Forcing refresh.`);
|
|
||||||
|
|
||||||
// Clear logo to force a new fetch according to preference
|
|
||||||
setMetadata((prevMetadata: any) => ({
|
setMetadata((prevMetadata: any) => ({
|
||||||
...prevMetadata!,
|
...prevMetadata!,
|
||||||
logo: undefined
|
logo: undefined
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we've checked this so we don't endlessly loop
|
logger.log(`[useMetadataAssets] Preference changed to ${settings.logoSourcePreference}, forcing refresh of all assets`);
|
||||||
forcedLogoRefreshDone.current = true;
|
|
||||||
}
|
}
|
||||||
}, [metadata?.logo, settings.logoSourcePreference, setMetadata]);
|
}, [settings.logoSourcePreference, setMetadata]);
|
||||||
|
|
||||||
// Reset logo load error when metadata changes
|
// Original reset logo load error effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLogoLoadError(false);
|
setLogoLoadError(false);
|
||||||
}, [metadata?.logo]);
|
}, [metadata?.logo]);
|
||||||
|
|
||||||
// Force refresh logo when logo preference changes - only when preference actually changes
|
|
||||||
useEffect(() => {
|
|
||||||
// Reset the counter when preference actually changes
|
|
||||||
if (logoRefreshCounter.current === 0) {
|
|
||||||
logoRefreshCounter.current = 1; // Mark that we've started a refresh cycle
|
|
||||||
|
|
||||||
// Only clear logo if we already have metadata with a logo
|
|
||||||
if (metadata?.logo) {
|
|
||||||
// Check if the current logo source doesn't match the preference
|
|
||||||
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
|
||||||
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
|
||||||
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
|
||||||
const preferenceIsTmdb = settings.logoSourcePreference === 'tmdb';
|
|
||||||
|
|
||||||
// Only refresh if the current logo source clearly doesn't match the preference
|
|
||||||
const needsRefresh = (preferenceIsMetahub && currentLogoIsTmdb) ||
|
|
||||||
(preferenceIsTmdb && currentLogoIsMetahub);
|
|
||||||
|
|
||||||
if (needsRefresh) {
|
|
||||||
logger.log(`[useMetadataAssets] Logo preference (${settings.logoSourcePreference}) doesn't match current logo source, triggering one-time refresh`);
|
|
||||||
|
|
||||||
// Prevent endless refreshes
|
|
||||||
if (logoRefreshCounter.current < MAX_LOGO_REFRESHES) {
|
|
||||||
logoRefreshCounter.current++;
|
|
||||||
setMetadata((prevMetadata: any) => ({
|
|
||||||
...prevMetadata!,
|
|
||||||
logo: undefined
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
logger.warn(`[useMetadataAssets] Maximum logo refreshes (${MAX_LOGO_REFRESHES}) reached, stopping to prevent loop`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(`[useMetadataAssets] Logo source already matches preference (${settings.logoSourcePreference}), no refresh needed`);
|
|
||||||
logoRefreshCounter.current = 0; // Reset for future changes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logoRefreshCounter.current++;
|
|
||||||
logger.log(`[useMetadataAssets] Logo refresh already in progress (${logoRefreshCounter.current}/${MAX_LOGO_REFRESHES})`);
|
|
||||||
|
|
||||||
// Reset counter after max refreshes to allow future preference changes to work
|
|
||||||
if (logoRefreshCounter.current >= MAX_LOGO_REFRESHES) {
|
|
||||||
logger.warn(`[useMetadataAssets] Maximum refreshes reached, resetting counter`);
|
|
||||||
// After a timeout to avoid immediate re-triggering
|
|
||||||
setTimeout(() => {
|
|
||||||
logoRefreshCounter.current = 0;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [settings.logoSourcePreference, metadata?.logo, setMetadata]);
|
|
||||||
|
|
||||||
// Add effect to track when logo source matches preference
|
|
||||||
useEffect(() => {
|
|
||||||
if (metadata?.logo) {
|
|
||||||
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
|
||||||
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
|
||||||
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
|
||||||
const preferenceIsTmdb = settings.logoSourcePreference === 'tmdb';
|
|
||||||
|
|
||||||
// Check if current logo source matches preference
|
|
||||||
const logoSourceMatches = (preferenceIsMetahub && currentLogoIsMetahub) ||
|
|
||||||
(preferenceIsTmdb && currentLogoIsTmdb);
|
|
||||||
|
|
||||||
if (logoSourceMatches) {
|
|
||||||
logger.log(`[useMetadataAssets] Logo source (${currentLogoIsMetahub ? 'Metahub' : 'TMDB'}) now matches preference (${settings.logoSourcePreference}), refresh complete`);
|
|
||||||
logoRefreshCounter.current = 0; // Reset counter since we've achieved our goal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [metadata?.logo, settings.logoSourcePreference]);
|
|
||||||
|
|
||||||
// Fetch logo immediately for TMDB content - with guard against recursive updates
|
// Fetch logo immediately for TMDB content - with guard against recursive updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Guard against infinite loops by checking if we're already fetching
|
// Guard against infinite loops by checking if we're already fetching
|
||||||
if (metadata && !metadata.logo && !logoFetchInProgress.current) {
|
if (metadata && !metadata.logo && !logoFetchInProgress.current) {
|
||||||
console.log('[useMetadataAssets] Current settings:', JSON.stringify(settings));
|
|
||||||
console.log('[useMetadataAssets] Current metadata:', JSON.stringify(metadata, null, 2));
|
|
||||||
|
|
||||||
const fetchLogo = async () => {
|
|
||||||
// Set fetch in progress flag
|
|
||||||
logoFetchInProgress.current = true;
|
logoFetchInProgress.current = true;
|
||||||
|
|
||||||
|
const fetchLogo = async () => {
|
||||||
try {
|
try {
|
||||||
// Get logo source preference from settings
|
// Get logo source preference from settings
|
||||||
const logoPreference = settings.logoSourcePreference || 'metahub'; // Default to metahub if not set
|
const logoPreference = settings.logoSourcePreference || 'metahub';
|
||||||
|
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||||
|
|
||||||
console.log(`[useMetadataAssets] Using logo preference: ${logoPreference}, TMDB first: ${logoPreference === 'tmdb'}`);
|
logger.log(`[useMetadataAssets] Fetching logo with strict preference: ${logoPreference}`);
|
||||||
logger.log(`[useMetadataAssets] Logo source preference: ${logoPreference}`);
|
|
||||||
|
|
||||||
// First source based on preference
|
if (logoPreference === 'metahub' && imdbId) {
|
||||||
if (logoPreference === 'metahub') {
|
// Metahub path - direct fetch without HEAD request for speed
|
||||||
// Try to get logo from Metahub first
|
|
||||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||||
|
|
||||||
logger.log(`[useMetadataAssets] Attempting to fetch logo from Metahub for ${imdbId}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Verify Metahub image exists to prevent showing broken images
|
||||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
logger.log(`[useMetadataAssets] Successfully fetched logo from Metahub:
|
|
||||||
- Content ID: ${id}
|
|
||||||
- Content Type: ${type}
|
|
||||||
- Logo URL: ${metahubUrl}
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Update metadata with Metahub logo
|
// Update metadata with Metahub logo
|
||||||
setMetadata((prevMetadata: any) => ({
|
setMetadata((prevMetadata: any) => ({
|
||||||
...prevMetadata!,
|
...prevMetadata!,
|
||||||
logo: metahubUrl
|
logo: metahubUrl
|
||||||
}));
|
}));
|
||||||
|
logger.log(`[useMetadataAssets] Set Metahub logo: ${metahubUrl}`);
|
||||||
// Clear fetch in progress flag when done
|
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
return; // Exit if Metahub logo was found
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`[useMetadataAssets] Metahub logo request failed with status ${response.status}`);
|
logger.warn(`[useMetadataAssets] Metahub logo not found for ${imdbId}`);
|
||||||
}
|
}
|
||||||
} catch (metahubError) {
|
} catch (error) {
|
||||||
logger.warn(`[useMetadataAssets] Failed to fetch logo from Metahub:`, metahubError);
|
logger.error(`[useMetadataAssets] Error checking Metahub logo:`, error);
|
||||||
}
|
}
|
||||||
|
} else if (logoPreference === 'tmdb') {
|
||||||
|
// TMDB path - optimized flow
|
||||||
|
let tmdbId: string | null = null;
|
||||||
|
let contentType = type === 'series' ? 'tv' : 'movie';
|
||||||
|
|
||||||
// If Metahub fails, try TMDB as fallback
|
// Extract or find TMDB ID in one step
|
||||||
if (id.startsWith('tmdb:')) {
|
if (id.startsWith('tmdb:')) {
|
||||||
const tmdbId = id.split(':')[1];
|
tmdbId = id.split(':')[1];
|
||||||
const tmdbType = type === 'series' ? 'tv' : 'movie';
|
} else if (imdbId) {
|
||||||
|
// Only look up TMDB ID if we don't already have it
|
||||||
|
try {
|
||||||
|
const tmdbService = TMDBService.getInstance();
|
||||||
|
const foundId = await tmdbService.findTMDBIdByIMDB(imdbId);
|
||||||
|
if (foundId) {
|
||||||
|
tmdbId = String(foundId);
|
||||||
|
setFoundTmdbId(tmdbId); // Save for banner fetching
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[useMetadataAssets] Error finding TMDB ID:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.log(`[useMetadataAssets] Attempting to fetch logo from TMDB as fallback for ${tmdbType} (ID: ${tmdbId})`);
|
if (tmdbId) {
|
||||||
|
try {
|
||||||
const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId);
|
// Direct fetch - avoid multiple service calls
|
||||||
|
const tmdbService = TMDBService.getInstance();
|
||||||
|
const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage);
|
||||||
|
|
||||||
if (logoUrl) {
|
if (logoUrl) {
|
||||||
logger.log(`[useMetadataAssets] Successfully fetched logo from TMDB:
|
|
||||||
- Content Type: ${tmdbType}
|
|
||||||
- TMDB ID: ${tmdbId}
|
|
||||||
- Logo URL: ${logoUrl}
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Update metadata with TMDB logo
|
|
||||||
setMetadata((prevMetadata: any) => ({
|
setMetadata((prevMetadata: any) => ({
|
||||||
...prevMetadata!,
|
...prevMetadata!,
|
||||||
logo: logoUrl
|
logo: logoUrl
|
||||||
}));
|
}));
|
||||||
|
logger.log(`[useMetadataAssets] Set TMDB logo: ${logoUrl}`);
|
||||||
// Clear fetch in progress flag when done
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[useMetadataAssets] Error fetching TMDB logo:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[useMetadataAssets] Error in fetchLogo:`, error);
|
||||||
|
} finally {
|
||||||
logoFetchInProgress.current = false;
|
logoFetchInProgress.current = false;
|
||||||
return; // Exit if TMDB logo was found
|
|
||||||
} else {
|
|
||||||
// If both Metahub and TMDB fail, use the title as text instead of a logo
|
|
||||||
logger.warn(`[useMetadataAssets] No logo found from either Metahub or TMDB for ${type} (ID: ${id}), using title text instead`);
|
|
||||||
|
|
||||||
// Leave logo as null/undefined to trigger fallback to text
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} else { // TMDB first
|
|
||||||
let tmdbLogoUrl: string | null = null;
|
|
||||||
|
|
||||||
// 1. Attempt to fetch TMDB logo
|
// Execute fetch without awaiting
|
||||||
|
fetchLogo();
|
||||||
|
}
|
||||||
|
}, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference]);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Check if we need to refresh the banner based on source
|
||||||
|
const currentPreference = settings.logoSourcePreference || 'metahub';
|
||||||
|
if (bannerSource === currentPreference && forcedBannerRefreshDone.current) {
|
||||||
|
return; // Already have the correct source, no need to refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchBanner = async () => {
|
||||||
|
setLoadingBanner(true);
|
||||||
|
setBannerImage(null); // Clear existing banner to prevent mixed sources
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Extract all possible IDs at once
|
||||||
|
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||||
|
const contentType = type === 'series' ? 'tv' : 'movie';
|
||||||
|
|
||||||
|
// Get TMDB ID once
|
||||||
|
let tmdbId = null;
|
||||||
if (id.startsWith('tmdb:')) {
|
if (id.startsWith('tmdb:')) {
|
||||||
const tmdbId = id.split(':')[1];
|
tmdbId = id.split(':')[1];
|
||||||
const tmdbType = type === 'series' ? 'tv' : 'movie';
|
} else if (foundTmdbId) {
|
||||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
tmdbId = foundTmdbId;
|
||||||
|
} else if ((metadata as any).tmdbId) {
|
||||||
|
tmdbId = (metadata as any).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] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId}, preferred language: ${preferredLanguage})`);
|
|
||||||
try {
|
try {
|
||||||
|
// Use TMDBService instead of direct fetch with hardcoded API key
|
||||||
const tmdbService = TMDBService.getInstance();
|
const tmdbService = TMDBService.getInstance();
|
||||||
tmdbLogoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
|
logger.log(`[useMetadataAssets] Fetching TMDB details for ${endpoint}/${tmdbId}`);
|
||||||
|
|
||||||
if (tmdbLogoUrl) {
|
|
||||||
logger.log(`[useMetadataAssets] Successfully fetched logo from TMDB: ${tmdbLogoUrl}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`[useMetadataAssets] No logo found from TMDB for ${type} (ID: ${tmdbId})`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[useMetadataAssets] Error fetching TMDB logo for ID ${tmdbId}:`, error);
|
|
||||||
}
|
|
||||||
} else if (imdbId) {
|
|
||||||
// If we have IMDB ID but no direct TMDB ID, try to find TMDB ID
|
|
||||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
|
||||||
logger.log(`[useMetadataAssets] Content has IMDB ID (${imdbId}), looking up TMDB ID for TMDB logo, preferred language: ${preferredLanguage}`);
|
|
||||||
try {
|
try {
|
||||||
const tmdbService = TMDBService.getInstance();
|
// Get details with backdrop path using TMDBService
|
||||||
const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId);
|
let details;
|
||||||
|
let images = null;
|
||||||
|
|
||||||
if (foundTmdbId) {
|
// Step 1: Get basic details
|
||||||
logger.log(`[useMetadataAssets] Found TMDB ID ${foundTmdbId} for IMDB ID ${imdbId}`);
|
if (endpoint === 'movie') {
|
||||||
setFoundTmdbId(String(foundTmdbId)); // Save for banner fetching
|
details = await tmdbService.getMovieDetails(tmdbId);
|
||||||
|
|
||||||
tmdbLogoUrl = await tmdbService.getContentLogo(type === 'series' ? 'tv' : 'movie', foundTmdbId.toString(), preferredLanguage);
|
// 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);
|
||||||
|
|
||||||
if (tmdbLogoUrl) {
|
// We'll use the backdrop from the details
|
||||||
logger.log(`[useMetadataAssets] Successfully fetched logo from TMDB via IMDB lookup: ${tmdbLogoUrl}`);
|
logger.log(`[useMetadataAssets] Got movie details for ${tmdbId}`);
|
||||||
} else {
|
} catch (imageError) {
|
||||||
logger.warn(`[useMetadataAssets] No logo found from TMDB via IMDB lookup for ${type} (IMDB: ${imdbId})`);
|
logger.warn(`[useMetadataAssets] Could not get movie images: ${imageError}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`[useMetadataAssets] Could not find TMDB ID for IMDB ID ${imdbId}`);
|
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}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[useMetadataAssets] Error finding TMDB ID or fetching logo for IMDB ID ${imdbId}:`, error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. If TMDB logo was fetched successfully, update and return
|
// Check if we have a backdrop path from details
|
||||||
if (tmdbLogoUrl) {
|
if (details && details.backdrop_path) {
|
||||||
setMetadata((prevMetadata: any) => ({
|
finalBanner = tmdbService.getImageUrl(details.backdrop_path);
|
||||||
...prevMetadata!,
|
bannerSourceType = 'tmdb';
|
||||||
logo: tmdbLogoUrl
|
logger.log(`[useMetadataAssets] Using TMDB backdrop from details: ${finalBanner}`);
|
||||||
}));
|
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// If no backdrop, try poster as fallback
|
||||||
// 3. If TMDB failed, try Metahub as fallback
|
else if (details && details.poster_path) {
|
||||||
logger.log(`[useMetadataAssets] TMDB logo fetch failed or not applicable. Attempting Metahub fallback.`);
|
logger.warn(`[useMetadataAssets] No backdrop available, using poster as fallback`);
|
||||||
if (imdbId) {
|
finalBanner = tmdbService.getImageUrl(details.poster_path);
|
||||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
bannerSourceType = 'tmdb';
|
||||||
logger.log(`[useMetadataAssets] Attempting to fetch logo from Metahub as fallback for ${imdbId}`);
|
}
|
||||||
|
else {
|
||||||
|
logger.warn(`[useMetadataAssets] No backdrop or poster found for ${endpoint}/${tmdbId}`);
|
||||||
|
}
|
||||||
|
} catch (innerErr) {
|
||||||
|
logger.error(`[useMetadataAssets] Error fetching TMDB details/images:`, innerErr);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[useMetadataAssets] TMDB service initialization error:`, err);
|
||||||
|
}
|
||||||
|
} else if (currentPreference === 'metahub' && imdbId) {
|
||||||
|
// Metahub path - verify it exists to prevent broken images
|
||||||
|
const metahubUrl = `https://images.metahub.space/background/medium/${imdbId}/img`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
logger.log(`[useMetadataAssets] Successfully fetched fallback logo from Metahub: ${metahubUrl}`);
|
finalBanner = metahubUrl;
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: metahubUrl }));
|
bannerSourceType = 'metahub';
|
||||||
|
logger.log(`[useMetadataAssets] Using Metahub banner: ${finalBanner}`);
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`[useMetadataAssets] Metahub fallback failed. Using title text.`);
|
logger.warn(`[useMetadataAssets] Metahub banner not found, using default`);
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
|
|
||||||
}
|
|
||||||
} catch (metahubError) {
|
|
||||||
logger.warn(`[useMetadataAssets] Failed to fetch fallback logo from Metahub:`, metahubError);
|
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No IMDB ID for Metahub fallback
|
|
||||||
logger.warn(`[useMetadataAssets] No IMDB ID for Metahub fallback. Using title text.`);
|
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[useMetadataAssets] Failed to fetch logo from all sources:', {
|
logger.error(`[useMetadataAssets] Error checking Metahub banner:`, error);
|
||||||
error,
|
|
||||||
contentId: id,
|
|
||||||
contentType: type
|
|
||||||
});
|
|
||||||
// Fallback to text on general error
|
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
|
|
||||||
} finally {
|
|
||||||
// Clear fetch in progress flag when done
|
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchLogo();
|
|
||||||
} else if (logoFetchInProgress.current) {
|
|
||||||
console.log('[useMetadataAssets] Logo fetch already in progress, skipping');
|
|
||||||
} else if (metadata?.logo) {
|
|
||||||
logger.log(`[useMetadataAssets] Using existing logo from metadata:
|
|
||||||
- Content ID: ${id}
|
|
||||||
- Content Type: ${type}
|
|
||||||
- Logo URL: ${metadata.logo}
|
|
||||||
- Source: ${isMetahubUrl(metadata.logo) ? 'Metahub' : (isTmdbUrl(metadata.logo) ? 'TMDB' : 'Other')}
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
}, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference]);
|
|
||||||
|
|
||||||
// Fetch banner image based on logo source preference
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchBanner = async () => {
|
|
||||||
if (metadata) {
|
|
||||||
setLoadingBanner(true);
|
|
||||||
|
|
||||||
// Clear the banner initially when starting a preference-driven fetch
|
|
||||||
setBannerImage(null);
|
|
||||||
|
|
||||||
let finalBanner: string | null = metadata.banner || metadata.poster; // Default fallback
|
|
||||||
const preference = settings.logoSourcePreference || 'metahub';
|
|
||||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
|
||||||
const apiKey = '439c478a771f35c05022f9feabcca01c'; // Re-using API key
|
|
||||||
|
|
||||||
// Extract IDs
|
|
||||||
let currentTmdbId = null;
|
|
||||||
if (id.startsWith('tmdb:')) {
|
|
||||||
currentTmdbId = id.split(':')[1];
|
|
||||||
} else if (foundTmdbId) {
|
|
||||||
currentTmdbId = foundTmdbId;
|
|
||||||
} else if ((metadata as any).tmdbId) {
|
|
||||||
currentTmdbId = (metadata as any).tmdbId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentImdbId = imdbId;
|
|
||||||
const contentType = type === 'series' ? 'tv' : 'movie';
|
|
||||||
|
|
||||||
logger.log(`[useMetadataAssets] Fetching banner with preference: ${preference}, language: ${preferredLanguage}, TMDB ID: ${currentTmdbId}, IMDB ID: ${currentImdbId}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (preference === 'tmdb') {
|
|
||||||
// 1. Try TMDB first
|
|
||||||
let tmdbBannerUrl: string | null = null;
|
|
||||||
if (currentTmdbId) {
|
|
||||||
logger.log(`[useMetadataAssets] Attempting TMDB banner fetch with ID: ${currentTmdbId}`);
|
|
||||||
try {
|
|
||||||
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
|
|
||||||
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${currentTmdbId}/images?api_key=${apiKey}&include_image_language=${preferredLanguage},en,null`);
|
|
||||||
const imagesData = await response.json();
|
|
||||||
|
|
||||||
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
|
||||||
// Try to find backdrop in preferred language first
|
|
||||||
let backdropPath = null;
|
|
||||||
|
|
||||||
if (preferredLanguage !== 'en') {
|
|
||||||
const preferredBackdrop = imagesData.backdrops.find((backdrop: any) => backdrop.iso_639_1 === preferredLanguage);
|
|
||||||
if (preferredBackdrop) {
|
|
||||||
backdropPath = preferredBackdrop.file_path;
|
|
||||||
logger.log(`[useMetadataAssets] Found ${preferredLanguage} backdrop for ID: ${currentTmdbId}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to English backdrop
|
// If no source-specific banner was found, use default
|
||||||
if (!backdropPath) {
|
if (!finalBanner) {
|
||||||
const englishBackdrop = imagesData.backdrops.find((backdrop: any) => backdrop.iso_639_1 === 'en');
|
finalBanner = metadata.banner || metadata.poster;
|
||||||
if (englishBackdrop) {
|
bannerSourceType = 'default';
|
||||||
backdropPath = englishBackdrop.file_path;
|
logger.log(`[useMetadataAssets] Using default banner: ${finalBanner}`);
|
||||||
logger.log(`[useMetadataAssets] Found English backdrop for ID: ${currentTmdbId}`);
|
|
||||||
} else {
|
|
||||||
// Last resort: use the first backdrop
|
|
||||||
backdropPath = imagesData.backdrops[0].file_path;
|
|
||||||
logger.log(`[useMetadataAssets] Using first available backdrop for ID: ${currentTmdbId}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmdbBannerUrl = `https://image.tmdb.org/t/p/original${backdropPath}`;
|
// Set banner image once at the end
|
||||||
logger.log(`[useMetadataAssets] Found TMDB banner via images endpoint: ${tmdbBannerUrl}`);
|
|
||||||
} else {
|
|
||||||
// Add log for when no backdrops are found
|
|
||||||
logger.warn(`[useMetadataAssets] TMDB API successful, but no backdrops found for ID: ${currentTmdbId}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[useMetadataAssets] Error fetching TMDB banner via images endpoint:`, err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add log for when no TMDB ID is available
|
|
||||||
logger.warn(`[useMetadataAssets] No TMDB ID available to fetch TMDB banner.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmdbBannerUrl) {
|
|
||||||
// TMDB SUCCESS: Set banner and EXIT
|
|
||||||
finalBanner = tmdbBannerUrl;
|
|
||||||
logger.log(`[useMetadataAssets] Setting final banner to TMDB source: ${finalBanner}`);
|
|
||||||
setBannerImage(finalBanner);
|
setBannerImage(finalBanner);
|
||||||
setLoadingBanner(false);
|
setBannerSource(bannerSourceType);
|
||||||
forcedBannerRefreshDone.current = true;
|
|
||||||
return; // <-- Exit here, don't attempt fallback
|
|
||||||
} else {
|
|
||||||
// TMDB FAILED: Proceed to Metahub fallback
|
|
||||||
logger.log(`[useMetadataAssets] TMDB banner failed, trying Metahub fallback.`);
|
|
||||||
if (currentImdbId) {
|
|
||||||
const metahubBannerUrl = `https://images.metahub.space/background/medium/${currentImdbId}/img`;
|
|
||||||
try {
|
|
||||||
const metahubResponse = await fetch(metahubBannerUrl, { method: 'HEAD' });
|
|
||||||
if (metahubResponse.ok) {
|
|
||||||
finalBanner = metahubBannerUrl;
|
|
||||||
logger.log(`[useMetadataAssets] Found Metahub banner as fallback: ${finalBanner}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[useMetadataAssets] Error fetching Metahub fallback banner:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // Preference is Metahub
|
|
||||||
// 1. Try Metahub first
|
|
||||||
let metahubBannerUrl: string | null = null;
|
|
||||||
if (currentImdbId) {
|
|
||||||
const url = `https://images.metahub.space/background/medium/${currentImdbId}/img`;
|
|
||||||
try {
|
|
||||||
const metahubResponse = await fetch(url, { method: 'HEAD' });
|
|
||||||
if (metahubResponse.ok) {
|
|
||||||
metahubBannerUrl = url;
|
|
||||||
logger.log(`[useMetadataAssets] Found Metahub banner: ${metahubBannerUrl}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[useMetadataAssets] Error fetching Metahub banner:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metahubBannerUrl) {
|
|
||||||
// METAHUB SUCCESS: Set banner and EXIT
|
|
||||||
finalBanner = metahubBannerUrl;
|
|
||||||
logger.log(`[useMetadataAssets] Setting final banner to Metahub source: ${finalBanner}`);
|
|
||||||
setBannerImage(finalBanner);
|
|
||||||
setLoadingBanner(false);
|
|
||||||
forcedBannerRefreshDone.current = true;
|
|
||||||
return; // <-- Exit here, don't attempt fallback
|
|
||||||
} else {
|
|
||||||
// METAHUB FAILED: Proceed to TMDB fallback
|
|
||||||
logger.log(`[useMetadataAssets] Metahub banner failed, trying TMDB fallback.`);
|
|
||||||
if (currentTmdbId) {
|
|
||||||
try {
|
|
||||||
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
|
|
||||||
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${currentTmdbId}/images?api_key=${apiKey}`);
|
|
||||||
const imagesData = await response.json();
|
|
||||||
|
|
||||||
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
|
||||||
const backdropPath = imagesData.backdrops[0].file_path;
|
|
||||||
finalBanner = `https://image.tmdb.org/t/p/original${backdropPath}`;
|
|
||||||
logger.log(`[useMetadataAssets] Found TMDB banner as fallback: ${finalBanner}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[useMetadataAssets] Error fetching TMDB fallback banner:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the final determined banner (could be fallback or initial default)
|
|
||||||
setBannerImage(finalBanner);
|
|
||||||
logger.log(`[useMetadataAssets] Final banner set after fallbacks (if any): ${finalBanner}`);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[useMetadataAssets] General error fetching banner:`, error);
|
logger.error(`[useMetadataAssets] Banner fetch error:`, error);
|
||||||
// Fallback to initial banner on general error
|
// Use default banner if error occurred
|
||||||
setBannerImage(metadata.banner || metadata.poster);
|
setBannerImage(metadata.banner || metadata.poster);
|
||||||
|
setBannerSource('default');
|
||||||
} finally {
|
} finally {
|
||||||
// Only set loading to false here if we didn't exit early
|
|
||||||
setLoadingBanner(false);
|
setLoadingBanner(false);
|
||||||
forcedBannerRefreshDone.current = true; // Mark refresh as done
|
forcedBannerRefreshDone.current = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only run fetchBanner if metadata exists and preference/content might have changed
|
|
||||||
// The dependencies array handles triggering this effect
|
|
||||||
fetchBanner();
|
fetchBanner();
|
||||||
|
}, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId, bannerSource]);
|
||||||
|
|
||||||
}, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId]);
|
// Original reset forced refresh effect
|
||||||
|
|
||||||
// Reset forced refresh when preference changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forcedBannerRefreshDone.current) {
|
if (forcedBannerRefreshDone.current) {
|
||||||
logger.log(`[useMetadataAssets] Logo preference changed, resetting banner refresh flag`);
|
logger.log(`[useMetadataAssets] Logo preference changed, resetting banner refresh flag`);
|
||||||
forcedBannerRefreshDone.current = false;
|
forcedBannerRefreshDone.current = false;
|
||||||
// Clear the banner image immediately to prevent showing the wrong source briefly
|
// Clear the banner image immediately to prevent showing the wrong source briefly
|
||||||
setBannerImage(null);
|
setBannerImage(null);
|
||||||
|
setBannerSource(null);
|
||||||
// This will trigger the banner fetch effect to run again
|
// This will trigger the banner fetch effect to run again
|
||||||
}
|
}
|
||||||
}, [settings.logoSourcePreference]);
|
}, [settings.logoSourcePreference]);
|
||||||
|
|
@ -506,5 +309,6 @@ export const useMetadataAssets = (
|
||||||
foundTmdbId,
|
foundTmdbId,
|
||||||
setLogoLoadError,
|
setLogoLoadError,
|
||||||
setBannerImage,
|
setBannerImage,
|
||||||
|
bannerSource, // Export banner source for debugging
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -762,7 +762,7 @@ const LogoSourceSettings = () => {
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<View style={styles.descriptionContainer}>
|
<View style={styles.descriptionContainer}>
|
||||||
<Text style={styles.description}>
|
<Text style={styles.description}>
|
||||||
Choose the primary source for content logos and backgrounds.
|
Choose the primary source for content logos and backgrounds. The selected source will be used exclusively.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -903,7 +903,7 @@ const LogoSourceSettings = () => {
|
||||||
{/* Additional Info */}
|
{/* Additional Info */}
|
||||||
<View style={styles.infoBox}>
|
<View style={styles.infoBox}>
|
||||||
<Text style={styles.infoText}>
|
<Text style={styles.infoText}>
|
||||||
Unavailable logos will fall back to the alternate source, or display text if none found.
|
The app will use only the selected source for logos and backgrounds. If no image is available from your chosen source, a text fallback will be used.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,10 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
const fetchShowData = async () => {
|
const fetchShowData = async () => {
|
||||||
try {
|
try {
|
||||||
const tmdb = TMDBService.getInstance();
|
const tmdb = TMDBService.getInstance();
|
||||||
|
|
||||||
|
// Log the showId being used
|
||||||
|
logger.log(`[ShowRatingsScreen] Fetching show details for ID: ${showId}`);
|
||||||
|
|
||||||
const showData = await tmdb.getTVShowDetails(showId);
|
const showData = await tmdb.getTVShowDetails(showId);
|
||||||
if (showData) {
|
if (showData) {
|
||||||
setShow(showData);
|
setShow(showData);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue