soem fixes for trakt

This commit is contained in:
tapframe 2025-07-04 18:16:13 +05:30
parent af82eee3f1
commit 2feba6f6eb
3 changed files with 133 additions and 26 deletions

View file

@ -11,6 +11,7 @@ import { logger } from '../../utils/logger';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MaterialIcons } from '@expo/vector-icons';
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings';
import {
DEFAULT_SUBTITLE_SIZE,
@ -79,6 +80,9 @@ const AndroidVideoPlayer: React.FC = () => {
episodeId: episodeId
});
// Get the Trakt autosync settings to use the user-configured sync frequency
const { settings: traktSettings } = useTraktAutosyncSettings();
safeDebugLog("Android Component mounted with props", {
uri, title, season, episode, episodeTitle, quality, year,
streamProvider, id, type, episodeId, imdbId
@ -321,16 +325,24 @@ const AndroidVideoPlayer: React.FC = () => {
if (progressSaveInterval) {
clearInterval(progressSaveInterval);
}
// Use the user's configured sync frequency instead of hard-coded 5000ms
// But ensure we have a minimum interval of 5 seconds
const syncInterval = Math.max(5000, traktSettings.syncFrequency);
const interval = setInterval(() => {
saveWatchProgress();
}, 5000);
}, syncInterval);
logger.log(`[AndroidVideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
setProgressSaveInterval(interval);
return () => {
clearInterval(interval);
setProgressSaveInterval(null);
};
}
}, [id, type, paused, currentTime, duration]);
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
useEffect(() => {
return () => {
@ -721,7 +733,14 @@ const AndroidVideoPlayer: React.FC = () => {
const togglePlayback = () => {
if (videoRef.current) {
setPaused(!paused);
const newPausedState = !paused;
setPaused(newPausedState);
// Send a forced pause update to Trakt immediately when user pauses
if (newPausedState && duration > 0) {
traktAutosync.handleProgressUpdate(currentTime, duration, true);
logger.log('[AndroidVideoPlayer] Sent forced pause update to Trakt');
}
}
};

View file

@ -12,6 +12,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { MaterialIcons } from '@expo/vector-icons';
import AndroidVideoPlayer from './AndroidVideoPlayer';
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings';
import {
DEFAULT_SUBTITLE_SIZE,
@ -74,6 +75,9 @@ const VideoPlayer: React.FC = () => {
episodeId: episodeId
});
// Get the Trakt autosync settings to use the user-configured sync frequency
const { settings: traktSettings } = useTraktAutosyncSettings();
safeDebugLog("Component mounted with props", {
uri, title, season, episode, episodeTitle, quality, year,
streamProvider, id, type, episodeId, imdbId
@ -316,16 +320,24 @@ const VideoPlayer: React.FC = () => {
if (progressSaveInterval) {
clearInterval(progressSaveInterval);
}
// Use the user's configured sync frequency instead of hard-coded 5000ms
// But ensure we have a minimum interval of 5 seconds
const syncInterval = Math.max(5000, traktSettings.syncFrequency);
const interval = setInterval(() => {
saveWatchProgress();
}, 5000);
}, syncInterval);
logger.log(`[VideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
setProgressSaveInterval(interval);
return () => {
clearInterval(interval);
setProgressSaveInterval(null);
};
}
}, [id, type, paused, currentTime, duration]);
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
useEffect(() => {
return () => {
@ -341,16 +353,20 @@ const VideoPlayer: React.FC = () => {
if (isMounted.current && !isSeeking.current) {
setPaused(false);
// Start Trakt watching session only if duration is loaded
if (duration > 0) {
traktAutosync.handlePlaybackStart(currentTime, duration);
}
// Note: handlePlaybackStart is already called in onLoad
// We don't need to call it again here to avoid duplicate calls
}
};
const onPaused = () => {
if (isMounted.current) {
setPaused(true);
// Send a forced pause update to Trakt immediately when user pauses
if (duration > 0) {
traktAutosync.handleProgressUpdate(currentTime, duration, true);
logger.log('[VideoPlayer] Sent forced pause update to Trakt');
}
}
};

View file

@ -64,7 +64,7 @@ export interface TraktWatchlistItem {
};
show?: {
title: string;
year: number;
year: number
ids: {
trakt: number;
slug: string;
@ -271,37 +271,93 @@ export class TraktService {
// Track currently watching sessions to avoid duplicate starts
private currentlyWatching: Set<string> = new Set();
private lastSyncTime: number = 0;
private lastSyncTimes: Map<string, number> = new Map();
private readonly SYNC_DEBOUNCE_MS = 60000; // 60 seconds
// Enhanced deduplication for stop calls
// Debounce for stop calls
private lastStopCalls: Map<string, number> = new Map();
private readonly STOP_DEBOUNCE_MS = 10000; // 10 seconds debounce for stop calls
// Default completion threshold (overridden by user settings)
private readonly DEFAULT_COMPLETION_THRESHOLD = 80; // 80%
private constructor() {
// Initialization happens in initialize method
// Initialize the cleanup interval for old stop calls
setInterval(() => this.cleanupOldStopCalls(), 5 * 60 * 1000); // Clean up every 5 minutes
// Cleanup old stop call records every 5 minutes
setInterval(() => {
this.cleanupOldStopCalls();
}, 5 * 60 * 1000);
// Load user settings
this.loadCompletionThreshold();
}
/**
* Cleanup old stop call records to prevent memory leaks
* Load user-configured completion threshold from AsyncStorage
*/
private async loadCompletionThreshold(): Promise<void> {
try {
const thresholdStr = await AsyncStorage.getItem('@trakt_completion_threshold');
if (thresholdStr) {
const threshold = parseInt(thresholdStr, 10);
if (!isNaN(threshold) && threshold >= 50 && threshold <= 100) {
logger.log(`[TraktService] Loaded user completion threshold: ${threshold}%`);
this.completionThreshold = threshold;
}
}
} catch (error) {
logger.error('[TraktService] Error loading completion threshold:', error);
}
}
/**
* Get the current completion threshold (user-configured or default)
*/
private get completionThreshold(): number {
return this._completionThreshold || this.DEFAULT_COMPLETION_THRESHOLD;
}
/**
* Set the completion threshold
*/
private set completionThreshold(value: number) {
this._completionThreshold = value;
}
// Backing field for completion threshold
private _completionThreshold: number | null = null;
/**
* Clean up old stop call records to prevent memory leaks
*/
private cleanupOldStopCalls(): void {
const now = Date.now();
const cutoff = now - (this.STOP_DEBOUNCE_MS * 2); // Keep records for 2x the debounce time
let cleanupCount = 0;
// Remove stop calls older than the debounce window
for (const [key, timestamp] of this.lastStopCalls.entries()) {
if (timestamp < cutoff) {
if (now - timestamp > this.STOP_DEBOUNCE_MS) {
this.lastStopCalls.delete(key);
cleanupCount++;
}
}
if (this.lastStopCalls.size > 0) {
logger.log(`[TraktService] Cleaned up old stop call records. Remaining: ${this.lastStopCalls.size}`);
// Also clean up old scrobbled timestamps
for (const [key, timestamp] of this.scrobbledTimestamps.entries()) {
if (now - timestamp > this.SCROBBLE_EXPIRY_MS) {
this.scrobbledTimestamps.delete(key);
this.scrobbledItems.delete(key);
cleanupCount++;
}
}
// Clean up old sync times that haven't been updated in a while
for (const [key, timestamp] of this.lastSyncTimes.entries()) {
if (now - timestamp > 24 * 60 * 60 * 1000) { // 24 hours
this.lastSyncTimes.delete(key);
cleanupCount++;
}
}
if (cleanupCount > 0) {
logger.log(`[TraktService] Cleaned up ${cleanupCount} old tracking entries`);
}
}
@ -1109,6 +1165,19 @@ export class TraktService {
payload.show.ids.imdb = cleanShowImdbId;
}
// Add episode IMDB ID if available (for specific episode IDs)
if (contentData.imdbId && contentData.imdbId !== contentData.showImdbId) {
const cleanEpisodeImdbId = contentData.imdbId.startsWith('tt')
? contentData.imdbId.substring(2)
: contentData.imdbId;
if (!payload.episode.ids) {
payload.episode.ids = {};
}
payload.episode.ids.imdb = cleanEpisodeImdbId;
}
logger.log('[TraktService] DEBUG episode payload:', JSON.stringify(payload, null, 2));
return payload;
}
@ -1250,12 +1319,15 @@ export class TraktService {
const now = Date.now();
const watchingKey = this.getWatchingKey(contentData);
const lastSync = this.lastSyncTimes.get(watchingKey) || 0;
// Debounce API calls unless forced
if (!force && (now - this.lastSyncTime) < this.SYNC_DEBOUNCE_MS) {
if (!force && (now - lastSync) < this.SYNC_DEBOUNCE_MS) {
return true; // Skip this sync, but return success
}
this.lastSyncTime = now;
this.lastSyncTimes.set(watchingKey, now);
const result = await this.queueRequest(async () => {
return await this.pauseWatching(contentData, progress);
@ -1309,7 +1381,7 @@ export class TraktService {
this.currentlyWatching.delete(watchingKey);
// Mark as scrobbled if >= 80% to prevent future duplicates and restarts
if (progress >= 80) {
if (progress >= this.completionThreshold) {
this.scrobbledItems.add(watchingKey);
this.scrobbledTimestamps.set(watchingKey, Date.now());
logger.log(`[TraktService] Marked as scrobbled to prevent restarts: ${watchingKey}`);
@ -1317,7 +1389,7 @@ export class TraktService {
// The stop endpoint automatically handles the 80%+ completion logic
// and will mark as scrobbled if >= 80%, or pause if < 80%
const action = progress >= 80 ? 'scrobbled' : 'paused';
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
logger.log(`[TraktService] Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
return true;