episodes not fetching backdrop fix tablet layout

This commit is contained in:
tapframe 2025-10-23 01:57:04 +05:30
parent ef1c34a9c0
commit 786e06b27f
4 changed files with 75 additions and 39 deletions

View file

@ -128,6 +128,7 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
const backdropOpacity = useSharedValue(0); const backdropOpacity = useSharedValue(0);
const backdropScale = useSharedValue(1.05); const backdropScale = useSharedValue(1.05);
const [backdropLoaded, setBackdropLoaded] = useState(false); const [backdropLoaded, setBackdropLoaded] = useState(false);
const [backdropError, setBackdropError] = useState(false);
// Animation values for content panels // Animation values for content panels
const leftPanelOpacity = useSharedValue(0); const leftPanelOpacity = useSharedValue(0);
@ -135,10 +136,46 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
const rightPanelOpacity = useSharedValue(0); const rightPanelOpacity = useSharedValue(0);
const rightPanelTranslateX = useSharedValue(30); const rightPanelTranslateX = useSharedValue(30);
// Get the backdrop source // Get the backdrop source - prioritize episode thumbnail, then show backdrop, then poster
const backdropSource = episodeImage ? { uri: episodeImage } : // For episodes without thumbnails, use show's backdrop instead of poster
bannerImage ? { uri: bannerImage } : const backdropSource = React.useMemo(() => {
metadata?.poster ? { uri: metadata.poster } : undefined; // Debug logging
if (__DEV__) {
console.log('[TabletStreamsLayout] Backdrop source selection:', {
episodeImage,
bannerImage,
metadataPoster: metadata?.poster,
episodeImageIsPoster: episodeImage === metadata?.poster,
backdropError
});
}
// If episodeImage failed to load, skip it and use backdrop
if (backdropError && episodeImage && episodeImage !== metadata?.poster) {
if (__DEV__) console.log('[TabletStreamsLayout] Episode thumbnail failed, falling back to backdrop');
if (bannerImage) {
if (__DEV__) console.log('[TabletStreamsLayout] Using show backdrop (episode failed):', bannerImage);
return { uri: bannerImage };
}
}
// If episodeImage exists and is not the same as poster, use it (real episode thumbnail)
if (episodeImage && episodeImage !== metadata?.poster && !backdropError) {
if (__DEV__) console.log('[TabletStreamsLayout] Using episode thumbnail:', episodeImage);
return { uri: episodeImage };
}
// If episodeImage is the same as poster (fallback case), prioritize backdrop
if (bannerImage) {
if (__DEV__) console.log('[TabletStreamsLayout] Using show backdrop:', bannerImage);
return { uri: bannerImage };
}
// No fallback to poster images
if (__DEV__) console.log('[TabletStreamsLayout] No backdrop source found');
return undefined;
}, [episodeImage, bannerImage, metadata?.poster, backdropError]);
// Animate backdrop when it loads // Animate backdrop when it loads
useEffect(() => { useEffect(() => {
@ -173,18 +210,17 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
} }
}, [backdropSource?.uri, backdropLoaded]); }, [backdropSource?.uri, backdropLoaded]);
// Reset animation when source changes // Reset animation when episode changes
useEffect(() => { useEffect(() => {
if (backdropSource?.uri) { backdropOpacity.value = 0;
backdropOpacity.value = 0; backdropScale.value = 1.05;
backdropScale.value = 1.05; leftPanelOpacity.value = 0;
leftPanelOpacity.value = 0; leftPanelTranslateX.value = -30;
leftPanelTranslateX.value = -30; rightPanelOpacity.value = 0;
rightPanelOpacity.value = 0; rightPanelTranslateX.value = 30;
rightPanelTranslateX.value = 30; setBackdropLoaded(false);
setBackdropLoaded(false); setBackdropError(false);
} }, [episodeImage]);
}, [backdropSource?.uri]);
// Animated styles for backdrop // Animated styles for backdrop
const backdropAnimatedStyle = useAnimatedStyle(() => ({ const backdropAnimatedStyle = useAnimatedStyle(() => ({
@ -206,6 +242,12 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
const handleBackdropLoad = () => { const handleBackdropLoad = () => {
setBackdropLoaded(true); setBackdropLoaded(true);
}; };
const handleBackdropError = () => {
if (__DEV__) console.log('[TabletStreamsLayout] Backdrop image failed to load:', backdropSource?.uri);
setBackdropError(true);
setBackdropLoaded(false);
};
const renderStreamContent = () => { const renderStreamContent = () => {
if (showNoSourcesError) { if (showNoSourcesError) {
@ -284,7 +326,7 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
theme={currentTheme} theme={currentTheme}
showLogos={settings.showScraperLogos} showLogos={settings.showScraperLogos}
scraperLogo={(item.addonId && scraperLogos[item.addonId]) || (item as any).addon ? scraperLogos[(item.addonId || (item as any).addon) as string] || null : null} scraperLogo={(item.addonId && scraperLogos[item.addonId]) || (item as any).addon ? scraperLogos[(item.addonId || (item as any).addon) as string] || null : null}
showAlert={(t, m) => openAlert(t, m)} showAlert={(t: string, m: string) => openAlert(t, m)}
parentTitle={metadata?.name} parentTitle={metadata?.name}
parentType={type as 'movie' | 'series'} parentType={type as 'movie' | 'series'}
parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined} parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined}
@ -332,6 +374,7 @@ const TabletStreamsLayout: React.FC<TabletStreamsLayoutProps> = ({
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
resizeMode={FastImage.resizeMode.cover} resizeMode={FastImage.resizeMode.cover}
onLoad={handleBackdropLoad} onLoad={handleBackdropLoad}
onError={handleBackdropError}
/> />
</Animated.View> </Animated.View>
<LinearGradient <LinearGradient

View file

@ -875,6 +875,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
} }
// Commit final metadata once and cache it // Commit final metadata once and cache it
// Clear banner field if TMDB enrichment is enabled to prevent flash
if (settings.enrichMetadataWithTMDB) {
finalMetadata = {
...finalMetadata,
banner: undefined, // Let useMetadataAssets handle banner via TMDB
};
}
setMetadata(finalMetadata); setMetadata(finalMetadata);
cacheService.setMetadata(id, type, finalMetadata); cacheService.setMetadata(id, type, finalMetadata);
const isInLib = catalogService.getLibraryItems().some(item => item.id === id); const isInLib = catalogService.getLibraryItems().some(item => item.id === id);

View file

@ -251,16 +251,10 @@ export const useMetadataAssets = (
setLoadingBanner(true); setLoadingBanner(true);
// Show fallback banner immediately to prevent blank state
const fallbackBanner = metadata?.banner || metadata?.poster || null;
if (fallbackBanner && !bannerImage) {
setBannerImage(fallbackBanner);
setBannerSource('default');
}
// If enrichment is disabled, use addon banner and don't fetch from external sources // If enrichment is disabled, use addon banner and don't fetch from external sources
if (!settings.enrichMetadataWithTMDB) { if (!settings.enrichMetadataWithTMDB) {
const addonBanner = metadata?.banner || metadata?.poster || null; const addonBanner = metadata?.banner || null;
if (addonBanner && addonBanner !== bannerImage) { if (addonBanner && addonBanner !== bannerImage) {
setBannerImage(addonBanner); setBannerImage(addonBanner);
setBannerSource('default'); setBannerSource('default');
@ -312,15 +306,6 @@ export const useMetadataAssets = (
finalBanner = tmdbService.getImageUrl(details.backdrop_path); finalBanner = tmdbService.getImageUrl(details.backdrop_path);
bannerSourceType = 'tmdb'; bannerSourceType = 'tmdb';
// Preload the image
if (finalBanner) {
FastImage.preload([{ uri: finalBanner }]);
}
}
else if (details?.poster_path) {
finalBanner = tmdbService.getImageUrl(details.poster_path);
bannerSourceType = 'tmdb';
// Preload the image // Preload the image
if (finalBanner) { if (finalBanner) {
FastImage.preload([{ uri: finalBanner }]); FastImage.preload([{ uri: finalBanner }]);
@ -332,9 +317,9 @@ export const useMetadataAssets = (
} }
} }
// Final fallback to metadata // Final fallback to metadata banner only
if (!finalBanner) { if (!finalBanner) {
finalBanner = metadata?.banner || metadata?.poster || null; finalBanner = metadata?.banner || null;
bannerSourceType = 'default'; bannerSourceType = 'default';
} }
@ -346,8 +331,8 @@ export const useMetadataAssets = (
forcedBannerRefreshDone.current = true; forcedBannerRefreshDone.current = true;
} catch (error) { } catch (error) {
// Use default banner on error // Use default banner on error (only addon banner)
const defaultBanner = metadata?.banner || metadata?.poster || null; const defaultBanner = metadata?.banner || null;
if (defaultBanner !== bannerImage) { if (defaultBanner !== bannerImage) {
setBannerImage(defaultBanner); setBannerImage(defaultBanner);
setBannerSource('default'); setBannerSource('default');

View file

@ -227,7 +227,7 @@ export const StreamsScreen = () => {
// Get backdrop from metadata assets // Get backdrop from metadata assets
const setMetadataStub = useCallback(() => {}, []); const setMetadataStub = useCallback(() => {}, []);
const memoizedSettings = useMemo(() => settings, [settings.logoSourcePreference, settings.tmdbLanguagePreference]); const memoizedSettings = useMemo(() => settings, [settings.logoSourcePreference, settings.tmdbLanguagePreference, settings.enrichMetadataWithTMDB]);
const { bannerImage } = useMetadataAssets(metadata, id, type, imdbId, memoizedSettings, setMetadataStub); const { bannerImage } = useMetadataAssets(metadata, id, type, imdbId, memoizedSettings, setMetadataStub);
// Create styles using current theme colors // Create styles using current theme colors
@ -1532,8 +1532,9 @@ export const StreamsScreen = () => {
const path = currentEpisode.still_path || hydratedStill || ''; const path = currentEpisode.still_path || hydratedStill || '';
return tmdbService.getImageUrl(path, 'original'); return tmdbService.getImageUrl(path, 'original');
} }
return metadata?.poster || null; // No poster fallback
}, [currentEpisode, metadata, episodeThumbnail, tmdbEpisodeOverride?.still_path]); return null;
}, [currentEpisode, metadata, episodeThumbnail, tmdbEpisodeOverride?.still_path, settings.enrichMetadataWithTMDB]);
// Effective TMDB fields for hero (series) // Effective TMDB fields for hero (series)
const effectiveEpisodeVote = useMemo(() => { const effectiveEpisodeVote = useMemo(() => {