some changes

This commit is contained in:
tapframe 2025-09-22 23:02:01 +05:30
parent c91546dc1e
commit cdec19db1f
5 changed files with 292 additions and 223 deletions

View file

@ -430,7 +430,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio"; PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -464,7 +464,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio"; PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -527,10 +527,7 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = "$(inherited) ";
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@ -585,10 +582,7 @@
); );
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = "$(inherited) ";
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
USE_HERMES = true; USE_HERMES = true;

View file

@ -1,99 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Nuvio</string> <string>Nuvio</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.2.0</string> <string>1.2.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>nuvio</string> <string>nuvio</string>
<string>com.nuvio.app</string> <string>com.nuvio.app</string>
</array> </array>
</dict> </dict>
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>exp+nuvio</string> <string>exp+nuvio</string>
</array> </array>
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>16</string> <string>15</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0</string> <string>12.0</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>LSSupportsOpeningDocumentsInPlace</key> <key>LSSupportsOpeningDocumentsInPlace</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<true/> <true/>
</dict> </dict>
<key>NSBonjourServices</key> <key>NSBonjourServices</key>
<array> <array>
<string>_http._tcp</string> <string>_http._tcp</string>
</array> </array>
<key>NSLocalNetworkUsageDescription</key> <key>NSLocalNetworkUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your local network</string> <string>Allow $(PRODUCT_NAME) to access your local network</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>This app does not require microphone access.</string> <string>This app does not require microphone access.</string>
<key>RCTRootViewBackgroundColor</key> <key>RCTRootViewBackgroundColor</key>
<integer>4278322180</integer> <integer>4278322180</integer>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>audio</string> <string>audio</string>
</array> </array>
<key>UIFileSharingEnabled</key> <key>UIFileSharingEnabled</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>SplashScreen</string> <string>SplashScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
</array> </array>
<key>UIRequiresFullScreen</key> <key>UIRequiresFullScreen</key>
<false/> <false/>
<key>UIStatusBarStyle</key> <key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string> <string>UIStatusBarStyleDefault</string>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIUserInterfaceStyle</key> <key>UIUserInterfaceStyle</key>
<string>Dark</string> <string>Dark</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

View file

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict/>
<key>aps-environment</key> </plist>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array/>
</dict>
</plist>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View file

