mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-19 15:52:03 +00:00
Enhance logo fetching logic in FeaturedContent and MetadataScreen components
Refactor the FeaturedContent component to dynamically fetch logos based on user preferences for TMDB or Metahub, improving error handling and loading states. Update the MetadataScreen to implement a more robust banner fetching mechanism, utilizing both sources with appropriate fallbacks. Introduce loading indicators and ensure seamless user experience during logo and banner retrieval processes.
This commit is contained in:
parent
e1eb88c9ba
commit
ba834ed3a8
3 changed files with 696 additions and 239 deletions
|
|
@ -28,7 +28,10 @@ import Animated, {
|
|||
} from 'react-native-reanimated';
|
||||
import { StreamingContent } from '../../services/catalogService';
|
||||
import { SkeletonFeatured } from './SkeletonLoaders';
|
||||
import { isValidMetahubLogo, hasValidLogoFormat } from '../../utils/logoUtils';
|
||||
import { isValidMetahubLogo, hasValidLogoFormat, isMetahubUrl, isTmdbUrl } from '../../utils/logoUtils';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { TMDBService } from '../../services/tmdbService';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
interface FeaturedContentProps {
|
||||
featuredContent: StreamingContent | null;
|
||||
|
|
@ -43,11 +46,14 @@ const { width, height } = Dimensions.get('window');
|
|||
|
||||
const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: FeaturedContentProps) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { settings } = useSettings();
|
||||
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
||||
const [bannerUrl, setBannerUrl] = useState<string | null>(null);
|
||||
const prevContentIdRef = useRef<string | null>(null);
|
||||
// Add state for tracking logo load errors
|
||||
const [logoLoadError, setLogoLoadError] = useState(false);
|
||||
// Add a ref to track logo fetch in progress
|
||||
const logoFetchInProgress = useRef<boolean>(false);
|
||||
|
||||
// Animation values
|
||||
const posterOpacity = useSharedValue(0);
|
||||
|
|
@ -107,13 +113,171 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
useEffect(() => {
|
||||
setLogoLoadError(false);
|
||||
}, [featuredContent?.id]);
|
||||
|
||||
// Fetch logo based on preference
|
||||
useEffect(() => {
|
||||
if (!featuredContent || logoFetchInProgress.current) return;
|
||||
|
||||
const fetchLogo = async () => {
|
||||
// Set fetch in progress flag
|
||||
logoFetchInProgress.current = true;
|
||||
|
||||
try {
|
||||
const contentId = featuredContent.id;
|
||||
|
||||
// Get logo source preference from settings
|
||||
const logoPreference = settings.logoSourcePreference || 'metahub'; // Default to metahub if not set
|
||||
|
||||
// Check if current logo matches preferences
|
||||
const currentLogo = featuredContent.logo;
|
||||
if (currentLogo) {
|
||||
const isCurrentMetahub = isMetahubUrl(currentLogo);
|
||||
const isCurrentTmdb = isTmdbUrl(currentLogo);
|
||||
|
||||
// If logo already matches preference, use it
|
||||
if ((logoPreference === 'metahub' && isCurrentMetahub) ||
|
||||
(logoPreference === 'tmdb' && isCurrentTmdb)) {
|
||||
setLogoUrl(currentLogo);
|
||||
logoFetchInProgress.current = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(`[FeaturedContent] Fetching logo with preference: ${logoPreference}, ID: ${contentId}`);
|
||||
|
||||
// Extract IMDB ID if available
|
||||
let imdbId = null;
|
||||
if (featuredContent.id.startsWith('tt')) {
|
||||
// If the ID itself is an IMDB ID
|
||||
imdbId = featuredContent.id;
|
||||
} else if ((featuredContent as any).imdbId) {
|
||||
// Try to get IMDB ID from the content object if available
|
||||
imdbId = (featuredContent as any).imdbId;
|
||||
}
|
||||
|
||||
// Extract TMDB ID if available
|
||||
let tmdbId = null;
|
||||
if (contentId.startsWith('tmdb:')) {
|
||||
tmdbId = contentId.split(':')[1];
|
||||
}
|
||||
|
||||
// First source based on preference
|
||||
if (logoPreference === 'metahub' && imdbId) {
|
||||
// Try to get logo from Metahub first
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Fall back to TMDB if Metahub fails and we have a TMDB ID
|
||||
if (tmdbId) {
|
||||
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId);
|
||||
|
||||
if (logoUrl) {
|
||||
logger.log(`[FeaturedContent] Using fallback TMDB logo: ${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);
|
||||
if (currentLogo) setLogoUrl(currentLogo);
|
||||
}
|
||||
} else if (currentLogo) {
|
||||
// Use existing logo if we don't have TMDB ID
|
||||
setLogoUrl(currentLogo);
|
||||
}
|
||||
} else if (logoPreference === 'tmdb') {
|
||||
// Try to get logo from TMDB first
|
||||
if (tmdbId) {
|
||||
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId);
|
||||
|
||||
if (logoUrl) {
|
||||
logger.log(`[FeaturedContent] Using TMDB logo: ${logoUrl}`);
|
||||
setLogoUrl(logoUrl);
|
||||
logoFetchInProgress.current = false;
|
||||
return; // Exit if TMDB logo was found
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[FeaturedContent] Error fetching TMDB logo:', error);
|
||||
}
|
||||
} else if (imdbId) {
|
||||
// If we have IMDB ID but no TMDB ID, try to find TMDB ID
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId);
|
||||
|
||||
if (foundTmdbId) {
|
||||
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, foundTmdbId.toString());
|
||||
|
||||
if (logoUrl) {
|
||||
logger.log(`[FeaturedContent] Using TMDB logo 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to Metahub if TMDB fails and we have an IMDB ID
|
||||
if (imdbId) {
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
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);
|
||||
if (currentLogo) setLogoUrl(currentLogo);
|
||||
}
|
||||
} else if (currentLogo) {
|
||||
// Use existing logo if we don't have IMDB ID
|
||||
setLogoUrl(currentLogo);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[FeaturedContent] Error fetching logo:', error);
|
||||
if (featuredContent?.logo) setLogoUrl(featuredContent.logo);
|
||||
} finally {
|
||||
// Clear fetch in progress flag
|
||||
logoFetchInProgress.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
fetchLogo();
|
||||
}, [featuredContent?.id, settings.logoSourcePreference]);
|
||||
|
||||
// Load poster and logo
|
||||
useEffect(() => {
|
||||
if (!featuredContent) return;
|
||||
|
||||
const posterUrl = featuredContent.banner || featuredContent.poster;
|
||||
const titleLogo = featuredContent.logo;
|
||||
const contentId = featuredContent.id;
|
||||
|
||||
// Reset states for new content
|
||||
|
|
@ -124,9 +288,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
|
||||
prevContentIdRef.current = contentId;
|
||||
|
||||
// Set URLs immediately for instant display
|
||||
// Set poster URL immediately for instant display
|
||||
if (posterUrl) setBannerUrl(posterUrl);
|
||||
if (titleLogo) setLogoUrl(titleLogo);
|
||||
|
||||
// Load images in background
|
||||
const loadImages = async () => {
|
||||
|
|
@ -142,8 +305,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
}
|
||||
|
||||
// Load logo if available
|
||||
if (titleLogo) {
|
||||
const logoSuccess = await preloadImage(titleLogo);
|
||||
if (logoUrl) {
|
||||
const logoSuccess = await preloadImage(logoUrl);
|
||||
if (logoSuccess) {
|
||||
logoOpacity.value = withDelay(300, withTiming(1, {
|
||||
duration: 500,
|
||||
|
|
@ -152,13 +315,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
} else {
|
||||
// If prefetch fails, mark as error to show title text instead
|
||||
setLogoLoadError(true);
|
||||
console.warn(`[FeaturedContent] Logo prefetch failed, falling back to text: ${titleLogo}`);
|
||||
console.warn(`[FeaturedContent] Logo prefetch failed, falling back to text: ${logoUrl}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadImages();
|
||||
}, [featuredContent?.id]);
|
||||
}, [featuredContent?.id, logoUrl]);
|
||||
|
||||
if (!featuredContent) {
|
||||
return <SkeletonFeatured />;
|
||||
|
|
@ -194,16 +357,16 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
<Animated.View
|
||||
style={[styles.featuredContentContainer as ViewStyle, contentAnimatedStyle]}
|
||||
>
|
||||
{featuredContent.logo && !logoLoadError ? (
|
||||
{logoUrl && !logoLoadError ? (
|
||||
<Animated.View style={logoAnimatedStyle}>
|
||||
<ExpoImage
|
||||
source={{ uri: logoUrl || featuredContent.logo }}
|
||||
source={{ uri: logoUrl }}
|
||||
style={styles.featuredLogo as ImageStyle}
|
||||
contentFit="contain"
|
||||
cachePolicy="memory-disk"
|
||||
transition={400}
|
||||
onError={() => {
|
||||
console.warn(`[FeaturedContent] Logo failed to load: ${featuredContent.logo}`);
|
||||
console.warn(`[FeaturedContent] Logo failed to load: ${logoUrl}`);
|
||||
setLogoLoadError(true);
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
Alert,
|
||||
StatusBar,
|
||||
Platform,
|
||||
ActivityIndicator
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { NavigationProp, useNavigation } from '@react-navigation/native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
|
|
@ -25,6 +25,52 @@ import { logger } from '../utils/logger';
|
|||
// TMDB API key - since the default key might be private in the service, we'll use our own
|
||||
const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c';
|
||||
|
||||
// Define example shows with their IMDB IDs and TMDB IDs
|
||||
const EXAMPLE_SHOWS = [
|
||||
{
|
||||
name: 'Breaking Bad',
|
||||
imdbId: 'tt0903747',
|
||||
tmdbId: '1396',
|
||||
type: 'tv' as const
|
||||
},
|
||||
{
|
||||
name: 'Friends',
|
||||
imdbId: 'tt0108778',
|
||||
tmdbId: '1668',
|
||||
type: 'tv' as const
|
||||
},
|
||||
{
|
||||
name: 'Game of Thrones',
|
||||
imdbId: 'tt0944947',
|
||||
tmdbId: '1399',
|
||||
type: 'tv' as const
|
||||
},
|
||||
{
|
||||
name: 'Stranger Things',
|
||||
imdbId: 'tt4574334',
|
||||
tmdbId: '66732',
|
||||
type: 'tv' as const
|
||||
},
|
||||
{
|
||||
name: 'Squid Game',
|
||||
imdbId: 'tt10919420',
|
||||
tmdbId: '93405',
|
||||
type: 'tv' as const
|
||||
},
|
||||
{
|
||||
name: 'Avatar',
|
||||
imdbId: 'tt0499549',
|
||||
tmdbId: '19995',
|
||||
type: 'movie' as const
|
||||
},
|
||||
{
|
||||
name: 'The Witcher',
|
||||
imdbId: 'tt5180504',
|
||||
tmdbId: '71912',
|
||||
type: 'tv' as const
|
||||
}
|
||||
];
|
||||
|
||||
const LogoSourceSettings = () => {
|
||||
const { settings, updateSetting } = useSettings();
|
||||
const navigation = useNavigation<NavigationProp<any>>();
|
||||
|
|
@ -35,117 +81,125 @@ const LogoSourceSettings = () => {
|
|||
settings.logoSourcePreference || 'metahub'
|
||||
);
|
||||
|
||||
// Add state for example logos
|
||||
// Make sure logoSource stays in sync with settings
|
||||
useEffect(() => {
|
||||
setLogoSource(settings.logoSourcePreference || 'metahub');
|
||||
}, [settings.logoSourcePreference]);
|
||||
|
||||
// Selected example show
|
||||
const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]);
|
||||
|
||||
// Add state for example logos and banners
|
||||
const [tmdbLogo, setTmdbLogo] = useState<string | null>(null);
|
||||
const [metahubLogo, setMetahubLogo] = useState<string | null>(null);
|
||||
const [tmdbBanner, setTmdbBanner] = useState<string | null>(null);
|
||||
const [metahubBanner, setMetahubBanner] = useState<string | null>(null);
|
||||
const [loadingLogos, setLoadingLogos] = useState(true);
|
||||
|
||||
// Load example logos on mount
|
||||
// Load example logos for selected show
|
||||
useEffect(() => {
|
||||
const fetchExampleLogos = async () => {
|
||||
setLoadingLogos(true);
|
||||
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
|
||||
// Specifically search for Breaking Bad
|
||||
const searchResults = await tmdbService.searchTVShow("Breaking Bad");
|
||||
|
||||
if (searchResults && searchResults.length > 0) {
|
||||
// Get Breaking Bad (should be the first result)
|
||||
const breakingBad = searchResults[0];
|
||||
const breakingBadId = breakingBad.id;
|
||||
|
||||
logger.log(`[LogoSourceSettings] Found Breaking Bad with TMDB ID: ${breakingBadId}`);
|
||||
|
||||
// Get the external IDs to get IMDB ID
|
||||
const externalIds = await tmdbService.getShowExternalIds(breakingBadId);
|
||||
|
||||
if (externalIds?.imdb_id) {
|
||||
const imdbId = externalIds.imdb_id;
|
||||
logger.log(`[LogoSourceSettings] Breaking Bad IMDB ID: ${imdbId}`);
|
||||
|
||||
// Get TMDB logo using the images endpoint
|
||||
try {
|
||||
// Manually fetch images from TMDB API
|
||||
const apiKey = TMDB_API_KEY; // Use the TMDB API key
|
||||
const response = await fetch(`https://api.themoviedb.org/3/tv/${breakingBadId}/images?api_key=${apiKey}`);
|
||||
const imagesData = await response.json();
|
||||
|
||||
if (imagesData.logos && imagesData.logos.length > 0) {
|
||||
// Look for English logo first
|
||||
let logoPath = null;
|
||||
|
||||
// First try to find an English logo
|
||||
const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) =>
|
||||
logo.iso_639_1 === 'en'
|
||||
);
|
||||
if (englishLogo) {
|
||||
logoPath = englishLogo.file_path;
|
||||
} else if (imagesData.logos[0]) {
|
||||
// Fallback to the first logo
|
||||
logoPath = imagesData.logos[0].file_path;
|
||||
}
|
||||
|
||||
if (logoPath) {
|
||||
const tmdbLogoUrl = `https://image.tmdb.org/t/p/original${logoPath}`;
|
||||
setTmdbLogo(tmdbLogoUrl);
|
||||
logger.log(`[LogoSourceSettings] Got Breaking Bad TMDB logo: ${tmdbLogoUrl}`);
|
||||
} else {
|
||||
// Fallback to hardcoded Breaking Bad TMDB logo
|
||||
setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png');
|
||||
logger.log(`[LogoSourceSettings] Using fallback Breaking Bad TMDB logo`);
|
||||
}
|
||||
} else {
|
||||
// No logos found in the response
|
||||
setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png');
|
||||
logger.log(`[LogoSourceSettings] No logos found in TMDB response, using fallback`);
|
||||
}
|
||||
} catch (tmdbError) {
|
||||
logger.error(`[LogoSourceSettings] Error fetching TMDB images:`, tmdbError);
|
||||
// Fallback to hardcoded Breaking Bad TMDB logo
|
||||
setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png');
|
||||
}
|
||||
|
||||
// Get Metahub logo
|
||||
const metahubLogoUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
// Check if Metahub logo exists
|
||||
try {
|
||||
const metahubResponse = await fetch(metahubLogoUrl, { method: 'HEAD' });
|
||||
if (metahubResponse.ok) {
|
||||
setMetahubLogo(metahubLogoUrl);
|
||||
logger.log(`[LogoSourceSettings] Got Breaking Bad Metahub logo: ${metahubLogoUrl}`);
|
||||
} else {
|
||||
// Fallback to hardcoded Breaking Bad Metahub logo
|
||||
setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img');
|
||||
logger.log(`[LogoSourceSettings] Using fallback Breaking Bad Metahub logo`);
|
||||
}
|
||||
} catch (metahubErr) {
|
||||
logger.error(`[LogoSourceSettings] Error checking Metahub logo:`, metahubErr);
|
||||
// Fallback to hardcoded Breaking Bad Metahub logo
|
||||
setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(`[LogoSourceSettings] Breaking Bad not found in search results`);
|
||||
// Use hardcoded Breaking Bad logos
|
||||
setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png');
|
||||
setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img');
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('[LogoSourceSettings] Error fetching Breaking Bad logos:', err);
|
||||
|
||||
// Use hardcoded Breaking Bad logos
|
||||
setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png');
|
||||
setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img');
|
||||
} finally {
|
||||
setLoadingLogos(false);
|
||||
}
|
||||
};
|
||||
fetchExampleLogos(selectedShow);
|
||||
}, [selectedShow]);
|
||||
|
||||
// Function to fetch logos and banners for a specific show
|
||||
const fetchExampleLogos = async (show: typeof EXAMPLE_SHOWS[0]) => {
|
||||
setLoadingLogos(true);
|
||||
setTmdbLogo(null);
|
||||
setMetahubLogo(null);
|
||||
setTmdbBanner(null);
|
||||
setMetahubBanner(null);
|
||||
|
||||
fetchExampleLogos();
|
||||
}, []);
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const imdbId = show.imdbId;
|
||||
const tmdbId = show.tmdbId;
|
||||
const contentType = show.type;
|
||||
|
||||
logger.log(`[LogoSourceSettings] Fetching ${show.name} with TMDB ID: ${tmdbId}, IMDB ID: ${imdbId}`);
|
||||
|
||||
// Get TMDB logo and banner
|
||||
try {
|
||||
// Manually fetch images from TMDB API
|
||||
const apiKey = TMDB_API_KEY;
|
||||
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
|
||||
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}/images?api_key=${apiKey}`);
|
||||
const imagesData = await response.json();
|
||||
|
||||
// Get TMDB logo
|
||||
if (imagesData.logos && imagesData.logos.length > 0) {
|
||||
// Look for English logo first
|
||||
let logoPath = null;
|
||||
|
||||
// First try to find an English logo
|
||||
const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) =>
|
||||
logo.iso_639_1 === 'en'
|
||||
);
|
||||
if (englishLogo) {
|
||||
logoPath = englishLogo.file_path;
|
||||
} else if (imagesData.logos[0]) {
|
||||
// Fallback to the first logo
|
||||
logoPath = imagesData.logos[0].file_path;
|
||||
}
|
||||
|
||||
if (logoPath) {
|
||||
const tmdbLogoUrl = `https://image.tmdb.org/t/p/original${logoPath}`;
|
||||
setTmdbLogo(tmdbLogoUrl);
|
||||
logger.log(`[LogoSourceSettings] Got ${show.name} TMDB logo: ${tmdbLogoUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get TMDB banner (backdrop)
|
||||
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
||||
const backdropPath = imagesData.backdrops[0].file_path;
|
||||
const tmdbBannerUrl = `https://image.tmdb.org/t/p/original${backdropPath}`;
|
||||
setTmdbBanner(tmdbBannerUrl);
|
||||
logger.log(`[LogoSourceSettings] Got ${show.name} TMDB banner: ${tmdbBannerUrl}`);
|
||||
} else {
|
||||
// Try to get backdrop from details
|
||||
const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`);
|
||||
const details = await detailsResponse.json();
|
||||
|
||||
if (details.backdrop_path) {
|
||||
const tmdbBannerUrl = `https://image.tmdb.org/t/p/original${details.backdrop_path}`;
|
||||
setTmdbBanner(tmdbBannerUrl);
|
||||
logger.log(`[LogoSourceSettings] Got ${show.name} TMDB banner from details: ${tmdbBannerUrl}`);
|
||||
}
|
||||
}
|
||||
} catch (tmdbError) {
|
||||
logger.error(`[LogoSourceSettings] Error fetching TMDB images:`, tmdbError);
|
||||
}
|
||||
|
||||
// Get Metahub logo and banner
|
||||
try {
|
||||
// Metahub logo
|
||||
const metahubLogoUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
const logoResponse = await fetch(metahubLogoUrl, { method: 'HEAD' });
|
||||
|
||||
if (logoResponse.ok) {
|
||||
setMetahubLogo(metahubLogoUrl);
|
||||
logger.log(`[LogoSourceSettings] Got ${show.name} Metahub logo: ${metahubLogoUrl}`);
|
||||
}
|
||||
|
||||
// Metahub banner
|
||||
const metahubBannerUrl = `https://images.metahub.space/background/medium/${imdbId}/img`;
|
||||
const bannerResponse = await fetch(metahubBannerUrl, { method: 'HEAD' });
|
||||
|
||||
if (bannerResponse.ok) {
|
||||
setMetahubBanner(metahubBannerUrl);
|
||||
logger.log(`[LogoSourceSettings] Got ${show.name} Metahub banner: ${metahubBannerUrl}`);
|
||||
} else if (tmdbBanner) {
|
||||
// If Metahub banner doesn't exist, use TMDB banner
|
||||
setMetahubBanner(tmdbBanner);
|
||||
}
|
||||
} catch (metahubErr) {
|
||||
logger.error(`[LogoSourceSettings] Error checking Metahub images:`, metahubErr);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[LogoSourceSettings] Error fetching ${show.name} logos:`, err);
|
||||
} finally {
|
||||
setLoadingLogos(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Apply setting and show confirmation
|
||||
const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => {
|
||||
|
|
@ -167,13 +221,47 @@ const LogoSourceSettings = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// Save selected show to AsyncStorage to persist across navigation
|
||||
const saveSelectedShow = async (show: typeof EXAMPLE_SHOWS[0]) => {
|
||||
try {
|
||||
await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId);
|
||||
} catch (e) {
|
||||
console.error('Error saving selected show:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Load selected show from AsyncStorage on mount
|
||||
useEffect(() => {
|
||||
const loadSelectedShow = async () => {
|
||||
try {
|
||||
const savedShowId = await AsyncStorage.getItem('logo_settings_selected_show');
|
||||
if (savedShowId) {
|
||||
const foundShow = EXAMPLE_SHOWS.find(show => show.imdbId === savedShowId);
|
||||
if (foundShow) {
|
||||
setSelectedShow(foundShow);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading selected show:', e);
|
||||
}
|
||||
};
|
||||
|
||||
loadSelectedShow();
|
||||
}, []);
|
||||
|
||||
// Update selected show and save to AsyncStorage
|
||||
const handleShowSelect = (show: typeof EXAMPLE_SHOWS[0]) => {
|
||||
setSelectedShow(show);
|
||||
saveSelectedShow(show);
|
||||
};
|
||||
|
||||
// Handle back navigation
|
||||
const handleBack = () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
// Render logo example with loading state
|
||||
const renderLogoExample = (url: string | null, isLoading: boolean) => {
|
||||
// Render logo example with loading state and background
|
||||
const renderLogoExample = (logo: string | null, banner: string | null, isLoading: boolean) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={[styles.exampleImage, styles.loadingContainer]}>
|
||||
|
|
@ -183,11 +271,26 @@ const LogoSourceSettings = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: url || undefined }}
|
||||
style={styles.exampleImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.bannerContainer}>
|
||||
<Image
|
||||
source={{ uri: banner || undefined }}
|
||||
style={styles.bannerImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
<View style={styles.bannerOverlay} />
|
||||
{logo && (
|
||||
<Image
|
||||
source={{ uri: logo }}
|
||||
style={styles.logoOverBanner}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
)}
|
||||
{!logo && (
|
||||
<View style={styles.noLogoContainer}>
|
||||
<Text style={styles.noLogoText}>No logo available</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -217,6 +320,36 @@ const LogoSourceSettings = () => {
|
|||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Show selector */}
|
||||
<View style={styles.showSelectorContainer}>
|
||||
<Text style={styles.selectorLabel}>Select a show/movie to preview:</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.showsScrollContent}
|
||||
>
|
||||
{EXAMPLE_SHOWS.map((show) => (
|
||||
<TouchableOpacity
|
||||
key={show.imdbId}
|
||||
style={[
|
||||
styles.showItem,
|
||||
selectedShow.imdbId === show.imdbId && styles.selectedShowItem
|
||||
]}
|
||||
onPress={() => handleShowSelect(show)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.showItemText,
|
||||
selectedShow.imdbId === show.imdbId && styles.selectedShowItemText
|
||||
]}
|
||||
>
|
||||
{show.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Options */}
|
||||
<View style={styles.optionsContainer}>
|
||||
<TouchableOpacity
|
||||
|
|
@ -240,8 +373,8 @@ const LogoSourceSettings = () => {
|
|||
|
||||
<View style={styles.exampleContainer}>
|
||||
<Text style={styles.exampleLabel}>Example:</Text>
|
||||
{renderLogoExample(metahubLogo, loadingLogos)}
|
||||
<Text style={styles.logoSourceLabel}>Breaking Bad logo from Metahub</Text>
|
||||
{renderLogoExample(metahubLogo, metahubBanner, loadingLogos)}
|
||||
<Text style={styles.logoSourceLabel}>{selectedShow.name} logo from Metahub</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
|
@ -266,8 +399,8 @@ const LogoSourceSettings = () => {
|
|||
|
||||
<View style={styles.exampleContainer}>
|
||||
<Text style={styles.exampleLabel}>Example:</Text>
|
||||
{renderLogoExample(tmdbLogo, loadingLogos)}
|
||||
<Text style={styles.logoSourceLabel}>Breaking Bad logo from TMDB</Text>
|
||||
{renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)}
|
||||
<Text style={styles.logoSourceLabel}>{selectedShow.name} logo from TMDB</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -324,6 +457,39 @@ const styles = StyleSheet.create({
|
|||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
showSelectorContainer: {
|
||||
padding: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
selectorLabel: {
|
||||
color: colors.text,
|
||||
fontSize: 16,
|
||||
marginBottom: 12,
|
||||
},
|
||||
showsScrollContent: {
|
||||
paddingRight: 16,
|
||||
},
|
||||
showItem: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: colors.elevation2,
|
||||
borderRadius: 20,
|
||||
marginRight: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
selectedShowItem: {
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.elevation3,
|
||||
},
|
||||
showItemText: {
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 14,
|
||||
},
|
||||
selectedShowItemText: {
|
||||
color: colors.white,
|
||||
fontWeight: '600',
|
||||
},
|
||||
optionsContainer: {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
|
|
@ -391,6 +557,44 @@ const styles = StyleSheet.create({
|
|||
fontSize: 12,
|
||||
marginTop: 4,
|
||||
},
|
||||
bannerContainer: {
|
||||
height: 120,
|
||||
width: '100%',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
},
|
||||
bannerImage: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
bannerOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
logoOverBanner: {
|
||||
position: 'absolute',
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
alignSelf: 'center',
|
||||
top: '10%',
|
||||
},
|
||||
noLogoContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
noLogoText: {
|
||||
color: colors.white,
|
||||
fontSize: 14,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export default LogoSourceSettings;
|
||||
|
|
@ -222,6 +222,7 @@ const MetadataScreen = () => {
|
|||
// Add state for custom banner
|
||||
const [bannerImage, setBannerImage] = useState<string | null>(null);
|
||||
const forcedBannerRefreshDone = useRef<boolean>(false);
|
||||
const [loadingBanner, setLoadingBanner] = useState<boolean>(false);
|
||||
|
||||
// Add debug log for settings when component mounts
|
||||
useEffect(() => {
|
||||
|
|
@ -278,46 +279,147 @@ const MetadataScreen = () => {
|
|||
// Fetch banner image based on logo source preference
|
||||
useEffect(() => {
|
||||
const fetchBanner = async () => {
|
||||
if (metadata && (!forcedBannerRefreshDone.current || foundTmdbId)) {
|
||||
// Extract any existing TMDB ID if available
|
||||
let tmdbId = null;
|
||||
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 apiKey = '439c478a771f35c05022f9feabcca01c'; // Re-using API key
|
||||
|
||||
// Extract IDs
|
||||
let currentTmdbId = null;
|
||||
if (id.startsWith('tmdb:')) {
|
||||
tmdbId = id.split(':')[1];
|
||||
currentTmdbId = id.split(':')[1];
|
||||
} else if (foundTmdbId) {
|
||||
currentTmdbId = foundTmdbId;
|
||||
} else if ((metadata as any).tmdbId) {
|
||||
currentTmdbId = (metadata as any).tmdbId;
|
||||
}
|
||||
|
||||
// Use our stored TMDB ID if we have one
|
||||
const effectiveTmdbId = foundTmdbId || tmdbId || (metadata as any).tmdbId;
|
||||
const currentImdbId = imdbId;
|
||||
const contentType = type === 'series' ? 'tv' : 'movie';
|
||||
|
||||
logger.log(`[MetadataScreen] Fetching banner with preference: ${settings.logoSourcePreference}, TMDB ID: ${effectiveTmdbId}`);
|
||||
logger.log(`[MetadataScreen] Fetching banner with preference: ${preference}, TMDB ID: ${currentTmdbId}, IMDB ID: ${currentImdbId}`);
|
||||
|
||||
try {
|
||||
// Use our utility function to get the banner based on preference
|
||||
const newBanner = await fetchBannerWithPreference(
|
||||
imdbId,
|
||||
effectiveTmdbId,
|
||||
type as 'movie' | 'series',
|
||||
settings.logoSourcePreference
|
||||
);
|
||||
|
||||
if (newBanner) {
|
||||
logger.log(`[MetadataScreen] Setting new banner: ${newBanner}`);
|
||||
setBannerImage(newBanner);
|
||||
} else {
|
||||
// If no banner found from preferred source, use the existing one from metadata
|
||||
logger.log(`[MetadataScreen] Using existing banner from metadata: ${metadata.banner}`);
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
if (preference === 'tmdb') {
|
||||
// 1. Try TMDB first
|
||||
let tmdbBannerUrl: string | null = null;
|
||||
if (currentTmdbId) {
|
||||
logger.log(`[MetadataScreen] 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}`);
|
||||
const imagesData = await response.json();
|
||||
|
||||
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
||||
const backdropPath = imagesData.backdrops[0].file_path;
|
||||
tmdbBannerUrl = `https://image.tmdb.org/t/p/original${backdropPath}`;
|
||||
logger.log(`[MetadataScreen] Found TMDB banner via images endpoint: ${tmdbBannerUrl}`);
|
||||
} else {
|
||||
// Add log for when no backdrops are found
|
||||
logger.warn(`[MetadataScreen] TMDB API successful, but no backdrops found for ID: ${currentTmdbId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[MetadataScreen] Error fetching TMDB banner via images endpoint:`, err);
|
||||
}
|
||||
} else {
|
||||
// Add log for when no TMDB ID is available
|
||||
logger.warn(`[MetadataScreen] No TMDB ID available to fetch TMDB banner.`);
|
||||
}
|
||||
|
||||
if (tmdbBannerUrl) {
|
||||
// TMDB SUCCESS: Set banner and EXIT
|
||||
finalBanner = tmdbBannerUrl;
|
||||
logger.log(`[MetadataScreen] Setting final banner to TMDB source: ${finalBanner}`);
|
||||
setBannerImage(finalBanner);
|
||||
setLoadingBanner(false);
|
||||
forcedBannerRefreshDone.current = true;
|
||||
return; // <-- Exit here, don't attempt fallback
|
||||
} else {
|
||||
// TMDB FAILED: Proceed to Metahub fallback
|
||||
logger.log(`[MetadataScreen] 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(`[MetadataScreen] Found Metahub banner as fallback: ${finalBanner}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[MetadataScreen] 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(`[MetadataScreen] Found Metahub banner: ${metahubBannerUrl}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[MetadataScreen] Error fetching Metahub banner:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
if (metahubBannerUrl) {
|
||||
// METAHUB SUCCESS: Set banner and EXIT
|
||||
finalBanner = metahubBannerUrl;
|
||||
logger.log(`[MetadataScreen] 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(`[MetadataScreen] 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(`[MetadataScreen] Found TMDB banner as fallback: ${finalBanner}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[MetadataScreen] Error fetching TMDB fallback banner:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the final determined banner (could be fallback or initial default)
|
||||
setBannerImage(finalBanner);
|
||||
logger.log(`[MetadataScreen] Final banner set after fallbacks (if any): ${finalBanner}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[MetadataScreen] Error fetching banner:`, error);
|
||||
// Use existing banner as fallback
|
||||
logger.error(`[MetadataScreen] General error fetching banner:`, error);
|
||||
// Fallback to initial banner on general error
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
} finally {
|
||||
// Only set loading to false here if we didn't exit early
|
||||
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();
|
||||
|
||||
}, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId]);
|
||||
|
||||
// Reset forced refresh when preference changes
|
||||
|
|
@ -325,7 +427,7 @@ const MetadataScreen = () => {
|
|||
if (forcedBannerRefreshDone.current) {
|
||||
logger.log(`[MetadataScreen] Logo preference changed, resetting banner refresh flag`);
|
||||
forcedBannerRefreshDone.current = false;
|
||||
// Clear the banner image to force a new fetch
|
||||
// Clear the banner image immediately to prevent showing the wrong source briefly
|
||||
setBannerImage(null);
|
||||
// This will trigger the banner fetch effect to run again
|
||||
}
|
||||
|
|
@ -582,102 +684,84 @@ const MetadataScreen = () => {
|
|||
}
|
||||
}
|
||||
} else { // TMDB first
|
||||
// Try to get logo from TMDB first
|
||||
let tmdbId = null;
|
||||
const tmdbType = type === 'series' ? 'tv' : 'movie';
|
||||
let tmdbLogoUrl: string | null = null;
|
||||
|
||||
// 1. Attempt to fetch TMDB logo
|
||||
if (id.startsWith('tmdb:')) {
|
||||
// Direct TMDB ID
|
||||
tmdbId = id.split(':')[1];
|
||||
logger.log(`[MetadataScreen] Content has direct TMDB ID: ${tmdbId}`);
|
||||
} else if (id.startsWith('tt')) {
|
||||
// IMDB ID - need to find the corresponding TMDB ID
|
||||
logger.log(`[MetadataScreen] Content has IMDB ID (${id}), looking up TMDB ID`);
|
||||
try {
|
||||
// Use the passed imdbId if available, otherwise use id directly
|
||||
const imdbIdToUse = imdbId || id;
|
||||
logger.log(`[MetadataScreen] Using IMDB ID for lookup: ${imdbIdToUse}`);
|
||||
|
||||
tmdbId = await TMDBService.getInstance().findTMDBIdByIMDB(imdbIdToUse);
|
||||
if (tmdbId) {
|
||||
logger.log(`[MetadataScreen] Found TMDB ID ${tmdbId} for IMDB ID ${imdbIdToUse}`);
|
||||
|
||||
// Save the TMDB ID for banner fetching
|
||||
setFoundTmdbId(String(tmdbId));
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] Could not find TMDB ID for IMDB ID ${imdbIdToUse}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[MetadataScreen] Error finding TMDB ID for IMDB ID ${id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (tmdbId) {
|
||||
const tmdbId = id.split(':')[1];
|
||||
const tmdbType = type === 'series' ? 'tv' : 'movie';
|
||||
logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId})`);
|
||||
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
logger.log(`[MetadataScreen] Calling getContentLogo with type=${tmdbType}, id=${tmdbId}`);
|
||||
tmdbLogoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId);
|
||||
|
||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId);
|
||||
|
||||
if (logoUrl) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched logo from TMDB:
|
||||
- Content Type: ${tmdbType}
|
||||
- TMDB ID: ${tmdbId}
|
||||
- Logo URL: ${logoUrl}
|
||||
`);
|
||||
|
||||
// Update metadata with TMDB logo
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata!,
|
||||
logo: logoUrl
|
||||
}));
|
||||
|
||||
// Clear fetch in progress flag when done
|
||||
logoFetchInProgress.current = false;
|
||||
return; // Exit if TMDB logo was found
|
||||
if (tmdbLogoUrl) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched logo from TMDB: ${tmdbLogoUrl}`);
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId}), trying Metahub`);
|
||||
logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId})`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[MetadataScreen] Error fetching TMDB logo for ID ${tmdbId}:`, error);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] No TMDB ID available, falling back to Metahub`);
|
||||
} else if (imdbId) {
|
||||
// If we have IMDB ID but no direct TMDB ID, try to find TMDB ID
|
||||
logger.log(`[MetadataScreen] Content has IMDB ID (${imdbId}), looking up TMDB ID for TMDB logo`);
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId);
|
||||
|
||||
if (foundTmdbId) {
|
||||
logger.log(`[MetadataScreen] Found TMDB ID ${foundTmdbId} for IMDB ID ${imdbId}`);
|
||||
setFoundTmdbId(String(foundTmdbId)); // Save for banner fetching
|
||||
|
||||
tmdbLogoUrl = await tmdbService.getContentLogo(type === 'series' ? 'tv' : 'movie', foundTmdbId.toString());
|
||||
|
||||
if (tmdbLogoUrl) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched logo from TMDB via IMDB lookup: ${tmdbLogoUrl}`);
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] No logo found from TMDB via IMDB lookup for ${type} (IMDB: ${imdbId})`);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] Could not find TMDB ID for IMDB ID ${imdbId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[MetadataScreen] Error finding TMDB ID or fetching logo for IMDB ID ${imdbId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// If TMDB fails or isn't a TMDB ID, try Metahub as fallback
|
||||
// 2. If TMDB logo was fetched successfully, update and return
|
||||
if (tmdbLogoUrl) {
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata!,
|
||||
logo: tmdbLogoUrl
|
||||
}));
|
||||
logoFetchInProgress.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. If TMDB failed, try Metahub as fallback
|
||||
logger.log(`[MetadataScreen] TMDB logo fetch failed or not applicable. Attempting Metahub fallback.`);
|
||||
if (imdbId) {
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub as fallback for ${imdbId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub:
|
||||
- Content ID: ${id}
|
||||
- Content Type: ${type}
|
||||
- Logo URL: ${metahubUrl}
|
||||
`);
|
||||
|
||||
// Update metadata with Metahub logo
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata!,
|
||||
logo: metahubUrl
|
||||
}));
|
||||
logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub: ${metahubUrl}`);
|
||||
setMetadata(prevMetadata => ({ ...prevMetadata!, logo: metahubUrl }));
|
||||
} else {
|
||||
// If both TMDB and Metahub fail, use the title as text instead of a logo
|
||||
logger.warn(`[MetadataScreen] No logo found from either source for ${type} (ID: ${id}), using title text instead`);
|
||||
|
||||
// Leave logo as null/undefined to trigger fallback to text
|
||||
logger.warn(`[MetadataScreen] Metahub fallback failed. Using title text.`);
|
||||
setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined }));
|
||||
}
|
||||
} catch (metahubError) {
|
||||
logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError);
|
||||
|
||||
// Leave logo as null/undefined to trigger fallback to text
|
||||
logger.warn(`[MetadataScreen] Failed to fetch fallback logo from Metahub:`, metahubError);
|
||||
setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined }));
|
||||
}
|
||||
} else {
|
||||
// No IMDB ID for Metahub fallback
|
||||
logger.warn(`[MetadataScreen] No IMDB ID for Metahub fallback. Using title text.`);
|
||||
setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined }));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -686,6 +770,8 @@ const MetadataScreen = () => {
|
|||
contentId: id,
|
||||
contentType: type
|
||||
});
|
||||
// Fallback to text on general error
|
||||
setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined }));
|
||||
} finally {
|
||||
// Clear fetch in progress flag when done
|
||||
logoFetchInProgress.current = false;
|
||||
|
|
@ -1484,18 +1570,22 @@ const MetadataScreen = () => {
|
|||
<Animated.View style={heroAnimatedStyle}>
|
||||
<View style={styles.heroSection}>
|
||||
{/* Use Animated.Image directly instead of ImageBackground with imageStyle */}
|
||||
<Animated.Image
|
||||
source={{ uri: bannerImage || metadata.banner || metadata.poster }}
|
||||
style={[styles.absoluteFill, parallaxImageStyle]}
|
||||
resizeMode="cover"
|
||||
onError={() => {
|
||||
logger.warn(`[MetadataScreen] Banner failed to load: ${bannerImage}`);
|
||||
// If custom banner fails, fall back to original metadata banner
|
||||
if (bannerImage !== metadata.banner) {
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{loadingBanner ? (
|
||||
<View style={[styles.absoluteFill, { backgroundColor: colors.black }]} />
|
||||
) : (
|
||||
<Animated.Image
|
||||
source={{ uri: bannerImage || metadata.banner || metadata.poster }}
|
||||
style={[styles.absoluteFill, parallaxImageStyle]}
|
||||
resizeMode="cover"
|
||||
onError={() => {
|
||||
logger.warn(`[MetadataScreen] Banner failed to load: ${bannerImage}`);
|
||||
// If custom banner fails, fall back to original metadata banner
|
||||
if (bannerImage !== metadata.banner) {
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<LinearGradient
|
||||
colors={[
|
||||
`${colors.darkBackground}00`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue