From c710a173f2eca7d6695ac7463677b0316ab947b6 Mon Sep 17 00:00:00 2001 From: tapframe Date: Fri, 19 Sep 2025 14:46:24 +0530 Subject: [PATCH] videoplayer fix --- src/components/player/AndroidVideoPlayer.tsx | 26 ++++++++- src/components/player/VideoPlayer.tsx | 57 ++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 78c1f17a..203efa19 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -276,6 +276,9 @@ const AndroidVideoPlayer: React.FC = () => { const loadStartAtRef = useRef(null); const firstFrameAtRef = useRef(null); + // iOS playback state tracking for system interruptions + const wasPlayingBeforeIOSInterruptionRef = useRef(false); + // Pause overlay state const [showPauseOverlay, setShowPauseOverlay] = useState(false); const pauseOverlayTimerRef = useRef(null); @@ -615,13 +618,34 @@ const AndroidVideoPlayer: React.FC = () => { const onAppStateChange = (state: string) => { if (state === 'active') { enableImmersiveMode(); + // On iOS, if we were playing before system interruption and the app becomes active again, + // ensure playback resumes (handles status bar pull-down case) + if (Platform.OS === 'ios' && wasPlayingBeforeIOSInterruptionRef.current && isPlayerReady) { + logger.log('[AndroidVideoPlayer] iOS app active - resuming playback after system interruption'); + // Small delay to allow system UI to settle + setTimeout(() => { + if (isMounted.current && wasPlayingBeforeIOSInterruptionRef.current) { + setPaused(false); // Resume playback + wasPlayingBeforeIOSInterruptionRef.current = false; // Reset flag + } + }, 300); // Slightly longer delay for iOS + } + } else if (state === 'background' || state === 'inactive') { + // On iOS, when app goes inactive (like status bar pull), track if we were playing + if (Platform.OS === 'ios') { + wasPlayingBeforeIOSInterruptionRef.current = !paused; + if (!paused) { + logger.log('[AndroidVideoPlayer] iOS app inactive - tracking playing state for resume'); + setPaused(true); + } + } } }; const sub = AppState.addEventListener('change', onAppStateChange); return () => { sub.remove(); }; - }, []); + }, [paused, isPlayerReady]); const startOpeningAnimation = () => { // Logo entrance animation - optimized for faster appearance diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index 964c64a0..8db2e8c1 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -226,6 +226,11 @@ const VideoPlayer: React.FC = () => { const controlsTimeout = useRef(null); const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false); + // Silent startup-timeout retry state + const startupRetryCountRef = useRef(0); + const startupRetryTimerRef = useRef(null); + const MAX_STARTUP_RETRIES = 3; + // Pause overlay state const [showPauseOverlay, setShowPauseOverlay] = useState(false); const pauseOverlayTimerRef = useRef(null); @@ -979,6 +984,12 @@ const VideoPlayer: React.FC = () => { if (DEBUG_MODE) { logger.log('[VideoPlayer] Video loaded:', data); } + // Clear any pending startup silent retry timers and counters on success + if (startupRetryTimerRef.current) { + clearTimeout(startupRetryTimerRef.current); + startupRetryTimerRef.current = null; + } + startupRetryCountRef.current = 0; if (!isMounted.current) { logger.warn('[VideoPlayer] Component unmounted, skipping onLoad'); return; @@ -1374,6 +1385,48 @@ const VideoPlayer: React.FC = () => { try { logger.error('[VideoPlayer] Playback Error:', error); + // Detect KSPlayer startup timeout and silently retry without UI + const errText = typeof error === 'string' + ? error + : (error?.message || error?.error?.message || error?.title || ''); + const isStartupTimeout = /timeout/i.test(errText) && /stream.*ready/i.test(errText); + if (isStartupTimeout && !isVideoLoaded) { + // Suppress any error modal and retry silently + if (errorTimeoutRef.current) { + clearTimeout(errorTimeoutRef.current); + errorTimeoutRef.current = null; + } + setShowErrorModal(false); + + const attempt = startupRetryCountRef.current; + if (attempt < MAX_STARTUP_RETRIES) { + const backoffMs = [4000, 8000, 12000][attempt] ?? 8000; + startupRetryCountRef.current = attempt + 1; + logger.warn(`[VideoPlayer] Startup timeout; retrying (${attempt + 1}/${MAX_STARTUP_RETRIES}) in ${backoffMs}ms`); + + if (startupRetryTimerRef.current) { + clearTimeout(startupRetryTimerRef.current); + } + startupRetryTimerRef.current = setTimeout(() => { + if (!ksPlayerRef.current) return; + try { + // Reload the same source silently using native bridge + ksPlayerRef.current.setSource({ + uri: currentStreamUrl, + headers: headers && Object.keys(headers).length > 0 ? headers : undefined + }); + // Ensure playback resumes if not paused + ksPlayerRef.current.setPaused(paused); + logger.log('[VideoPlayer] Retried source load via KSPlayer.setSource'); + } catch (e) { + logger.error('[VideoPlayer] Error during silent retry setSource:', e); + } + }, backoffMs); + return; // Exit handler; do not show UI + } + logger.error('[VideoPlayer] Max startup retries reached; proceeding to normal error handling'); + } + // Check for audio codec errors (TrueHD, DTS, Dolby, etc.) const isAudioCodecError = (error?.message && /(trhd|truehd|true\s?hd|dts|dolby|atmos|e-ac3|ac3)/i.test(error.message)) || @@ -2063,6 +2116,10 @@ const VideoPlayer: React.FC = () => { if (brightnessOverlayTimeout.current) { clearTimeout(brightnessOverlayTimeout.current); } + if (startupRetryTimerRef.current) { + clearTimeout(startupRetryTimerRef.current); + startupRetryTimerRef.current = null; + } }; }, []);