feat: Implement profilespecific watch progress syncing with upsert logic and trigger progress saving on pause and seek completion.

This commit is contained in:
tapframe 2026-02-23 00:16:38 +05:30
parent 81d528e0f6
commit 32e93ea25d
4 changed files with 36 additions and 21 deletions

View file

@ -768,7 +768,17 @@ const AndroidVideoPlayer: React.FC = () => {
onProgress={handleProgress} onProgress={handleProgress}
onSeek={(data) => { onSeek={(data) => {
playerState.isSeeking.current = false; playerState.isSeeking.current = false;
if (data.currentTime) traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true); if (data.currentTime) {
if (id && type && playerState.duration > 0) {
void storageService.setWatchProgress(id, type, {
currentTime: data.currentTime,
duration: playerState.duration,
lastUpdated: Date.now(),
addonId: currentStreamProvider
}, episodeId);
}
traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
}
}} }}
onEnd={() => { onEnd={() => {
if (modals.showEpisodeStreamsModal) return; if (modals.showEpisodeStreamsModal) return;

View file

@ -48,6 +48,7 @@ import { useTraktAutosync } from '../../hooks/useTraktAutosync';
import { useMetadata } from '../../hooks/useMetadata'; import { useMetadata } from '../../hooks/useMetadata';
import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls'; import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls';
import stremioService from '../../services/stremioService'; import stremioService from '../../services/stremioService';
import { storageService } from '../../services/storageService';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
// Utils // Utils
@ -227,7 +228,15 @@ const KSPlayerCore: React.FC = () => {
currentTime, currentTime,
duration, duration,
isSeeking, isSeeking,
isMounted isMounted,
onSeekComplete: (timeInSeconds) => {
if (!id || !type || duration <= 0) return;
void storageService.setWatchProgress(id, type, {
currentTime: timeInSeconds,
duration,
lastUpdated: Date.now()
}, episodeId);
}
}); });
const watchProgress = useWatchProgress( const watchProgress = useWatchProgress(

View file

@ -17,6 +17,7 @@ interface PlayerControlsConfig {
duration: number; duration: number;
isSeeking: MutableRefObject<boolean>; isSeeking: MutableRefObject<boolean>;
isMounted: MutableRefObject<boolean>; isMounted: MutableRefObject<boolean>;
onSeekComplete?: (timeInSeconds: number) => void;
} }
export const usePlayerControls = (config: PlayerControlsConfig) => { export const usePlayerControls = (config: PlayerControlsConfig) => {
@ -27,7 +28,8 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
currentTime, currentTime,
duration, duration,
isSeeking, isSeeking,
isMounted isMounted,
onSeekComplete
} = config; } = config;
// iOS seeking helpers // iOS seeking helpers
@ -54,6 +56,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
// Actually perform the seek // Actually perform the seek
playerRef.current.seek(timeInSeconds); playerRef.current.seek(timeInSeconds);
onSeekComplete?.(timeInSeconds);
// Debounce the seeking state reset // Debounce the seeking state reset
seekTimeoutRef.current = setTimeout(() => { seekTimeoutRef.current = setTimeout(() => {
@ -62,7 +65,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
} }
}, 500); }, 500);
} }
}, [duration, paused, playerRef, isSeeking, isMounted]); }, [duration, paused, playerRef, isSeeking, isMounted, onSeekComplete]);
const skip = useCallback((seconds: number) => { const skip = useCallback((seconds: number) => {
seekToTime(currentTime + seconds); seekToTime(currentTime + seconds);

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native'; import { AppState } from 'react-native';
import { storageService } from '../../../services/storageService'; import { storageService } from '../../../services/storageService';
import { logger } from '../../../utils/logger'; import { logger } from '../../../utils/logger';
import { useSettings } from '../../../hooks/useSettings'; import { useSettings } from '../../../hooks/useSettings';
@ -19,10 +19,9 @@ export const useWatchProgress = (
const [savedDuration, setSavedDuration] = useState<number | null>(null); const [savedDuration, setSavedDuration] = useState<number | null>(null);
const [initialPosition, setInitialPosition] = useState<number | null>(null); const [initialPosition, setInitialPosition] = useState<number | null>(null);
const [showResumeOverlay, setShowResumeOverlay] = useState(false); const [showResumeOverlay, setShowResumeOverlay] = useState(false);
const [progressSaveInterval, setProgressSaveInterval] = useState<NodeJS.Timeout | null>(null);
const { settings: appSettings } = useSettings(); const { settings: appSettings } = useSettings();
const initialSeekTargetRef = useRef<number | null>(null); const initialSeekTargetRef = useRef<number | null>(null);
const wasPausedRef = useRef<boolean>(paused);
// Values refs for unmount cleanup // Values refs for unmount cleanup
const currentTimeRef = useRef(currentTime); const currentTimeRef = useRef(currentTime);
@ -126,22 +125,16 @@ export const useWatchProgress = (
} }
}; };
// Save Interval
useEffect(() => { useEffect(() => {
if (id && type && !paused && duration > 0) { if (wasPausedRef.current !== paused) {
if (progressSaveInterval) clearInterval(progressSaveInterval); const becamePaused = paused;
wasPausedRef.current = paused;
const interval = setInterval(() => { if (becamePaused) {
saveWatchProgress(); void saveWatchProgress();
}, 10000); }
setProgressSaveInterval(interval);
return () => {
clearInterval(interval);
setProgressSaveInterval(null);
};
} }
}, [id, type, paused, currentTime, duration]); }, [paused]);
// Unmount Save - deferred to allow navigation to complete first // Unmount Save - deferred to allow navigation to complete first
useEffect(() => { useEffect(() => {