mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
fixed backdrop zoom in laoding overlay.
This commit is contained in:
parent
03da6c9a0c
commit
d691189973
4 changed files with 44 additions and 141 deletions
1
react-native-vlc-media-player
Submodule
1
react-native-vlc-media-player
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 22fae0eb0964990c030889e2be3ed7a3d5c92a45
|
||||||
|
|
@ -28,8 +28,6 @@ import Animated, {
|
||||||
import { StreamingContent } from '../../services/catalogService';
|
import { StreamingContent } from '../../services/catalogService';
|
||||||
import { SkeletonFeatured } from './SkeletonLoaders';
|
import { SkeletonFeatured } from './SkeletonLoaders';
|
||||||
import { hasValidLogoFormat, isTmdbUrl } from '../../utils/logoUtils';
|
import { hasValidLogoFormat, isTmdbUrl } from '../../utils/logoUtils';
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
|
||||||
import { TMDBService } from '../../services/tmdbService';
|
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
|
||||||
|
|
@ -150,15 +148,11 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
||||||
const [showSkeleton, setShowSkeleton] = useState(true);
|
const [showSkeleton, setShowSkeleton] = useState(true);
|
||||||
const [logoError, setLogoError] = useState(false);
|
const [logoError, setLogoError] = useState(false);
|
||||||
const [bannerError, setBannerError] = useState(false);
|
const [bannerError, setBannerError] = useState(false);
|
||||||
const { settings } = useSettings();
|
|
||||||
const logoOpacity = useSharedValue(0);
|
const logoOpacity = useSharedValue(0);
|
||||||
const bannerOpacity = useSharedValue(0);
|
const bannerOpacity = useSharedValue(0);
|
||||||
const posterOpacity = useSharedValue(0);
|
const posterOpacity = useSharedValue(0);
|
||||||
const prevContentIdRef = useRef<string | null>(null);
|
const prevContentIdRef = useRef<string | null>(null);
|
||||||
// Add state for tracking logo load errors
|
|
||||||
const [logoLoadError, setLogoLoadError] = useState(false);
|
const [logoLoadError, setLogoLoadError] = useState(false);
|
||||||
// Add a ref to track logo fetch in progress
|
|
||||||
const logoFetchInProgress = useRef<boolean>(false);
|
|
||||||
const firstRenderTsRef = useRef<number>(nowMs());
|
const firstRenderTsRef = useRef<number>(nowMs());
|
||||||
const lastContentChangeTsRef = useRef<number>(0);
|
const lastContentChangeTsRef = useRef<number>(0);
|
||||||
|
|
||||||
|
|
@ -251,130 +245,29 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
||||||
setLogoLoadError(false);
|
setLogoLoadError(false);
|
||||||
}, [featuredContent?.id]);
|
}, [featuredContent?.id]);
|
||||||
|
|
||||||
// Fetch logo when enrichment is enabled; otherwise only use addon logo
|
// Use logo from featuredContent data (already processed by useFeaturedContent hook)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!featuredContent || logoFetchInProgress.current) return;
|
if (!featuredContent) {
|
||||||
|
setLogoUrl(null);
|
||||||
|
setLogoLoadError(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchLogo = async () => {
|
// Simply use the logo that's already been processed by the useFeaturedContent hook
|
||||||
logoFetchInProgress.current = true;
|
const logo = featuredContent.logo;
|
||||||
const t0 = nowMs();
|
logger.info('[FeaturedContent] using logo from data', {
|
||||||
logger.info('[FeaturedContent] fetchLogo:start', { id: featuredContent?.id, type: featuredContent?.type });
|
id: featuredContent.id,
|
||||||
|
name: featuredContent.name,
|
||||||
try {
|
hasLogo: Boolean(logo),
|
||||||
const contentId = featuredContent.id;
|
logo: logo,
|
||||||
const contentData = featuredContent; // Use a clearer variable name
|
logoSource: logo ? (isTmdbUrl(logo) ? 'tmdb' : 'addon') : 'none',
|
||||||
const currentLogo = contentData.logo;
|
type: featuredContent.type
|
||||||
|
|
||||||
// Get language preference (only relevant when enrichment is enabled)
|
|
||||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
|
||||||
|
|
||||||
// If enrichment is disabled, use addon logo and don't fetch from external sources
|
|
||||||
if (!settings.enrichMetadataWithTMDB) {
|
|
||||||
logger.info('[FeaturedContent] enrichment disabled, checking for addon logo', {
|
|
||||||
hasLogo: !!contentData.logo,
|
|
||||||
logo: contentData.logo,
|
|
||||||
isExternal: contentData.logo ? isTmdbUrl(contentData.logo) : false,
|
|
||||||
isTmdb: contentData.logo ? isTmdbUrl(contentData.logo) : false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// If we have an addon logo, use it and don't fetch external logos
|
setLogoUrl(logo || null);
|
||||||
if (contentData.logo) {
|
setLogoLoadError(!logo);
|
||||||
logger.info('[FeaturedContent] enrichment disabled, using addon logo', { logo: contentData.logo });
|
setLogoError(false); // Reset any previous errors
|
||||||
setLogoUrl(contentData.logo);
|
}, [featuredContent]);
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If no addon logo, don't fetch external logos when enrichment is disabled
|
|
||||||
logger.info('[FeaturedContent] enrichment disabled, no addon logo available');
|
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset state for new fetch only if switching to a different item
|
|
||||||
if (prevContentIdRef.current !== contentId) {
|
|
||||||
setLogoUrl(null);
|
|
||||||
}
|
|
||||||
setLogoLoadError(false);
|
|
||||||
|
|
||||||
// Extract IDs (only when enrichment is enabled)
|
|
||||||
let imdbId: string | null = null;
|
|
||||||
if (contentData.id.startsWith('tt')) {
|
|
||||||
imdbId = contentData.id;
|
|
||||||
} else if ((contentData as any).imdbId) {
|
|
||||||
imdbId = (contentData as any).imdbId;
|
|
||||||
} else if ((contentData as any).externalIds?.imdb_id) {
|
|
||||||
imdbId = (contentData as any).externalIds.imdb_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmdbId: string | null = null;
|
|
||||||
if (contentData.id.startsWith('tmdb:')) {
|
|
||||||
tmdbId = contentData.id.split(':')[1];
|
|
||||||
} else if ((contentData as any).tmdb_id) {
|
|
||||||
tmdbId = String((contentData as any).tmdb_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we only have IMDB ID, try to find TMDB ID proactively (only when enrichment is enabled)
|
|
||||||
if (imdbId && !tmdbId) {
|
|
||||||
try {
|
|
||||||
const tmdbService = TMDBService.getInstance();
|
|
||||||
const foundData = await tmdbService.findTMDBIdByIMDB(imdbId);
|
|
||||||
if (foundData) {
|
|
||||||
tmdbId = String(foundData);
|
|
||||||
}
|
|
||||||
} catch (findError) {
|
|
||||||
// logger.warn(`[FeaturedContent] Failed to find TMDB ID for ${imdbId}:`, findError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmdbType = contentData.type === 'series' ? 'tv' : 'movie';
|
|
||||||
let finalLogoUrl: string | null = null;
|
|
||||||
let primaryAttempted = false;
|
|
||||||
let fallbackAttempted = false;
|
|
||||||
|
|
||||||
// --- Logo Fetching Logic (TMDB only when enrichment is enabled) ---
|
|
||||||
logger.debug('[FeaturedContent] fetchLogo:ids', { imdbId, tmdbId, lang: preferredLanguage });
|
|
||||||
|
|
||||||
// Try TMDB if we have a TMDB id
|
|
||||||
if (tmdbId) {
|
|
||||||
primaryAttempted = true;
|
|
||||||
try {
|
|
||||||
const tmdbService = TMDBService.getInstance();
|
|
||||||
const tTmdb = nowMs();
|
|
||||||
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
|
|
||||||
if (logoUrl) {
|
|
||||||
finalLogoUrl = logoUrl;
|
|
||||||
logger.debug('[FeaturedContent] fetchLogo:tmdb:ok', { url: logoUrl, duration: since(tTmdb) });
|
|
||||||
}
|
|
||||||
} catch (error) { /* Log if needed */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Set Final Logo ---
|
|
||||||
if (finalLogoUrl) {
|
|
||||||
setLogoUrl(finalLogoUrl);
|
|
||||||
logger.info('[FeaturedContent] fetchLogo:done', { id: contentId, result: 'tmdb', url: finalLogoUrl, duration: since(t0) });
|
|
||||||
} else if (currentLogo) {
|
|
||||||
// Use existing logo only if primary and fallback failed or weren't applicable
|
|
||||||
setLogoUrl(currentLogo);
|
|
||||||
logger.info('[FeaturedContent] fetchLogo:done', { id: contentId, result: 'addon', url: currentLogo, duration: since(t0) });
|
|
||||||
} else {
|
|
||||||
// No logo found from any source
|
|
||||||
setLogoLoadError(true);
|
|
||||||
logger.warn('[FeaturedContent] fetchLogo:none', { id: contentId, primaryAttempted, fallbackAttempted, duration: since(t0) });
|
|
||||||
// logger.warn(`[FeaturedContent] No logo found for ${contentData.name} (${contentId}) with preference ${logoPreference}. Primary attempted: ${primaryAttempted}, Fallback attempted: ${fallbackAttempted}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// logger.error('[FeaturedContent] Error in fetchLogo:', error);
|
|
||||||
setLogoLoadError(true);
|
|
||||||
logger.error('[FeaturedContent] fetchLogo:error', { error: String(error), duration: since(t0) });
|
|
||||||
} finally {
|
|
||||||
logoFetchInProgress.current = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Trigger fetch when content changes
|
|
||||||
fetchLogo();
|
|
||||||
}, [featuredContent, settings.tmdbLanguagePreference, settings.enrichMetadataWithTMDB]);
|
|
||||||
|
|
||||||
// Load poster and logo
|
// Load poster and logo
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -475,17 +368,20 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
||||||
// Load logo if available with enhanced timing
|
// Load logo if available with enhanced timing
|
||||||
if (logoUrl) {
|
if (logoUrl) {
|
||||||
const tLogo = nowMs();
|
const tLogo = nowMs();
|
||||||
|
// Try to preload but don't fail if it times out - still show the logo
|
||||||
const logoSuccess = await preloadImage(logoUrl);
|
const logoSuccess = await preloadImage(logoUrl);
|
||||||
if (logoSuccess) {
|
if (logoSuccess) {
|
||||||
|
logger.debug('[FeaturedContent] logo:preload:success', { id: contentId, duration: since(tLogo) });
|
||||||
|
} else {
|
||||||
|
logger.debug('[FeaturedContent] logo:preload:failed', { id: contentId, duration: since(tLogo) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always animate in the logo since we have the URL
|
||||||
logoOpacity.value = withDelay(500, withTiming(1, {
|
logoOpacity.value = withDelay(500, withTiming(1, {
|
||||||
duration: 600,
|
duration: 600,
|
||||||
easing: Easing.out(Easing.cubic)
|
easing: Easing.out(Easing.cubic)
|
||||||
}));
|
}));
|
||||||
logger.debug('[FeaturedContent] logo:ready', { id: contentId, duration: since(tLogo) });
|
logger.debug('[FeaturedContent] logo:animated', { id: contentId });
|
||||||
} else {
|
|
||||||
setLogoLoadError(true);
|
|
||||||
logger.warn('[FeaturedContent] logo:failed', { id: contentId, duration: since(tLogo) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.info('[FeaturedContent] images:load:done', { id: contentId, total: since(t0) });
|
logger.info('[FeaturedContent] images:load:done', { id: contentId, total: since(t0) });
|
||||||
};
|
};
|
||||||
|
|
@ -495,7 +391,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
||||||
|
|
||||||
const onLogoLoadError = () => {
|
const onLogoLoadError = () => {
|
||||||
setLogoLoaded(true); // Treat error as "loaded" to stop spinner
|
setLogoLoaded(true); // Treat error as "loaded" to stop spinner
|
||||||
setLogoError(true);
|
setLogoLoadError(true);
|
||||||
logger.warn('[FeaturedContent] logo:onError', { id: featuredContent?.id, url: logoUrl });
|
logger.warn('[FeaturedContent] logo:onError', { id: featuredContent?.id, url: logoUrl });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState } from 'react-native';
|
import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState, Image } from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video';
|
import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video';
|
||||||
import FastImage from '@d11/react-native-fast-image';
|
import FastImage from '@d11/react-native-fast-image';
|
||||||
|
|
@ -3129,10 +3129,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
opacity: backdropImageOpacityAnim
|
opacity: backdropImageOpacityAnim
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<FastImage
|
<Image
|
||||||
source={{ uri: backdrop }}
|
source={{ uri: backdrop }}
|
||||||
style={StyleSheet.absoluteFillObject}
|
style={StyleSheet.absoluteFillObject}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode="cover"
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ export function useFeaturedContent() {
|
||||||
logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none',
|
logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none',
|
||||||
logo: c.logo || undefined,
|
logo: c.logo || undefined,
|
||||||
}));
|
}));
|
||||||
logger.debug('[useFeaturedContent] catalogs:logos:details', { items: details });
|
logger.info('[useFeaturedContent] catalogs:logos:details (enrich=true)', { items: details });
|
||||||
} catch {}
|
} catch {}
|
||||||
} else {
|
} else {
|
||||||
// When enrichment is disabled, prefer addon-provided logos; if missing, fetch basic meta to pull logo (like HeroSection)
|
// When enrichment is disabled, prefer addon-provided logos; if missing, fetch basic meta to pull logo (like HeroSection)
|
||||||
|
|
@ -257,7 +257,7 @@ export function useFeaturedContent() {
|
||||||
logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none',
|
logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none',
|
||||||
logo: c.logo || undefined,
|
logo: c.logo || undefined,
|
||||||
}));
|
}));
|
||||||
logger.debug('[useFeaturedContent] catalogs:logos:details (no-enrich)', { items: details });
|
logger.info('[useFeaturedContent] catalogs:logos:details (no-enrich)', { items: details });
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,6 +295,12 @@ export function useFeaturedContent() {
|
||||||
if (formattedContent.length > 0) {
|
if (formattedContent.length > 0) {
|
||||||
persistentStore.featuredContent = formattedContent[0];
|
persistentStore.featuredContent = formattedContent[0];
|
||||||
setFeaturedContent(formattedContent[0]);
|
setFeaturedContent(formattedContent[0]);
|
||||||
|
logger.info('[useFeaturedContent] setting featuredContent', {
|
||||||
|
id: formattedContent[0].id,
|
||||||
|
name: formattedContent[0].name,
|
||||||
|
hasLogo: Boolean(formattedContent[0].logo),
|
||||||
|
logo: formattedContent[0].logo
|
||||||
|
});
|
||||||
currentIndexRef.current = 0;
|
currentIndexRef.current = 0;
|
||||||
// Persist cache for fast startup (skipped when cache disabled)
|
// Persist cache for fast startup (skipped when cache disabled)
|
||||||
if (!DISABLE_CACHE) {
|
if (!DISABLE_CACHE) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue