From 9452b01e9cab8686643629d07a90e87c94041e1c Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 26 Oct 2025 20:10:17 +0530 Subject: [PATCH] improved source modal behaviour --- src/components/player/AndroidVideoPlayer.tsx | 153 ++++-------------- src/components/player/KSPlayerCore.tsx | 152 ++++------------- src/components/player/modals/SourcesModal.tsx | 10 +- src/navigation/AppNavigator.tsx | 3 +- 4 files changed, 74 insertions(+), 244 deletions(-) diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 686ffa4b..e80f2cd3 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -479,8 +479,6 @@ const AndroidVideoPlayer: React.FC = () => { }, [currentStreamUrl, useVLC, processUrlForVLC]); // Track a single silent retry per source to avoid loops const retryAttemptRef = useRef(0); - const [isChangingSource, setIsChangingSource] = useState(false); - const [pendingSeek, setPendingSeek] = useState<{ position: number; shouldPlay: boolean } | null>(null); const [currentQuality, setCurrentQuality] = useState(quality); const [currentStreamProvider, setCurrentStreamProvider] = useState(streamProvider); const [currentStreamName, setCurrentStreamName] = useState(streamName); @@ -2941,110 +2939,49 @@ const AndroidVideoPlayer: React.FC = () => { setSubtitleBackground(!subtitleBackground); }; - useEffect(() => { - if (pendingSeek && isPlayerReady && isVideoLoaded && duration > 0) { - logger.log(`[AndroidVideoPlayer] Player ready after source change, seeking to position: ${pendingSeek.position}s out of ${duration}s total`); - - if (pendingSeek.position > 0 && videoRef.current) { - const delayTime = 800; // Shorter delay for react-native-video - - setTimeout(() => { - if (videoRef.current && duration > 0 && pendingSeek) { - logger.log(`[AndroidVideoPlayer] Executing seek to ${pendingSeek.position}s`); - - seekToTime(pendingSeek.position); - - if (pendingSeek.shouldPlay) { - setTimeout(() => { - logger.log('[AndroidVideoPlayer] Resuming playback after source change seek'); - setPaused(false); - }, 300); - } - - setTimeout(() => { - setPendingSeek(null); - setIsChangingSource(false); - }, 400); - } - }, delayTime); - } else { - // No seeking needed, just resume playback if it was playing - if (pendingSeek.shouldPlay) { - setTimeout(() => { - logger.log('[AndroidVideoPlayer] No seek needed, just resuming playback'); - setPaused(false); - }, 300); - } - - setTimeout(() => { - setPendingSeek(null); - setIsChangingSource(false); - }, 400); - } - } - }, [pendingSeek, isPlayerReady, isVideoLoaded, duration]); - const handleSelectStream = async (newStream: any) => { if (newStream.url === currentStreamUrl) { setShowSourcesModal(false); return; } - // Note: iOS now always uses KSPlayer, so this AndroidVideoPlayer should never be used on iOS - // This logic is kept for safety in case routing changes - - setIsChangingSource(true); setShowSourcesModal(false); - try { - // Save current state - const savedPosition = currentTime; - const wasPlaying = !paused; - - logger.log(`[AndroidVideoPlayer] Changing source from ${currentStreamUrl} to ${newStream.url}`); - logger.log(`[AndroidVideoPlayer] Saved position: ${savedPosition}, was playing: ${wasPlaying}`); - - // Extract quality and provider information from the new stream - let newQuality = newStream.quality; - if (!newQuality && newStream.title) { - // Try to extract quality from title (e.g., "1080p", "720p") - const qualityMatch = newStream.title.match(/(\d+)p/); - newQuality = qualityMatch ? qualityMatch[0] : undefined; - } - - // For provider, try multiple fields - const newProvider = newStream.addonName || newStream.name || newStream.addon || 'Unknown'; - - // For stream name, prioritize the stream name over title - const newStreamName = newStream.name || newStream.title || 'Unknown Stream'; - - logger.log(`[AndroidVideoPlayer] Stream object:`, newStream); - logger.log(`[AndroidVideoPlayer] Extracted - Quality: ${newQuality}, Provider: ${newProvider}, Stream Name: ${newStreamName}`); - - // Stop current playback - setPaused(true); - - // Set pending seek state - setPendingSeek({ position: savedPosition, shouldPlay: wasPlaying }); - - // Update the stream URL and details immediately - setCurrentStreamUrl(newStream.url); - setCurrentQuality(newQuality); - setCurrentStreamProvider(newProvider); - setCurrentStreamName(newStreamName); - - // Reset player state for new source - setCurrentTime(0); - setDuration(0); - setIsPlayerReady(false); - setIsVideoLoaded(false); - vlcLoadedRef.current = false; - - } catch (error) { - logger.error('[AndroidVideoPlayer] Error changing source:', error); - setPendingSeek(null); - setIsChangingSource(false); + // Extract quality and provider information + let newQuality = newStream.quality; + if (!newQuality && newStream.title) { + const qualityMatch = newStream.title.match(/(\d+)p/); + newQuality = qualityMatch ? qualityMatch[0] : undefined; } + + const newProvider = newStream.addonName || newStream.name || newStream.addon || 'Unknown'; + const newStreamName = newStream.name || newStream.title || 'Unknown Stream'; + + // Pause current playback + setPaused(true); + + // Navigate with replace to reload player with new source + setTimeout(() => { + (navigation as any).replace('PlayerAndroid', { + uri: newStream.url, + title: title, + episodeTitle: episodeTitle, + season: season, + episode: episode, + quality: newQuality, + year: year, + streamProvider: newProvider, + streamName: newStreamName, + headers: newStream.headers || undefined, + forceVlc: false, + id, + type, + episodeId, + imdbId: imdbId ?? undefined, + backdrop: backdrop || undefined, + availableStreams: availableStreams, + }); + }, 100); }; useEffect(() => { @@ -3156,27 +3093,6 @@ const AndroidVideoPlayer: React.FC = () => { - {/* Source Change Loading Overlay */} - {isChangingSource && ( - - - - Changing source... - Please wait while we load the new stream - - - )} - { availableStreams={availableStreams} currentStreamUrl={currentStreamUrl} onSelectStream={handleSelectStream} - isChangingSource={isChangingSource} /> {/* Error Modal */} diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index d5646f2b..21502261 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -218,11 +218,9 @@ const KSPlayerCore: React.FC = () => { setPlaybackSpeed(speedOptions[nextIdx]); }, [playbackSpeed, speedOptions]); const [currentStreamUrl, setCurrentStreamUrl] = useState(uri); - const [isChangingSource, setIsChangingSource] = useState(false); const [showErrorModal, setShowErrorModal] = useState(false); const [errorDetails, setErrorDetails] = useState(''); const errorTimeoutRef = useRef(null); - const [pendingSeek, setPendingSeek] = useState<{ position: number; shouldPlay: boolean } | null>(null); const [currentQuality, setCurrentQuality] = useState(quality); const [currentStreamProvider, setCurrentStreamProvider] = useState(streamProvider); const [currentStreamName, setCurrentStreamName] = useState(streamName); @@ -2305,49 +2303,6 @@ const KSPlayerCore: React.FC = () => { setSubtitleBackground(prev => !prev); }; - useEffect(() => { - if (pendingSeek && isPlayerReady && isVideoLoaded && duration > 0) { - logger.log(`[VideoPlayer] Player ready after source change, seeking to position: ${pendingSeek.position}s out of ${duration}s total`); - - if (pendingSeek.position > 0) { - const delayTime = Platform.OS === 'android' ? 1500 : 1000; - - setTimeout(() => { - if (duration > 0 && pendingSeek) { - logger.log(`[VideoPlayer] Executing seek to ${pendingSeek.position}s`); - - seekToTime(pendingSeek.position); - - if (pendingSeek.shouldPlay) { - setTimeout(() => { - logger.log('[VideoPlayer] Resuming playback after source change seek'); - setPaused(false); - }, 850); // Delay should be slightly more than seekToTime's internal timeout - } - - setTimeout(() => { - setPendingSeek(null); - setIsChangingSource(false); - }, 900); - } - }, delayTime); - } else { - // No seeking needed, just resume playback if it was playing - if (pendingSeek.shouldPlay) { - setTimeout(() => { - logger.log('[VideoPlayer] No seek needed, just resuming playback'); - setPaused(false); - }, 500); - } - - setTimeout(() => { - setPendingSeek(null); - setIsChangingSource(false); - }, 600); - } - } - }, [pendingSeek, isPlayerReady, isVideoLoaded, duration]); - // AirPlay handler const handleAirPlayPress = async () => { if (!ksPlayerRef.current) return; @@ -2375,61 +2330,42 @@ const KSPlayerCore: React.FC = () => { return; } - // On iOS: All streams use KSPlayer, no need to switch players - // Stream switching is handled internally by KSPlayerCore - - setIsChangingSource(true); setShowSourcesModal(false); - try { - // Save current state - const savedPosition = currentTime; - const wasPlaying = !paused; - - logger.log(`[VideoPlayer] Changing source from ${currentStreamUrl} to ${newStream.url}`); - logger.log(`[VideoPlayer] Saved position: ${savedPosition}, was playing: ${wasPlaying}`); - - // Extract quality and provider information from the new stream - let newQuality = newStream.quality; - if (!newQuality && newStream.title) { - // Try to extract quality from title (e.g., "1080p", "720p") - const qualityMatch = newStream.title.match(/(\d+)p/); - newQuality = qualityMatch ? qualityMatch[0] : undefined; // Use [0] to get full match like "1080p" - } - - // For provider, try multiple fields - const newProvider = newStream.addonName || newStream.name || newStream.addon || 'Unknown'; - - // For stream name, prioritize the stream name over title - const newStreamName = newStream.name || newStream.title || 'Unknown Stream'; - - logger.log(`[VideoPlayer] Stream object:`, newStream); - logger.log(`[VideoPlayer] Extracted - Quality: ${newQuality}, Provider: ${newProvider}, Stream Name: ${newStreamName}`); - logger.log(`[VideoPlayer] Available fields - quality: ${newStream.quality}, title: ${newStream.title}, addonName: ${newStream.addonName}, name: ${newStream.name}, addon: ${newStream.addon}`); - - // Stop current playback - setPaused(true); - - // Set pending seek state - setPendingSeek({ position: savedPosition, shouldPlay: wasPlaying }); - - // Update the stream URL and details immediately - setCurrentStreamUrl(newStream.url); - setCurrentQuality(newQuality); - setCurrentStreamProvider(newProvider); - setCurrentStreamName(newStreamName); - - // Reset player state for new source - setCurrentTime(0); - setDuration(0); - setIsPlayerReady(false); - setIsVideoLoaded(false); - - } catch (error) { - logger.error('[VideoPlayer] Error changing source:', error); - setPendingSeek(null); - setIsChangingSource(false); + // Extract quality and provider information + let newQuality = newStream.quality; + if (!newQuality && newStream.title) { + const qualityMatch = newStream.title.match(/(\d+)p/); + newQuality = qualityMatch ? qualityMatch[0] : undefined; } + + const newProvider = newStream.addonName || newStream.name || newStream.addon || 'Unknown'; + const newStreamName = newStream.name || newStream.title || 'Unknown Stream'; + + // Pause current playback + setPaused(true); + + // Navigate with replace to reload player with new source + setTimeout(() => { + navigation.replace('PlayerIOS', { + uri: newStream.url, + title: title, + episodeTitle: episodeTitle, + season: season, + episode: episode, + quality: newQuality, + year: year, + streamProvider: newProvider, + streamName: newStreamName, + headers: newStream.headers || undefined, + id, + type, + episodeId, + imdbId: imdbId ?? undefined, + backdrop: backdrop || undefined, + availableStreams: availableStreams, + }); + }, 100); }; useEffect(() => { @@ -2551,27 +2487,6 @@ const KSPlayerCore: React.FC = () => { )} - {/* Source Change Loading Overlay */} - {isChangingSource && ( - - - - Changing source... - Please wait while we load the new stream - - - )} - { availableStreams={availableStreams} currentStreamUrl={currentStreamUrl} onSelectStream={handleSelectStream} - isChangingSource={isChangingSource} /> {/* Error Modal */} diff --git a/src/components/player/modals/SourcesModal.tsx b/src/components/player/modals/SourcesModal.tsx index ada3bf39..6d993412 100644 --- a/src/components/player/modals/SourcesModal.tsx +++ b/src/components/player/modals/SourcesModal.tsx @@ -15,7 +15,7 @@ interface SourcesModalProps { availableStreams: { [providerId: string]: { streams: Stream[]; addonName: string } }; currentStreamUrl: string; onSelectStream: (stream: Stream) => void; - isChangingSource: boolean; + isChangingSource?: boolean; } const { width } = Dimensions.get('window'); @@ -70,7 +70,7 @@ export const SourcesModal: React.FC = ({ availableStreams, currentStreamUrl, onSelectStream, - isChangingSource, + isChangingSource = false, }) => { const handleClose = () => { setShowSourcesModal(false); @@ -81,7 +81,7 @@ export const SourcesModal: React.FC = ({ const sortedProviders = Object.entries(availableStreams); const handleStreamSelect = (stream: Stream) => { - if (stream.url !== currentStreamUrl && !isChangingSource) { + if (stream.url !== currentStreamUrl && (!isChangingSource || isChangingSource === false)) { onSelectStream(stream); } }; @@ -228,11 +228,11 @@ export const SourcesModal: React.FC = ({ padding: 16, borderWidth: 1, borderColor: isSelected ? 'rgba(59, 130, 246, 0.3)' : 'rgba(255, 255, 255, 0.1)', - opacity: isChangingSource && !isSelected ? 0.6 : 1, + opacity: (isChangingSource && !isSelected) ? 0.6 : 1, }} onPress={() => handleStreamSelect(stream)} activeOpacity={0.7} - disabled={isChangingSource} + disabled={isChangingSource === true} > diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index cd784d03..b6c31e0e 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -862,6 +862,7 @@ const MainTabs = () => { // Prefer native lazy/freeze when available; still pass for parity lazy: true, freezeOnBlur: true, + tabBarStyle: { display: 'none' }, }} > { /> null} screenOptions={({ route, navigation, theme }) => ({ transitionSpec: { animation: 'timing',