diff --git a/nuvio-source.json b/nuvio-source.json index 06059a82..0ac08b83 100644 --- a/nuvio-source.json +++ b/nuvio-source.json @@ -11,7 +11,7 @@ "apps": [ { "name": "Nuvio", - "bundleIdentifier": "com.nuvio.app", + "bundleIdentifier": "com.nuvio.hub", "developerName": "Tapframe", "subtitle": "Media player and discovery app", "localizedDescription": "Nuvio is a media player and metadata discovery application for user-provided and user-installed sources.", @@ -272,4 +272,4 @@ } ], "news": [] -} +} \ No newline at end of file diff --git a/src/components/home/AppleTVHero.tsx b/src/components/home/AppleTVHero.tsx index 18a1e0a1..35976293 100644 --- a/src/components/home/AppleTVHero.tsx +++ b/src/components/home/AppleTVHero.tsx @@ -460,7 +460,7 @@ const AppleTVHero: React.FC = ({ // Fetch video list from TMDB to get the YouTube video ID const tmdbApiKey = await TMDBService.getInstance().getApiKey(); const videosRes = await fetch( - `https://api.themoviedb.org/3/${contentType}/${tmdbId}/videos?api_key=${tmdbApiKey}&language=en-US` + `https://api.themoviedb.org/3/${contentType}/${tmdbId}/videos?api_key=${tmdbApiKey}` ); if (!alive) return; @@ -475,9 +475,8 @@ const AppleTVHero: React.FC = ({ const videosData = await videosRes.json(); const results: any[] = videosData.results ?? []; - // Pick best YouTube trailer: official trailer > any trailer > teaser > any YouTube video + // Pick best YouTube trailer: any trailer > teaser > any YouTube video const pick = - results.find((v) => v.site === 'YouTube' && v.type === 'Trailer' && v.official) ?? results.find((v) => v.site === 'YouTube' && v.type === 'Trailer') ?? results.find((v) => v.site === 'YouTube' && v.type === 'Teaser') ?? results.find((v) => v.site === 'YouTube'); diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 67f6221c..32743cc9 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -1155,8 +1155,9 @@ const HeroSection: React.FC = memo(({ logger.info('HeroSection', `Fetching TMDB videos for ${metadata.name} (tmdbId: ${resolvedTmdbId})`); // Fetch video list from TMDB to get the YouTube video ID + const tmdbApiKey = await TMDBService.getInstance().getApiKey(); const videosRes = await fetch( - `https://api.themoviedb.org/3/${contentType}/${resolvedTmdbId}/videos?api_key=d131017ccc6e5462a81c9304d21476de&language=en-US` + `https://api.themoviedb.org/3/${contentType}/${resolvedTmdbId}/videos?api_key=${tmdbApiKey}` ); if (!alive) return; @@ -1170,9 +1171,8 @@ const HeroSection: React.FC = memo(({ const videosData = await videosRes.json(); const results: any[] = videosData.results ?? []; - // Pick best YouTube trailer: official trailer > any trailer > teaser > any YouTube video + // Pick best YouTube trailer: any trailer > teaser > any YouTube video const pick = - results.find((v) => v.site === 'YouTube' && v.type === 'Trailer' && v.official) ?? results.find((v) => v.site === 'YouTube' && v.type === 'Trailer') ?? results.find((v) => v.site === 'YouTube' && v.type === 'Teaser') ?? results.find((v) => v.site === 'YouTube'); @@ -1612,29 +1612,13 @@ const HeroSection: React.FC = memo(({ )} - {/* Hidden preload trailer player - loads in background */} - {shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && !trailerPreloaded && ( - - - - )} - - {/* Visible trailer player - rendered on top with fade transition and parallax */} - {shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && trailerPreloaded && ( + {/* Single trailer player - starts hidden (opacity 0), fades in when ready */} + {shouldLoadSecondaryData && settings?.showTrailers && trailerUrl && !trailerLoading && !trailerError && ( = memo(({ const handleVideoError = useCallback((error: any) => { logger.error('TrailerModal', 'Video error:', error); - // Check if this is a permission/network error that might benefit from retry const errorCode = error?.error?.code; const isRetryableError = errorCode === -1102 || errorCode === -1009 || errorCode === -1005; if (isRetryableError && retryCount < 2) { - // Silent retry - increment count and try again logger.info('TrailerModal', `Retrying video load (attempt ${retryCount + 1}/2)`); setRetryCount(prev => prev + 1); - // Small delay before retry to avoid rapid-fire attempts - setTimeout(() => { - if (videoRef.current) { - // Force video to reload by changing the source briefly - setTrailerUrl(null); - setTimeout(() => { - if (trailerUrl) { - setTrailerUrl(trailerUrl); - } - }, 100); - } - }, 1000); + // Capture current URL before clearing it + setTrailerUrl(current => { + const urlToRestore = current; + setTimeout(() => { + setTrailerUrl(urlToRestore); + }, 500); + return null; // Clear first to force remount + }); return; } - // After 2 retries or for non-retryable errors, show the error logger.error('TrailerModal', 'Video error after retries or non-retryable:', error); setError('Unable to play trailer. Please try again.'); setLoading(false); - }, [retryCount, trailerUrl]); + }, [retryCount]); const handleTrailerEnd = useCallback(() => { setIsPlaying(false); @@ -270,7 +263,18 @@ const TrailerModal: React.FC = memo(({