episodes not fetching backdrop fix tablet layout
This commit is contained in:
parent
ef1c34a9c0
commit
786e06b27f
4 changed files with 75 additions and 39 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue