mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Improved trakt.
This commit is contained in:
parent
df89c16246
commit
6405fd2c71
6 changed files with 185 additions and 225 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -42,4 +42,5 @@ CHANGELOG.md
|
|||
android/
|
||||
HEATING_OPTIMIZATIONS.md
|
||||
ios
|
||||
android
|
||||
android
|
||||
sliderreadme.md
|
||||
|
|
|
|||
|
|
@ -264,16 +264,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}, []);
|
||||
|
||||
const startOpeningAnimation = () => {
|
||||
// Logo entrance animation
|
||||
// Logo entrance animation - optimized for faster appearance
|
||||
Animated.parallel([
|
||||
Animated.timing(logoOpacityAnim, {
|
||||
toValue: 1,
|
||||
duration: 600,
|
||||
duration: 300, // Reduced from 600ms to 300ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.spring(logoScaleAnim, {
|
||||
toValue: 1,
|
||||
tension: 50,
|
||||
tension: 80, // Increased tension for faster spring
|
||||
friction: 8,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
|
|
@ -284,12 +284,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
return Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1.05,
|
||||
duration: 1000,
|
||||
duration: 800, // Reduced from 1000ms to 800ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
duration: 800, // Reduced from 1000ms to 800ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]);
|
||||
|
|
@ -303,38 +303,34 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
});
|
||||
};
|
||||
|
||||
// Start pulsing after a short delay
|
||||
setTimeout(() => {
|
||||
if (!isOpeningAnimationComplete) {
|
||||
loopPulse();
|
||||
}
|
||||
}, 800);
|
||||
// Start pulsing immediately without delay
|
||||
// Removed the 800ms delay
|
||||
loopPulse();
|
||||
};
|
||||
|
||||
const completeOpeningAnimation = () => {
|
||||
Animated.parallel([
|
||||
Animated.timing(openingFadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 600,
|
||||
duration: 300, // Reduced from 600ms to 300ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(openingScaleAnim, {
|
||||
toValue: 1,
|
||||
duration: 700,
|
||||
duration: 350, // Reduced from 700ms to 350ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(backgroundFadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 800,
|
||||
duration: 400, // Reduced from 800ms to 400ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start(() => {
|
||||
openingScaleAnim.setValue(1);
|
||||
openingFadeAnim.setValue(1);
|
||||
setIsOpeningAnimationComplete(true);
|
||||
setTimeout(() => {
|
||||
backgroundFadeAnim.setValue(0);
|
||||
}, 100);
|
||||
// Removed the 100ms delay
|
||||
backgroundFadeAnim.setValue(0);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -396,15 +392,14 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
clearInterval(progressSaveInterval);
|
||||
}
|
||||
|
||||
// Use the user's configured sync frequency with increased minimum to reduce heating
|
||||
// Minimum interval increased from 5s to 30s to reduce CPU usage
|
||||
const syncInterval = Math.max(30000, traktSettings.syncFrequency);
|
||||
// IMMEDIATE SYNC: Reduce sync interval to 5 seconds for near real-time sync
|
||||
const syncInterval = 5000; // 5 seconds for immediate sync
|
||||
|
||||
const interval = setInterval(() => {
|
||||
saveWatchProgress();
|
||||
}, syncInterval);
|
||||
|
||||
logger.log(`[AndroidVideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
|
||||
logger.log(`[AndroidVideoPlayer] Watch progress save interval set to ${syncInterval}ms (immediate sync mode)`);
|
||||
|
||||
setProgressSaveInterval(interval);
|
||||
return () => {
|
||||
|
|
@ -412,7 +407,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
setProgressSaveInterval(null);
|
||||
};
|
||||
}
|
||||
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
|
||||
}, [id, type, paused, currentTime, duration]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -583,8 +578,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
traktAutosync.handlePlaybackStart(currentTime, videoDuration);
|
||||
}
|
||||
|
||||
// Complete opening animation immediately before seeking
|
||||
completeOpeningAnimation();
|
||||
|
||||
if (initialPosition && !isInitialSeekComplete) {
|
||||
logger.log(`[AndroidVideoPlayer] Seeking to initial position: ${initialPosition}s (duration: ${videoDuration}s)`);
|
||||
// Reduced timeout from 1000ms to 500ms
|
||||
setTimeout(() => {
|
||||
if (videoRef.current && videoDuration > 0 && isMounted.current) {
|
||||
seekToTime(initialPosition);
|
||||
|
|
@ -593,9 +592,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
} else {
|
||||
logger.error(`[AndroidVideoPlayer] Initial seek failed: videoRef=${!!videoRef.current}, duration=${videoDuration}, mounted=${isMounted.current}`);
|
||||
}
|
||||
}, 1000);
|
||||
}, 500);
|
||||
}
|
||||
completeOpeningAnimation();
|
||||
|
||||
controlsTimeout.current = setTimeout(hideControls, 5000);
|
||||
} catch (error) {
|
||||
logger.error('[AndroidVideoPlayer] Error in onLoad:', error);
|
||||
|
|
@ -651,9 +650,13 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
logger.log('[AndroidVideoPlayer] Close button pressed - syncing to Trakt before closing');
|
||||
|
||||
// Set syncing state to prevent multiple close attempts
|
||||
// Prevent multiple close attempts
|
||||
if (isSyncingBeforeClose) {
|
||||
logger.log('[AndroidVideoPlayer] Close already in progress, ignoring duplicate call');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('[AndroidVideoPlayer] Close button pressed - closing immediately and syncing to Trakt in background');
|
||||
setIsSyncingBeforeClose(true);
|
||||
|
||||
// Make sure we have the most accurate current time
|
||||
|
|
@ -662,44 +665,34 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
|
||||
logger.log(`[AndroidVideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
|
||||
|
||||
try {
|
||||
// Force one last progress update (scrobble/pause) with the exact time
|
||||
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
|
||||
|
||||
// Sync progress to Trakt before closing
|
||||
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount');
|
||||
|
||||
// Start exit animation
|
||||
Animated.parallel([
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(openingFadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
|
||||
// Longer delay to ensure Trakt sync completes
|
||||
setTimeout(() => {
|
||||
ScreenOrientation.unlockAsync().then(() => {
|
||||
disableImmersiveMode();
|
||||
navigation.goBack();
|
||||
}).catch(() => {
|
||||
// Fallback: navigate even if orientation unlock fails
|
||||
disableImmersiveMode();
|
||||
navigation.goBack();
|
||||
});
|
||||
}, 500); // Increased from 100ms to 500ms
|
||||
} catch (error) {
|
||||
logger.error('[AndroidVideoPlayer] Error syncing to Trakt before closing:', error);
|
||||
// Navigate anyway even if sync fails
|
||||
// Navigate immediately without delay
|
||||
ScreenOrientation.unlockAsync().then(() => {
|
||||
disableImmersiveMode();
|
||||
navigation.goBack();
|
||||
}
|
||||
}).catch(() => {
|
||||
// Fallback: navigate even if orientation unlock fails
|
||||
disableImmersiveMode();
|
||||
navigation.goBack();
|
||||
});
|
||||
|
||||
// Send Trakt sync in background (don't await)
|
||||
const backgroundSync = async () => {
|
||||
try {
|
||||
logger.log('[AndroidVideoPlayer] Starting background Trakt sync');
|
||||
// Force one last progress update (scrobble/pause) with the exact time
|
||||
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
|
||||
|
||||
// Sync progress to Trakt
|
||||
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount');
|
||||
|
||||
logger.log('[AndroidVideoPlayer] Background Trakt sync completed successfully');
|
||||
} catch (error) {
|
||||
logger.error('[AndroidVideoPlayer] Error in background Trakt sync:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Start background sync without blocking UI
|
||||
backgroundSync();
|
||||
};
|
||||
|
||||
const handleResume = async () => {
|
||||
|
|
@ -752,10 +745,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
logger.log('[AndroidVideoPlayer] Video ended naturally, sending final progress update with 100%');
|
||||
await traktAutosync.handleProgressUpdate(finalTime, duration, true);
|
||||
|
||||
// Small delay to ensure the progress update is processed
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Now send the stop call
|
||||
// IMMEDIATE SYNC: Remove delay for instant sync
|
||||
// Now send the stop call immediately
|
||||
logger.log('[AndroidVideoPlayer] Sending final stop call after natural end');
|
||||
await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended');
|
||||
|
||||
|
|
@ -1280,4 +1271,4 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default AndroidVideoPlayer;
|
||||
export default AndroidVideoPlayer;
|
||||
|
|
@ -286,16 +286,16 @@ const VideoPlayer: React.FC = () => {
|
|||
}, []);
|
||||
|
||||
const startOpeningAnimation = () => {
|
||||
// Logo entrance animation
|
||||
// Logo entrance animation - optimized for faster appearance
|
||||
Animated.parallel([
|
||||
Animated.timing(logoOpacityAnim, {
|
||||
toValue: 1,
|
||||
duration: 600,
|
||||
duration: 300, // Reduced from 600ms to 300ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.spring(logoScaleAnim, {
|
||||
toValue: 1,
|
||||
tension: 50,
|
||||
tension: 80, // Increased tension for faster spring
|
||||
friction: 8,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
|
|
@ -306,12 +306,12 @@ const VideoPlayer: React.FC = () => {
|
|||
return Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1.05,
|
||||
duration: 1000,
|
||||
duration: 800, // Reduced from 1000ms to 800ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
duration: 800, // Reduced from 1000ms to 800ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]);
|
||||
|
|
@ -325,29 +325,26 @@ const VideoPlayer: React.FC = () => {
|
|||
});
|
||||
};
|
||||
|
||||
// Start pulsing after a short delay
|
||||
setTimeout(() => {
|
||||
if (!isOpeningAnimationComplete) {
|
||||
loopPulse();
|
||||
}
|
||||
}, 800);
|
||||
// Start pulsing immediately without delay
|
||||
// Removed the 800ms delay
|
||||
loopPulse();
|
||||
};
|
||||
|
||||
const completeOpeningAnimation = () => {
|
||||
Animated.parallel([
|
||||
Animated.timing(openingFadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 600,
|
||||
duration: 300, // Reduced from 600ms to 300ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(openingScaleAnim, {
|
||||
toValue: 1,
|
||||
duration: 700,
|
||||
duration: 350, // Reduced from 700ms to 350ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(backgroundFadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 800,
|
||||
duration: 400, // Reduced from 800ms to 400ms
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start(() => {
|
||||
|
|
@ -418,15 +415,14 @@ const VideoPlayer: React.FC = () => {
|
|||
clearInterval(progressSaveInterval);
|
||||
}
|
||||
|
||||
// Use the user's configured sync frequency with increased minimum to reduce heating
|
||||
// Minimum interval increased from 5s to 30s to reduce CPU usage
|
||||
const syncInterval = Math.max(30000, traktSettings.syncFrequency);
|
||||
// IMMEDIATE SYNC: Reduce sync interval to 5 seconds for near real-time sync
|
||||
const syncInterval = 5000; // 5 seconds for immediate sync
|
||||
|
||||
const interval = setInterval(() => {
|
||||
saveWatchProgress();
|
||||
}, syncInterval);
|
||||
|
||||
logger.log(`[VideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
|
||||
logger.log(`[VideoPlayer] Watch progress save interval set to ${syncInterval}ms (immediate sync mode)`);
|
||||
|
||||
setProgressSaveInterval(interval);
|
||||
return () => {
|
||||
|
|
@ -434,7 +430,7 @@ const VideoPlayer: React.FC = () => {
|
|||
setProgressSaveInterval(null);
|
||||
};
|
||||
}
|
||||
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
|
||||
}, [id, type, paused, currentTime, duration]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -629,8 +625,12 @@ const VideoPlayer: React.FC = () => {
|
|||
traktAutosync.handlePlaybackStart(currentTime, videoDuration);
|
||||
}
|
||||
|
||||
// Complete opening animation immediately before seeking
|
||||
completeOpeningAnimation();
|
||||
|
||||
if (initialPosition && !isInitialSeekComplete) {
|
||||
logger.log(`[VideoPlayer] Seeking to initial position: ${initialPosition}s (duration: ${videoDuration}s)`);
|
||||
// Reduced timeout from 1000ms to 500ms
|
||||
setTimeout(() => {
|
||||
if (vlcRef.current && videoDuration > 0 && isMounted.current) {
|
||||
seekToTime(initialPosition);
|
||||
|
|
@ -639,9 +639,9 @@ const VideoPlayer: React.FC = () => {
|
|||
} else {
|
||||
logger.error(`[VideoPlayer] Initial seek failed: vlcRef=${!!vlcRef.current}, duration=${videoDuration}, mounted=${isMounted.current}`);
|
||||
}
|
||||
}, 1000);
|
||||
}, 500);
|
||||
}
|
||||
completeOpeningAnimation();
|
||||
|
||||
controlsTimeout.current = setTimeout(hideControls, 5000);
|
||||
} catch (error) {
|
||||
logger.error('[VideoPlayer] Error in onLoad:', error);
|
||||
|
|
@ -704,9 +704,13 @@ const VideoPlayer: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
logger.log('[VideoPlayer] Close button pressed - syncing to Trakt before closing');
|
||||
// Prevent multiple close attempts
|
||||
if (isSyncingBeforeClose) {
|
||||
logger.log('[VideoPlayer] Close already in progress, ignoring duplicate call');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set syncing state to prevent multiple close attempts
|
||||
logger.log('[VideoPlayer] Close button pressed - closing immediately and syncing to Trakt in background');
|
||||
setIsSyncingBeforeClose(true);
|
||||
|
||||
// Make sure we have the most accurate current time
|
||||
|
|
@ -715,75 +719,56 @@ const VideoPlayer: React.FC = () => {
|
|||
|
||||
logger.log(`[VideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
|
||||
|
||||
try {
|
||||
// Force one last progress update (scrobble/pause) with the exact time
|
||||
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
|
||||
// Cleanup and navigate back immediately without delay
|
||||
const cleanup = async () => {
|
||||
try {
|
||||
// Unlock orientation first
|
||||
await ScreenOrientation.unlockAsync();
|
||||
logger.log('[VideoPlayer] Orientation unlocked');
|
||||
} catch (orientationError) {
|
||||
logger.warn('[VideoPlayer] Failed to unlock orientation:', orientationError);
|
||||
}
|
||||
|
||||
// Sync progress to Trakt before closing
|
||||
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount');
|
||||
// Disable immersive mode
|
||||
disableImmersiveMode();
|
||||
|
||||
// Start exit animation
|
||||
Animated.parallel([
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(openingFadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
|
||||
// Cleanup and navigate back
|
||||
const cleanup = async () => {
|
||||
try {
|
||||
// Unlock orientation first
|
||||
await ScreenOrientation.unlockAsync();
|
||||
logger.log('[VideoPlayer] Orientation unlocked');
|
||||
} catch (orientationError) {
|
||||
logger.warn('[VideoPlayer] Failed to unlock orientation:', orientationError);
|
||||
}
|
||||
|
||||
// Disable immersive mode
|
||||
disableImmersiveMode();
|
||||
|
||||
// Navigate back with proper handling for fullscreen modal
|
||||
try {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
// Fallback: navigate to main tabs if can't go back
|
||||
navigation.navigate('MainTabs');
|
||||
}
|
||||
logger.log('[VideoPlayer] Navigation completed');
|
||||
} catch (navError) {
|
||||
logger.error('[VideoPlayer] Navigation error:', navError);
|
||||
// Last resort: try to navigate to home
|
||||
// Navigate back with proper handling for fullscreen modal
|
||||
try {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
// Fallback: navigate to main tabs if can't go back
|
||||
navigation.navigate('MainTabs');
|
||||
}
|
||||
};
|
||||
|
||||
// Delay to ensure Trakt sync completes and animations finish
|
||||
setTimeout(cleanup, 500);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[VideoPlayer] Error syncing to Trakt before closing:', error);
|
||||
// Navigate anyway even if sync fails
|
||||
disableImmersiveMode();
|
||||
try {
|
||||
await ScreenOrientation.unlockAsync();
|
||||
} catch (orientationError) {
|
||||
// Ignore orientation unlock errors
|
||||
}
|
||||
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
logger.log('[VideoPlayer] Navigation completed');
|
||||
} catch (navError) {
|
||||
logger.error('[VideoPlayer] Navigation error:', navError);
|
||||
// Last resort: try to navigate to home
|
||||
navigation.navigate('MainTabs');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Navigate immediately
|
||||
cleanup();
|
||||
|
||||
// Send Trakt sync in background (don't await)
|
||||
const backgroundSync = async () => {
|
||||
try {
|
||||
logger.log('[VideoPlayer] Starting background Trakt sync');
|
||||
// Force one last progress update (scrobble/pause) with the exact time
|
||||
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
|
||||
|
||||
// Sync progress to Trakt
|
||||
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount');
|
||||
|
||||
logger.log('[VideoPlayer] Background Trakt sync completed successfully');
|
||||
} catch (error) {
|
||||
logger.error('[VideoPlayer] Error in background Trakt sync:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Start background sync without blocking UI
|
||||
backgroundSync();
|
||||
};
|
||||
|
||||
const handleResume = async () => {
|
||||
|
|
@ -836,10 +821,8 @@ const VideoPlayer: React.FC = () => {
|
|||
logger.log('[VideoPlayer] Video ended naturally, sending final progress update with 100%');
|
||||
await traktAutosync.handleProgressUpdate(finalTime, duration, true);
|
||||
|
||||
// Small delay to ensure the progress update is processed
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Now send the stop call
|
||||
// IMMEDIATE SYNC: Remove delay for instant sync
|
||||
// Now send the stop call immediately
|
||||
logger.log('[VideoPlayer] Sending final stop call after natural end');
|
||||
await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended');
|
||||
|
||||
|
|
|
|||
|
|
@ -165,11 +165,11 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
const progressPercent = (currentTime / duration) * 100;
|
||||
const now = Date.now();
|
||||
|
||||
// Use the user's configured sync frequency
|
||||
const timeSinceLastSync = now - lastSyncTime.current;
|
||||
// IMMEDIATE SYNC: Remove all debouncing and frequency checks for instant sync
|
||||
const progressDiff = Math.abs(progressPercent - lastSyncProgress.current);
|
||||
|
||||
if (!force && timeSinceLastSync < autosyncSettings.syncFrequency && progressDiff < 5) {
|
||||
// Only skip if not forced and progress difference is minimal (< 1%)
|
||||
if (!force && progressDiff < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
} catch (error) {
|
||||
logger.error('[TraktAutosync] Error syncing progress:', error);
|
||||
}
|
||||
}, [isAuthenticated, autosyncSettings.enabled, autosyncSettings.syncFrequency, updateProgress, buildContentData, options]);
|
||||
}, [isAuthenticated, autosyncSettings.enabled, updateProgress, buildContentData, options]);
|
||||
|
||||
// Handle playback end/pause
|
||||
const handlePlaybackEnd = useCallback(async (currentTime: number, duration: number, reason: 'ended' | 'unmount' = 'ended') => {
|
||||
|
|
@ -232,10 +232,10 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
// ENHANCED DEDUPLICATION: Prevent rapid successive calls (within 5 seconds)
|
||||
// Bypass for significant updates
|
||||
if (!isSignificantUpdate && now - lastStopCall.current < 5000) {
|
||||
logger.log(`[TraktAutosync] Ignoring rapid successive stop call within 5 seconds (reason: ${reason})`);
|
||||
// IMMEDIATE SYNC: Remove debouncing for instant sync when closing
|
||||
// Only prevent truly duplicate calls (within 1 second)
|
||||
if (!isSignificantUpdate && now - lastStopCall.current < 1000) {
|
||||
logger.log(`[TraktAutosync] Ignoring duplicate stop call within 1 second (reason: ${reason})`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -377,4 +377,4 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
handlePlaybackEnd,
|
||||
resetState
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -645,60 +645,45 @@ export const StreamsScreen = () => {
|
|||
}, [selectedEpisode, groupedEpisodes, id]);
|
||||
|
||||
const navigateToPlayer = useCallback(async (stream: Stream) => {
|
||||
// Prepare available streams for the change source feature
|
||||
const streamsToPass = type === 'series' ? episodeStreams : groupedStreams;
|
||||
|
||||
// Determine the stream name using the same logic as StreamCard
|
||||
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
||||
|
||||
// Navigate to player immediately without waiting for orientation lock
|
||||
// This prevents delay in player opening
|
||||
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,
|
||||
year: metadata?.year,
|
||||
streamProvider: stream.name,
|
||||
streamName: streamName,
|
||||
id,
|
||||
type,
|
||||
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
||||
imdbId: imdbId || undefined,
|
||||
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 {
|
||||
// Lock orientation to landscape before navigation to prevent glitches
|
||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||
|
||||
// Small delay to ensure orientation is set before navigation
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Prepare available streams for the change source feature
|
||||
const streamsToPass = type === 'series' ? episodeStreams : groupedStreams;
|
||||
|
||||
// Determine the stream name using the same logic as StreamCard
|
||||
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
||||
|
||||
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,
|
||||
year: metadata?.year,
|
||||
streamProvider: stream.name,
|
||||
streamName: streamName,
|
||||
id,
|
||||
type,
|
||||
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
||||
imdbId: imdbId || undefined,
|
||||
availableStreams: streamsToPass,
|
||||
backdrop: bannerImage || undefined,
|
||||
});
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
|
||||
.catch(error => {
|
||||
logger.error('[StreamsScreen] Error locking orientation after navigation:', error);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StreamsScreen] Error locking orientation before navigation:', error);
|
||||
// Fallback: navigate anyway
|
||||
const streamsToPass = type === 'series' ? episodeStreams : groupedStreams;
|
||||
|
||||
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,
|
||||
year: metadata?.year,
|
||||
streamProvider: stream.name,
|
||||
id,
|
||||
type,
|
||||
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
||||
imdbId: imdbId || undefined,
|
||||
availableStreams: streamsToPass,
|
||||
backdrop: bannerImage || undefined,
|
||||
});
|
||||
logger.error('[StreamsScreen] Error locking orientation after navigation:', error);
|
||||
}
|
||||
}, [metadata, type, currentEpisode, navigation, id, selectedEpisode, imdbId, episodeStreams, groupedStreams, bannerImage]);
|
||||
|
||||
|
||||
// Update handleStreamPress
|
||||
const handleStreamPress = useCallback(async (stream: Stream) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -269,14 +269,14 @@ export class TraktService {
|
|||
private readonly SCROBBLE_EXPIRY_MS = 46 * 60 * 1000; // 46 minutes (based on Trakt's expiry window)
|
||||
private scrobbledTimestamps: Map<string, number> = new Map();
|
||||
|
||||
// Track currently watching sessions to avoid duplicate starts
|
||||
// Track currently watching sessions to avoid duplicate starts// Sync debouncing
|
||||
private currentlyWatching: Set<string> = new Set();
|
||||
private lastSyncTimes: Map<string, number> = new Map();
|
||||
private readonly SYNC_DEBOUNCE_MS = 60000; // 60 seconds
|
||||
private readonly SYNC_DEBOUNCE_MS = 1000; // 1 second for immediate sync
|
||||
|
||||
// Debounce for stop calls
|
||||
private lastStopCalls: Map<string, number> = new Map();
|
||||
private readonly STOP_DEBOUNCE_MS = 10000; // 10 seconds debounce for stop calls
|
||||
private readonly STOP_DEBOUNCE_MS = 1000; // 1 second debounce for immediate stop calls
|
||||
|
||||
// Default completion threshold (overridden by user settings)
|
||||
private readonly DEFAULT_COMPLETION_THRESHOLD = 80; // 80%
|
||||
|
|
@ -1336,8 +1336,8 @@ export class TraktService {
|
|||
const watchingKey = this.getWatchingKey(contentData);
|
||||
const lastSync = this.lastSyncTimes.get(watchingKey) || 0;
|
||||
|
||||
// Debounce API calls unless forced
|
||||
if (!force && (now - lastSync) < this.SYNC_DEBOUNCE_MS) {
|
||||
// IMMEDIATE SYNC: Remove debouncing for instant sync, only prevent truly rapid calls (< 500ms)
|
||||
if (!force && (now - lastSync) < 500) {
|
||||
return true; // Skip this sync, but return success
|
||||
}
|
||||
|
||||
|
|
@ -1377,9 +1377,9 @@ export class TraktService {
|
|||
const watchingKey = this.getWatchingKey(contentData);
|
||||
const now = Date.now();
|
||||
|
||||
// Enhanced deduplication: Check if we recently stopped this content
|
||||
// IMMEDIATE SYNC: Reduce debouncing for instant sync, only prevent truly duplicate calls (< 1 second)
|
||||
const lastStopTime = this.lastStopCalls.get(watchingKey);
|
||||
if (lastStopTime && (now - lastStopTime) < this.STOP_DEBOUNCE_MS) {
|
||||
if (lastStopTime && (now - lastStopTime) < 1000) {
|
||||
logger.log(`[TraktService] Ignoring duplicate stop call for ${contentData.title} (last stop ${((now - lastStopTime) / 1000).toFixed(1)}s ago)`);
|
||||
return true; // Return success to avoid error handling
|
||||
}
|
||||
|
|
@ -1588,4 +1588,4 @@ export class TraktService {
|
|||
}
|
||||
|
||||
// Export a singleton instance
|
||||
export const traktService = TraktService.getInstance();
|
||||
export const traktService = TraktService.getInstance();
|
||||
Loading…
Reference in a new issue