mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
soem fixes for trakt
This commit is contained in:
parent
af82eee3f1
commit
2feba6f6eb
3 changed files with 133 additions and 26 deletions
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue