mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 08:41:57 +00:00
TRAKT TEST
This commit is contained in:
parent
5a6d5a66b0
commit
7379a81f01
5 changed files with 242 additions and 96 deletions
|
|
@ -1364,12 +1364,12 @@ 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) {
|
||||||
logger.error('[AndroidVideoPlayer] Error in background Trakt sync:', error);
|
logger.error('[AndroidVideoPlayer] Error in background Trakt sync:', error);
|
||||||
|
|
@ -1756,15 +1756,14 @@ 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');
|
||||||
|
|
||||||
logger.log('[AndroidVideoPlayer] Completed video end sync to Trakt');
|
logger.log('[AndroidVideoPlayer] Completed video end sync to Trakt');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[AndroidVideoPlayer] Error syncing to Trakt on video end:', error);
|
logger.error('[AndroidVideoPlayer] Error syncing to Trakt on video end:', error);
|
||||||
|
|
@ -2048,10 +2047,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,12 +1331,12 @@ 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) {
|
||||||
logger.error('[VideoPlayer] Error in background Trakt sync:', error);
|
logger.error('[VideoPlayer] Error in background Trakt sync:', 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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ interface TraktAutosyncOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTraktAutosync(options: TraktAutosyncOptions) {
|
export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
const {
|
const {
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
startWatching,
|
startWatching,
|
||||||
updateProgress,
|
updateProgress,
|
||||||
stopWatching
|
updateProgressImmediate,
|
||||||
|
stopWatching,
|
||||||
|
stopWatchingImmediate
|
||||||
} = useTraktIntegration();
|
} = useTraktIntegration();
|
||||||
|
|
||||||
const { settings: autosyncSettings } = useTraktAutosyncSettings();
|
const { settings: autosyncSettings } = useTraktAutosyncSettings();
|
||||||
|
|
@ -148,8 +150,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
|
|
||||||
// Sync progress during playback
|
// Sync progress during playback
|
||||||
const handleProgressUpdate = useCallback(async (
|
const handleProgressUpdate = useCallback(async (
|
||||||
currentTime: number,
|
currentTime: number,
|
||||||
duration: number,
|
duration: number,
|
||||||
force: boolean = false
|
force: boolean = false
|
||||||
) => {
|
) => {
|
||||||
if (!isAuthenticated || !autosyncSettings.enabled || duration <= 0) {
|
if (!isAuthenticated || !autosyncSettings.enabled || duration <= 0) {
|
||||||
|
|
@ -164,45 +166,72 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
try {
|
try {
|
||||||
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
|
|
||||||
const progressDiff = Math.abs(progressPercent - lastSyncProgress.current);
|
|
||||||
|
|
||||||
// Only skip if not forced and progress difference is minimal (< 1%)
|
|
||||||
if (!force && progressDiff < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentData = buildContentData();
|
// IMMEDIATE SYNC: Use immediate method for user-triggered actions (force=true)
|
||||||
const success = await updateProgress(contentData, progressPercent, force);
|
// Use regular queued method for background periodic syncs
|
||||||
|
let success: boolean;
|
||||||
if (success) {
|
|
||||||
lastSyncTime.current = now;
|
if (force) {
|
||||||
lastSyncProgress.current = progressPercent;
|
// IMMEDIATE: User action (pause/unpause) - bypass queue
|
||||||
|
const contentData = buildContentData();
|
||||||
// Update local storage sync status
|
success = await updateProgressImmediate(contentData, progressPercent);
|
||||||
await storageService.updateTraktSyncStatus(
|
|
||||||
options.id,
|
if (success) {
|
||||||
options.type,
|
lastSyncTime.current = now;
|
||||||
true,
|
lastSyncProgress.current = progressPercent;
|
||||||
progressPercent,
|
|
||||||
options.episodeId,
|
// Update local storage sync status
|
||||||
currentTime
|
await storageService.updateTraktSyncStatus(
|
||||||
);
|
options.id,
|
||||||
|
options.type,
|
||||||
// Progress sync logging removed
|
true,
|
||||||
|
progressPercent,
|
||||||
|
options.episodeId,
|
||||||
|
currentTime
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.log(`[TraktAutosync] IMMEDIATE: Progress updated to ${progressPercent.toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// BACKGROUND: Periodic sync - use queued method
|
||||||
|
const progressDiff = Math.abs(progressPercent - lastSyncProgress.current);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
if (!isAuthenticated || !autosyncSettings.enabled) {
|
if (!isAuthenticated || !autosyncSettings.enabled) {
|
||||||
// logger.log(`[TraktAutosync] Skipping handlePlaybackEnd: authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}`);
|
// logger.log(`[TraktAutosync] Skipping handlePlaybackEnd: authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -220,7 +249,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
if (hasStopped.current) {
|
if (hasStopped.current) {
|
||||||
const currentProgressPercent = duration > 0 ? (currentTime / duration) * 100 : 0;
|
const currentProgressPercent = duration > 0 ? (currentTime / duration) * 100 : 0;
|
||||||
const progressImprovement = currentProgressPercent - lastSyncProgress.current;
|
const progressImprovement = currentProgressPercent - lastSyncProgress.current;
|
||||||
|
|
||||||
if (progressImprovement > 5) {
|
if (progressImprovement > 5) {
|
||||||
logger.log(`[TraktAutosync] Session already stopped, but progress improved significantly by ${progressImprovement.toFixed(1)}% (${lastSyncProgress.current.toFixed(1)}% → ${currentProgressPercent.toFixed(1)}%), allowing update`);
|
logger.log(`[TraktAutosync] Session already stopped, but progress improved significantly by ${progressImprovement.toFixed(1)}% (${lastSyncProgress.current.toFixed(1)}% → ${currentProgressPercent.toFixed(1)}%), allowing update`);
|
||||||
// Reset stopped flag to allow this significant update
|
// Reset stopped flag to allow this significant update
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,25 +281,25 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
try {
|
try {
|
||||||
let progressPercent = duration > 0 ? (currentTime / duration) * 100 : 0;
|
let progressPercent = duration > 0 ? (currentTime / duration) * 100 : 0;
|
||||||
// Initial progress calculation logging removed
|
// Initial progress calculation logging removed
|
||||||
|
|
||||||
// For unmount calls, always use the highest available progress
|
// For unmount calls, always use the highest available progress
|
||||||
// Check current progress, last synced progress, and local storage progress
|
// Check current progress, last synced progress, and local storage progress
|
||||||
if (reason === 'unmount') {
|
if (reason === 'unmount') {
|
||||||
let maxProgress = progressPercent;
|
let maxProgress = progressPercent;
|
||||||
|
|
||||||
// Check last synced progress
|
// Check last synced progress
|
||||||
if (lastSyncProgress.current > maxProgress) {
|
if (lastSyncProgress.current > maxProgress) {
|
||||||
maxProgress = lastSyncProgress.current;
|
maxProgress = lastSyncProgress.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check local storage for the highest recorded progress
|
// Also check local storage for the highest recorded progress
|
||||||
try {
|
try {
|
||||||
const savedProgress = await storageService.getWatchProgress(
|
const savedProgress = await storageService.getWatchProgress(
|
||||||
options.id,
|
options.id,
|
||||||
options.type,
|
options.type,
|
||||||
options.episodeId
|
options.episodeId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (savedProgress && savedProgress.duration > 0) {
|
if (savedProgress && savedProgress.duration > 0) {
|
||||||
const savedProgressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
|
const savedProgressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
|
||||||
if (savedProgressPercent > maxProgress) {
|
if (savedProgressPercent > maxProgress) {
|
||||||
|
|
@ -276,7 +309,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[TraktAutosync] Error checking saved progress:', error);
|
logger.error('[TraktAutosync] Error checking saved progress:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxProgress !== progressPercent) {
|
if (maxProgress !== progressPercent) {
|
||||||
// Highest progress logging removed
|
// Highest progress logging removed
|
||||||
progressPercent = maxProgress;
|
progressPercent = maxProgress;
|
||||||
|
|
@ -293,29 +326,31 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
hasStartedWatching.current = true;
|
hasStartedWatching.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only stop if we have meaningful progress (>= 0.5%) or it's a natural video end
|
// Only stop if we have meaningful progress (>= 0.5%) or it's a natural video end
|
||||||
// Lower threshold for unmount calls to catch more edge cases
|
// Lower threshold for unmount calls to catch more edge cases
|
||||||
if (reason === 'unmount' && progressPercent < 0.5) {
|
if (reason === 'unmount' && progressPercent < 0.5) {
|
||||||
// Early unmount stop logging removed
|
// Early unmount stop logging removed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For natural end events, always set progress to at least 90%
|
// For natural end events, always set progress to at least 90%
|
||||||
if (reason === 'ended' && progressPercent < 90) {
|
if (reason === 'ended' && progressPercent < 90) {
|
||||||
logger.log(`[TraktAutosync] Natural end detected but progress is low (${progressPercent.toFixed(1)}%), boosting to 90%`);
|
logger.log(`[TraktAutosync] Natural end detected but progress is low (${progressPercent.toFixed(1)}%), boosting to 90%`);
|
||||||
progressPercent = 90;
|
progressPercent = 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark stop attempt and update timestamp
|
// Mark stop attempt and update timestamp
|
||||||
lastStopCall.current = now;
|
lastStopCall.current = now;
|
||||||
hasStopped.current = true;
|
hasStopped.current = true;
|
||||||
|
|
||||||
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
|
||||||
await storageService.updateTraktSyncStatus(
|
await storageService.updateTraktSyncStatus(
|
||||||
|
|
@ -326,20 +361,20 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
options.episodeId,
|
options.episodeId,
|
||||||
currentTime
|
currentTime
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mark session as complete if high progress (scrobbled)
|
// Mark session as complete if high progress (scrobbled)
|
||||||
if (progressPercent >= 80) {
|
if (progressPercent >= 80) {
|
||||||
isSessionComplete.current = true;
|
isSessionComplete.current = true;
|
||||||
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;
|
||||||
logger.warn(`[TraktAutosync] Failed to stop watching, reset stop flag for retry`);
|
logger.warn(`[TraktAutosync] Failed to stop watching, reset stop flag for retry`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset state only for natural end or very high progress unmounts
|
// Reset state only for natural end or very high progress unmounts
|
||||||
if (reason === 'ended' || progressPercent >= 80) {
|
if (reason === 'ended' || progressPercent >= 80) {
|
||||||
hasStartedWatching.current = false;
|
hasStartedWatching.current = false;
|
||||||
|
|
@ -347,13 +382,13 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
||||||
lastSyncProgress.current = 0;
|
lastSyncProgress.current = 0;
|
||||||
logger.log(`[TraktAutosync] Reset session state for ${reason} at ${progressPercent.toFixed(1)}%`);
|
logger.log(`[TraktAutosync] Reset session state for ${reason} at ${progressPercent.toFixed(1)}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[TraktAutosync] Error ending watch:', error);
|
logger.error('[TraktAutosync] Error ending watch:', error);
|
||||||
// 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(() => {
|
||||||
|
|
|
||||||
|
|
@ -199,12 +199,12 @@ export function useTraktIntegration() {
|
||||||
|
|
||||||
// Update progress while watching (scrobble pause)
|
// Update progress while watching (scrobble pause)
|
||||||
const updateProgress = useCallback(async (
|
const updateProgress = useCallback(async (
|
||||||
contentData: TraktContentData,
|
contentData: TraktContentData,
|
||||||
progress: number,
|
progress: number,
|
||||||
force: boolean = false
|
force: boolean = false
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
if (!isAuthenticated) return false;
|
if (!isAuthenticated) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await traktService.scrobblePause(contentData, progress, force);
|
return await traktService.scrobblePause(contentData, progress, force);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -213,10 +213,27 @@ 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;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await traktService.scrobbleStop(contentData, progress);
|
return await traktService.scrobbleStop(contentData, progress);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -1492,7 +1492,7 @@ export class TraktService {
|
||||||
|
|
||||||
const watchingKey = this.getWatchingKey(contentData);
|
const watchingKey = this.getWatchingKey(contentData);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// IMMEDIATE SYNC: Reduce debouncing for instant sync, only prevent truly duplicate calls (< 1 second)
|
// IMMEDIATE SYNC: Reduce debouncing for instant sync, only prevent truly duplicate calls (< 1 second)
|
||||||
const lastStopTime = this.lastStopCalls.get(watchingKey);
|
const lastStopTime = this.lastStopCalls.get(watchingKey);
|
||||||
if (lastStopTime && (now - lastStopTime) < 1000) {
|
if (lastStopTime && (now - lastStopTime) < 1000) {
|
||||||
|
|
@ -1509,19 +1509,19 @@ export class TraktService {
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this.currentlyWatching.delete(watchingKey);
|
this.currentlyWatching.delete(watchingKey);
|
||||||
|
|
||||||
// Mark as scrobbled if >= 80% to prevent future duplicates and restarts
|
// Mark as scrobbled if >= 80% to prevent future duplicates and restarts
|
||||||
if (progress >= this.completionThreshold) {
|
if (progress >= this.completionThreshold) {
|
||||||
this.scrobbledItems.add(watchingKey);
|
this.scrobbledItems.add(watchingKey);
|
||||||
this.scrobbledTimestamps.set(watchingKey, Date.now());
|
this.scrobbledTimestamps.set(watchingKey, Date.now());
|
||||||
logger.log(`[TraktService] Marked as scrobbled to prevent restarts: ${watchingKey}`);
|
logger.log(`[TraktService] Marked as scrobbled to prevent restarts: ${watchingKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The stop endpoint automatically handles the 80%+ completion logic
|
// The stop endpoint automatically handles the 80%+ completion logic
|
||||||
// and will mark as scrobbled if >= 80%, or pause if < 80%
|
// and will mark as scrobbled if >= 80%, or pause if < 80%
|
||||||
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
|
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
|
||||||
logger.log(`[TraktService] Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
|
logger.log(`[TraktService] Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// If failed, remove from lastStopCalls so we can try again
|
// If failed, remove from lastStopCalls so we can try again
|
||||||
|
|
@ -1535,12 +1535,94 @@ export class TraktService {
|
||||||
logger.warn('[TraktService] Rate limited, will retry later');
|
logger.warn('[TraktService] Rate limited, will retry later');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error('[TraktService] Failed to stop scrobbling:', error);
|
logger.error('[TraktService] Failed to stop scrobbling:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue