mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
Merge pull request #348 from chrisk325/patch-5
This commit is contained in:
commit
6cb115ed74
2 changed files with 94 additions and 60 deletions
|
|
@ -36,6 +36,9 @@ interface ContinueWatchingItem extends StreamingContent {
|
||||||
episode?: number;
|
episode?: number;
|
||||||
episodeTitle?: string;
|
episodeTitle?: string;
|
||||||
addonId?: string;
|
addonId?: string;
|
||||||
|
addonPoster?: string;
|
||||||
|
addonName?: string;
|
||||||
|
addonDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the ref interface
|
// Define the ref interface
|
||||||
|
|
@ -212,9 +215,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
const metadataCache = useRef<Record<string, { metadata: any; basicContent: StreamingContent | null; timestamp: number }>>({});
|
const metadataCache = useRef<Record<string, { metadata: any; basicContent: StreamingContent | null; timestamp: number }>>({});
|
||||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
// Helper function to get cached or fetch metadata
|
const getCachedMetadata = useCallback(async (type: string, id: string, addonId?: string) => {
|
||||||
const getCachedMetadata = useCallback(async (type: string, id: string) => {
|
const cacheKey = `${type}:${id}:${addonId || 'default'}`;
|
||||||
const cacheKey = `${type}:${id}`;
|
|
||||||
const cached = metadataCache.current[cacheKey];
|
const cached = metadataCache.current[cacheKey];
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
|
@ -224,60 +226,67 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldFetchMeta = await stremioService.isValidContentId(type, id);
|
const shouldFetchMeta = await stremioService.isValidContentId(type, id);
|
||||||
const [metadata, basicContent] = await Promise.all([
|
|
||||||
|
const [metadata, basicContent, addonContent] = await Promise.all([
|
||||||
shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null),
|
shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null),
|
||||||
catalogService.getBasicContentDetails(type, id)
|
catalogService.getBasicContentDetails(type, id),
|
||||||
|
addonId ? stremioService.getMetaDetails(type, id, addonId).catch(() => null) : Promise.resolve(null)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (basicContent) {
|
const finalContent = basicContent ? {
|
||||||
const result = { metadata, basicContent, timestamp: now };
|
...basicContent,
|
||||||
|
...(addonContent?.name && { name: addonContent.name }),
|
||||||
|
...(addonContent?.poster && { poster: addonContent.poster }),
|
||||||
|
...(addonContent?.description && { description: addonContent.description }),
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
if (finalContent) {
|
||||||
|
const result = { metadata, basicContent: finalContent, addonContent, timestamp: now };
|
||||||
metadataCache.current[cacheKey] = result;
|
metadataCache.current[cacheKey] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Skip logging 404 errors to reduce noise
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
// Helper function to find the next episode
|
const findNextEpisode = useCallback((
|
||||||
const findNextEpisode = useCallback((currentSeason: number, currentEpisode: number, videos: any[]) => {
|
currentSeason: number,
|
||||||
|
currentEpisode: number,
|
||||||
|
videos: any[],
|
||||||
|
watchedSet?: Set<string>,
|
||||||
|
showId?: string
|
||||||
|
) => {
|
||||||
if (!videos || !Array.isArray(videos)) return null;
|
if (!videos || !Array.isArray(videos)) return null;
|
||||||
|
|
||||||
// Sort videos to ensure correct order
|
|
||||||
const sortedVideos = [...videos].sort((a, b) => {
|
const sortedVideos = [...videos].sort((a, b) => {
|
||||||
if (a.season !== b.season) return a.season - b.season;
|
if (a.season !== b.season) return a.season - b.season;
|
||||||
return a.episode - b.episode;
|
return a.episode - b.episode;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Strategy 1: Look for next episode in the same season
|
const isAlreadyWatched = (season: number, episode: number): boolean => {
|
||||||
let nextEp = sortedVideos.find(v => v.season === currentSeason && v.episode === currentEpisode + 1);
|
if (!watchedSet || !showId) return false;
|
||||||
|
const cleanShowId = showId.startsWith('tt') ? showId : `tt${showId}`;
|
||||||
|
return watchedSet.has(`${cleanShowId}:${season}:${episode}`) ||
|
||||||
|
watchedSet.has(`${showId}:${season}:${episode}`);
|
||||||
|
};
|
||||||
|
|
||||||
// Strategy 2: If not found, look for the first episode of the next season
|
for (const video of sortedVideos) {
|
||||||
if (!nextEp) {
|
if (video.season < currentSeason) continue;
|
||||||
nextEp = sortedVideos.find(v => v.season === currentSeason + 1 && v.episode === 1);
|
if (video.season === currentSeason && video.episode <= currentEpisode) continue;
|
||||||
}
|
|
||||||
|
if (isAlreadyWatched(video.season, video.episode)) continue;
|
||||||
// Strategy 3: Just find the very next video in the list after the current one
|
|
||||||
// This handles cases where episode numbering isn't sequential or S+1 E1 isn't the standard start
|
if (isEpisodeReleased(video)) {
|
||||||
if (!nextEp) {
|
return video;
|
||||||
const currentIndex = sortedVideos.findIndex(v => v.season === currentSeason && v.episode === currentEpisode);
|
|
||||||
if (currentIndex !== -1 && currentIndex + 1 < sortedVideos.length) {
|
|
||||||
const candidate = sortedVideos[currentIndex + 1];
|
|
||||||
// Ensure we didn't just jump to a random special; check reasonable bounds if needed,
|
|
||||||
// but generally taking the next sorted item is correct for sequential viewing.
|
|
||||||
nextEp = candidate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the found episode is released
|
|
||||||
if (nextEp && isEpisodeReleased(nextEp)) {
|
|
||||||
return nextEp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
// Modified loadContinueWatching to render incrementally
|
// Modified loadContinueWatching to render incrementally
|
||||||
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
|
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
|
||||||
|
|
@ -461,7 +470,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cachedData = await getCachedMetadata(group.type, group.id);
|
const cachedData = await getCachedMetadata(group.type, group.id, group.episodes[0]?.progress?.addonId);
|
||||||
if (!cachedData?.basicContent) return;
|
if (!cachedData?.basicContent) return;
|
||||||
const { metadata, basicContent } = cachedData;
|
const { metadata, basicContent } = cachedData;
|
||||||
|
|
||||||
|
|
@ -660,7 +669,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
const movieKey = `movie:${imdbId}`;
|
const movieKey = `movie:${imdbId}`;
|
||||||
if (recentlyRemovedRef.current.has(movieKey)) continue;
|
if (recentlyRemovedRef.current.has(movieKey)) continue;
|
||||||
|
|
||||||
const cachedData = await getCachedMetadata('movie', imdbId);
|
const cachedData = await getCachedMetadata('movie', imdbId, item.addonId);
|
||||||
if (!cachedData?.basicContent) continue;
|
if (!cachedData?.basicContent) continue;
|
||||||
|
|
||||||
const pausedAt = new Date(item.paused_at).getTime();
|
const pausedAt = new Date(item.paused_at).getTime();
|
||||||
|
|
@ -692,7 +701,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedData = await getCachedMetadata('series', showImdb);
|
const cachedData = await getCachedMetadata('series', showImdb, item.addonId);
|
||||||
if (!cachedData?.basicContent) continue;
|
if (!cachedData?.basicContent) continue;
|
||||||
|
|
||||||
traktBatch.push({
|
traktBatch.push({
|
||||||
|
|
|
||||||
|
|
@ -559,15 +559,23 @@ export function useTraktIntegration() {
|
||||||
return undefined;
|
return undefined;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
updatePromises.push(
|
// Merge with local progress
|
||||||
storageService.mergeWithTraktProgress(
|
await storageService.mergeWithTraktProgress(
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
item.progress,
|
item.progress,
|
||||||
item.paused_at,
|
item.paused_at,
|
||||||
episodeId,
|
episodeId,
|
||||||
exactTime
|
exactTime
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// FIX: Mark as already synced so it won't be re-uploaded to Trakt
|
||||||
|
await storageService.updateTraktSyncStatus(
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
true, // synced = true
|
||||||
|
item.progress,
|
||||||
|
episodeId
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error);
|
logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error);
|
||||||
|
|
@ -581,19 +589,27 @@ export function useTraktIntegration() {
|
||||||
const id = movie.movie.ids.imdb;
|
const id = movie.movie.ids.imdb;
|
||||||
const watchedAt = movie.last_watched_at;
|
const watchedAt = movie.last_watched_at;
|
||||||
|
|
||||||
updatePromises.push(
|
await storageService.mergeWithTraktProgress(
|
||||||
storageService.mergeWithTraktProgress(
|
id,
|
||||||
id,
|
'movie',
|
||||||
'movie',
|
100,
|
||||||
100, // 100% progress for watched items
|
watchedAt
|
||||||
watchedAt
|
);
|
||||||
)
|
|
||||||
|
// FIX: Mark as already synced
|
||||||
|
await storageService.updateTraktSyncStatus(
|
||||||
|
id,
|
||||||
|
'movie',
|
||||||
|
true,
|
||||||
|
100
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[useTraktIntegration] Error preparing watched movie update:', error);
|
logger.error('[useTraktIntegration] Error preparing watched movie update:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process watched shows (100% completed episodes)
|
||||||
for (const show of watchedShows) {
|
for (const show of watchedShows) {
|
||||||
try {
|
try {
|
||||||
if (show.show?.ids?.imdb && show.seasons) {
|
if (show.show?.ids?.imdb && show.seasons) {
|
||||||
|
|
@ -602,14 +618,22 @@ export function useTraktIntegration() {
|
||||||
for (const season of show.seasons) {
|
for (const season of show.seasons) {
|
||||||
for (const episode of season.episodes) {
|
for (const episode of season.episodes) {
|
||||||
const episodeId = `${showImdbId}:${season.number}:${episode.number}`;
|
const episodeId = `${showImdbId}:${season.number}:${episode.number}`;
|
||||||
updatePromises.push(
|
|
||||||
storageService.mergeWithTraktProgress(
|
await storageService.mergeWithTraktProgress(
|
||||||
showImdbId,
|
showImdbId,
|
||||||
'series',
|
'series',
|
||||||
100,
|
100,
|
||||||
episode.last_watched_at,
|
episode.last_watched_at,
|
||||||
episodeId
|
episodeId
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// FIX: Mark as already synced
|
||||||
|
await storageService.updateTraktSyncStatus(
|
||||||
|
showImdbId,
|
||||||
|
'series',
|
||||||
|
true,
|
||||||
|
100,
|
||||||
|
episodeId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -618,6 +642,7 @@ export function useTraktIntegration() {
|
||||||
logger.error('[useTraktIntegration] Error preparing watched show update:', error);
|
logger.error('[useTraktIntegration] Error preparing watched show update:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute all updates in parallel
|
// Execute all updates in parallel
|
||||||
await Promise.all(updatePromises);
|
await Promise.all(updatePromises);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue