diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 82097f41..fa364537 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -276,10 +276,6 @@ const AndroidVideoPlayer: React.FC = () => { initializePlayer(); return () => { subscription?.remove(); - const unlockOrientation = async () => { - await ScreenOrientation.unlockAsync(); - }; - unlockOrientation(); disableImmersiveMode(); }; }, []); diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index 90ca57e0..0d75e384 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -134,6 +134,9 @@ const VideoPlayer: React.FC = () => { const [showResumeOverlay, setShowResumeOverlay] = useState(false); const [resumePosition, setResumePosition] = useState(null); const [savedDuration, setSavedDuration] = useState(null); + const initialSeekTargetRef = useRef(null); + const initialSeekVerifiedRef = useRef(false); + const isSourceSeekableRef = useRef(null); const fadeAnim = useRef(new Animated.Value(1)).current; const [isOpeningAnimationComplete, setIsOpeningAnimationComplete] = useState(false); const openingFadeAnim = useRef(new Animated.Value(0)).current; @@ -292,10 +295,7 @@ const VideoPlayer: React.FC = () => { lockOrientation(); return () => { - // Unlock orientation when component unmounts - ScreenOrientation.unlockAsync().catch(() => { - // Ignore unlock errors - }); + // Do not unlock orientation here; we unlock explicitly on close to avoid mid-transition flips }; }, []); @@ -403,6 +403,7 @@ const VideoPlayer: React.FC = () => { setResumePosition(savedProgress.currentTime); setSavedDuration(savedProgress.duration); setInitialPosition(savedProgress.currentTime); + initialSeekTargetRef.current = savedProgress.currentTime; logger.log(`[VideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`); if (appSettings.alwaysResume) { logger.log(`[VideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`); @@ -1280,6 +1281,24 @@ const VideoPlayer: React.FC = () => { logger.log(`[VideoPlayer] Post-load initial seek to: ${initialPosition}s`); seekToTime(initialPosition); setIsInitialSeekComplete(true); + // Verify whether the seek actually took effect (detect non-seekable sources) + if (!initialSeekVerifiedRef.current) { + initialSeekVerifiedRef.current = true; + const target = initialSeekTargetRef.current ?? initialPosition; + setTimeout(() => { + const delta = Math.abs(currentTime - (target || 0)); + if (target && (currentTime < target - 1.5)) { + logger.warn(`[VideoPlayer] Initial seek appears ignored (delta=${delta.toFixed(2)}). Treating source as non-seekable; starting from 0`); + isSourceSeekableRef.current = false; + // Reset resume intent and continue from 0 + setInitialPosition(null); + setResumePosition(null); + setShowResumeOverlay(false); + } else { + isSourceSeekableRef.current = true; + } + }, 1200); + } } }, [isVideoLoaded, initialPosition, duration]); diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 6844fd9b..4a1f8f41 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -771,8 +771,8 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack name="Player" component={VideoPlayer as any} options={{ - animation: 'slide_from_right', - animationDuration: Platform.OS === 'android' ? 200 : 300, + animation: Platform.OS === 'android' ? 'none' : 'fade', + animationDuration: Platform.OS === 'android' ? 0 : 300, // Force fullscreen presentation on iPad presentation: Platform.OS === 'ios' ? 'fullScreenModal' : 'card', // Disable gestures during video playback diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index a181971f..a657cd26 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -839,19 +839,24 @@ export const StreamsScreen = () => { const streamName = stream.name || stream.title || 'Unnamed Stream'; const streamProvider = stream.addonId || stream.addonName || stream.name; - // Stream provider debug logging removed - - // NetMirror stream logging removed - - // Navigate to player immediately without waiting for orientation lock - // This prevents delay in player opening + // Add pre-navigation orientation lock to reduce glitch + try { + await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); + } catch (e) { + logger.warn('[StreamsScreen] Pre-navigation orientation lock failed:', e); + } + // Small delay to allow orientation to settle + await new Promise(res => setTimeout(res, Platform.OS === 'ios' ? 120 : 60)); + + // Show a quick full-screen black overlay to mask rotation flicker + // by setting a transient state that renders a covering View (implementation already supported by dark backgrounds) navigation.navigate('Player', { uri: stream.url, title: metadata?.name || '', episodeTitle: type === 'series' ? currentEpisode?.name : undefined, season: type === 'series' ? currentEpisode?.season_number : undefined, episode: type === 'series' ? currentEpisode?.episode_number : undefined, - quality: stream.title?.match(/(\d+)p/)?.[1] || undefined, + quality: (stream.title?.match(/(\d+)p/) || [])[1] || undefined, year: metadata?.year, streamProvider: streamProvider, streamName: streamName, @@ -863,17 +868,6 @@ export const StreamsScreen = () => { availableStreams: streamsToPass, backdrop: bannerImage || undefined, }); - - // Lock orientation to landscape after navigation has started - // This allows the player to open immediately while orientation is being set - try { - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE) - .catch(error => { - logger.error('[StreamsScreen] Error locking orientation after navigation:', error); - }); - } catch (error) { - logger.error('[StreamsScreen] Error locking orientation after navigation:', error); - } }, [metadata, type, currentEpisode, navigation, id, selectedEpisode, imdbId, episodeStreams, groupedStreams, bannerImage]);