diff --git a/package-lock.json b/package-lock.json index b4c0c06..b085182 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "expo-glass-effect": "~0.1.4", "expo-haptics": "~15.0.7", "expo-intent-launcher": "~13.0.7", - "expo-libvlc-player": "^2.2.3", + "expo-keep-awake": "~15.0.8", "expo-linear-gradient": "~15.0.7", "expo-localization": "~17.0.7", "expo-navigation-bar": "~5.0.10", @@ -6512,17 +6512,6 @@ "react": "*" } }, - "node_modules/expo-libvlc-player": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/expo-libvlc-player/-/expo-libvlc-player-2.2.5.tgz", - "integrity": "sha512-Hl0XiRNK5iwPMDRWYouA7+Xzf804GZ/AMVTU87ktUlQMU5bgTUFgmi8QjlOLGEF5LpVp7LDFfQwsDpXP1ggpag==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, "node_modules/expo-linear-gradient": { "version": "15.0.8", "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.8.tgz", diff --git a/package.json b/package.json index 54fbb23..084952f 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "expo-glass-effect": "~0.1.4", "expo-haptics": "~15.0.7", "expo-intent-launcher": "~13.0.7", + "expo-keep-awake": "~15.0.8", "expo-linear-gradient": "~15.0.7", "expo-localization": "~17.0.7", "expo-navigation-bar": "~5.0.10", @@ -103,4 +104,4 @@ "xcode": "^3.0.1" }, "private": true -} \ No newline at end of file +} diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 57588ca..6bbc838 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -10,13 +10,14 @@ import { usePlayerState, usePlayerModals, useSpeedControl, - useOpeningAnimation + useOpeningAnimation, + useWatchProgress } from './hooks'; // Android-specific hooks import { usePlayerSetup } from './android/hooks/usePlayerSetup'; import { usePlayerTracks } from './android/hooks/usePlayerTracks'; -import { useWatchProgress } from './android/hooks/useWatchProgress'; + import { usePlayerControls } from './android/hooks/usePlayerControls'; import { useNextEpisode } from './android/hooks/useNextEpisode'; diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 270c83c..79bf387 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -16,6 +16,7 @@ import EpisodesModal from './modals/EpisodesModal'; import { EpisodeStreamsModal } from './modals/EpisodeStreamsModal'; import { ErrorModal } from './modals/ErrorModal'; import CustomSubtitles from './subtitles/CustomSubtitles'; +import ResumeOverlay from './modals/ResumeOverlay'; import { SpeedActivatedOverlay, PauseOverlay, GestureControls } from './components'; // Platform-specific components @@ -30,7 +31,8 @@ import { usePlayerTracks, useCustomSubtitles, usePlayerControls, - usePlayerSetup + usePlayerSetup, + useWatchProgress } from './hooks'; // Platform-specific hooks @@ -137,6 +139,15 @@ const KSPlayerCore: React.FC = () => { isMounted }); + const watchProgress = useWatchProgress( + id, type, episodeId, + currentTime, + duration, + paused, + traktAutosync, + controls.seekToTime + ); + // Gestures const fadeAnim = useRef(new Animated.Value(1)).current; @@ -157,9 +168,6 @@ const KSPlayerCore: React.FC = () => { const [brightness, setBrightnessState] = useState(0.5); const [isSliderDragging, setIsSliderDragging] = useState(false); - // Watch Progress State - const [initialPosition, setInitialPosition] = useState(routeInitialPosition || null); - // Shared Gesture Hook const gestureControls = usePlayerGestureControls({ volume: volume, @@ -173,7 +181,8 @@ const KSPlayerCore: React.FC = () => { setScreenDimensions, setVolume: setVolumeState, setBrightness: setBrightnessState, - isOpeningAnimationComplete: openingAnim.isOpeningAnimationComplete + isOpeningAnimationComplete: openingAnim.isOpeningAnimationComplete, + paused: paused }); // Refs for Logic @@ -328,9 +337,10 @@ const KSPlayerCore: React.FC = () => { openingAnim.completeOpeningAnimation(); // Initial Seek - if (initialPosition && initialPosition > 0) { + const resumeTarget = routeInitialPosition || watchProgress.initialPosition || watchProgress.initialSeekTargetRef?.current; + if (resumeTarget && resumeTarget > 0 && !watchProgress.showResumeOverlay && data.duration > 0) { setTimeout(() => { - controls.seekToTime(initialPosition); + controls.seekToTime(resumeTarget); }, 500); } @@ -602,6 +612,23 @@ const KSPlayerCore: React.FC = () => { speed={speedControl.holdToSpeedValue} /> + { + watchProgress.setShowResumeOverlay(false); + if (watchProgress.resumePosition) controls.seekToTime(watchProgress.resumePosition); + }} + handleStartFromBeginning={() => { + watchProgress.setShowResumeOverlay(false); + controls.seekToTime(0); + }} + /> + {/* Pause Overlay */} (null); const isAppBackgrounded = useRef(false); + // Prevent screen sleep while playing + // Prevent screen sleep while playing + useEffect(() => { + if (!paused) { + activateKeepAwakeAsync(); + } else { + deactivateKeepAwake(); + } + return () => { + deactivateKeepAwake(); + }; + }, [paused]); + const enableImmersiveMode = async () => { if (Platform.OS === 'android') { // Standard immersive mode diff --git a/src/components/player/hooks/index.ts b/src/components/player/hooks/index.ts index 9c91138..570d3de 100644 --- a/src/components/player/hooks/index.ts +++ b/src/components/player/hooks/index.ts @@ -19,3 +19,4 @@ export { usePlayerSetup } from './usePlayerSetup'; // Content export { useNextEpisode } from './useNextEpisode'; +export { useWatchProgress } from './useWatchProgress'; diff --git a/src/components/player/hooks/usePlayerControls.ts b/src/components/player/hooks/usePlayerControls.ts index c94cc74..16a5a18 100644 --- a/src/components/player/hooks/usePlayerControls.ts +++ b/src/components/player/hooks/usePlayerControls.ts @@ -46,10 +46,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => { isSeeking.current = true; // iOS optimization: pause while seeking for smoother experience - if (Platform.OS === 'ios') { - iosWasPausedDuringSeekRef.current = paused; - if (!paused) setPaused(true); - } + // Actually perform the seek playerRef.current.seek(timeInSeconds); @@ -59,10 +56,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => { if (isMounted.current && isSeeking.current) { isSeeking.current = false; // Resume if it was playing (iOS specific) - if (Platform.OS === 'ios' && iosWasPausedDuringSeekRef.current === false) { - setPaused(false); - iosWasPausedDuringSeekRef.current = null; - } + } }, 500); } diff --git a/src/components/player/hooks/usePlayerSetup.ts b/src/components/player/hooks/usePlayerSetup.ts index 3b43f6a..45851ca 100644 --- a/src/components/player/hooks/usePlayerSetup.ts +++ b/src/components/player/hooks/usePlayerSetup.ts @@ -1,12 +1,8 @@ -/** - * Shared Player Setup Hook - * Used by both Android (VLC) and iOS (KSPlayer) players - * Handles StatusBar, orientation, brightness, and app state - */ import { useEffect, useRef, useCallback } from 'react'; import { StatusBar, Dimensions, AppState, InteractionManager, Platform } from 'react-native'; import * as Brightness from 'expo-brightness'; import * as ScreenOrientation from 'expo-screen-orientation'; +import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake'; import { logger } from '../../../utils/logger'; import { useFocusEffect } from '@react-navigation/native'; @@ -15,6 +11,7 @@ interface PlayerSetupConfig { setVolume: (vol: number) => void; setBrightness: (bri: number) => void; isOpeningAnimationComplete: boolean; + paused: boolean; } export const usePlayerSetup = (config: PlayerSetupConfig) => { @@ -22,9 +19,23 @@ export const usePlayerSetup = (config: PlayerSetupConfig) => { setScreenDimensions, setVolume, setBrightness, - isOpeningAnimationComplete + isOpeningAnimationComplete, + paused } = config; + // Prevent screen sleep while playing + // Prevent screen sleep while playing + useEffect(() => { + if (!paused) { + activateKeepAwakeAsync(); + } else { + deactivateKeepAwake(); + } + return () => { + deactivateKeepAwake(); + }; + }, [paused]); + const isAppBackgrounded = useRef(false); const enableImmersiveMode = () => { diff --git a/src/components/player/android/hooks/useWatchProgress.ts b/src/components/player/hooks/useWatchProgress.ts similarity index 96% rename from src/components/player/android/hooks/useWatchProgress.ts rename to src/components/player/hooks/useWatchProgress.ts index 8f38d98..506d84c 100644 --- a/src/components/player/android/hooks/useWatchProgress.ts +++ b/src/components/player/hooks/useWatchProgress.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; -import { storageService } from '../../../../services/storageService'; -import { logger } from '../../../../utils/logger'; -import { useSettings } from '../../../../hooks/useSettings'; +import { storageService } from '../../../services/storageService'; +import { logger } from '../../../utils/logger'; +import { useSettings } from '../../../hooks/useSettings'; export const useWatchProgress = ( id: string | undefined,