import React, { useState, useEffect, useRef, useCallback, memo } from 'react'; import { View, StyleSheet, TouchableOpacity, Dimensions, Platform, ActivityIndicator, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import Video, { VideoRef, OnLoadData, OnProgressData } from 'react-native-video'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { useAnimatedStyle, useSharedValue, withTiming, withDelay, runOnJS, } from 'react-native-reanimated'; import { useTheme } from '../../contexts/ThemeContext'; import { logger } from '../../utils/logger'; const { width, height } = Dimensions.get('window'); const isTablet = width >= 768; interface TrailerPlayerProps { trailerUrl: string; autoPlay?: boolean; muted?: boolean; onLoadStart?: () => void; onLoad?: () => void; onError?: (error: string) => void; onProgress?: (data: OnProgressData) => void; onPlaybackStatusUpdate?: (status: { isLoaded: boolean; didJustFinish: boolean }) => void; style?: any; hideLoadingSpinner?: boolean; onFullscreenToggle?: () => void; } const TrailerPlayer = React.forwardRef(({ trailerUrl, autoPlay = true, muted = true, onLoadStart, onLoad, onError, onProgress, onPlaybackStatusUpdate, style, hideLoadingSpinner = false, onFullscreenToggle, }, ref) => { const { currentTheme } = useTheme(); const videoRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [isPlaying, setIsPlaying] = useState(autoPlay); const [isMuted, setIsMuted] = useState(muted); const [hasError, setHasError] = useState(false); const [showControls, setShowControls] = useState(false); const [duration, setDuration] = useState(0); const [position, setPosition] = useState(0); // Animated values const controlsOpacity = useSharedValue(0); const loadingOpacity = useSharedValue(1); const playButtonScale = useSharedValue(1); // Auto-hide controls after 3 seconds const hideControlsTimeout = useRef(null); const showControlsWithTimeout = useCallback(() => { setShowControls(true); controlsOpacity.value = withTiming(1, { duration: 200 }); // Clear existing timeout if (hideControlsTimeout.current) { clearTimeout(hideControlsTimeout.current); } // Set new timeout to hide controls hideControlsTimeout.current = setTimeout(() => { setShowControls(false); controlsOpacity.value = withTiming(0, { duration: 200 }); }, 3000); }, [controlsOpacity]); const handleVideoPress = useCallback(() => { if (showControls) { // If controls are visible, toggle play/pause handlePlayPause(); } else { // If controls are hidden, show them showControlsWithTimeout(); } }, [showControls, showControlsWithTimeout]); const handlePlayPause = useCallback(async () => { try { if (!videoRef.current) return; playButtonScale.value = withTiming(0.8, { duration: 100 }, () => { playButtonScale.value = withTiming(1, { duration: 100 }); }); setIsPlaying(!isPlaying); showControlsWithTimeout(); } catch (error) { logger.error('TrailerPlayer', 'Error toggling playback:', error); } }, [isPlaying, playButtonScale, showControlsWithTimeout]); const handleMuteToggle = useCallback(async () => { try { if (!videoRef.current) return; setIsMuted(!isMuted); showControlsWithTimeout(); } catch (error) { logger.error('TrailerPlayer', 'Error toggling mute:', error); } }, [isMuted, showControlsWithTimeout]); const handleLoadStart = useCallback(() => { setIsLoading(true); setHasError(false); // Only show loading spinner if not hidden loadingOpacity.value = hideLoadingSpinner ? 0 : 1; onLoadStart?.(); logger.info('TrailerPlayer', 'Video load started'); }, [loadingOpacity, onLoadStart, hideLoadingSpinner]); const handleLoad = useCallback((data: OnLoadData) => { setIsLoading(false); loadingOpacity.value = withTiming(0, { duration: 300 }); setDuration(data.duration * 1000); // Convert to milliseconds onLoad?.(); logger.info('TrailerPlayer', 'Video loaded successfully'); }, [loadingOpacity, onLoad]); const handleError = useCallback((error: string) => { setIsLoading(false); setHasError(true); loadingOpacity.value = withTiming(0, { duration: 300 }); onError?.(error); logger.error('TrailerPlayer', 'Video error:', error); }, [loadingOpacity, onError]); const handleProgress = useCallback((data: OnProgressData) => { setPosition(data.currentTime * 1000); // Convert to milliseconds onProgress?.(data); if (onPlaybackStatusUpdate) { onPlaybackStatusUpdate({ isLoaded: data.currentTime > 0, didJustFinish: false }); } }, [onProgress, onPlaybackStatusUpdate]); // Sync internal muted state with prop useEffect(() => { setIsMuted(muted); }, [muted]); // Cleanup timeout on unmount useEffect(() => { return () => { if (hideControlsTimeout.current) { clearTimeout(hideControlsTimeout.current); } }; }, []); // Forward the ref to the video element React.useImperativeHandle(ref, () => ({ presentFullscreenPlayer: () => { if (videoRef.current) { return videoRef.current.presentFullscreenPlayer(); } } })); // Animated styles const controlsAnimatedStyle = useAnimatedStyle(() => ({ opacity: controlsOpacity.value, })); const loadingAnimatedStyle = useAnimatedStyle(() => ({ opacity: loadingOpacity.value, })); const playButtonAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: playButtonScale.value }], })); const progressPercentage = duration > 0 ? (position / duration) * 100 : 0; if (hasError) { return ( ); } return ( ); }); const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', overflow: 'hidden', }, video: { flex: 1, width: '100%', height: '100%', }, videoOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, }, loadingContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.5)', }, errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000', }, controlsContainer: { flex: 1, justifyContent: 'space-between', }, topGradient: { height: 100, width: '100%', }, centerControls: { flex: 1, justifyContent: 'center', alignItems: 'center', }, playButton: { width: isTablet ? 100 : 80, height: isTablet ? 100 : 80, borderRadius: isTablet ? 50 : 40, backgroundColor: 'rgba(0,0,0,0.6)', justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: 'rgba(255,255,255,0.8)', }, bottomGradient: { paddingBottom: Platform.OS === 'ios' ? 20 : 16, paddingTop: 20, }, bottomControls: { paddingHorizontal: isTablet ? 32 : 16, }, progressContainer: { marginBottom: 16, }, progressBar: { height: 3, backgroundColor: 'rgba(255,255,255,0.3)', borderRadius: 1.5, overflow: 'hidden', }, progressFill: { height: '100%', backgroundColor: '#fff', borderRadius: 1.5, }, controlButtons: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, controlButton: { padding: 8, borderRadius: 20, backgroundColor: 'rgba(0,0,0,0.4)', }, }); export default TrailerPlayer;