@ -231,6 +231,12 @@ const AndroidVideoPlayer: React.FC = () => {
const [vlcRestoreTime, setVlcRestoreTime] = useState<number | undefined>(undefined); // Time to restore after remount const [vlcRestoreTime, setVlcRestoreTime] = useState<number | undefined>(undefined); // Time to restore after remount
const [forceVlcRemount, setForceVlcRemount] = useState(false); // Force complete unmount/remount const [forceVlcRemount, setForceVlcRemount] = useState(false); // Force complete unmount/remount
// Debounce track updates to prevent excessive processing
const trackUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Debounce resize operations to prevent rapid successive clicks
const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Memoize VLC tracks prop to prevent unnecessary re-renders // Memoize VLC tracks prop to prevent unnecessary re-renders
const vlcTracks = useMemo(() => ({ const vlcTracks = useMemo(() => ({
audio: vlcSelectedAudioTrack, audio: vlcSelectedAudioTrack,
@ -238,8 +244,9 @@ const AndroidVideoPlayer: React.FC = () => {
subtitle: vlcSelectedSubtitleTrack subtitle: vlcSelectedSubtitleTrack
}), [vlcSelectedAudioTrack, vlcSelectedSubtitleTrack]); }), [vlcSelectedAudioTrack, vlcSelectedSubtitleTrack]);
// Format VLC tracks to match RN Video format // Format VLC tracks to match RN Video format - optimized version
const formatVlcTracks = useCallback((vlcTracks: Array<{id: number, name: string}>) => { const formatVlcTracks = useCallback((vlcTracks: Array<{id: number, name: string}>) => {
if (!Array.isArray(vlcTracks)) return [];
return vlcTracks.map(track => ({ return vlcTracks.map(track => ({
id: track.id, id: track.id,
name: track.name || `Track ${track.id + 1}`, name: track.name || `Track ${track.id + 1}`,
@ -247,18 +254,96 @@ const AndroidVideoPlayer: React.FC = () => {
})); }));
}, []); }, []);
// Optimized VLC track processing function
const processVlcTracks = useCallback((tracks: any, source: string) => {
if (!tracks) return;
// Clear any pending updates
if (trackUpdateTimeoutRef.current) {
clearTimeout(trackUpdateTimeoutRef.current);
}
// Debounce track updates to prevent excessive processing
trackUpdateTimeoutRef.current = setTimeout(() => {
const { audio = [], subtitle = [] } = tracks;
let hasUpdates = false;
// Process audio tracks
if (Array.isArray(audio) && audio.length > 0) {
const formattedAudio = formatVlcTracks(audio);
if (formattedAudio.length !== vlcAudioTracks.length ||
JSON.stringify(formattedAudio) !== JSON.stringify(vlcAudioTracks)) {
setVlcAudioTracks(formattedAudio);
hasUpdates = true;
// Only log in debug mode or when tracks actually change
if (DEBUG_MODE) {
console.log(`🎬 [VLC] ${source} - Audio tracks updated:`, formattedAudio.length);
}
}
}
// Process subtitle tracks
if (Array.isArray(subtitle) && subtitle.length > 0) {
const formattedSubs = formatVlcTracks(subtitle);
if (formattedSubs.length !== vlcSubtitleTracks.length ||
JSON.stringify(formattedSubs) !== JSON.stringify(vlcSubtitleTracks)) {
setVlcSubtitleTracks(formattedSubs);
hasUpdates = true;
if (DEBUG_MODE) {
console.log(`🎬 [VLC] ${source} - Subtitle tracks updated:`, formattedSubs.length);
}
}
}
// Log summary only if tracks were actually updated
if (hasUpdates && DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer][VLC] ${source} - Track processing complete. Audio: ${vlcAudioTracks.length}, Subs: ${vlcSubtitleTracks.length}`);
}
trackUpdateTimeoutRef.current = null;
}, 100); // 100ms debounce
}, [formatVlcTracks, vlcAudioTracks, vlcSubtitleTracks]);
// Use VLC tracks directly (they only update when tracks change) // Use VLC tracks directly (they only update when tracks change)
const vlcAudioTracksForModal = vlcAudioTracks; const vlcAudioTracksForModal = vlcAudioTracks;
const vlcSubtitleTracksForModal = vlcSubtitleTracks; const vlcSubtitleTracksForModal = vlcSubtitleTracks;
// Debug: log when VLC tracks change // Memoized computed props for child components
useEffect(() => { const ksAudioTracks = useMemo(() =>
console.log('🎬 [VLC] vlcAudioTracks changed:', vlcAudioTracks); useVLC ? vlcAudioTracksForModal : rnVideoAudioTracks,
}, [vlcAudioTracks]); [useVLC, vlcAudioTracksForModal, rnVideoAudioTracks]
);
const computedSelectedAudioTrack = useMemo(() =>
useVLC
? (vlcSelectedAudioTrack ?? null)
: (selectedAudioTrack?.type === SelectedTrackType.INDEX && selectedAudioTrack.value !== undefined
? Number(selectedAudioTrack.value)
: null),
[useVLC, vlcSelectedAudioTrack, selectedAudioTrack]
);
const ksTextTracks = useMemo(() =>
useVLC ? vlcSubtitleTracksForModal : rnVideoTextTracks,
[useVLC, vlcSubtitleTracksForModal, rnVideoTextTracks]
);
const computedSelectedTextTrack = useMemo(() =>
useVLC ? (vlcSelectedSubtitleTrack ?? -1) : selectedTextTrack,
[useVLC, vlcSelectedSubtitleTrack, selectedTextTrack]
);
// Clean up timeouts on unmount
useEffect(() => { useEffect(() => {
console.log('🎬 [VLC] vlcSubtitleTracks changed:', vlcSubtitleTracks); return () => {
}, [vlcSubtitleTracks]); if (trackUpdateTimeoutRef.current) {
clearTimeout(trackUpdateTimeoutRef.current);
}
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
};
}, []);
// Reset forceVlcRemount when VLC becomes inactive // Reset forceVlcRemount when VLC becomes inactive
useEffect(() => { useEffect(() => {
@ -290,6 +375,40 @@ const AndroidVideoPlayer: React.FC = () => {
const [isVideoLoaded, setIsVideoLoaded] = useState(false); const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [videoAspectRatio, setVideoAspectRatio] = useState<number | null>(null); const [videoAspectRatio, setVideoAspectRatio] = useState<number | null>(null);
const [is16by9Content, setIs16by9Content] = useState(false); const [is16by9Content, setIs16by9Content] = useState(false);
const calculateVideoStyles = (videoWidth: number, videoHeight: number, screenWidth: number, screenHeight: number) => {
return {
position: 'absolute',
top: 0,
left: 0,
width: screenWidth,
height: screenHeight,
};
};
// Memoize expensive video style calculations
const videoStyles = useMemo(() => {
if (videoAspectRatio && screenDimensions.width > 0 && screenDimensions.height > 0) {
return calculateVideoStyles(
videoAspectRatio * 1000,
1000,
screenDimensions.width,
screenDimensions.height
);
}
return {};
}, [videoAspectRatio, screenDimensions.width, screenDimensions.height]);
// Memoize zoom factor calculations to prevent expensive recalculations
const zoomFactor = useMemo(() => {
if (resizeMode === 'cover' && videoAspectRatio && screenDimensions.width > 0 && screenDimensions.height > 0) {
const screenAspect = screenDimensions.width / screenDimensions.height;
return Math.max(screenAspect / videoAspectRatio, videoAspectRatio / screenAspect);
} else if (resizeMode === 'none') {
return 1;
}
return 1; // Default for other modes
}, [resizeMode, videoAspectRatio, screenDimensions.width, screenDimensions.height]);
const [customVideoStyles, setCustomVideoStyles] = useState<any>({}); const [customVideoStyles, setCustomVideoStyles] = useState<any>({});
const [zoomScale, setZoomScale] = useState(1); const [zoomScale, setZoomScale] = useState(1);
const [zoomTranslateX, setZoomTranslateX] = useState(0); const [zoomTranslateX, setZoomTranslateX] = useState(0);
@ -479,7 +598,7 @@ const AndroidVideoPlayer: React.FC = () => {
}, [metadata]); }, [metadata]);
// Resolve current episode description for series // Resolve current episode description for series
const currentEpisodeDescription = (() => { const currentEpisodeDescription = useMemo(() => {
try { try {
if ((type as any) !== 'series') return ''; if ((type as any) !== 'series') return '';
const allEpisodes = Object.values(groupedEpisodes || {}).flat() as any[]; const allEpisodes = Object.values(groupedEpisodes || {}).flat() as any[];
@ -495,7 +614,7 @@ const AndroidVideoPlayer: React.FC = () => {
} catch { } catch {
return ''; return '';
} }
})(); }, [type, groupedEpisodes, episodeId, season, episode]);
// Find next episode for series // Find next episode for series
const nextEpisode = useMemo(() => { const nextEpisode = useMemo(() => {
@ -538,15 +657,6 @@ const AndroidVideoPlayer: React.FC = () => {
}).start(() => setShowControls(false)); }).start(() => setShowControls(false));
}; };
const calculateVideoStyles = (videoWidth: number, videoHeight: number, screenWidth: number, screenHeight: number) => {
return {
position: 'absolute',
top: 0,
left: 0,
width: screenWidth,
height: screenHeight,
};
};
const onPinchGestureEvent = (event: PinchGestureHandlerGestureEvent) => { const onPinchGestureEvent = (event: PinchGestureHandlerGestureEvent) => {
const { scale } = event.nativeEvent; const { scale } = event.nativeEvent;
@ -677,34 +787,15 @@ const AndroidVideoPlayer: React.FC = () => {
} }
}; };
// Apply memoized calculations to state
useEffect(() => { useEffect(() => {
if (videoAspectRatio && screenDimensions.width > 0 && screenDimensions.height > 0) { setCustomVideoStyles(videoStyles);
const styles = calculateVideoStyles( setZoomScale(zoomFactor);
videoAspectRatio * 1000,
1000,
screenDimensions.width,
screenDimensions.height
);
setCustomVideoStyles(styles);
// Recalculate zoom for cover mode when video aspect ratio changes if (DEBUG_MODE && resizeMode === 'cover') {
if (resizeMode === 'cover') { logger.log(`[AndroidVideoPlayer] Cover zoom updated: ${zoomFactor.toFixed(2)}x (video AR: ${videoAspectRatio?.toFixed(2)})`);
const screenAspect = screenDimensions.width / screenDimensions.height;
const zoomFactor = Math.max(screenAspect / videoAspectRatio, videoAspectRatio / screenAspect);
setZoomScale(zoomFactor);
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Cover zoom updated: ${zoomFactor.toFixed(2)}x (video AR: ${videoAspectRatio.toFixed(2)})`);
}
} else if (resizeMode === 'none') {
// Ensure none mode has no zoom
setZoomScale(1);
}
if (DEBUG_MODE) {
if (__DEV__) logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
}
} }
}, [screenDimensions, videoAspectRatio, resizeMode]); }, [videoStyles, zoomFactor, resizeMode, videoAspectRatio]);
useEffect(() => { useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ screen }) => { const subscription = Dimensions.addEventListener('change', ({ screen }) => {
@ -1063,15 +1154,15 @@ const AndroidVideoPlayer: React.FC = () => {
}; };
// Slider callback functions for React Native Community Slider // Slider callback functions for React Native Community Slider
const handleSliderValueChange = (value: number) => { const handleSliderValueChange = useCallback((value: number) => {
if (isDragging && duration > 0) { if (isDragging && duration > 0) {
const seekTime = Math.min(value, duration - END_EPSILON); const seekTime = Math.min(value, duration - END_EPSILON);
pendingSeekValue.current = seekTime; pendingSeekValue.current = seekTime;
} }
}; }, [isDragging, duration]);
const handleSlidingStart = () => { const handleSlidingStart = useCallback(() => {
setIsDragging(true); setIsDragging(true);
// Keep controls visible while dragging and cancel any hide timeout // Keep controls visible while dragging and cancel any hide timeout
if (!showControls) setShowControls(true); if (!showControls) setShowControls(true);
@ -1085,9 +1176,9 @@ const AndroidVideoPlayer: React.FC = () => {
if (!paused) setPaused(true); if (!paused) setPaused(true);
logger.log('[AndroidVideoPlayer] handleSlidingStart: pausing for iOS drag'); logger.log('[AndroidVideoPlayer] handleSlidingStart: pausing for iOS drag');
} }
}; }, [showControls, paused]);
const handleSlidingComplete = (value: number) => { const handleSlidingComplete = useCallback((value: number) => {
setIsDragging(false); setIsDragging(false);
if (duration > 0) { if (duration > 0) {
const seekTime = Math.min(value, duration - END_EPSILON); const seekTime = Math.min(value, duration - END_EPSILON);
@ -1108,7 +1199,7 @@ const AndroidVideoPlayer: React.FC = () => {
// Ensure controls are visible, then schedule auto-hide // Ensure controls are visible, then schedule auto-hide
if (!showControls) setShowControls(true); if (!showControls) setShowControls(true);
controlsTimeout.current = setTimeout(hideControls, 5000); controlsTimeout.current = setTimeout(hideControls, 5000);
}; }, [duration, showControls]);
// Ensure auto-hide resumes after drag ends // Ensure auto-hide resumes after drag ends
useEffect(() => { useEffect(() => {
@ -1367,12 +1458,19 @@ const AndroidVideoPlayer: React.FC = () => {
} }
}; };
const skip = (seconds: number) => { const skip = useCallback((seconds: number) => {
const newTime = Math.max(0, Math.min(currentTime + seconds, duration - END_EPSILON)); const newTime = Math.max(0, Math.min(currentTime + seconds, duration - END_EPSILON));
seekToTime(newTime); seekToTime(newTime);
}; }, [currentTime, duration]);
const cycleAspectRatio = () => { const cycleAspectRatio = useCallback(() => {
// Prevent rapid successive resize operations
if (resizeTimeoutRef.current) {
if (DEBUG_MODE) {
logger.log('[AndroidVideoPlayer] Resize operation debounced - ignoring rapid click');
}
return;
}
// Cycle through allowed resize modes per platform // Cycle through allowed resize modes per platform
// Android: exclude 'contain' for both VLC and RN Video (not well supported) // Android: exclude 'contain' for both VLC and RN Video (not well supported)
let resizeModes: ResizeModeType[]; let resizeModes: ResizeModeType[];
@ -1414,7 +1512,12 @@ const AndroidVideoPlayer: React.FC = () => {
if (DEBUG_MODE) { if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Resize mode changed to: ${newResizeMode}`); logger.log(`[AndroidVideoPlayer] Resize mode changed to: ${newResizeMode}`);
} }
};
// Debounce for 300ms to prevent rapid successive operations
resizeTimeoutRef.current = setTimeout(() => {
resizeTimeoutRef.current = null;
}, 300);
}, [resizeMode]);
const enableImmersiveMode = () => { const enableImmersiveMode = () => {
StatusBar.setHidden(true, 'none'); StatusBar.setHidden(true, 'none');
@ -1439,7 +1542,7 @@ const AndroidVideoPlayer: React.FC = () => {
} }
}; };
const handleClose = async () => { const handleClose = useCallback(async () => {
// Prevent multiple close attempts // Prevent multiple close attempts
if (isSyncingBeforeClose) { if (isSyncingBeforeClose) {
logger.log('[AndroidVideoPlayer] Close already in progress, ignoring duplicate call'); logger.log('[AndroidVideoPlayer] Close already in progress, ignoring duplicate call');
@ -1515,7 +1618,7 @@ const AndroidVideoPlayer: React.FC = () => {
// Start background sync without blocking UI // Start background sync without blocking UI
backgroundSync(); backgroundSync();
}; }, [isSyncingBeforeClose, currentTime, duration, traktAutosync, navigation, metadata, imdbId, backdrop]);
const handleResume = async () => { const handleResume = async () => {
if (resumePosition) { if (resumePosition) {
@ -1831,7 +1934,7 @@ const AndroidVideoPlayer: React.FC = () => {
}; };
// Wrapper function to convert number to SelectedTrack for modal usage // Wrapper function to convert number to SelectedTrack for modal usage
const selectAudioTrackById = (trackId: number) => { const selectAudioTrackById = useCallback((trackId: number) => {
if (useVLC) { if (useVLC) {
// For VLC, directly set the selected track // For VLC, directly set the selected track
selectVlcAudioTrack(trackId); selectVlcAudioTrack(trackId);
@ -1840,9 +1943,9 @@ const AndroidVideoPlayer: React.FC = () => {
const trackSelection: SelectedTrack = { type: SelectedTrackType.INDEX, value: trackId }; const trackSelection: SelectedTrack = { type: SelectedTrackType.INDEX, value: trackId };
selectAudioTrack(trackSelection); selectAudioTrack(trackSelection);
} }
}; }, [useVLC, selectVlcAudioTrack, selectAudioTrack]);
const selectTextTrack = (trackId: number) => { const selectTextTrack = useCallback((trackId: number) => {
if (useVLC) { if (useVLC) {
// For VLC, directly set the selected subtitle track and disable custom subtitles // For VLC, directly set the selected subtitle track and disable custom subtitles
if (trackId === -999) { if (trackId === -999) {
@ -1866,7 +1969,7 @@ const AndroidVideoPlayer: React.FC = () => {
setSelectedTextTrack(trackId); setSelectedTextTrack(trackId);
} }
} }
}; }, [useVLC, selectVlcSubtitleTrack]);
const loadSubtitleSize = async () => { const loadSubtitleSize = async () => {
try { try {
@ -2071,7 +2174,7 @@ const AndroidVideoPlayer: React.FC = () => {
} }
}; };
const togglePlayback = () => { const togglePlayback = useCallback(() => {
const newPausedState = !paused; const newPausedState = !paused;
if (useVLC && vlcRef.current) { if (useVLC && vlcRef.current) {
try { try {
@ -2089,7 +2192,7 @@ const AndroidVideoPlayer: React.FC = () => {
if (duration > 0) { if (duration > 0) {
traktAutosync.handleProgressUpdate(currentTime, duration, true); traktAutosync.handleProgressUpdate(currentTime, duration, true);
} }
}; }, [paused, useVLC, currentTime, duration, traktAutosync]);
// Handle next episode button press // Handle next episode button press
const handlePlayNextEpisode = useCallback(async () => { const handlePlayNextEpisode = useCallback(async () => {
@ -2906,29 +3009,14 @@ const AndroidVideoPlayer: React.FC = () => {
autoplay={!paused} autoplay={!paused}
onFirstPlay={(info: any) => { onFirstPlay={(info: any) => {
try { try {
console.log('🎬 [VLC] Video loaded, extracting tracks...'); if (DEBUG_MODE) {
console.log('🎬 [VLC] Video loaded, extracting tracks...');
}
logger.log('[AndroidVideoPlayer][VLC] Video loaded successfully'); logger.log('[AndroidVideoPlayer][VLC] Video loaded successfully');
// Extract and format VLC tracks // Process VLC tracks using optimized function
if (info?.tracks) { if (info?.tracks) {
const { audio = [], subtitle = [] } = info.tracks; processVlcTracks(info.tracks, 'onFirstPlay');
// Format audio tracks
if (Array.isArray(audio) && audio.length > 0) {
const formattedAudio = formatVlcTracks(audio);
setVlcAudioTracks(formattedAudio);
console.log('🎬 [VLC] Audio tracks loaded:', formattedAudio.length, formattedAudio);
console.log('🎬 [VLC] Setting vlcAudioTracks state:', formattedAudio);
} else {
console.log('🎬 [VLC] No audio tracks to set');
}
// Format subtitle tracks
if (Array.isArray(subtitle) && subtitle.length > 0) {
const formattedSubs = formatVlcTracks(subtitle);
setVlcSubtitleTracks(formattedSubs);
console.log('🎬 [VLC] Subtitle tracks loaded:', formattedSubs.length);
}
} }
const lenSec = (info?.length ?? 0) / 1000; const lenSec = (info?.length ?? 0) / 1000;
@ -2938,12 +3026,16 @@ const AndroidVideoPlayer: React.FC = () => {
// Restore playback position after remount (workaround for surface detach) // Restore playback position after remount (workaround for surface detach)
if (vlcRestoreTime !== undefined && vlcRestoreTime > 0) { if (vlcRestoreTime !== undefined && vlcRestoreTime > 0) {
console.log('🎬 [VLC] Restoring playback position:', vlcRestoreTime); if (DEBUG_MODE) {
console.log('🎬 [VLC] Restoring playback position:', vlcRestoreTime);
}
setTimeout(() => { setTimeout(() => {
if (vlcRef.current && typeof vlcRef.current.seek === 'function') { if (vlcRef.current && typeof vlcRef.current.seek === 'function') {
const seekPosition = Math.min(vlcRestoreTime / lenSec, 0.999); // Convert to fraction const seekPosition = Math.min(vlcRestoreTime / lenSec, 0.999); // Convert to fraction
vlcRef.current.seek(seekPosition); vlcRef.current.seek(seekPosition);
console.log('🎬 [VLC] Seeked to restore position'); if (DEBUG_MODE) {
console.log('🎬 [VLC] Seeked to restore position');
}
} }
}, 500); // Small delay to ensure player is ready }, 500); // Small delay to ensure player is ready
setVlcRestoreTime(undefined); // Clear restore time setVlcRestoreTime(undefined); // Clear restore time
@ -2973,24 +3065,13 @@ const AndroidVideoPlayer: React.FC = () => {
}} }}
onESAdded={(tracks: any) => { onESAdded={(tracks: any) => {
try { try {
console.log('🎬 [VLC] ES Added - processing tracks...'); if (DEBUG_MODE) {
console.log('🎬 [VLC] ES Added - processing tracks...');
}
// Process VLC tracks using optimized function
if (tracks) { if (tracks) {
const { audio = [], subtitle = [] } = tracks; processVlcTracks(tracks, 'onESAdded');
// Format audio tracks
if (Array.isArray(audio) && audio.length > 0) {
const formattedAudio = formatVlcTracks(audio);
setVlcAudioTracks(formattedAudio);
console.log('🎬 [VLC] ES Added - Audio tracks loaded:', formattedAudio.length);
}
// Format subtitle tracks
if (Array.isArray(subtitle) && subtitle.length > 0) {
const formattedSubs = formatVlcTracks(subtitle);
setVlcSubtitleTracks(formattedSubs);
console.log('🎬 [VLC] ES Added - Subtitle tracks loaded:', formattedSubs.length);
}
} }
} catch (e) { } catch (e) {
console.error('🎬 [VLC] onESAdded error:', e); console.error('🎬 [VLC] onESAdded error:', e);
@ -3102,8 +3183,8 @@ const AndroidVideoPlayer: React.FC = () => {
duration={duration} duration={duration}
zoomScale={zoomScale} zoomScale={zoomScale}
currentResizeMode={resizeMode} currentResizeMode={resizeMode}
ksAudioTracks={useVLC ? vlcAudioTracksForModal : rnVideoAudioTracks} ksAudioTracks={ksAudioTracks}
selectedAudioTrack={useVLC ? (vlcSelectedAudioTrack ?? null) : (selectedAudioTrack?.type === SelectedTrackType.INDEX && selectedAudioTrack.value !== undefined ? Number(selectedAudioTrack.value) : null)} selectedAudioTrack={computedSelectedAudioTrack}
availableStreams={availableStreams} availableStreams={availableStreams}
togglePlayback={togglePlayback} togglePlayback={togglePlayback}
skip={skip} skip={skip}
@ -3698,12 +3779,6 @@ const AndroidVideoPlayer: React.FC = () => {
<> <>
{console.log('🎬 [AndroidVideoPlayer] AudioTrackModal props:', {
useVLC,
vlcAudioTracksForModal,
rnVideoAudioTracks,
finalTracks: useVLC ? vlcAudioTracksForModal : rnVideoAudioTracks
})}
<AudioTrackModal <AudioTrackModal
showAudioModal={showAudioModal} showAudioModal={showAudioModal}
setShowAudioModal={setShowAudioModal} setShowAudioModal={setShowAudioModal}
@ -3721,8 +3796,8 @@ const AndroidVideoPlayer: React.FC = () => {
isLoadingSubtitles={isLoadingSubtitles} isLoadingSubtitles={isLoadingSubtitles}
customSubtitles={customSubtitles} customSubtitles={customSubtitles}
availableSubtitles={availableSubtitles} availableSubtitles={availableSubtitles}
ksTextTracks={useVLC ? vlcSubtitleTracksForModal : rnVideoTextTracks} ksTextTracks={ksTextTracks}
selectedTextTrack={useVLC ? (vlcSelectedSubtitleTrack ?? -1) : selectedTextTrack} selectedTextTrack={computedSelectedTextTrack}
useCustomSubtitles={useCustomSubtitles} useCustomSubtitles={useCustomSubtitles}
subtitleSize={subtitleSize} subtitleSize={subtitleSize}
subtitleBackground={subtitleBackground} subtitleBackground={subtitleBackground}