import React, { useState, useEffect, useCallback, memo } from 'react'; import { View, Text, StyleSheet, Modal, TouchableOpacity, ActivityIndicator, Dimensions, Platform, Alert, } from 'react-native'; import { useTheme } from '../../contexts/ThemeContext'; import { useTrailer } from '../../contexts/TrailerContext'; import { logger } from '../../utils/logger'; import TrailerService from '../../services/trailerService'; import Video, { VideoRef, OnLoadData, OnProgressData } from 'react-native-video'; const { width, height } = Dimensions.get('window'); const isTablet = width >= 768; // Helper function to format trailer type const formatTrailerType = (type: string): string => { switch (type) { case 'Trailer': return 'Official Trailer'; case 'Teaser': return 'Teaser'; case 'Clip': return 'Clip'; case 'Featurette': return 'Featurette'; case 'Behind the Scenes': return 'Behind the Scenes'; default: return type; } }; interface TrailerVideo { id: string; key: string; name: string; site: string; size: number; type: string; official: boolean; published_at: string; } interface TrailerModalProps { visible: boolean; onClose: () => void; trailer: TrailerVideo | null; contentTitle: string; } const TrailerModal: React.FC = memo(({ visible, onClose, trailer, contentTitle }) => { const { currentTheme } = useTheme(); const { pauseTrailer, resumeTrailer } = useTrailer(); const videoRef = React.useRef(null); const [trailerUrl, setTrailerUrl] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [retryCount, setRetryCount] = useState(0); // Load trailer when modal opens or trailer changes useEffect(() => { if (visible && trailer) { loadTrailer(); } else { // Reset state when modal closes setTrailerUrl(null); setLoading(false); setError(null); setIsPlaying(false); setRetryCount(0); } }, [visible, trailer]); const loadTrailer = useCallback(async () => { if (!trailer) return; // Pause hero section trailer when modal opens try { pauseTrailer(); logger.info('TrailerModal', 'Paused hero section trailer'); } catch (error) { logger.warn('TrailerModal', 'Error pausing hero trailer:', error); } setLoading(true); setError(null); setTrailerUrl(null); setRetryCount(0); // Reset retry count when starting fresh load try { const youtubeUrl = `https://www.youtube.com/watch?v=${trailer.key}`; logger.info('TrailerModal', `Loading trailer: ${trailer.name} (${youtubeUrl})`); // Use the direct YouTube URL method - much more efficient! const directUrl = await TrailerService.getTrailerFromYouTubeUrl( youtubeUrl, `${contentTitle} - ${trailer.name}`, new Date(trailer.published_at).getFullYear().toString() ); if (directUrl) { setTrailerUrl(directUrl); setIsPlaying(true); logger.info('TrailerModal', `Successfully loaded direct trailer URL for: ${trailer.name}`); } else { throw new Error('No streaming URL available'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load trailer'; setError(errorMessage); setLoading(false); logger.error('TrailerModal', 'Error loading trailer:', err); Alert.alert( 'Trailer Unavailable', 'This trailer could not be loaded at this time. Please try again later.', [{ text: 'OK', style: 'default' }] ); } }, [trailer, contentTitle, pauseTrailer]); const handleClose = useCallback(() => { setIsPlaying(false); // Resume hero section trailer when modal closes try { resumeTrailer(); logger.info('TrailerModal', 'Resumed hero section trailer'); } catch (error) { logger.warn('TrailerModal', 'Error resuming hero trailer:', error); } onClose(); }, [onClose, resumeTrailer]); const handleTrailerError = useCallback(() => { setError('Failed to play trailer'); setIsPlaying(false); }, []); // Handle video playback errors with retry logic 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); 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]); const handleTrailerEnd = useCallback(() => { setIsPlaying(false); }, []); if (!visible || !trailer) return null; const modalHeight = isTablet ? height * 0.8 : height * 0.7; const modalWidth = isTablet ? width * 0.8 : width * 0.95; return ( {/* Enhanced Header */} {trailer.name} {formatTrailerType(trailer.type)} • {new Date(trailer.published_at).getFullYear()} Close {/* Player Container */} {loading && ( Loading trailer... )} {error && !loading && ( {error} Try Again )} {/* Render the Video as soon as we have a URL; keep spinner overlay until onLoad */} {trailerUrl && !error && ( )} {/* Enhanced Footer */} {contentTitle} ); }); const styles = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.92)', justifyContent: 'center', alignItems: 'center', }, modal: { borderRadius: 20, overflow: 'hidden', elevation: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.4, shadowRadius: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, // Enhanced Header Styles header: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 18, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.08)', }, headerLeft: { flexDirection: 'row', flex: 1, }, headerTextContainer: { flex: 1, gap: 4, }, title: { fontSize: 16, fontWeight: '700', lineHeight: 20, color: '#fff', }, headerMeta: { flexDirection: 'row', alignItems: 'center', gap: 8, }, meta: { fontSize: 12, opacity: 0.7, fontWeight: '500', }, closeButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, alignItems: 'center', justifyContent: 'center', }, closeButtonText: { fontSize: 14, fontWeight: '600', }, playerContainer: { aspectRatio: 16 / 9, backgroundColor: '#000', position: 'relative', }, loadingContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000', gap: 16, }, loadingText: { fontSize: 14, opacity: 0.8, }, errorContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000', padding: 20, gap: 16, }, errorText: { fontSize: 14, textAlign: 'center', opacity: 0.8, }, retryButton: { paddingHorizontal: 20, paddingVertical: 10, borderRadius: 20, }, retryButtonText: { color: '#fff', fontSize: 14, fontWeight: '600', }, playerWrapper: { flex: 1, }, player: { flex: 1, }, // Enhanced Footer Styles footer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 16, borderTopWidth: 1, borderTopColor: 'rgba(255,255,255,0.08)', }, footerContent: { flexDirection: 'row', alignItems: 'center', flex: 1, }, footerText: { fontSize: 13, fontWeight: '500', opacity: 0.8, }, footerMeta: { alignItems: 'flex-end', }, footerMetaText: { fontSize: 11, opacity: 0.6, fontWeight: '500', }, }); export default TrailerModal;