TRAKT TEST

This commit is contained in:
tapframe 2025-09-19 15:00:18 +05:30
parent 5a6d5a66b0
commit 7379a81f01
5 changed files with 242 additions and 96 deletions

View file

@ -1364,11 +1364,11 @@ const AndroidVideoPlayer: React.FC = () => {
const backgroundSync = async () => { const backgroundSync = async () => {
try { try {
logger.log('[AndroidVideoPlayer] Starting background Trakt sync'); logger.log('[AndroidVideoPlayer] Starting background Trakt sync');
// Force one last progress update (scrobble/pause) with the exact time // IMMEDIATE: Force immediate progress update (scrobble/pause) with the exact time
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true); await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
// Sync progress to Trakt // IMMEDIATE: Use user_close reason to trigger immediate scrobble stop
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount'); await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'user_close');
logger.log('[AndroidVideoPlayer] Background Trakt sync completed successfully'); logger.log('[AndroidVideoPlayer] Background Trakt sync completed successfully');
} catch (error) { } catch (error) {
@ -1756,12 +1756,11 @@ const AndroidVideoPlayer: React.FC = () => {
setCurrentTime(finalTime); setCurrentTime(finalTime);
try { try {
// Force one last progress update (scrobble/pause) with the exact final time // REGULAR: Use regular sync for natural video end (not immediate since it's not user-triggered)
logger.log('[AndroidVideoPlayer] Video ended naturally, sending final progress update with 100%'); logger.log('[AndroidVideoPlayer] Video ended naturally, sending final progress update with 100%');
await traktAutosync.handleProgressUpdate(finalTime, duration, true); await traktAutosync.handleProgressUpdate(finalTime, duration, false); // force=false for regular sync
// IMMEDIATE SYNC: Remove delay for instant sync // REGULAR: Use 'ended' reason for natural video end (uses regular queued method)
// Now send the stop call immediately
logger.log('[AndroidVideoPlayer] Sending final stop call after natural end'); logger.log('[AndroidVideoPlayer] Sending final stop call after natural end');
await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended'); await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended');
@ -2049,9 +2048,9 @@ const AndroidVideoPlayer: React.FC = () => {
const newPausedState = !paused; const newPausedState = !paused;
setPaused(newPausedState); setPaused(newPausedState);
// Send a forced pause update to Trakt immediately when user pauses // IMMEDIATE: Send immediate progress update to Trakt for both pause and unpause
if (newPausedState && duration > 0) { if (duration > 0) {
traktAutosync.handleProgressUpdate(currentTime, duration, true); traktAutosync.handleProgressUpdate(currentTime, duration, true); // force=true triggers immediate sync
} }
} }
}; };

View file

@ -799,9 +799,9 @@ const VideoPlayer: React.FC = () => {
if (isMounted.current) { if (isMounted.current) {
setPaused(true); setPaused(true);
// Send a forced pause update to Trakt immediately when user pauses // IMMEDIATE: Send immediate pause update to Trakt when user pauses
if (duration > 0) { if (duration > 0) {
traktAutosync.handleProgressUpdate(currentTime, duration, true); traktAutosync.handleProgressUpdate(currentTime, duration, true); // force=true triggers immediate sync
} }
} }
}; };
@ -1331,11 +1331,11 @@ const VideoPlayer: React.FC = () => {
const backgroundSync = async () => { const backgroundSync = async () => {
try { try {
logger.log('[VideoPlayer] Starting background Trakt sync'); logger.log('[VideoPlayer] Starting background Trakt sync');
// Force one last progress update (scrobble/pause) with the exact time // IMMEDIATE: Force immediate progress update (scrobble/pause) with the exact time
await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true); await traktAutosync.handleProgressUpdate(actualCurrentTime, duration, true);
// Sync progress to Trakt // IMMEDIATE: Use user_close reason to trigger immediate scrobble stop
await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'unmount'); await traktAutosync.handlePlaybackEnd(actualCurrentTime, duration, 'user_close');
logger.log('[VideoPlayer] Background Trakt sync completed successfully'); logger.log('[VideoPlayer] Background Trakt sync completed successfully');
} catch (error) { } catch (error) {
@ -1543,12 +1543,11 @@ const VideoPlayer: React.FC = () => {
setCurrentTime(finalTime); setCurrentTime(finalTime);
try { try {
// Force one last progress update (scrobble/pause) with the exact final time // REGULAR: Use regular sync for natural video end (not immediate since it's not user-triggered)
logger.log('[VideoPlayer] Video ended naturally, sending final progress update with 100%'); logger.log('[VideoPlayer] Video ended naturally, sending final progress update with 100%');
await traktAutosync.handleProgressUpdate(finalTime, duration, true); await traktAutosync.handleProgressUpdate(finalTime, duration, false); // force=false for regular sync
// IMMEDIATE SYNC: Remove delay for instant sync // REGULAR: Use 'ended' reason for natural video end (uses regular queued method)
// Now send the stop call immediately
logger.log('[VideoPlayer] Sending final stop call after natural end'); logger.log('[VideoPlayer] Sending final stop call after natural end');
await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended'); await traktAutosync.handlePlaybackEnd(finalTime, duration, 'ended');

View file

@ -25,7 +25,9 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
isAuthenticated, isAuthenticated,
startWatching, startWatching,
updateProgress, updateProgress,
stopWatching updateProgressImmediate,
stopWatching,
stopWatchingImmediate
} = useTraktIntegration(); } = useTraktIntegration();
const { settings: autosyncSettings } = useTraktAutosyncSettings(); const { settings: autosyncSettings } = useTraktAutosyncSettings();
@ -165,40 +167,67 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
const progressPercent = (currentTime / duration) * 100; const progressPercent = (currentTime / duration) * 100;
const now = Date.now(); const now = Date.now();
// IMMEDIATE SYNC: Remove all debouncing and frequency checks for instant sync // IMMEDIATE SYNC: Use immediate method for user-triggered actions (force=true)
const progressDiff = Math.abs(progressPercent - lastSyncProgress.current); // Use regular queued method for background periodic syncs
let success: boolean;
// Only skip if not forced and progress difference is minimal (< 1%) if (force) {
if (!force && progressDiff < 1) { // IMMEDIATE: User action (pause/unpause) - bypass queue
return; const contentData = buildContentData();
} success = await updateProgressImmediate(contentData, progressPercent);
const contentData = buildContentData(); if (success) {
const success = await updateProgress(contentData, progressPercent, force); lastSyncTime.current = now;
lastSyncProgress.current = progressPercent;
if (success) { // Update local storage sync status
lastSyncTime.current = now; await storageService.updateTraktSyncStatus(
lastSyncProgress.current = progressPercent; options.id,
options.type,
true,
progressPercent,
options.episodeId,
currentTime
);
// Update local storage sync status logger.log(`[TraktAutosync] IMMEDIATE: Progress updated to ${progressPercent.toFixed(1)}%`);
await storageService.updateTraktSyncStatus( }
options.id, } else {
options.type, // BACKGROUND: Periodic sync - use queued method
true, const progressDiff = Math.abs(progressPercent - lastSyncProgress.current);
progressPercent,
options.episodeId,
currentTime
);
// Progress sync logging removed // Only skip if not forced and progress difference is minimal (< 1%)
if (progressDiff < 1) {
return;
}
const contentData = buildContentData();
success = await updateProgress(contentData, progressPercent, force);
if (success) {
lastSyncTime.current = now;
lastSyncProgress.current = progressPercent;
// Update local storage sync status
await storageService.updateTraktSyncStatus(
options.id,
options.type,
true,
progressPercent,
options.episodeId,
currentTime
);
// Progress sync logging removed
}
} }
} catch (error) { } catch (error) {
logger.error('[TraktAutosync] Error syncing progress:', error); logger.error('[TraktAutosync] Error syncing progress:', error);
} }
}, [isAuthenticated, autosyncSettings.enabled, updateProgress, buildContentData, options]); }, [isAuthenticated, autosyncSettings.enabled, updateProgress, updateProgressImmediate, buildContentData, options]);
// Handle playback end/pause // Handle playback end/pause
const handlePlaybackEnd = useCallback(async (currentTime: number, duration: number, reason: 'ended' | 'unmount' = 'ended') => { const handlePlaybackEnd = useCallback(async (currentTime: number, duration: number, reason: 'ended' | 'unmount' | 'user_close' = 'ended') => {
const now = Date.now(); const now = Date.now();
// Removed excessive logging for handlePlaybackEnd calls // Removed excessive logging for handlePlaybackEnd calls
@ -232,10 +261,14 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
} }
} }
// IMMEDIATE SYNC: Use immediate method for user-initiated actions (user_close)
let useImmediate = reason === 'user_close';
// IMMEDIATE SYNC: Remove debouncing for instant sync when closing // IMMEDIATE SYNC: Remove debouncing for instant sync when closing
// Only prevent truly duplicate calls (within 1 second) // Only prevent truly duplicate calls (within 1 second for regular, 200ms for immediate)
if (!isSignificantUpdate && now - lastStopCall.current < 1000) { const debounceThreshold = useImmediate ? 200 : 1000;
logger.log(`[TraktAutosync] Ignoring duplicate stop call within 1 second (reason: ${reason})`); if (!isSignificantUpdate && now - lastStopCall.current < debounceThreshold) {
logger.log(`[TraktAutosync] Ignoring duplicate stop call within ${debounceThreshold}ms (reason: ${reason})`);
return; return;
} }
@ -313,8 +346,10 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
const contentData = buildContentData(); const contentData = buildContentData();
// Use stopWatching for proper scrobble stop // IMMEDIATE: Use immediate method for user-initiated closes, regular method for natural ends
const success = await stopWatching(contentData, progressPercent); const success = useImmediate
? await stopWatchingImmediate(contentData, progressPercent)
: await stopWatching(contentData, progressPercent);
if (success) { if (success) {
// Update local storage sync status // Update local storage sync status
@ -333,7 +368,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
logger.log(`[TraktAutosync] Session marked as complete (scrobbled) at ${progressPercent.toFixed(1)}%`); logger.log(`[TraktAutosync] Session marked as complete (scrobbled) at ${progressPercent.toFixed(1)}%`);
} }
logger.log(`[TraktAutosync] Successfully stopped watching: ${contentData.title} (${progressPercent.toFixed(1)}% - ${reason})`); logger.log(`[TraktAutosync] ${useImmediate ? 'IMMEDIATE: ' : ''}Successfully stopped watching: ${contentData.title} (${progressPercent.toFixed(1)}% - ${reason})`);
} else { } else {
// If stop failed, reset the stop flag so we can try again later // If stop failed, reset the stop flag so we can try again later
hasStopped.current = false; hasStopped.current = false;
@ -353,7 +388,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
// Reset stop flag on error so we can try again // Reset stop flag on error so we can try again
hasStopped.current = false; hasStopped.current = false;
} }
}, [isAuthenticated, autosyncSettings.enabled, stopWatching, startWatching, buildContentData, options]); }, [isAuthenticated, autosyncSettings.enabled, stopWatching, stopWatchingImmediate, startWatching, buildContentData, options]);
// Reset state (useful when switching content) // Reset state (useful when switching content)
const resetState = useCallback(() => { const resetState = useCallback(() => {

View file

@ -213,6 +213,23 @@ export function useTraktIntegration() {
} }
}, [isAuthenticated]); }, [isAuthenticated]);
// IMMEDIATE SCROBBLE METHODS - Bypass queue for instant user feedback
// Immediate update progress while watching (scrobble pause)
const updateProgressImmediate = useCallback(async (
contentData: TraktContentData,
progress: number
): Promise<boolean> => {
if (!isAuthenticated) return false;
try {
return await traktService.scrobblePauseImmediate(contentData, progress);
} catch (error) {
logger.error('[useTraktIntegration] Error updating progress immediately:', error);
return false;
}
}, [isAuthenticated]);
// Stop watching content (scrobble stop) // Stop watching content (scrobble stop)
const stopWatching = useCallback(async (contentData: TraktContentData, progress: number): Promise<boolean> => { const stopWatching = useCallback(async (contentData: TraktContentData, progress: number): Promise<boolean> => {
if (!isAuthenticated) return false; if (!isAuthenticated) return false;
@ -225,6 +242,18 @@ export function useTraktIntegration() {
} }
}, [isAuthenticated]); }, [isAuthenticated]);
// Immediate stop watching content (scrobble stop)
const stopWatchingImmediate = useCallback(async (contentData: TraktContentData, progress: number): Promise<boolean> => {
if (!isAuthenticated) return false;
try {
return await traktService.scrobbleStopImmediate(contentData, progress);
} catch (error) {
logger.error('[useTraktIntegration] Error stopping watch immediately:', error);
return false;
}
}, [isAuthenticated]);
// Sync progress to Trakt (legacy method) // Sync progress to Trakt (legacy method)
const syncProgress = useCallback(async ( const syncProgress = useCallback(async (
contentData: TraktContentData, contentData: TraktContentData,
@ -494,7 +523,9 @@ export function useTraktIntegration() {
refreshAuthStatus, refreshAuthStatus,
startWatching, startWatching,
updateProgress, updateProgress,
updateProgressImmediate,
stopWatching, stopWatching,
stopWatchingImmediate,
syncProgress, // legacy syncProgress, // legacy
getTraktPlaybackProgress, getTraktPlaybackProgress,
syncAllProgress, syncAllProgress,

View file

@ -1541,6 +1541,88 @@ export class TraktService {
} }
} }
/**
* IMMEDIATE SCROBBLE METHODS - Bypass rate limiting queue for critical user actions
*/
/**
* Immediate scrobble pause - bypasses queue for instant user feedback
*/
public async scrobblePauseImmediate(contentData: TraktContentData, progress: number): Promise<boolean> {
try {
if (!await this.isAuthenticated()) {
return false;
}
const watchingKey = this.getWatchingKey(contentData);
// MINIMAL DEDUPLICATION: Only prevent calls within 100ms for immediate actions
const lastSync = this.lastSyncTimes.get(watchingKey) || 0;
if ((Date.now() - lastSync) < 100) {
return true; // Skip this sync, but return success
}
this.lastSyncTimes.set(watchingKey, Date.now());
// BYPASS QUEUE: Call API directly for immediate response
const result = await this.pauseWatching(contentData, progress);
if (result) {
logger.log(`[TraktService] IMMEDIATE: Updated progress ${progress.toFixed(1)}% for ${contentData.type}: ${contentData.title}`);
return true;
}
return false;
} catch (error) {
logger.error('[TraktService] Failed to pause scrobbling immediately:', error);
return false;
}
}
/**
* Immediate scrobble stop - bypasses queue for instant user feedback
*/
public async scrobbleStopImmediate(contentData: TraktContentData, progress: number): Promise<boolean> {
try {
if (!await this.isAuthenticated()) {
return false;
}
const watchingKey = this.getWatchingKey(contentData);
// MINIMAL DEDUPLICATION: Only prevent calls within 200ms for immediate actions
const lastStopTime = this.lastStopCalls.get(watchingKey);
if (lastStopTime && (Date.now() - lastStopTime) < 200) {
return true;
}
this.lastStopCalls.set(watchingKey, Date.now());
// BYPASS QUEUE: Call API directly for immediate response
const result = await this.stopWatching(contentData, progress);
if (result) {
this.currentlyWatching.delete(watchingKey);
// Mark as scrobbled if >= 80% to prevent future duplicates and restarts
if (progress >= this.completionThreshold) {
this.scrobbledItems.add(watchingKey);
this.scrobbledTimestamps.set(watchingKey, Date.now());
}
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
logger.log(`[TraktService] IMMEDIATE: Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
return true;
}
return false;
} catch (error) {
logger.error('[TraktService] Failed to stop scrobbling immediately:', error);
return false;
}
}
/** /**
* Legacy sync method - now delegates to proper scrobble methods * Legacy sync method - now delegates to proper scrobble methods
* @deprecated Use scrobbleStart, scrobblePause, scrobbleStop instead * @deprecated Use scrobbleStart, scrobblePause, scrobbleStop instead