mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Enhance video player components with content duration tracking and progress updates
This update introduces functionality to store and update the actual video duration in both AndroidVideoPlayer and VideoPlayer components. It ensures that the progress is accurately maintained when the video duration changes significantly. Additionally, the useTraktIntegration and storageService have been updated to handle exact playback times from Trakt, improving synchronization and user experience. Logging has been refined for better clarity during progress updates.
This commit is contained in:
parent
7627de32a9
commit
5e733f9eb2
6 changed files with 275 additions and 93 deletions
|
|
@ -455,6 +455,17 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const videoDuration = data.duration;
|
||||
if (data.duration > 0) {
|
||||
setDuration(videoDuration);
|
||||
|
||||
// Store the actual duration for future reference and update existing progress
|
||||
if (id && type) {
|
||||
storageService.setContentDuration(id, type, videoDuration, episodeId);
|
||||
storageService.updateProgressDuration(id, type, videoDuration, episodeId);
|
||||
|
||||
// Update the saved duration for resume overlay if it was using an estimate
|
||||
if (savedDuration && Math.abs(savedDuration - videoDuration) > 60) {
|
||||
setSavedDuration(videoDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set aspect ratio from video dimensions
|
||||
|
|
@ -621,8 +632,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
|
||||
const handleResume = async () => {
|
||||
if (resumePosition !== null) {
|
||||
logger.log(`[AndroidVideoPlayer] Resume requested to position: ${resumePosition}s, duration: ${duration}, isPlayerReady: ${isPlayerReady}`);
|
||||
|
||||
if (rememberChoice) {
|
||||
try {
|
||||
await AsyncStorage.setItem(RESUME_PREF_KEY, RESUME_PREF.ALWAYS_RESUME);
|
||||
|
|
@ -635,11 +644,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
|
||||
// If video is already loaded and ready, seek immediately
|
||||
if (isPlayerReady && duration > 0 && videoRef.current) {
|
||||
logger.log(`[AndroidVideoPlayer] Video ready, seeking immediately to: ${resumePosition}s`);
|
||||
seekToTime(resumePosition);
|
||||
} else {
|
||||
// Otherwise, set initial position for when video loads
|
||||
logger.log(`[AndroidVideoPlayer] Video not ready, setting initial position: ${resumePosition}s`);
|
||||
setInitialPosition(resumePosition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -479,6 +479,17 @@ const VideoPlayer: React.FC = () => {
|
|||
const videoDuration = data.duration / 1000;
|
||||
if (data.duration > 0) {
|
||||
setDuration(videoDuration);
|
||||
|
||||
// Store the actual duration for future reference and update existing progress
|
||||
if (id && type) {
|
||||
storageService.setContentDuration(id, type, videoDuration, episodeId);
|
||||
storageService.updateProgressDuration(id, type, videoDuration, episodeId);
|
||||
|
||||
// Update the saved duration for resume overlay if it was using an estimate
|
||||
if (savedDuration && Math.abs(savedDuration - videoDuration) > 60) {
|
||||
setSavedDuration(videoDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
setVideoAspectRatio(data.videoSize.width / data.videoSize.height);
|
||||
|
||||
|
|
@ -636,8 +647,6 @@ const VideoPlayer: React.FC = () => {
|
|||
|
||||
const handleResume = async () => {
|
||||
if (resumePosition !== null) {
|
||||
logger.log(`[VideoPlayer] Resume requested to position: ${resumePosition}s, duration: ${duration}, isPlayerReady: ${isPlayerReady}`);
|
||||
|
||||
if (rememberChoice) {
|
||||
try {
|
||||
await AsyncStorage.setItem(RESUME_PREF_KEY, RESUME_PREF.ALWAYS_RESUME);
|
||||
|
|
@ -650,11 +659,9 @@ const VideoPlayer: React.FC = () => {
|
|||
|
||||
// If video is already loaded and ready, seek immediately
|
||||
if (isPlayerReady && duration > 0 && vlcRef.current) {
|
||||
logger.log(`[VideoPlayer] Video ready, seeking immediately to: ${resumePosition}s`);
|
||||
seekToTime(resumePosition);
|
||||
} else {
|
||||
// Otherwise, set initial position for when video loads
|
||||
logger.log(`[VideoPlayer] Video not ready, setting initial position: ${resumePosition}s`);
|
||||
setInitialPosition(resumePosition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
options.type,
|
||||
true,
|
||||
progressPercent,
|
||||
options.episodeId
|
||||
options.episodeId,
|
||||
currentTime
|
||||
);
|
||||
|
||||
logger.log(`[TraktAutosync] Synced progress ${progressPercent.toFixed(1)}%: ${contentData.title}`);
|
||||
|
|
@ -318,7 +319,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
options.type,
|
||||
true,
|
||||
progressPercent,
|
||||
options.episodeId
|
||||
options.episodeId,
|
||||
currentTime
|
||||
);
|
||||
|
||||
// Mark session as complete if high progress (scrobbled)
|
||||
|
|
|
|||
|
|
@ -324,22 +324,21 @@ export function useTraktIntegration() {
|
|||
|
||||
// Fetch and merge Trakt progress with local progress
|
||||
const fetchAndMergeTraktProgress = useCallback(async (): Promise<boolean> => {
|
||||
logger.log(`[useTraktIntegration] fetchAndMergeTraktProgress called - isAuthenticated: ${isAuthenticated}`);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
logger.log('[useTraktIntegration] Not authenticated, skipping Trakt progress fetch');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch both playback progress and recently watched movies
|
||||
logger.log('[useTraktIntegration] Fetching Trakt playback progress and watched movies...');
|
||||
const [traktProgress, watchedMovies] = await Promise.all([
|
||||
getTraktPlaybackProgress(),
|
||||
traktService.getWatchedMovies()
|
||||
]);
|
||||
|
||||
logger.log(`[useTraktIntegration] Retrieved ${traktProgress.length} Trakt progress items, ${watchedMovies.length} watched movies`);
|
||||
logger.log(`[useTraktIntegration] Retrieved ${traktProgress.length} progress items, ${watchedMovies.length} watched movies`);
|
||||
|
||||
// Batch process all updates to reduce storage notifications
|
||||
const updatePromises: Promise<void>[] = [];
|
||||
|
||||
// Process playback progress (in-progress items)
|
||||
for (const item of traktProgress) {
|
||||
|
|
@ -351,27 +350,35 @@ export function useTraktIntegration() {
|
|||
if (item.type === 'movie' && item.movie) {
|
||||
id = item.movie.ids.imdb;
|
||||
type = 'movie';
|
||||
logger.log(`[useTraktIntegration] Processing Trakt movie progress: ${item.movie.title} (${id}) - ${item.progress}%`);
|
||||
} else if (item.type === 'episode' && item.show && item.episode) {
|
||||
id = item.show.ids.imdb;
|
||||
type = 'series';
|
||||
episodeId = `${id}:${item.episode.season}:${item.episode.number}`;
|
||||
logger.log(`[useTraktIntegration] Processing Trakt episode progress: ${item.show.title} S${item.episode.season}E${item.episode.number} (${id}) - ${item.progress}%`);
|
||||
} else {
|
||||
logger.warn(`[useTraktIntegration] Skipping invalid Trakt progress item:`, item);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.log(`[useTraktIntegration] Merging progress for ${type} ${id}: ${item.progress}% from ${item.paused_at}`);
|
||||
await storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
type,
|
||||
item.progress,
|
||||
item.paused_at,
|
||||
episodeId
|
||||
// Try to calculate exact time if we have stored duration
|
||||
const exactTime = await (async () => {
|
||||
const storedDuration = await storageService.getContentDuration(id, type, episodeId);
|
||||
if (storedDuration && storedDuration > 0) {
|
||||
return (item.progress / 100) * storedDuration;
|
||||
}
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
updatePromises.push(
|
||||
storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
type,
|
||||
item.progress,
|
||||
item.paused_at,
|
||||
episodeId,
|
||||
exactTime
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error merging individual Trakt progress:', error);
|
||||
logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,21 +388,25 @@ export function useTraktIntegration() {
|
|||
if (movie.movie?.ids?.imdb) {
|
||||
const id = movie.movie.ids.imdb;
|
||||
const watchedAt = movie.last_watched_at;
|
||||
logger.log(`[useTraktIntegration] Processing watched movie: ${movie.movie.title} (${id}) - 100% watched on ${watchedAt}`);
|
||||
|
||||
await storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
'movie',
|
||||
100, // 100% progress for watched items
|
||||
watchedAt
|
||||
updatePromises.push(
|
||||
storageService.mergeWithTraktProgress(
|
||||
id,
|
||||
'movie',
|
||||
100, // 100% progress for watched items
|
||||
watchedAt
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error merging watched movie:', error);
|
||||
logger.error('[useTraktIntegration] Error preparing watched movie update:', error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(`[useTraktIntegration] Successfully merged ${traktProgress.length} progress items + ${watchedMovies.length} watched movies`);
|
||||
// Execute all updates in parallel
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
logger.log(`[useTraktIntegration] Successfully merged ${updatePromises.length} items from Trakt`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error fetching and merging Trakt progress:', error);
|
||||
|
|
@ -419,17 +430,10 @@ export function useTraktIntegration() {
|
|||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
// Fetch Trakt progress and merge with local
|
||||
logger.log('[useTraktIntegration] User authenticated, fetching Trakt progress to replace local data');
|
||||
fetchAndMergeTraktProgress().then((success) => {
|
||||
if (success) {
|
||||
logger.log('[useTraktIntegration] Trakt progress merged successfully - local data replaced with Trakt data');
|
||||
} else {
|
||||
logger.warn('[useTraktIntegration] Failed to merge Trakt progress');
|
||||
logger.log('[useTraktIntegration] Trakt progress merged successfully');
|
||||
}
|
||||
// Small delay to ensure storage subscribers are notified
|
||||
setTimeout(() => {
|
||||
logger.log('[useTraktIntegration] Trakt progress merge completed, UI should refresh');
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}, [isAuthenticated, fetchAndMergeTraktProgress]);
|
||||
|
|
@ -440,12 +444,7 @@ export function useTraktIntegration() {
|
|||
|
||||
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||
if (nextAppState === 'active') {
|
||||
logger.log('[useTraktIntegration] App became active, syncing Trakt data');
|
||||
fetchAndMergeTraktProgress().then((success) => {
|
||||
if (success) {
|
||||
logger.log('[useTraktIntegration] App focus sync completed successfully');
|
||||
}
|
||||
}).catch(error => {
|
||||
fetchAndMergeTraktProgress().catch(error => {
|
||||
logger.error('[useTraktIntegration] App focus sync failed:', error);
|
||||
});
|
||||
}
|
||||
|
|
@ -461,12 +460,7 @@ export function useTraktIntegration() {
|
|||
// Trigger sync when auth status is manually refreshed (for login scenarios)
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
logger.log('[useTraktIntegration] Auth status refresh detected, triggering Trakt progress merge');
|
||||
fetchAndMergeTraktProgress().then((success) => {
|
||||
if (success) {
|
||||
logger.log('[useTraktIntegration] Trakt progress merged after manual auth refresh');
|
||||
}
|
||||
});
|
||||
fetchAndMergeTraktProgress();
|
||||
}
|
||||
}, [lastAuthCheck, isAuthenticated, fetchAndMergeTraktProgress]);
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,6 @@ export const useWatchProgress = (
|
|||
// Subscribe to storage changes for real-time updates
|
||||
useEffect(() => {
|
||||
const unsubscribe = storageService.subscribeToWatchProgressUpdates(() => {
|
||||
logger.log('[useWatchProgress] Storage updated, reloading progress');
|
||||
loadWatchProgress();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ interface WatchProgress {
|
|||
class StorageService {
|
||||
private static instance: StorageService;
|
||||
private readonly WATCH_PROGRESS_KEY = '@watch_progress:';
|
||||
private readonly CONTENT_DURATION_KEY = '@content_duration:';
|
||||
private watchProgressSubscribers: (() => void)[] = [];
|
||||
private notificationDebounceTimer: NodeJS.Timeout | null = null;
|
||||
private lastNotificationTime: number = 0;
|
||||
private readonly NOTIFICATION_DEBOUNCE_MS = 1000; // 1 second debounce
|
||||
private readonly MIN_NOTIFICATION_INTERVAL = 500; // Minimum 500ms between notifications
|
||||
|
||||
private constructor() {}
|
||||
|
||||
|
|
@ -25,7 +30,65 @@ class StorageService {
|
|||
}
|
||||
|
||||
private getWatchProgressKey(id: string, type: string, episodeId?: string): string {
|
||||
return this.WATCH_PROGRESS_KEY + `${type}:${id}${episodeId ? `:${episodeId}` : ''}`;
|
||||
return `${this.WATCH_PROGRESS_KEY}${type}:${id}${episodeId ? `:${episodeId}` : ''}`;
|
||||
}
|
||||
|
||||
private getContentDurationKey(id: string, type: string, episodeId?: string): string {
|
||||
return `${this.CONTENT_DURATION_KEY}${type}:${id}${episodeId ? `:${episodeId}` : ''}`;
|
||||
}
|
||||
|
||||
public async setContentDuration(
|
||||
id: string,
|
||||
type: string,
|
||||
duration: number,
|
||||
episodeId?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const key = this.getContentDurationKey(id, type, episodeId);
|
||||
await AsyncStorage.setItem(key, duration.toString());
|
||||
} catch (error) {
|
||||
logger.error('Error setting content duration:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getContentDuration(
|
||||
id: string,
|
||||
type: string,
|
||||
episodeId?: string
|
||||
): Promise<number | null> {
|
||||
try {
|
||||
const key = this.getContentDurationKey(id, type, episodeId);
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
return data ? parseFloat(data) : null;
|
||||
} catch (error) {
|
||||
logger.error('Error getting content duration:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async updateProgressDuration(
|
||||
id: string,
|
||||
type: string,
|
||||
newDuration: number,
|
||||
episodeId?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const existingProgress = await this.getWatchProgress(id, type, episodeId);
|
||||
if (existingProgress && Math.abs(existingProgress.duration - newDuration) > 60) {
|
||||
// Calculate the new current time to maintain the same percentage
|
||||
const progressPercent = (existingProgress.currentTime / existingProgress.duration) * 100;
|
||||
const updatedProgress: WatchProgress = {
|
||||
...existingProgress,
|
||||
currentTime: (progressPercent / 100) * newDuration,
|
||||
duration: newDuration,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
await this.setWatchProgress(id, type, updatedProgress, episodeId);
|
||||
logger.log(`[StorageService] Updated progress duration from ${(existingProgress.duration/60).toFixed(0)}min to ${(newDuration/60).toFixed(0)}min`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error updating progress duration:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public async setWatchProgress(
|
||||
|
|
@ -36,16 +99,56 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = this.getWatchProgressKey(id, type, episodeId);
|
||||
|
||||
// Check if progress has actually changed significantly
|
||||
const existingProgress = await this.getWatchProgress(id, type, episodeId);
|
||||
if (existingProgress) {
|
||||
const timeDiff = Math.abs(progress.currentTime - existingProgress.currentTime);
|
||||
const durationDiff = Math.abs(progress.duration - existingProgress.duration);
|
||||
|
||||
// Only update if there's a significant change (>5 seconds or duration change)
|
||||
if (timeDiff < 5 && durationDiff < 1) {
|
||||
return; // Skip update for minor changes
|
||||
}
|
||||
}
|
||||
|
||||
await AsyncStorage.setItem(key, JSON.stringify(progress));
|
||||
// Notify subscribers
|
||||
this.notifyWatchProgressSubscribers();
|
||||
|
||||
// Use debounced notification to reduce spam
|
||||
this.debouncedNotifySubscribers();
|
||||
} catch (error) {
|
||||
logger.error('Error saving watch progress:', error);
|
||||
logger.error('Error setting watch progress:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private debouncedNotifySubscribers(): void {
|
||||
const now = Date.now();
|
||||
|
||||
// Clear existing timer
|
||||
if (this.notificationDebounceTimer) {
|
||||
clearTimeout(this.notificationDebounceTimer);
|
||||
}
|
||||
|
||||
// If we notified recently, debounce longer
|
||||
const timeSinceLastNotification = now - this.lastNotificationTime;
|
||||
if (timeSinceLastNotification < this.MIN_NOTIFICATION_INTERVAL) {
|
||||
this.notificationDebounceTimer = setTimeout(() => {
|
||||
this.notifyWatchProgressSubscribers();
|
||||
}, this.NOTIFICATION_DEBOUNCE_MS);
|
||||
} else {
|
||||
// Notify immediately if enough time has passed
|
||||
this.notifyWatchProgressSubscribers();
|
||||
}
|
||||
}
|
||||
|
||||
private notifyWatchProgressSubscribers(): void {
|
||||
this.watchProgressSubscribers.forEach(callback => callback());
|
||||
this.lastNotificationTime = Date.now();
|
||||
this.notificationDebounceTimer = null;
|
||||
|
||||
// Only notify if we have subscribers
|
||||
if (this.watchProgressSubscribers.length > 0) {
|
||||
this.watchProgressSubscribers.forEach(callback => callback());
|
||||
}
|
||||
}
|
||||
|
||||
public subscribeToWatchProgressUpdates(callback: () => void): () => void {
|
||||
|
|
@ -115,7 +218,8 @@ class StorageService {
|
|||
type: string,
|
||||
traktSynced: boolean,
|
||||
traktProgress?: number,
|
||||
episodeId?: string
|
||||
episodeId?: string,
|
||||
exactTime?: number
|
||||
): Promise<void> {
|
||||
try {
|
||||
const existingProgress = await this.getWatchProgress(id, type, episodeId);
|
||||
|
|
@ -124,7 +228,9 @@ class StorageService {
|
|||
...existingProgress,
|
||||
traktSynced,
|
||||
traktLastSynced: traktSynced ? Date.now() : existingProgress.traktLastSynced,
|
||||
traktProgress: traktProgress !== undefined ? traktProgress : existingProgress.traktProgress
|
||||
traktProgress: traktProgress !== undefined ? traktProgress : existingProgress.traktProgress,
|
||||
// Update current time with exact time if provided
|
||||
...(exactTime && exactTime > 0 && { currentTime: exactTime })
|
||||
};
|
||||
await this.setWatchProgress(id, type, updatedProgress, episodeId);
|
||||
}
|
||||
|
|
@ -182,60 +288,127 @@ class StorageService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Merge Trakt progress with local progress
|
||||
* Merge Trakt progress with local progress using exact time when available
|
||||
*/
|
||||
public async mergeWithTraktProgress(
|
||||
id: string,
|
||||
type: string,
|
||||
traktProgress: number,
|
||||
traktPausedAt: string,
|
||||
episodeId?: string
|
||||
episodeId?: string,
|
||||
exactTime?: number // Optional exact time in seconds from Trakt scrobble data
|
||||
): Promise<void> {
|
||||
try {
|
||||
const localProgress = await this.getWatchProgress(id, type, episodeId);
|
||||
const traktTimestamp = new Date(traktPausedAt).getTime();
|
||||
|
||||
if (!localProgress) {
|
||||
// No local progress, use Trakt data (estimate duration)
|
||||
const estimatedDuration = traktProgress > 0 ? (100 / traktProgress) * 100 : 3600; // Default 1 hour
|
||||
// No local progress - use stored duration or estimate
|
||||
let duration = await this.getContentDuration(id, type, episodeId);
|
||||
let currentTime: number;
|
||||
|
||||
if (exactTime && exactTime > 0) {
|
||||
// Use exact time from Trakt if available
|
||||
currentTime = exactTime;
|
||||
if (!duration) {
|
||||
// Calculate duration from exact time and percentage
|
||||
duration = (exactTime / traktProgress) * 100;
|
||||
}
|
||||
} else {
|
||||
// Fallback to percentage-based calculation
|
||||
if (!duration) {
|
||||
// Use reasonable duration estimates as fallback
|
||||
if (type === 'movie') {
|
||||
duration = 6600; // 110 minutes for movies
|
||||
} else if (episodeId) {
|
||||
duration = 2700; // 45 minutes for TV episodes
|
||||
} else {
|
||||
duration = 3600; // 60 minutes default
|
||||
}
|
||||
}
|
||||
currentTime = (traktProgress / 100) * duration;
|
||||
}
|
||||
|
||||
const newProgress: WatchProgress = {
|
||||
currentTime: (traktProgress / 100) * estimatedDuration,
|
||||
duration: estimatedDuration,
|
||||
currentTime,
|
||||
duration,
|
||||
lastUpdated: traktTimestamp,
|
||||
traktSynced: true,
|
||||
traktLastSynced: Date.now(),
|
||||
traktProgress
|
||||
};
|
||||
await this.setWatchProgress(id, type, newProgress, episodeId);
|
||||
|
||||
const timeSource = exactTime ? 'exact' : 'calculated';
|
||||
const durationSource = await this.getContentDuration(id, type, episodeId) ? 'stored' : 'estimated';
|
||||
logger.log(`[StorageService] Created progress from Trakt: ${(currentTime/60).toFixed(1)}min (${timeSource}) of ${(duration/60).toFixed(0)}min (${durationSource})`);
|
||||
} else {
|
||||
// Always prioritize Trakt progress when merging
|
||||
// Local progress exists - merge intelligently
|
||||
const localProgressPercent = (localProgress.currentTime / localProgress.duration) * 100;
|
||||
|
||||
if (localProgress.duration > 0) {
|
||||
// Use Trakt progress, keeping the existing duration
|
||||
const updatedProgress: WatchProgress = {
|
||||
...localProgress,
|
||||
currentTime: (traktProgress / 100) * localProgress.duration,
|
||||
lastUpdated: traktTimestamp,
|
||||
traktSynced: true,
|
||||
traktLastSynced: Date.now(),
|
||||
traktProgress
|
||||
};
|
||||
await this.setWatchProgress(id, type, updatedProgress, episodeId);
|
||||
logger.log(`[StorageService] Replaced local progress (${localProgressPercent.toFixed(1)}%) with Trakt progress (${traktProgress}%)`);
|
||||
// Only proceed if there's a significant difference (>5% or different completion status)
|
||||
const progressDiff = Math.abs(traktProgress - localProgressPercent);
|
||||
if (progressDiff < 5 && traktProgress < 100 && localProgressPercent < 100) {
|
||||
return; // Skip minor updates
|
||||
}
|
||||
|
||||
let currentTime: number;
|
||||
let duration = localProgress.duration;
|
||||
|
||||
if (exactTime && exactTime > 0 && localProgress.duration > 0) {
|
||||
// Use exact time from Trakt, keep local duration
|
||||
currentTime = exactTime;
|
||||
|
||||
// If exact time doesn't match the duration well, recalculate duration
|
||||
const calculatedDuration = (exactTime / traktProgress) * 100;
|
||||
const durationDiff = Math.abs(calculatedDuration - localProgress.duration);
|
||||
if (durationDiff > 300) { // More than 5 minutes difference
|
||||
duration = calculatedDuration;
|
||||
logger.log(`[StorageService] Updated duration based on exact time: ${(localProgress.duration/60).toFixed(0)}min → ${(duration/60).toFixed(0)}min`);
|
||||
}
|
||||
} else if (localProgress.duration > 0) {
|
||||
// Use percentage calculation with local duration
|
||||
currentTime = (traktProgress / 100) * localProgress.duration;
|
||||
} else {
|
||||
// If no duration, estimate it from Trakt progress
|
||||
const estimatedDuration = traktProgress > 0 ? (100 / traktProgress) * 100 : 3600;
|
||||
const updatedProgress: WatchProgress = {
|
||||
currentTime: (traktProgress / 100) * estimatedDuration,
|
||||
duration: estimatedDuration,
|
||||
lastUpdated: traktTimestamp,
|
||||
traktSynced: true,
|
||||
traktLastSynced: Date.now(),
|
||||
traktProgress
|
||||
};
|
||||
await this.setWatchProgress(id, type, updatedProgress, episodeId);
|
||||
logger.log(`[StorageService] Replaced local progress (${localProgressPercent.toFixed(1)}%) with Trakt progress (${traktProgress}%) - estimated duration`);
|
||||
// No local duration, check stored duration
|
||||
const storedDuration = await this.getContentDuration(id, type, episodeId);
|
||||
duration = storedDuration || 0;
|
||||
|
||||
if (!duration || duration <= 0) {
|
||||
if (exactTime && exactTime > 0) {
|
||||
duration = (exactTime / traktProgress) * 100;
|
||||
currentTime = exactTime;
|
||||
} else {
|
||||
// Final fallback to estimates
|
||||
if (type === 'movie') {
|
||||
duration = 6600; // 110 minutes for movies
|
||||
} else if (episodeId) {
|
||||
duration = 2700; // 45 minutes for TV episodes
|
||||
} else {
|
||||
duration = 3600; // 60 minutes default
|
||||
}
|
||||
currentTime = (traktProgress / 100) * duration;
|
||||
}
|
||||
} else {
|
||||
currentTime = exactTime && exactTime > 0 ? exactTime : (traktProgress / 100) * duration;
|
||||
}
|
||||
}
|
||||
|
||||
const updatedProgress: WatchProgress = {
|
||||
...localProgress,
|
||||
currentTime,
|
||||
duration,
|
||||
lastUpdated: traktTimestamp,
|
||||
traktSynced: true,
|
||||
traktLastSynced: Date.now(),
|
||||
traktProgress
|
||||
};
|
||||
await this.setWatchProgress(id, type, updatedProgress, episodeId);
|
||||
|
||||
// Only log significant changes
|
||||
if (progressDiff > 10 || traktProgress === 100) {
|
||||
const timeSource = exactTime ? 'exact' : 'calculated';
|
||||
logger.log(`[StorageService] Updated progress: ${(currentTime/60).toFixed(1)}min (${timeSource}) = ${traktProgress}%`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue