From ff1f1b2669d7aef53f46aa2af625cbd9ef376a1c Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 10 Jan 2026 03:02:07 +0530 Subject: [PATCH] fix re render subs --- ios/Nuvio/Info.plist | 2 ++ src/components/player/KSPlayerCore.tsx | 34 ++++++++++++++++--- .../player/ios/components/AVPlayerSurface.tsx | 5 ++- .../player/ios/components/KSPlayerSurface.tsx | 9 +++-- .../player/modals/SubtitleModals.tsx | 4 ++- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist index 8acd911..ec762d1 100644 --- a/ios/Nuvio/Info.plist +++ b/ios/Nuvio/Info.plist @@ -68,6 +68,8 @@ UIBackgroundModes audio + airplay + picture-in-picture UIFileSharingEnabled diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 84809eb..84c5f6f 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -149,9 +149,12 @@ const KSPlayerCore: React.FC = () => { // Track auto-selection refs to prevent duplicate selections const hasAutoSelectedTracks = useRef(false); + // Guard to prevent auto-select useEffect from running during manual subtitle loading + const isManuallyLoadingSubtitle = useRef(false); // Track previous video session to reset subtitle offset only when video actually changes const previousVideoRef = useRef<{ uri?: string; episodeId?: string }>({}); + // Reset subtitle offset when starting a new video session useEffect(() => { @@ -315,6 +318,14 @@ const KSPlayerCore: React.FC = () => { }; const loadWyzieSubtitle = async (subtitle: WyzieSubtitle) => { + // Prevent concurrent calls - return early if already loading + if (isManuallyLoadingSubtitle.current) { + return; + } + + // Set guard to prevent auto-select useEffect and concurrent calls from interfering + isManuallyLoadingSubtitle.current = true; + modals.setShowSubtitleLanguageModal(false); customSubs.setIsLoadingSubtitles(true); try { @@ -327,9 +338,11 @@ const KSPlayerCore: React.FC = () => { srtContent = await resp.text(); } const parsedCues = parseSRT(srtContent); + + // Update all state together - the guards prevent interference from other effects customSubs.setCustomSubtitles(parsedCues); customSubs.setUseCustomSubtitles(true); - customSubs.setSelectedExternalSubtitleId(subtitle.id); // Track the selected external subtitle + customSubs.setSelectedExternalSubtitleId(subtitle.id); tracks.selectTextTrack(-1); const adjustedTime = currentTime + (customSubs.subtitleOffsetSec || 0); @@ -340,6 +353,10 @@ const KSPlayerCore: React.FC = () => { logger.error('[VideoPlayer] Error loading wyzie', e); } finally { customSubs.setIsLoadingSubtitles(false); + // Clear guard after a short delay to allow state to settle + setTimeout(() => { + isManuallyLoadingSubtitle.current = false; + }, 500); } }; @@ -353,7 +370,7 @@ const KSPlayerCore: React.FC = () => { // Auto-select subtitles when both internal tracks and video are loaded // This ensures we wait for internal tracks before falling back to external useEffect(() => { - if (!isVideoLoaded || hasAutoSelectedTracks.current || !settings?.enableSubtitleAutoSelect) { + if (!isVideoLoaded || hasAutoSelectedTracks.current || !settings?.enableSubtitleAutoSelect || isManuallyLoadingSubtitle.current) { return; } @@ -523,7 +540,10 @@ const KSPlayerCore: React.FC = () => { // Track selection handlers - update state, prop change triggers native update const handleSelectTextTrack = useCallback((trackId: number) => { - console.log('[KSPlayerCore] handleSelectTextTrack called with trackId:', trackId); + // Prevent interference during manual subtitle loading + if (isManuallyLoadingSubtitle.current && trackId === -1) { + return; + } // Disable custom subtitles when selecting a built-in track // This ensures the textTrack prop is actually passed to the native player @@ -655,7 +675,13 @@ const KSPlayerCore: React.FC = () => { audioTrack={tracks.selectedAudioTrack ?? undefined} textTrack={customSubs.useCustomSubtitles ? -1 : tracks.selectedTextTrack} onAudioTracks={(d) => tracks.setKsAudioTracks(d.audioTracks || [])} - onTextTracks={(d) => tracks.setKsTextTracks(d.textTracks || [])} + onTextTracks={(d) => { + // Prevent textTracks updates during manual subtitle loading to avoid infinite loops + if (isManuallyLoadingSubtitle.current) { + return; + } + tracks.setKsTextTracks(d.textTracks || []); + }} onLoad={(d) => { onLoad(d); // If we fell back from AVPlayer, continue from last time once MPV is ready. diff --git a/src/components/player/ios/components/AVPlayerSurface.tsx b/src/components/player/ios/components/AVPlayerSurface.tsx index b4301ab..153de9b 100644 --- a/src/components/player/ios/components/AVPlayerSurface.tsx +++ b/src/components/player/ios/components/AVPlayerSurface.tsx @@ -103,6 +103,9 @@ export const AVPlayerSurface: React.FC = ({ rate={playbackSpeed} resizeMode={resizeMode as any} allowsExternalPlayback={true} + // iOS PiP: enter PiP automatically when user leaves the app (home/app switcher) + // Docs: https://docs.thewidlarzgroup.com/react-native-video/docs/v6/component/props/#enterpictureinpictureonleave + enterPictureInPictureOnLeave={true} selectedAudioTrack={selectedAudioTrack} selectedTextTrack={selectedTextTrack} onLoad={handleLoad} @@ -113,7 +116,7 @@ export const AVPlayerSurface: React.FC = ({ progressUpdateInterval={250} // Keep background behavior consistent with the rest of the player logic playInBackground={false} - playWhenInactive={false} + playWhenInactive={true} ignoreSilentSwitch="ignore" /> diff --git a/src/components/player/ios/components/KSPlayerSurface.tsx b/src/components/player/ios/components/KSPlayerSurface.tsx index 7ce655e..8ca9875 100644 --- a/src/components/player/ios/components/KSPlayerSurface.tsx +++ b/src/components/player/ios/components/KSPlayerSurface.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useMemo, useRef } from 'react'; import { Animated } from 'react-native'; import { PinchGestureHandler, State, PinchGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import MPVPlayerComponent from '../../MPVPlayerComponent'; @@ -78,6 +78,11 @@ export const KSPlayerSurface: React.FC = ({ subtitleBottomOffset }) => { const pinchRef = useRef(null); + const memoSource = useMemo(() => { + const h = headers ?? undefined; + return h ? { uri, headers: h } : { uri }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [uri, headers ? JSON.stringify(headers) : '']); const onPinchGestureEvent = (event: PinchGestureHandlerGestureEvent) => { const { scale } = event.nativeEvent; @@ -130,7 +135,7 @@ export const KSPlayerSurface: React.FC = ({ }}> = ({ const menuMaxHeight = height * 0.95; React.useEffect(() => { - if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) fetchAvailableSubtitles(); + if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) { + fetchAvailableSubtitles(); + } }, [showSubtitleModal]); const handleClose = () => setShowSubtitleModal(false);