usemetadata maximum depth limit reached potential fix

This commit is contained in:
tapframe 2025-09-10 15:06:40 +05:30
parent 43f6f056c0
commit ccde944bfa
2 changed files with 121 additions and 68 deletions

View file

@ -723,6 +723,13 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
const shimmerOpacity = useSharedValue(0.3); const shimmerOpacity = useSharedValue(0.3);
const trailerOpacity = useSharedValue(0); const trailerOpacity = useSharedValue(0);
const thumbnailOpacity = useSharedValue(1); const thumbnailOpacity = useSharedValue(1);
// Scroll-based pause/resume control
const pausedByScrollSV = useSharedValue(0);
const scrollGuardEnabledSV = useSharedValue(0);
const isPlayingSV = useSharedValue(0);
// Guards to avoid repeated auto-starts
const startedOnFocusRef = useRef(false);
const startedOnReadyRef = useRef(false);
// Animation values for trailer unmute effects // Animation values for trailer unmute effects
const actionButtonsOpacity = useSharedValue(1); const actionButtonsOpacity = useSharedValue(1);
@ -753,14 +760,18 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
// Smooth transition: fade out thumbnail, fade in trailer // Smooth transition: fade out thumbnail, fade in trailer
thumbnailOpacity.value = withTiming(0, { duration: 500 }); thumbnailOpacity.value = withTiming(0, { duration: 500 });
trailerOpacity.value = withTiming(1, { duration: 500 }); trailerOpacity.value = withTiming(1, { duration: 500 });
// Enable scroll guard after a brief delay to avoid immediate pause on entry
scrollGuardEnabledSV.value = 0;
setTimeout(() => { scrollGuardEnabledSV.value = 1; }, 1000);
}, [thumbnailOpacity, trailerOpacity, trailerPreloaded]); }, [thumbnailOpacity, trailerOpacity, trailerPreloaded]);
// Ensure trailer state is properly synchronized when trailer becomes ready // Auto-start trailer when ready on initial entry if enabled
useEffect(() => { useEffect(() => {
if (trailerReady && settings?.showTrailers && !globalTrailerPlaying) { if (trailerReady && settings?.showTrailers && !globalTrailerPlaying && !startedOnReadyRef.current) {
// Only start trailer if it's the initial load, not when returning from other screens startedOnReadyRef.current = true;
// This prevents auto-starting when returning from StreamsScreen logger.info('HeroSection', 'Trailer ready - auto-starting playback');
logger.info('HeroSection', 'Trailer ready but not playing - not auto-starting to prevent unwanted playback'); setTrailerPlaying(true);
isPlayingSV.value = 1;
} }
}, [trailerReady, settings?.showTrailers, globalTrailerPlaying, setTrailerPlaying]); }, [trailerReady, settings?.showTrailers, globalTrailerPlaying, setTrailerPlaying]);
@ -1036,25 +1047,61 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
useCallback(() => { useCallback(() => {
// Screen is focused - only resume trailer if it was previously playing and got interrupted // Screen is focused - only resume trailer if it was previously playing and got interrupted
logger.info('HeroSection', 'Screen focused'); logger.info('HeroSection', 'Screen focused');
// If trailers are enabled and not playing, start playback (unless scrolled past resume threshold)
// Don't automatically resume trailer when returning from other screens if (settings?.showTrailers) {
// This prevents the trailer from starting when returning from StreamsScreen setTimeout(() => {
// The trailer should only resume if the user explicitly wants it to play try {
const y = (scrollY as any).value || 0;
const resumeThreshold = heroHeight.value * 0.4;
if (y < resumeThreshold && !startedOnFocusRef.current && isPlayingSV.value === 0) {
setTrailerPlaying(true);
isPlayingSV.value = 1;
startedOnFocusRef.current = true;
}
} catch (_e) {
if (!startedOnFocusRef.current && isPlayingSV.value === 0) {
setTrailerPlaying(true);
isPlayingSV.value = 1;
startedOnFocusRef.current = true;
}
}
}, 50);
}
return () => { return () => {
// Stop trailer when leaving this screen to prevent background playback/heat // Stop trailer when leaving this screen to prevent background playback/heat
logger.info('HeroSection', 'Screen unfocused - stopping trailer playback'); logger.info('HeroSection', 'Screen unfocused - stopping trailer playback');
setTrailerPlaying(false); setTrailerPlaying(false);
isPlayingSV.value = 0;
startedOnFocusRef.current = false;
startedOnReadyRef.current = false;
}; };
}, [setTrailerPlaying]) }, [setTrailerPlaying, settings?.showTrailers])
); );
// Pause trailer when the hero is scrolled substantially off-screen // Mirror playing state to shared value to use inside worklets
useEffect(() => {
isPlayingSV.value = globalTrailerPlaying ? 1 : 0;
}, [globalTrailerPlaying]);
// Pause/resume trailer based on scroll with hysteresis and guard
useDerivedValue(() => { useDerivedValue(() => {
'worklet';
try { try {
const threshold = heroHeight.value * 0.6; if (!scrollGuardEnabledSV.value) return;
if (scrollY.value > threshold) { const pauseThreshold = heroHeight.value * 0.7; // pause when beyond 70%
const resumeThreshold = heroHeight.value * 0.4; // resume when back within 40%
const y = scrollY.value;
if (y > pauseThreshold && isPlayingSV.value === 1 && pausedByScrollSV.value === 0) {
pausedByScrollSV.value = 1;
runOnJS(setTrailerPlaying)(false); runOnJS(setTrailerPlaying)(false);
isPlayingSV.value = 0;
} else if (y < resumeThreshold && pausedByScrollSV.value === 1) {
pausedByScrollSV.value = 0;
runOnJS(setTrailerPlaying)(true);
isPlayingSV.value = 1;
} }
} catch (e) { } catch (e) {
// no-op // no-op

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { StreamingContent } from '../services/catalogService'; import { StreamingContent } from '../services/catalogService';
import { catalogService } from '../services/catalogService'; import { catalogService } from '../services/catalogService';
import { stremioService } from '../services/stremioService'; import { stremioService } from '../services/stremioService';
@ -134,6 +134,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const [availableStreams, setAvailableStreams] = useState<{ [sourceType: string]: Stream }>({}); const [availableStreams, setAvailableStreams] = useState<{ [sourceType: string]: Stream }>({});
const [scraperStatuses, setScraperStatuses] = useState<ScraperStatus[]>([]); const [scraperStatuses, setScraperStatuses] = useState<ScraperStatus[]>([]);
const [activeFetchingScrapers, setActiveFetchingScrapers] = useState<string[]>([]); const [activeFetchingScrapers, setActiveFetchingScrapers] = useState<string[]>([]);
// Prevent re-initializing season selection repeatedly for the same series
const initializedSeasonRef = useRef(false);
// Add hook for persistent seasons // Add hook for persistent seasons
const { getSeason, saveSeason } = usePersistentSeasons(); const { getSeason, saveSeason } = usePersistentSeasons();
@ -591,12 +593,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setGroupedEpisodes(groupedAddonEpisodes); setGroupedEpisodes(groupedAddonEpisodes);
// Set the first available season // Determine initial season only once per series
const seasons = Object.keys(groupedAddonEpisodes).map(Number); const seasons = Object.keys(groupedAddonEpisodes).map(Number);
const firstSeason = Math.min(...seasons); const firstSeason = Math.min(...seasons);
logger.log(`📺 Setting season ${firstSeason} as selected (${groupedAddonEpisodes[firstSeason]?.length || 0} episodes)`); if (!initializedSeasonRef.current) {
setSelectedSeason(firstSeason); const nextSeason = firstSeason;
setEpisodes(groupedAddonEpisodes[firstSeason] || []); if (selectedSeason !== nextSeason) {
logger.log(`📺 Setting season ${nextSeason} as selected (${groupedAddonEpisodes[nextSeason]?.length || 0} episodes)`);
setSelectedSeason(nextSeason);
}
setEpisodes(groupedAddonEpisodes[nextSeason] || []);
initializedSeasonRef.current = true;
} else {
// Keep current selection; refresh episode list for selected season
setEpisodes(groupedAddonEpisodes[selectedSeason] || []);
}
// Try to get TMDB ID for additional metadata (cast, etc.) but don't override episodes // Try to get TMDB ID for additional metadata (cast, etc.) but don't override episodes
const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id); const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id);
@ -640,61 +651,55 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Get the first available season as fallback // Get the first available season as fallback
const firstSeason = Math.min(...Object.keys(allEpisodes).map(Number)); const firstSeason = Math.min(...Object.keys(allEpisodes).map(Number));
// Check for watch progress to auto-select season if (!initializedSeasonRef.current) {
let selectedSeasonNumber = firstSeason; // Check for watch progress to auto-select season
let selectedSeasonNumber = firstSeason;
try { try {
// Check watch progress for auto-season selection const allProgress = await storageService.getAllWatchProgress();
const allProgress = await storageService.getAllWatchProgress(); let mostRecentEpisodeId = '';
let mostRecentTimestamp = 0;
// Find the most recently watched episode for this series Object.entries(allProgress).forEach(([key, progress]) => {
let mostRecentEpisodeId = ''; if (key.includes(`series:${id}:`)) {
let mostRecentTimestamp = 0; const episodeId = key.split(`series:${id}:`)[1];
if (progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
Object.entries(allProgress).forEach(([key, progress]) => { mostRecentTimestamp = progress.lastUpdated;
if (key.includes(`series:${id}:`)) { mostRecentEpisodeId = episodeId;
const episodeId = key.split(`series:${id}:`)[1]; }
if (progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
mostRecentTimestamp = progress.lastUpdated;
mostRecentEpisodeId = episodeId;
} }
} });
}); if (mostRecentEpisodeId) {
const parts = mostRecentEpisodeId.split(':');
if (mostRecentEpisodeId) { if (parts.length === 3) {
// Parse season number from episode ID const watchProgressSeason = parseInt(parts[1], 10);
const parts = mostRecentEpisodeId.split(':'); if (transformedEpisodes[watchProgressSeason]) {
if (parts.length === 3) { selectedSeasonNumber = watchProgressSeason;
const watchProgressSeason = parseInt(parts[1], 10); logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for ${mostRecentEpisodeId}`);
if (transformedEpisodes[watchProgressSeason]) { }
selectedSeasonNumber = watchProgressSeason; } else {
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for ${mostRecentEpisodeId}`); const allEpisodesList = Object.values(transformedEpisodes).flat();
const episode = allEpisodesList.find(ep => ep.stremioId === mostRecentEpisodeId);
if (episode) {
selectedSeasonNumber = episode.season_number;
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for episode with stremioId ${mostRecentEpisodeId}`);
}
} }
} else { } else {
// Try to find episode by stremioId to get season selectedSeasonNumber = getSeason(id, firstSeason);
const allEpisodesList = Object.values(transformedEpisodes).flat(); logger.log(`[useMetadata] No watch progress found, using persistent season ${selectedSeasonNumber}`);
const episode = allEpisodesList.find(ep => ep.stremioId === mostRecentEpisodeId);
if (episode) {
selectedSeasonNumber = episode.season_number;
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for episode with stremioId ${mostRecentEpisodeId}`);
}
} }
} else { } catch (error) {
// No watch progress found, use persistent storage as fallback logger.error('[useMetadata] Error checking watch progress for season selection:', error);
selectedSeasonNumber = getSeason(id, firstSeason); selectedSeasonNumber = getSeason(id, firstSeason);
logger.log(`[useMetadata] No watch progress found, using persistent season ${selectedSeasonNumber}`);
} }
} catch (error) { if (selectedSeason !== selectedSeasonNumber) {
logger.error('[useMetadata] Error checking watch progress for season selection:', error); setSelectedSeason(selectedSeasonNumber);
// Fall back to persistent storage }
selectedSeasonNumber = getSeason(id, firstSeason); setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
initializedSeasonRef.current = true;
} else {
// Keep existing selection stable and only refresh episode list for it
setEpisodes(transformedEpisodes[selectedSeason] || []);
} }
// Set the selected season
setSelectedSeason(selectedSeasonNumber);
// Set episodes for the selected season
setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
} }
} catch (error) { } catch (error) {
if (__DEV__) console.error('Failed to load episodes:', error); if (__DEV__) console.error('Failed to load episodes:', error);
@ -1082,6 +1087,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Reset load attempts when id or type changes // Reset load attempts when id or type changes
useEffect(() => { useEffect(() => {
setLoadAttempts(0); setLoadAttempts(0);
initializedSeasonRef.current = false;
}, [id, type]); }, [id, type]);
// Auto-retry on error with delay // Auto-retry on error with delay