mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
trailer scroll fix
This commit is contained in:
parent
b81435be29
commit
665ff06ad1
4 changed files with 113 additions and 200 deletions
|
|
@ -1106,6 +1106,9 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
// Guards to avoid repeated auto-starts
|
||||
const startedOnFocusRef = useRef(false);
|
||||
const startedOnReadyRef = useRef(false);
|
||||
// Debounced pause/resume flag to avoid blocking scroll
|
||||
const pendingPauseResumeSV = useSharedValue(0); // 0 = no action, 1 = pause, 2 = resume
|
||||
const pauseResumeTimerRef = useRef<any>(null);
|
||||
|
||||
// Animation values for trailer unmute effects
|
||||
const actionButtonsOpacity = useSharedValue(1);
|
||||
|
|
@ -1672,6 +1675,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
}, [isFocused, setTrailerPlaying]);
|
||||
|
||||
// Ultra-optimized scroll-based pause/resume with cached calculations
|
||||
// This worklet only sets flags - actual pause/resume happens asynchronously to avoid blocking scroll
|
||||
useDerivedValue(() => {
|
||||
'worklet';
|
||||
try {
|
||||
|
|
@ -1684,21 +1688,49 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
const isPlaying = isPlayingSV.value === 1;
|
||||
const isPausedByScroll = pausedByScrollSV.value === 1;
|
||||
|
||||
// Optimized pause/resume logic with minimal branching
|
||||
// Set flags for pause/resume - don't execute immediately to keep scroll smooth
|
||||
if (y > pauseThreshold && isPlaying && !isPausedByScroll) {
|
||||
pausedByScrollSV.value = 1;
|
||||
runOnJS(setTrailerPlaying)(false);
|
||||
isPlayingSV.value = 0;
|
||||
pendingPauseResumeSV.value = 1; // Request pause
|
||||
} else if (y < resumeThreshold && isPausedByScroll) {
|
||||
pausedByScrollSV.value = 0;
|
||||
runOnJS(setTrailerPlaying)(true);
|
||||
isPlayingSV.value = 1;
|
||||
pendingPauseResumeSV.value = 2; // Request resume
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent error handling for performance
|
||||
}
|
||||
});
|
||||
|
||||
// Debounced pause/resume effect - executes asynchronously to keep scroll smooth
|
||||
useEffect(() => {
|
||||
const checkPendingAction = () => {
|
||||
const pendingAction = pendingPauseResumeSV.value;
|
||||
|
||||
if (pendingAction === 1) {
|
||||
// Execute pause
|
||||
pausedByScrollSV.value = 1;
|
||||
setTrailerPlaying(false);
|
||||
isPlayingSV.value = 0;
|
||||
pendingPauseResumeSV.value = 0; // Clear flag
|
||||
} else if (pendingAction === 2) {
|
||||
// Execute resume
|
||||
pausedByScrollSV.value = 0;
|
||||
setTrailerPlaying(true);
|
||||
isPlayingSV.value = 1;
|
||||
pendingPauseResumeSV.value = 0; // Clear flag
|
||||
}
|
||||
};
|
||||
|
||||
// Set up a recurring check with small delay to avoid blocking scroll
|
||||
const intervalId = setInterval(checkPendingAction, 100);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
if (pauseResumeTimerRef.current) {
|
||||
clearTimeout(pauseResumeTimerRef.current);
|
||||
pauseResumeTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [setTrailerPlaying, pendingPauseResumeSV, pausedByScrollSV, isPlayingSV]);
|
||||
|
||||
// Memory management and cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -132,16 +132,14 @@ export function useFeaturedContent() {
|
|||
genres: (item as any).genres,
|
||||
inLibrary: Boolean((item as any).inLibrary),
|
||||
};
|
||||
|
||||
try {
|
||||
// If enrichment is disabled, use addon logo if available
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (base.logo && !isTmdbUrl(base.logo)) {
|
||||
return base;
|
||||
}
|
||||
return { ...base, logo: undefined };
|
||||
// When enrichment is OFF, keep addon logo or undefined
|
||||
return { ...base, logo: base.logo || undefined };
|
||||
}
|
||||
|
||||
// Only proceed with TMDB enrichment if enrichment is enabled
|
||||
// When enrichment is ON, fetch from TMDB with language preference
|
||||
const rawId = String(item.id);
|
||||
const isTmdb = rawId.startsWith('tmdb:');
|
||||
const isImdb = rawId.startsWith('tt');
|
||||
|
|
@ -154,17 +152,15 @@ export function useFeaturedContent() {
|
|||
const found = await tmdbService.findTMDBIdByIMDB(imdbId);
|
||||
tmdbId = found ? String(found) : null;
|
||||
}
|
||||
if (!tmdbId && !imdbId) return base;
|
||||
// Try TMDB if we have a TMDB id
|
||||
|
||||
if (tmdbId) {
|
||||
const logoUrl = await tmdbService.getContentLogo(item.type === 'series' ? 'tv' : 'movie', tmdbId as string, preferredLanguage);
|
||||
if (logoUrl) {
|
||||
return { ...base, logo: logoUrl };
|
||||
}
|
||||
return { ...base, logo: logoUrl || undefined }; // TMDB logo or undefined (no addon fallback)
|
||||
}
|
||||
return base;
|
||||
|
||||
return { ...base, logo: undefined }; // No TMDB ID means no logo
|
||||
} catch (error) {
|
||||
return base;
|
||||
return { ...base, logo: undefined }; // Error means no logo
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -599,6 +599,17 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
logger.error('Failed to fetch credits for movie:', error);
|
||||
}
|
||||
|
||||
// Fetch movie logo from TMDB
|
||||
try {
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const logoUrl = await tmdbService.getContentLogo('movie', tmdbId, preferredLanguage);
|
||||
formattedMovie.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for movie ${tmdbId} from TMDB`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch logo from TMDB:', error);
|
||||
formattedMovie.logo = undefined; // Error means no logo
|
||||
}
|
||||
|
||||
setMetadata(formattedMovie);
|
||||
cacheService.setMetadata(id, type, formattedMovie);
|
||||
const isInLib = catalogService.getLibraryItems().some(item => item.id === id);
|
||||
|
|
@ -664,14 +675,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Fetch TV show logo from TMDB
|
||||
try {
|
||||
const logoUrl = await tmdbService.getTvShowImages(tmdbId);
|
||||
if (logoUrl) {
|
||||
formattedShow.logo = logoUrl;
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
}
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const logoUrl = await tmdbService.getContentLogo('tv', tmdbId, preferredLanguage);
|
||||
formattedShow.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch logo from TMDB:', error);
|
||||
// Continue with execution, logo is optional
|
||||
formattedShow.logo = undefined; // Error means no logo
|
||||
}
|
||||
|
||||
setMetadata(formattedShow);
|
||||
|
|
@ -878,6 +888,55 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (__DEV__) console.log('[useMetadata] failed to merge localized TMDB text', e);
|
||||
}
|
||||
|
||||
// Centralized logo fetching logic
|
||||
try {
|
||||
if (settings.enrichMetadataWithTMDB) {
|
||||
// Only use TMDB logos when enrichment is ON
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const contentType = type === 'series' ? 'tv' : 'movie';
|
||||
|
||||
// Get TMDB ID
|
||||
let tmdbIdForLogo = null;
|
||||
if (tmdbId) {
|
||||
tmdbIdForLogo = String(tmdbId);
|
||||
} else if (finalMetadata.imdb_id) {
|
||||
const foundId = await tmdbService.findTMDBIdByIMDB(finalMetadata.imdb_id);
|
||||
tmdbIdForLogo = foundId ? String(foundId) : null;
|
||||
}
|
||||
|
||||
if (tmdbIdForLogo) {
|
||||
const logoUrl = await tmdbService.getContentLogo(contentType, tmdbIdForLogo, preferredLanguage);
|
||||
finalMetadata.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadata] Logo fetch result:', {
|
||||
contentType,
|
||||
tmdbIdForLogo,
|
||||
preferredLanguage,
|
||||
logoUrl: !!logoUrl,
|
||||
enrichmentEnabled: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
finalMetadata.logo = undefined; // No TMDB ID means no logo
|
||||
if (__DEV__) console.log('[useMetadata] No TMDB ID found for logo, will show text title');
|
||||
}
|
||||
} else {
|
||||
// When enrichment is OFF, keep addon logo or undefined
|
||||
finalMetadata.logo = finalMetadata.logo || undefined;
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadata] TMDB enrichment disabled, using addon logo:', {
|
||||
hasAddonLogo: !!finalMetadata.logo,
|
||||
enrichmentEnabled: false
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle error silently, keep existing logo behavior
|
||||
if (__DEV__) console.error('[useMetadata] Unexpected error in logo fetch:', error);
|
||||
finalMetadata.logo = undefined;
|
||||
}
|
||||
|
||||
// Commit final metadata once and cache it
|
||||
// Clear banner field if TMDB enrichment is enabled to prevent flash
|
||||
if (settings.enrichMetadataWithTMDB) {
|
||||
|
|
|
|||
|
|
@ -62,13 +62,6 @@ export const useMetadataAssets = (
|
|||
// Add source tracking to prevent mixing sources
|
||||
const [bannerSource, setBannerSource] = useState<'tmdb' | 'metahub' | 'default' | null>(null);
|
||||
|
||||
// State for logo loading
|
||||
const [logoLoadError, setLogoLoadError] = useState(false);
|
||||
const logoFetchInProgress = useRef<boolean>(false);
|
||||
const logoRefreshCounter = useRef<number>(0);
|
||||
const MAX_LOGO_REFRESHES = 2;
|
||||
const forcedLogoRefreshDone = useRef<boolean>(false);
|
||||
|
||||
// For TMDB ID tracking
|
||||
const [foundTmdbId, setFoundTmdbId] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -78,173 +71,8 @@ export const useMetadataAssets = (
|
|||
setBannerImage(null);
|
||||
setBannerSource(null);
|
||||
forcedBannerRefreshDone.current = false;
|
||||
forcedLogoRefreshDone.current = false;
|
||||
logoRefreshCounter.current = 0;
|
||||
|
||||
// Mark that we need to refetch logo but DON'T clear it yet
|
||||
// This prevents text from showing during the transition
|
||||
logoFetchInProgress.current = false;
|
||||
}, [settings.logoSourcePreference]);
|
||||
|
||||
// Original reset logo load error effect
|
||||
useEffect(() => {
|
||||
setLogoLoadError(false);
|
||||
}, [metadata?.logo]);
|
||||
|
||||
// Optimized logo fetching
|
||||
useEffect(() => {
|
||||
const logoPreference = settings.logoSourcePreference || 'tmdb';
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadataAssets] Logo fetch triggered:', {
|
||||
id,
|
||||
type,
|
||||
logoPreference,
|
||||
hasImdbId: !!imdbId,
|
||||
tmdbEnrichmentEnabled: settings.enrichMetadataWithTMDB,
|
||||
logoFetchInProgress: logoFetchInProgress.current
|
||||
});
|
||||
}
|
||||
const currentLogoUrl = metadata?.logo;
|
||||
let shouldFetchLogo = false;
|
||||
|
||||
// If enrichment is disabled, use addon logo and don't fetch from external sources
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
// If we have an addon logo, preload it immediately for instant display
|
||||
if (metadata?.logo && !isTmdbUrl(metadata.logo)) {
|
||||
// Preload addon logo for instant display
|
||||
FastImage.preload([{ uri: metadata.logo }]);
|
||||
// This is an addon logo, keep it
|
||||
return;
|
||||
}
|
||||
// If no addon logo, don't fetch external logos when enrichment is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
// When TMDB enrichment is ON, remove addon logos immediately
|
||||
// We don't want to show addon logos briefly before TMDB logos load
|
||||
if (settings.enrichMetadataWithTMDB && currentLogoUrl && !isTmdbUrl(currentLogoUrl)) {
|
||||
// Clear addon logo when enrichment is enabled
|
||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined }));
|
||||
shouldFetchLogo = true;
|
||||
}
|
||||
// Determine if we need to fetch a new logo (only when enrichment is enabled)
|
||||
else if (!currentLogoUrl) {
|
||||
shouldFetchLogo = true;
|
||||
} else {
|
||||
const isCurrentLogoTmdb = isTmdbUrl(currentLogoUrl);
|
||||
|
||||
if (logoPreference === 'tmdb' && !isCurrentLogoTmdb) {
|
||||
shouldFetchLogo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against infinite loops by checking if we're already fetching
|
||||
if (shouldFetchLogo && !logoFetchInProgress.current) {
|
||||
logoFetchInProgress.current = true;
|
||||
|
||||
const fetchLogo = async () => {
|
||||
// Store the original logo to restore if needed
|
||||
const originalLogoUrl = currentLogoUrl;
|
||||
|
||||
try {
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
|
||||
if (logoPreference === 'tmdb') {
|
||||
// TMDB path - optimized flow
|
||||
let tmdbId: string | null = null;
|
||||
let contentType = type === 'series' ? 'tv' : 'movie';
|
||||
|
||||
// Extract or find TMDB ID in one step
|
||||
if (id.startsWith('tmdb:')) {
|
||||
tmdbId = id.split(':')[1];
|
||||
} else if (imdbId && settings.enrichMetadataWithTMDB) {
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const foundId = await tmdbService.findTMDBIdByIMDB(imdbId);
|
||||
if (foundId) {
|
||||
tmdbId = String(foundId);
|
||||
setFoundTmdbId(tmdbId); // Save for banner fetching
|
||||
} else if (__DEV__) {
|
||||
console.log('[useMetadataAssets] Could not find TMDB ID for IMDB:', imdbId);
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('[useMetadataAssets] Error finding TMDB ID:', error);
|
||||
}
|
||||
} else {
|
||||
const parsedId = parseInt(id, 10);
|
||||
if (!isNaN(parsedId)) {
|
||||
tmdbId = String(parsedId);
|
||||
}
|
||||
}
|
||||
|
||||
if (tmdbId) {
|
||||
try {
|
||||
// Direct fetch - avoid multiple service calls
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage);
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadataAssets] Logo fetch result:', {
|
||||
contentType,
|
||||
tmdbId,
|
||||
preferredLanguage,
|
||||
logoUrl,
|
||||
logoPreference
|
||||
});
|
||||
}
|
||||
|
||||
if (logoUrl) {
|
||||
// Preload the image before setting it
|
||||
await FastImage.preload([{ uri: logoUrl }]);
|
||||
|
||||
// Only update if we got a valid logo
|
||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl }));
|
||||
} else {
|
||||
// TMDB logo not found
|
||||
// When enrichment is ON, don't fallback to addon logos - show text instead
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadataAssets] No TMDB logo found for ID:', tmdbId);
|
||||
}
|
||||
// Keep logo as undefined to show text title
|
||||
}
|
||||
} catch (error) {
|
||||
// TMDB logo fetch failed
|
||||
// When enrichment is ON, don't fallback to addon logos - show text instead
|
||||
if (__DEV__) {
|
||||
console.error('[useMetadataAssets] Logo fetch error:', error);
|
||||
}
|
||||
// Keep logo as undefined to show text title
|
||||
}
|
||||
} else {
|
||||
// No TMDB ID found
|
||||
// When enrichment is ON, don't use addon logos - show text instead
|
||||
if (__DEV__) console.log('[useMetadataAssets] No TMDB ID found, will show text title');
|
||||
// Keep logo as undefined to show text title
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle error silently, keep existing logo
|
||||
if (__DEV__) console.error('[useMetadataAssets] Unexpected error in logo fetch:', error);
|
||||
} finally {
|
||||
logoFetchInProgress.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute fetch without awaiting
|
||||
fetchLogo();
|
||||
}
|
||||
}, [
|
||||
id,
|
||||
type,
|
||||
imdbId,
|
||||
metadata?.logo,
|
||||
settings.logoSourcePreference,
|
||||
settings.tmdbLanguagePreference,
|
||||
settings.enrichMetadataWithTMDB,
|
||||
setMetadata
|
||||
]);
|
||||
|
||||
// Optimized banner fetching
|
||||
const fetchBanner = useCallback(async () => {
|
||||
if (!metadata) return;
|
||||
|
|
@ -354,9 +182,7 @@ export const useMetadataAssets = (
|
|||
return {
|
||||
bannerImage,
|
||||
loadingBanner,
|
||||
logoLoadError,
|
||||
foundTmdbId,
|
||||
setLogoLoadError,
|
||||
setBannerImage,
|
||||
bannerSource,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue