mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
usemetadata maximum depth limit reached potential fix
This commit is contained in:
parent
43f6f056c0
commit
ccde944bfa
2 changed files with 121 additions and 68 deletions
|
|
@ -723,6 +723,13 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
const shimmerOpacity = useSharedValue(0.3);
|
||||
const trailerOpacity = useSharedValue(0);
|
||||
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
|
||||
const actionButtonsOpacity = useSharedValue(1);
|
||||
|
|
@ -753,14 +760,18 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
// Smooth transition: fade out thumbnail, fade in trailer
|
||||
thumbnailOpacity.value = withTiming(0, { 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]);
|
||||
|
||||
// Ensure trailer state is properly synchronized when trailer becomes ready
|
||||
// Auto-start trailer when ready on initial entry if enabled
|
||||
useEffect(() => {
|
||||
if (trailerReady && settings?.showTrailers && !globalTrailerPlaying) {
|
||||
// Only start trailer if it's the initial load, not when returning from other screens
|
||||
// This prevents auto-starting when returning from StreamsScreen
|
||||
logger.info('HeroSection', 'Trailer ready but not playing - not auto-starting to prevent unwanted playback');
|
||||
if (trailerReady && settings?.showTrailers && !globalTrailerPlaying && !startedOnReadyRef.current) {
|
||||
startedOnReadyRef.current = true;
|
||||
logger.info('HeroSection', 'Trailer ready - auto-starting playback');
|
||||
setTrailerPlaying(true);
|
||||
isPlayingSV.value = 1;
|
||||
}
|
||||
}, [trailerReady, settings?.showTrailers, globalTrailerPlaying, setTrailerPlaying]);
|
||||
|
||||
|
|
@ -1036,25 +1047,61 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
useCallback(() => {
|
||||
// Screen is focused - only resume trailer if it was previously playing and got interrupted
|
||||
logger.info('HeroSection', 'Screen focused');
|
||||
|
||||
// Don't automatically resume trailer when returning from other screens
|
||||
// This prevents the trailer from starting when returning from StreamsScreen
|
||||
// The trailer should only resume if the user explicitly wants it to play
|
||||
// If trailers are enabled and not playing, start playback (unless scrolled past resume threshold)
|
||||
if (settings?.showTrailers) {
|
||||
setTimeout(() => {
|
||||
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 () => {
|
||||
// Stop trailer when leaving this screen to prevent background playback/heat
|
||||
logger.info('HeroSection', 'Screen unfocused - stopping trailer playback');
|
||||
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(() => {
|
||||
'worklet';
|
||||
try {
|
||||
const threshold = heroHeight.value * 0.6;
|
||||
if (scrollY.value > threshold) {
|
||||
if (!scrollGuardEnabledSV.value) return;
|
||||
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);
|
||||
isPlayingSV.value = 0;
|
||||
} else if (y < resumeThreshold && pausedByScrollSV.value === 1) {
|
||||
pausedByScrollSV.value = 0;
|
||||
runOnJS(setTrailerPlaying)(true);
|
||||
isPlayingSV.value = 1;
|
||||
}
|
||||
} catch (e) {
|
||||
// no-op
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { StreamingContent } from '../services/catalogService';
|
||||
import { catalogService } from '../services/catalogService';
|
||||
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 [scraperStatuses, setScraperStatuses] = useState<ScraperStatus[]>([]);
|
||||
const [activeFetchingScrapers, setActiveFetchingScrapers] = useState<string[]>([]);
|
||||
// Prevent re-initializing season selection repeatedly for the same series
|
||||
const initializedSeasonRef = useRef(false);
|
||||
|
||||
// Add hook for persistent seasons
|
||||
const { getSeason, saveSeason } = usePersistentSeasons();
|
||||
|
|
@ -591,12 +593,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
setGroupedEpisodes(groupedAddonEpisodes);
|
||||
|
||||
// Set the first available season
|
||||
const seasons = Object.keys(groupedAddonEpisodes).map(Number);
|
||||
const firstSeason = Math.min(...seasons);
|
||||
logger.log(`📺 Setting season ${firstSeason} as selected (${groupedAddonEpisodes[firstSeason]?.length || 0} episodes)`);
|
||||
setSelectedSeason(firstSeason);
|
||||
setEpisodes(groupedAddonEpisodes[firstSeason] || []);
|
||||
// Determine initial season only once per series
|
||||
const seasons = Object.keys(groupedAddonEpisodes).map(Number);
|
||||
const firstSeason = Math.min(...seasons);
|
||||
if (!initializedSeasonRef.current) {
|
||||
const nextSeason = 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
|
||||
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
|
||||
const firstSeason = Math.min(...Object.keys(allEpisodes).map(Number));
|
||||
|
||||
// Check for watch progress to auto-select season
|
||||
let selectedSeasonNumber = firstSeason;
|
||||
|
||||
try {
|
||||
// Check watch progress for auto-season selection
|
||||
const allProgress = await storageService.getAllWatchProgress();
|
||||
|
||||
// Find the most recently watched episode for this series
|
||||
let mostRecentEpisodeId = '';
|
||||
let mostRecentTimestamp = 0;
|
||||
|
||||
Object.entries(allProgress).forEach(([key, progress]) => {
|
||||
if (key.includes(`series:${id}:`)) {
|
||||
const episodeId = key.split(`series:${id}:`)[1];
|
||||
if (progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
|
||||
mostRecentTimestamp = progress.lastUpdated;
|
||||
mostRecentEpisodeId = episodeId;
|
||||
if (!initializedSeasonRef.current) {
|
||||
// Check for watch progress to auto-select season
|
||||
let selectedSeasonNumber = firstSeason;
|
||||
try {
|
||||
const allProgress = await storageService.getAllWatchProgress();
|
||||
let mostRecentEpisodeId = '';
|
||||
let mostRecentTimestamp = 0;
|
||||
Object.entries(allProgress).forEach(([key, progress]) => {
|
||||
if (key.includes(`series:${id}:`)) {
|
||||
const episodeId = key.split(`series:${id}:`)[1];
|
||||
if (progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
|
||||
mostRecentTimestamp = progress.lastUpdated;
|
||||
mostRecentEpisodeId = episodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mostRecentEpisodeId) {
|
||||
// Parse season number from episode ID
|
||||
const parts = mostRecentEpisodeId.split(':');
|
||||
if (parts.length === 3) {
|
||||
const watchProgressSeason = parseInt(parts[1], 10);
|
||||
if (transformedEpisodes[watchProgressSeason]) {
|
||||
selectedSeasonNumber = watchProgressSeason;
|
||||
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for ${mostRecentEpisodeId}`);
|
||||
});
|
||||
if (mostRecentEpisodeId) {
|
||||
const parts = mostRecentEpisodeId.split(':');
|
||||
if (parts.length === 3) {
|
||||
const watchProgressSeason = parseInt(parts[1], 10);
|
||||
if (transformedEpisodes[watchProgressSeason]) {
|
||||
selectedSeasonNumber = watchProgressSeason;
|
||||
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for ${mostRecentEpisodeId}`);
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
// Try to find episode by stremioId to get season
|
||||
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}`);
|
||||
}
|
||||
selectedSeasonNumber = getSeason(id, firstSeason);
|
||||
logger.log(`[useMetadata] No watch progress found, using persistent season ${selectedSeasonNumber}`);
|
||||
}
|
||||
} else {
|
||||
// No watch progress found, use persistent storage as fallback
|
||||
} catch (error) {
|
||||
logger.error('[useMetadata] Error checking watch progress for season selection:', error);
|
||||
selectedSeasonNumber = getSeason(id, firstSeason);
|
||||
logger.log(`[useMetadata] No watch progress found, using persistent season ${selectedSeasonNumber}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[useMetadata] Error checking watch progress for season selection:', error);
|
||||
// Fall back to persistent storage
|
||||
selectedSeasonNumber = getSeason(id, firstSeason);
|
||||
if (selectedSeason !== selectedSeasonNumber) {
|
||||
setSelectedSeason(selectedSeasonNumber);
|
||||
}
|
||||
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) {
|
||||
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
|
||||
useEffect(() => {
|
||||
setLoadAttempts(0);
|
||||
initializedSeasonRef.current = false;
|
||||
}, [id, type]);
|
||||
|
||||
// Auto-retry on error with delay
|
||||
|
|
|
|||
Loading…
Reference in a new issue