mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-03 16:59:08 +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 AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
||||||
|
import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_SUBTITLE_SIZE,
|
DEFAULT_SUBTITLE_SIZE,
|
||||||
|
|
@ -79,6 +80,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
episodeId: episodeId
|
episodeId: episodeId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get the Trakt autosync settings to use the user-configured sync frequency
|
||||||
|
const { settings: traktSettings } = useTraktAutosyncSettings();
|
||||||
|
|
||||||
safeDebugLog("Android Component mounted with props", {
|
safeDebugLog("Android Component mounted with props", {
|
||||||
uri, title, season, episode, episodeTitle, quality, year,
|
uri, title, season, episode, episodeTitle, quality, year,
|
||||||
streamProvider, id, type, episodeId, imdbId
|
streamProvider, id, type, episodeId, imdbId
|
||||||
|
|
@ -321,16 +325,24 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
if (progressSaveInterval) {
|
if (progressSaveInterval) {
|
||||||
clearInterval(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(() => {
|
const interval = setInterval(() => {
|
||||||
saveWatchProgress();
|
saveWatchProgress();
|
||||||
}, 5000);
|
}, syncInterval);
|
||||||
|
|
||||||
|
logger.log(`[AndroidVideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
|
||||||
|
|
||||||
setProgressSaveInterval(interval);
|
setProgressSaveInterval(interval);
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
setProgressSaveInterval(null);
|
setProgressSaveInterval(null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [id, type, paused, currentTime, duration]);
|
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -721,7 +733,14 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
const togglePlayback = () => {
|
const togglePlayback = () => {
|
||||||
if (videoRef.current) {
|
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 { MaterialIcons } from '@expo/vector-icons';
|
||||||
import AndroidVideoPlayer from './AndroidVideoPlayer';
|
import AndroidVideoPlayer from './AndroidVideoPlayer';
|
||||||
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
||||||
|
import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_SUBTITLE_SIZE,
|
DEFAULT_SUBTITLE_SIZE,
|
||||||
|
|
@ -74,6 +75,9 @@ const VideoPlayer: React.FC = () => {
|
||||||
episodeId: episodeId
|
episodeId: episodeId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get the Trakt autosync settings to use the user-configured sync frequency
|
||||||
|
const { settings: traktSettings } = useTraktAutosyncSettings();
|
||||||
|
|
||||||
safeDebugLog("Component mounted with props", {
|
safeDebugLog("Component mounted with props", {
|
||||||
uri, title, season, episode, episodeTitle, quality, year,
|
uri, title, season, episode, episodeTitle, quality, year,
|
||||||
streamProvider, id, type, episodeId, imdbId
|
streamProvider, id, type, episodeId, imdbId
|
||||||
|
|
@ -316,16 +320,24 @@ const VideoPlayer: React.FC = () => {
|
||||||
if (progressSaveInterval) {
|
if (progressSaveInterval) {
|
||||||
clearInterval(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(() => {
|
const interval = setInterval(() => {
|
||||||
saveWatchProgress();
|
saveWatchProgress();
|
||||||
}, 5000);
|
}, syncInterval);
|
||||||
|
|
||||||
|
logger.log(`[VideoPlayer] Watch progress save interval set to ${syncInterval}ms`);
|
||||||
|
|
||||||
setProgressSaveInterval(interval);
|
setProgressSaveInterval(interval);
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
setProgressSaveInterval(null);
|
setProgressSaveInterval(null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [id, type, paused, currentTime, duration]);
|
}, [id, type, paused, currentTime, duration, traktSettings.syncFrequency]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -341,16 +353,20 @@ const VideoPlayer: React.FC = () => {
|
||||||
if (isMounted.current && !isSeeking.current) {
|
if (isMounted.current && !isSeeking.current) {
|
||||||
setPaused(false);
|
setPaused(false);
|
||||||
|
|
||||||
// Start Trakt watching session only if duration is loaded
|
// Note: handlePlaybackStart is already called in onLoad
|
||||||
if (duration > 0) {
|
// We don't need to call it again here to avoid duplicate calls
|
||||||
traktAutosync.handlePlaybackStart(currentTime, duration);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaused = () => {
|
const onPaused = () => {
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
setPaused(true);
|
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?: {
|
show?: {
|
||||||
title: string;
|
title: string;
|
||||||
year: number;
|
year: number
|
||||||
ids: {
|
ids: {
|
||||||
trakt: number;
|
trakt: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|
@ -271,37 +271,93 @@ export class TraktService {
|
||||||
|
|
||||||
// Track currently watching sessions to avoid duplicate starts
|
// Track currently watching sessions to avoid duplicate starts
|
||||||
private currentlyWatching: Set<string> = new Set();
|
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
|
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 lastStopCalls: Map<string, number> = new Map();
|
||||||
private readonly STOP_DEBOUNCE_MS = 10000; // 10 seconds debounce for stop calls
|
private readonly STOP_DEBOUNCE_MS = 10000; // 10 seconds debounce for stop calls
|
||||||
|
|
||||||
private constructor() {
|
// Default completion threshold (overridden by user settings)
|
||||||
// Initialization happens in initialize method
|
private readonly DEFAULT_COMPLETION_THRESHOLD = 80; // 80%
|
||||||
|
|
||||||
// Cleanup old stop call records every 5 minutes
|
private constructor() {
|
||||||
setInterval(() => {
|
// Initialize the cleanup interval for old stop calls
|
||||||
this.cleanupOldStopCalls();
|
setInterval(() => this.cleanupOldStopCalls(), 5 * 60 * 1000); // Clean up every 5 minutes
|
||||||
}, 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 {
|
private cleanupOldStopCalls(): void {
|
||||||
const now = Date.now();
|
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()) {
|
for (const [key, timestamp] of this.lastStopCalls.entries()) {
|
||||||
if (timestamp < cutoff) {
|
if (now - timestamp > this.STOP_DEBOUNCE_MS) {
|
||||||
this.lastStopCalls.delete(key);
|
this.lastStopCalls.delete(key);
|
||||||
|
cleanupCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.lastStopCalls.size > 0) {
|
// Also clean up old scrobbled timestamps
|
||||||
logger.log(`[TraktService] Cleaned up old stop call records. Remaining: ${this.lastStopCalls.size}`);
|
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;
|
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));
|
logger.log('[TraktService] DEBUG episode payload:', JSON.stringify(payload, null, 2));
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
@ -1250,12 +1319,15 @@ export class TraktService {
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
const watchingKey = this.getWatchingKey(contentData);
|
||||||
|
const lastSync = this.lastSyncTimes.get(watchingKey) || 0;
|
||||||
|
|
||||||
// Debounce API calls unless forced
|
// 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
|
return true; // Skip this sync, but return success
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastSyncTime = now;
|
this.lastSyncTimes.set(watchingKey, now);
|
||||||
|
|
||||||
const result = await this.queueRequest(async () => {
|
const result = await this.queueRequest(async () => {
|
||||||
return await this.pauseWatching(contentData, progress);
|
return await this.pauseWatching(contentData, progress);
|
||||||
|
|
@ -1309,7 +1381,7 @@ export class TraktService {
|
||||||
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 >= 80) {
|
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}`);
|
||||||
|
|
@ -1317,7 +1389,7 @@ export class TraktService {
|
||||||
|
|
||||||
// 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 >= 80 ? '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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue