mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-18 07:12:18 +00:00
critical bug fixes
This commit is contained in:
parent
ed82ef2ad1
commit
dfb856f441
2 changed files with 150 additions and 81 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
TouchableOpacity,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
AppState,
|
||||
} from 'react-native';
|
||||
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
|
|
@ -703,6 +704,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
// Performance optimization: Refs for avoiding re-renders
|
||||
const interactionComplete = useRef(false);
|
||||
const [shouldLoadSecondaryData, setShouldLoadSecondaryData] = useState(false);
|
||||
const appState = useRef(AppState.currentState);
|
||||
|
||||
// Image loading state with optimized management
|
||||
const [imageError, setImageError] = useState(false);
|
||||
|
|
@ -755,9 +757,12 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
// Handle fullscreen toggle
|
||||
const handleFullscreenToggle = useCallback(async () => {
|
||||
try {
|
||||
logger.info('HeroSection', 'Fullscreen button pressed');
|
||||
if (trailerVideoRef.current) {
|
||||
// Use the native fullscreen player
|
||||
await trailerVideoRef.current.presentFullscreenPlayer();
|
||||
} else {
|
||||
logger.warn('HeroSection', 'Trailer video ref not available');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('HeroSection', 'Error toggling fullscreen:', error);
|
||||
|
|
@ -803,18 +808,32 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
setTrailerPreloaded(false);
|
||||
|
||||
try {
|
||||
const url = await TrailerService.getTrailerUrl(metadata.name, metadata.year);
|
||||
if (url) {
|
||||
const bestUrl = TrailerService.getBestFormatUrl(url);
|
||||
setTrailerUrl(bestUrl);
|
||||
logger.info('HeroSection', `Trailer URL loaded for ${metadata.name}`);
|
||||
} else {
|
||||
logger.info('HeroSection', `No trailer found for ${metadata.name}`);
|
||||
}
|
||||
// Use requestIdleCallback or setTimeout to prevent blocking main thread
|
||||
const fetchWithDelay = () => {
|
||||
TrailerService.getTrailerUrl(metadata.name, metadata.year)
|
||||
.then(url => {
|
||||
if (url) {
|
||||
const bestUrl = TrailerService.getBestFormatUrl(url);
|
||||
setTrailerUrl(bestUrl);
|
||||
logger.info('HeroSection', `Trailer URL loaded for ${metadata.name}`);
|
||||
} else {
|
||||
logger.info('HeroSection', `No trailer found for ${metadata.name}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('HeroSection', 'Error fetching trailer:', error);
|
||||
setTrailerError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setTrailerLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
// Delay trailer fetch to prevent blocking UI
|
||||
setTimeout(fetchWithDelay, 100);
|
||||
} catch (error) {
|
||||
logger.error('HeroSection', 'Error fetching trailer:', error);
|
||||
logger.error('HeroSection', 'Error in trailer fetch setup:', error);
|
||||
setTrailerError(true);
|
||||
} finally {
|
||||
setTrailerLoading(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -982,18 +1001,50 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
return localWatched;
|
||||
}, [watchProgress, isTraktAuthenticated]);
|
||||
|
||||
// App state management to prevent background ANR
|
||||
useEffect(() => {
|
||||
const handleAppStateChange = (nextAppState: any) => {
|
||||
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
|
||||
// App came to foreground
|
||||
logger.info('HeroSection', 'App came to foreground');
|
||||
} else if (appState.current === 'active' && nextAppState.match(/inactive|background/)) {
|
||||
// App going to background - pause heavy operations
|
||||
logger.info('HeroSection', 'App going to background - pausing operations');
|
||||
setTrailerPlaying(false);
|
||||
}
|
||||
appState.current = nextAppState;
|
||||
};
|
||||
|
||||
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
||||
return () => subscription?.remove();
|
||||
}, []);
|
||||
|
||||
// Memory management and cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Reset animation values on unmount
|
||||
imageOpacity.value = 1;
|
||||
imageLoadOpacity.value = 0;
|
||||
shimmerOpacity.value = 0.3;
|
||||
interactionComplete.current = false;
|
||||
// Reset animation values on unmount to prevent memory leaks
|
||||
try {
|
||||
imageOpacity.value = 1;
|
||||
imageLoadOpacity.value = 0;
|
||||
shimmerOpacity.value = 0.3;
|
||||
trailerOpacity.value = 0;
|
||||
thumbnailOpacity.value = 1;
|
||||
actionButtonsOpacity.value = 1;
|
||||
titleCardTranslateY.value = 0;
|
||||
genreOpacity.value = 1;
|
||||
watchProgressOpacity.value = 1;
|
||||
buttonsOpacity.value = 1;
|
||||
buttonsTranslateY.value = 0;
|
||||
logoOpacity.value = 1;
|
||||
heroOpacity.value = 1;
|
||||
heroHeight.value = height * 0.6;
|
||||
} catch (error) {
|
||||
logger.error('HeroSection', 'Error cleaning up animation values:', error);
|
||||
}
|
||||
|
||||
// Cleanup on unmount
|
||||
interactionComplete.current = false;
|
||||
};
|
||||
}, []);
|
||||
}, [imageOpacity, imageLoadOpacity, shimmerOpacity, trailerOpacity, thumbnailOpacity, actionButtonsOpacity, titleCardTranslateY, genreOpacity, watchProgressOpacity, buttonsOpacity, buttonsTranslateY, logoOpacity, heroOpacity, heroHeight]);
|
||||
|
||||
// Development-only performance monitoring
|
||||
useEffect(() => {
|
||||
|
|
@ -1072,6 +1123,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
muted={trailerMuted}
|
||||
style={styles.absoluteFill}
|
||||
hideLoadingSpinner={true}
|
||||
hideControls={true}
|
||||
onFullscreenToggle={handleFullscreenToggle}
|
||||
onLoad={handleTrailerReady}
|
||||
onError={handleTrailerError}
|
||||
|
|
@ -1090,7 +1142,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
position: 'absolute',
|
||||
top: Platform.OS === 'android' ? 40 : 50,
|
||||
right: width >= 768 ? 32 : 16,
|
||||
zIndex: 10,
|
||||
zIndex: 1000,
|
||||
opacity: trailerOpacity,
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
|
|
@ -1099,6 +1151,8 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
<TouchableOpacity
|
||||
onPress={handleFullscreenToggle}
|
||||
activeOpacity={0.7}
|
||||
onPressIn={(e) => e.stopPropagation()}
|
||||
onPressOut={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
padding: 8,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
|
|
@ -1115,6 +1169,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
{/* Unmute button */}
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
logger.info('HeroSection', 'Mute toggle button pressed, current muted state:', trailerMuted);
|
||||
updateSetting('trailerMuted', !trailerMuted);
|
||||
if (trailerMuted) {
|
||||
// When unmuting, hide action buttons, genre, title card, and watch progress
|
||||
|
|
@ -1131,6 +1186,8 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
onPressIn={(e) => e.stopPropagation()}
|
||||
onPressOut={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
padding: 8,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ interface TrailerPlayerProps {
|
|||
style?: any;
|
||||
hideLoadingSpinner?: boolean;
|
||||
onFullscreenToggle?: () => void;
|
||||
hideControls?: boolean;
|
||||
}
|
||||
|
||||
const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
|
||||
|
|
@ -49,6 +50,7 @@ const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
|
|||
style,
|
||||
hideLoadingSpinner = false,
|
||||
onFullscreenToggle,
|
||||
hideControls = false,
|
||||
}, ref) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
|
|
@ -177,8 +179,16 @@ const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
|
|||
if (hideControlsTimeout.current) {
|
||||
clearTimeout(hideControlsTimeout.current);
|
||||
}
|
||||
// Reset all animated values to prevent memory leaks
|
||||
try {
|
||||
controlsOpacity.value = 0;
|
||||
loadingOpacity.value = 0;
|
||||
playButtonScale.value = 1;
|
||||
} catch (error) {
|
||||
logger.error('TrailerPlayer', 'Error cleaning up animation values:', error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [controlsOpacity, loadingOpacity, playButtonScale]);
|
||||
|
||||
// Forward the ref to the video element
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
|
|
@ -269,80 +279,82 @@ const TrailerPlayer = React.forwardRef<any, TrailerPlayerProps>(({
|
|||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Video controls overlay */}
|
||||
<TouchableOpacity
|
||||
style={styles.videoOverlay}
|
||||
onPress={handleVideoPress}
|
||||
activeOpacity={1}
|
||||
>
|
||||
<Animated.View style={[styles.controlsContainer, controlsAnimatedStyle]}>
|
||||
{/* Top gradient */}
|
||||
<LinearGradient
|
||||
colors={['rgba(0,0,0,0.6)', 'transparent']}
|
||||
style={styles.topGradient}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
{/* Video controls overlay */}
|
||||
{!hideControls && (
|
||||
<TouchableOpacity
|
||||
style={styles.videoOverlay}
|
||||
onPress={handleVideoPress}
|
||||
activeOpacity={1}
|
||||
>
|
||||
<Animated.View style={[styles.controlsContainer, controlsAnimatedStyle]}>
|
||||
{/* Top gradient */}
|
||||
<LinearGradient
|
||||
colors={['rgba(0,0,0,0.6)', 'transparent']}
|
||||
style={styles.topGradient}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
|
||||
{/* Center play/pause button */}
|
||||
<View style={styles.centerControls}>
|
||||
<Animated.View style={playButtonAnimatedStyle}>
|
||||
<TouchableOpacity style={styles.playButton} onPress={handlePlayPause}>
|
||||
<MaterialIcons
|
||||
name={isPlaying ? 'pause' : 'play-arrow'}
|
||||
size={isTablet ? 64 : 48}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</View>
|
||||
|
||||
{/* Bottom controls */}
|
||||
<LinearGradient
|
||||
colors={['transparent', 'rgba(0,0,0,0.8)']}
|
||||
style={styles.bottomGradient}
|
||||
>
|
||||
<View style={styles.bottomControls}>
|
||||
{/* Progress bar */}
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressBar}>
|
||||
<View
|
||||
style={[styles.progressFill, { width: `${progressPercentage}%` }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Control buttons */}
|
||||
<View style={styles.controlButtons}>
|
||||
<TouchableOpacity style={styles.controlButton} onPress={handlePlayPause}>
|
||||
{/* Center play/pause button */}
|
||||
<View style={styles.centerControls}>
|
||||
<Animated.View style={playButtonAnimatedStyle}>
|
||||
<TouchableOpacity style={styles.playButton} onPress={handlePlayPause}>
|
||||
<MaterialIcons
|
||||
name={isPlaying ? 'pause' : 'play-arrow'}
|
||||
size={isTablet ? 32 : 24}
|
||||
size={isTablet ? 64 : 48}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.controlButton} onPress={handleMuteToggle}>
|
||||
<MaterialIcons
|
||||
name={isMuted ? 'volume-off' : 'volume-up'}
|
||||
size={isTablet ? 32 : 24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{onFullscreenToggle && (
|
||||
<TouchableOpacity style={styles.controlButton} onPress={onFullscreenToggle}>
|
||||
</Animated.View>
|
||||
</View>
|
||||
|
||||
{/* Bottom controls */}
|
||||
<LinearGradient
|
||||
colors={['transparent', 'rgba(0,0,0,0.8)']}
|
||||
style={styles.bottomGradient}
|
||||
>
|
||||
<View style={styles.bottomControls}>
|
||||
{/* Progress bar */}
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressBar}>
|
||||
<View
|
||||
style={[styles.progressFill, { width: `${progressPercentage}%` }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Control buttons */}
|
||||
<View style={styles.controlButtons}>
|
||||
<TouchableOpacity style={styles.controlButton} onPress={handlePlayPause}>
|
||||
<MaterialIcons
|
||||
name="fullscreen"
|
||||
name={isPlaying ? 'pause' : 'play-arrow'}
|
||||
size={isTablet ? 32 : 24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity style={styles.controlButton} onPress={handleMuteToggle}>
|
||||
<MaterialIcons
|
||||
name={isMuted ? 'volume-off' : 'volume-up'}
|
||||
size={isTablet ? 32 : 24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{onFullscreenToggle && (
|
||||
<TouchableOpacity style={styles.controlButton} onPress={onFullscreenToggle}>
|
||||
<MaterialIcons
|
||||
name="fullscreen"
|
||||
size={isTablet ? 32 : 24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
</LinearGradient>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue