mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
moviecollection fix
This commit is contained in:
parent
dd542091e1
commit
d9fcc085a6
11 changed files with 235 additions and 62 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -74,3 +74,5 @@ build-and-publish-app-releases.sh
|
|||
bottomnav.md
|
||||
/TrailerServices
|
||||
mmkv.md
|
||||
src/services/tmdbService.ts
|
||||
fix-android-scroll-lag-summary.md
|
||||
|
|
|
|||
|
|
@ -4143,7 +4143,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
<EpisodeStreamsModal
|
||||
visible={showEpisodeStreamsModal}
|
||||
episode={selectedEpisodeForStreams}
|
||||
onClose={() => setShowEpisodeStreamsModal(false)}
|
||||
onClose={() => {
|
||||
setShowEpisodeStreamsModal(false);
|
||||
setShowEpisodesModal(true);
|
||||
}}
|
||||
onSelectStream={handleEpisodeStreamSelect}
|
||||
metadata={metadata ? { id: metadata.id, name: metadata.name } : undefined}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3436,7 +3436,10 @@ const KSPlayerCore: React.FC = () => {
|
|||
<EpisodeStreamsModal
|
||||
visible={showEpisodeStreamsModal}
|
||||
episode={selectedEpisodeForStreams}
|
||||
onClose={() => setShowEpisodeStreamsModal(false)}
|
||||
onClose={() => {
|
||||
setShowEpisodeStreamsModal(false);
|
||||
setShowEpisodesModal(true);
|
||||
}}
|
||||
onSelectStream={handleEpisodeStreamSelect}
|
||||
metadata={metadata ? { id: metadata.id, name: metadata.name } : undefined}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -47,11 +47,12 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
|||
}
|
||||
});
|
||||
|
||||
// Initialize season only when modal opens
|
||||
useEffect(() => {
|
||||
if (currentEpisode?.season) {
|
||||
if (showEpisodesModal && currentEpisode?.season) {
|
||||
setSelectedSeason(currentEpisode.season);
|
||||
}
|
||||
}, [currentEpisode]);
|
||||
}, [showEpisodesModal, currentEpisode?.season]);
|
||||
|
||||
const loadEpisodesProgress = async () => {
|
||||
if (!metadata?.id) return;
|
||||
|
|
|
|||
|
|
@ -954,7 +954,15 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
banner: undefined, // Let useMetadataAssets handle banner via TMDB
|
||||
};
|
||||
}
|
||||
setMetadata(finalMetadata);
|
||||
|
||||
// Preserve existing collection if it was set by fetchProductionInfo
|
||||
setMetadata((prev) => {
|
||||
const updated = { ...finalMetadata };
|
||||
if (prev?.collection) {
|
||||
updated.collection = prev.collection;
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
cacheService.setMetadata(id, type, finalMetadata);
|
||||
(async () => {
|
||||
const items = await catalogService.getLibraryItems();
|
||||
|
|
@ -1907,10 +1915,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Fetch TMDB networks/production companies when TMDB ID is available and enrichment is enabled
|
||||
const productionInfoFetchedRef = useRef<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!tmdbId || !settings.enrichMetadataWithTMDB || !metadata) return;
|
||||
if (!tmdbId || !settings.enrichMetadataWithTMDB || !metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentKey = `${type}-${tmdbId}`;
|
||||
if (productionInfoFetchedRef.current === contentKey || (metadata as any).networks) return;
|
||||
if (productionInfoFetchedRef.current === contentKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only skip if networks are set AND collection is already set (for movies)
|
||||
const hasNetworks = !!(metadata as any).networks;
|
||||
const hasCollection = !!(metadata as any).collection;
|
||||
if (hasNetworks && (type !== 'movie' || hasCollection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchProductionInfo = async () => {
|
||||
try {
|
||||
|
|
@ -2032,7 +2051,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Try to fetch movie images with language parameter
|
||||
try {
|
||||
const movieImages = await tmdbService.getMovieImagesFull(part.id);
|
||||
const movieImages = await tmdbService.getMovieImagesFull(part.id, lang);
|
||||
if (movieImages && movieImages.backdrops && movieImages.backdrops.length > 0) {
|
||||
// Filter and sort backdrops by language and quality
|
||||
const languageBackdrops = movieImages.backdrops
|
||||
|
|
@ -2105,12 +2124,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[useMetadata] Fetched production info via TMDB:', productionInfo);
|
||||
if (productionInfo.length > 0) {
|
||||
if (__DEV__) console.log('[useMetadata] Setting production info on metadata', { productionInfoCount: productionInfo.length });
|
||||
setMetadata((prev: any) => ({ ...prev, networks: productionInfo }));
|
||||
} else {
|
||||
if (__DEV__) console.log('[useMetadata] No production info found, not setting networks');
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('[useMetadata] Failed to fetch production info:', error);
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
|
||||
const SETTINGS_STORAGE_KEY = 'app_settings';
|
||||
|
||||
// Singleton settings cache
|
||||
let cachedSettings: AppSettings | null = null;
|
||||
let settingsCacheTimestamp = 0;
|
||||
const SETTINGS_CACHE_TTL = 60000; // 1 minute
|
||||
|
||||
export const useSettings = () => {
|
||||
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS);
|
||||
const [isLoaded, setIsLoaded] = useState<boolean>(false);
|
||||
|
|
@ -168,41 +173,46 @@ export const useSettings = () => {
|
|||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
// Use cached settings if available and fresh
|
||||
const now = Date.now();
|
||||
if (cachedSettings && (now - settingsCacheTimestamp) < SETTINGS_CACHE_TTL) {
|
||||
setSettings(cachedSettings);
|
||||
setIsLoaded(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
|
||||
|
||||
// Use synchronous MMKV reads for better performance
|
||||
const [scopedJson, legacyJson] = await Promise.all([
|
||||
mmkvStorage.getItem(scopedKey),
|
||||
mmkvStorage.getItem(SETTINGS_STORAGE_KEY),
|
||||
]);
|
||||
|
||||
const parsedScoped = scopedJson ? JSON.parse(scopedJson) : null;
|
||||
const parsedLegacy = legacyJson ? JSON.parse(legacyJson) : null;
|
||||
|
||||
let merged = parsedScoped || parsedLegacy;
|
||||
|
||||
// Fallback: scan any existing user-scoped settings if current scope not set yet
|
||||
// Simplified fallback - only use getAllKeys if absolutely necessary
|
||||
if (!merged) {
|
||||
try {
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const candidateKeys = (allKeys || []).filter(k => k.endsWith(`:${SETTINGS_STORAGE_KEY}`));
|
||||
if (candidateKeys.length > 0) {
|
||||
const pairs = await mmkvStorage.multiGet(candidateKeys);
|
||||
for (const [, value] of pairs) {
|
||||
if (value) {
|
||||
try {
|
||||
const candidate = JSON.parse(value);
|
||||
if (candidate && typeof candidate === 'object') {
|
||||
merged = candidate;
|
||||
break;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
// Use string search on MMKV storage instead of getAllKeys for performance
|
||||
const scoped = mmkvStorage.getString(scopedKey);
|
||||
if (scoped) {
|
||||
try {
|
||||
merged = JSON.parse(scoped);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (merged) setSettings({ ...DEFAULT_SETTINGS, ...merged });
|
||||
else setSettings(DEFAULT_SETTINGS);
|
||||
const finalSettings = merged ? { ...DEFAULT_SETTINGS, ...merged } : DEFAULT_SETTINGS;
|
||||
|
||||
// Update cache
|
||||
cachedSettings = finalSettings;
|
||||
settingsCacheTimestamp = now;
|
||||
|
||||
setSettings(finalSettings);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to load settings:', error);
|
||||
// Fallback to default settings on error
|
||||
|
|
@ -230,6 +240,11 @@ export const useSettings = () => {
|
|||
]);
|
||||
// Ensure a current scope exists to avoid future loads missing the chosen scope
|
||||
await mmkvStorage.setItem('@user:current', scope);
|
||||
|
||||
// Update cache
|
||||
cachedSettings = newSettings;
|
||||
settingsCacheTimestamp = Date.now();
|
||||
|
||||
setSettings(newSettings);
|
||||
if (__DEV__) console.log(`Setting updated: ${key}`, value);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import FastImage from '@d11/react-native-fast-image';
|
|||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { TMDBService } from '../services/tmdbService';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const BACKDROP_WIDTH = width * 0.9;
|
||||
|
|
@ -39,6 +40,7 @@ const BackdropGalleryScreen: React.FC = () => {
|
|||
const navigation = useNavigation();
|
||||
const { tmdbId, type, title } = route.params as RouteParams;
|
||||
const { currentTheme } = useTheme();
|
||||
const { settings } = useSettings();
|
||||
|
||||
const [backdrops, setBackdrops] = useState<BackdropItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -49,12 +51,15 @@ const BackdropGalleryScreen: React.FC = () => {
|
|||
try {
|
||||
setLoading(true);
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
|
||||
// Get language preference
|
||||
const language = settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en';
|
||||
|
||||
let images;
|
||||
if (type === 'movie') {
|
||||
images = await tmdbService.getMovieImagesFull(tmdbId);
|
||||
images = await tmdbService.getMovieImagesFull(tmdbId, language);
|
||||
} else {
|
||||
images = await tmdbService.getTvShowImagesFull(tmdbId);
|
||||
images = await tmdbService.getTvShowImagesFull(tmdbId, language);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
|
|
@ -83,7 +88,7 @@ const BackdropGalleryScreen: React.FC = () => {
|
|||
if (tmdbId) {
|
||||
fetchBackdrops();
|
||||
}
|
||||
}, [tmdbId, type]);
|
||||
}, [tmdbId, type, settings.useTmdbLocalizedMetadata, settings.tmdbLanguagePreference]);
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
|||
// Constants
|
||||
const CATALOG_SETTINGS_KEY = 'catalog_settings';
|
||||
|
||||
// In-memory cache for catalog settings to avoid repeated MMKV reads
|
||||
let cachedCatalogSettings: Record<string, boolean> | null = null;
|
||||
let catalogSettingsCacheTimestamp = 0;
|
||||
const CATALOG_SETTINGS_CACHE_TTL = 30000; // 30 seconds
|
||||
|
||||
// Define interfaces for our data
|
||||
interface Category {
|
||||
id: string;
|
||||
|
|
@ -153,9 +158,24 @@ const HomeScreen = () => {
|
|||
setLoadedCatalogCount(0);
|
||||
|
||||
try {
|
||||
const [addons, catalogSettingsJson, addonManifests] = await Promise.all([
|
||||
// Check cache first
|
||||
let catalogSettings: Record<string, boolean> = {};
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedCatalogSettings && (now - catalogSettingsCacheTimestamp) < CATALOG_SETTINGS_CACHE_TTL) {
|
||||
catalogSettings = cachedCatalogSettings;
|
||||
} else {
|
||||
// Load from storage
|
||||
const catalogSettingsJson = await mmkvStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {};
|
||||
|
||||
// Update cache
|
||||
cachedCatalogSettings = catalogSettings;
|
||||
catalogSettingsCacheTimestamp = now;
|
||||
}
|
||||
|
||||
const [addons, addonManifests] = await Promise.all([
|
||||
catalogService.getAllAddons(),
|
||||
mmkvStorage.getItem(CATALOG_SETTINGS_KEY),
|
||||
stremioService.getInstalledAddonsAsync()
|
||||
]);
|
||||
|
||||
|
|
@ -164,8 +184,6 @@ const HomeScreen = () => {
|
|||
setHasAddons(addons.length > 0);
|
||||
});
|
||||
|
||||
const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {};
|
||||
|
||||
// Create placeholder array with proper order and track indices
|
||||
let catalogIndex = 0;
|
||||
const catalogQueue: (() => Promise<void>)[] = [];
|
||||
|
|
@ -655,6 +673,9 @@ const HomeScreen = () => {
|
|||
// Track scroll direction manually for reliable behavior across platforms
|
||||
const lastScrollYRef = useRef(0);
|
||||
const lastToggleRef = useRef(0);
|
||||
const scrollAnimationFrameRef = useRef<number | null>(null);
|
||||
const isScrollingRef = useRef(false);
|
||||
|
||||
const toggleHeader = useCallback((hide: boolean) => {
|
||||
const now = Date.now();
|
||||
if (now - lastToggleRef.current < 120) return; // debounce
|
||||
|
|
@ -739,21 +760,40 @@ const HomeScreen = () => {
|
|||
</>
|
||||
), [catalogsLoading, catalogs, loadedCatalogCount, totalCatalogsRef.current, navigation, currentTheme.colors]);
|
||||
|
||||
// Memoize scroll handler to prevent recreating on every render
|
||||
// Memoize scroll handler with requestAnimationFrame throttling for better performance
|
||||
const handleScroll = useCallback((event: any) => {
|
||||
const y = event.nativeEvent.contentOffset.y;
|
||||
const dy = y - lastScrollYRef.current;
|
||||
lastScrollYRef.current = y;
|
||||
if (y <= 10) {
|
||||
toggleHeader(false);
|
||||
return;
|
||||
}
|
||||
// Threshold to avoid jitter
|
||||
if (dy > 6) {
|
||||
toggleHeader(true); // scrolling down
|
||||
} else if (dy < -6) {
|
||||
toggleHeader(false); // scrolling up
|
||||
// Persist the event before using requestAnimationFrame to prevent event pooling issues
|
||||
event.persist();
|
||||
|
||||
// Cancel any pending animation frame
|
||||
if (scrollAnimationFrameRef.current !== null) {
|
||||
cancelAnimationFrame(scrollAnimationFrameRef.current);
|
||||
}
|
||||
|
||||
// Capture scroll values immediately before async operation
|
||||
const scrollY = event.nativeEvent.contentOffset.y;
|
||||
|
||||
// Use requestAnimationFrame to throttle scroll handling
|
||||
scrollAnimationFrameRef.current = requestAnimationFrame(() => {
|
||||
const y = scrollY;
|
||||
const dy = y - lastScrollYRef.current;
|
||||
lastScrollYRef.current = y;
|
||||
|
||||
isScrollingRef.current = Math.abs(dy) > 0;
|
||||
|
||||
if (y <= 10) {
|
||||
toggleHeader(false);
|
||||
return;
|
||||
}
|
||||
// Threshold to avoid jitter
|
||||
if (dy > 6) {
|
||||
toggleHeader(true); // scrolling down
|
||||
} else if (dy < -6) {
|
||||
toggleHeader(false); // scrolling up
|
||||
}
|
||||
|
||||
scrollAnimationFrameRef.current = null;
|
||||
});
|
||||
}, [toggleHeader]);
|
||||
|
||||
// Memoize content container style - use stable insets to prevent iOS shifting
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import { logger } from '../utils/logger';
|
|||
class MMKVStorage {
|
||||
private static instance: MMKVStorage;
|
||||
private storage = createMMKV();
|
||||
// In-memory cache for frequently accessed data
|
||||
private cache = new Map<string, { value: any; timestamp: number }>();
|
||||
private readonly CACHE_TTL = 30000; // 30 seconds
|
||||
private readonly MAX_CACHE_SIZE = 100; // Limit cache size to prevent memory issues
|
||||
|
||||
private constructor() {}
|
||||
|
||||
|
|
@ -14,11 +18,56 @@ class MMKVStorage {
|
|||
return MMKVStorage.instance;
|
||||
}
|
||||
|
||||
// Cache management methods
|
||||
private getCached(key: string): string | null {
|
||||
const cached = this.cache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
|
||||
return cached.value;
|
||||
}
|
||||
if (cached) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private setCached(key: string, value: any): void {
|
||||
// Implement LRU-style eviction if cache is too large
|
||||
if (this.cache.size >= this.MAX_CACHE_SIZE) {
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, { value, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
private invalidateCache(key?: string): void {
|
||||
if (key) {
|
||||
this.cache.delete(key);
|
||||
} else {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// AsyncStorage-compatible API
|
||||
async getItem(key: string): Promise<string | null> {
|
||||
try {
|
||||
// Check cache first
|
||||
const cached = this.getCached(key);
|
||||
if (cached !== null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Read from storage
|
||||
const value = this.storage.getString(key);
|
||||
return value ?? null;
|
||||
const result = value ?? null;
|
||||
|
||||
// Cache the result
|
||||
if (result !== null) {
|
||||
this.setCached(key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error getting item ${key}:`, error);
|
||||
return null;
|
||||
|
|
@ -28,6 +77,8 @@ class MMKVStorage {
|
|||
async setItem(key: string, value: string): Promise<void> {
|
||||
try {
|
||||
this.storage.set(key, value);
|
||||
// Update cache immediately
|
||||
this.setCached(key, value);
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error setting item ${key}:`, error);
|
||||
}
|
||||
|
|
@ -39,6 +90,8 @@ class MMKVStorage {
|
|||
if (this.storage.contains(key)) {
|
||||
this.storage.remove(key);
|
||||
}
|
||||
// Invalidate cache
|
||||
this.invalidateCache(key);
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error removing item ${key}:`, error);
|
||||
}
|
||||
|
|
@ -71,6 +124,7 @@ class MMKVStorage {
|
|||
async clear(): Promise<void> {
|
||||
try {
|
||||
this.storage.clearAll();
|
||||
this.cache.clear();
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error clearing storage:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ class StorageService {
|
|||
private readonly NOTIFICATION_DEBOUNCE_MS = 1000; // 1 second debounce
|
||||
private readonly MIN_NOTIFICATION_INTERVAL = 500; // Minimum 500ms between notifications
|
||||
|
||||
// Cache for getAllWatchProgress
|
||||
private watchProgressCache: Record<string, WatchProgress> | null = null;
|
||||
private watchProgressCacheTimestamp = 0;
|
||||
private readonly WATCH_PROGRESS_CACHE_TTL = 5000; // 5 seconds
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): StorageService {
|
||||
|
|
@ -263,6 +268,9 @@ class StorageService {
|
|||
: Date.now();
|
||||
const updated = { ...progress, lastUpdated: timestamp };
|
||||
await mmkvStorage.setItem(key, JSON.stringify(updated));
|
||||
|
||||
// Invalidate cache
|
||||
this.invalidateWatchProgressCache();
|
||||
|
||||
// Notify subscribers; allow forcing immediate notification
|
||||
if (options?.forceNotify) {
|
||||
|
|
@ -349,6 +357,10 @@ class StorageService {
|
|||
const key = await this.getWatchProgressKeyScoped(id, type, episodeId);
|
||||
await mmkvStorage.removeItem(key);
|
||||
await this.addWatchProgressTombstone(id, type, episodeId);
|
||||
|
||||
// Invalidate cache
|
||||
this.invalidateWatchProgressCache();
|
||||
|
||||
// Notify subscribers
|
||||
this.notifyWatchProgressSubscribers();
|
||||
// Emit explicit remove event for sync layer
|
||||
|
|
@ -360,22 +372,40 @@ class StorageService {
|
|||
|
||||
public async getAllWatchProgress(): Promise<Record<string, WatchProgress>> {
|
||||
try {
|
||||
// Use cache if available and fresh
|
||||
const now = Date.now();
|
||||
if (this.watchProgressCache && (now - this.watchProgressCacheTimestamp) < this.WATCH_PROGRESS_CACHE_TTL) {
|
||||
return this.watchProgressCache;
|
||||
}
|
||||
|
||||
const scope = await this.getUserScope();
|
||||
const prefix = `@user:${scope}:${this.WATCH_PROGRESS_KEY}`;
|
||||
const keys = await mmkvStorage.getAllKeys();
|
||||
const watchProgressKeys = keys.filter(key => key.startsWith(prefix));
|
||||
const pairs = await mmkvStorage.multiGet(watchProgressKeys);
|
||||
return pairs.reduce((acc, [key, value]) => {
|
||||
|
||||
const result = pairs.reduce((acc, [key, value]) => {
|
||||
if (value) {
|
||||
acc[key.replace(prefix, '')] = JSON.parse(value);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, WatchProgress>);
|
||||
|
||||
// Update cache
|
||||
this.watchProgressCache = result;
|
||||
this.watchProgressCacheTimestamp = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error('Error getting all watch progress:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private invalidateWatchProgressCache(): void {
|
||||
this.watchProgressCache = null;
|
||||
this.watchProgressCacheTimestamp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Trakt sync status for a watch progress entry
|
||||
|
|
|
|||
|
|
@ -906,22 +906,27 @@ export class TMDBService {
|
|||
/**
|
||||
* Get movie images (logos, posters, backdrops) by TMDB ID - returns full images object
|
||||
*/
|
||||
async getMovieImagesFull(movieId: number | string): Promise<any> {
|
||||
const cacheKey = this.generateCacheKey(`movie_${movieId}_images_full`);
|
||||
async getMovieImagesFull(movieId: number | string, language: string = 'en'): Promise<any> {
|
||||
const cacheKey = this.generateCacheKey(`movie_${movieId}_images_full`, { language });
|
||||
|
||||
// Check cache
|
||||
const cached = this.getCachedData<any>(cacheKey);
|
||||
if (cached !== null) return cached;
|
||||
if (cached !== null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/movie/${movieId}/images`, {
|
||||
headers: await this.getHeaders(),
|
||||
params: await this.getParams({
|
||||
include_image_language: `en,null`
|
||||
include_image_language: `${language},en,null`
|
||||
}),
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
|
||||
|
||||
this.setCachedData(cacheKey, data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
|
@ -1056,8 +1061,8 @@ export class TMDBService {
|
|||
/**
|
||||
* Get TV show images (logos, posters, backdrops) by TMDB ID - returns full images object
|
||||
*/
|
||||
async getTvShowImagesFull(showId: number | string): Promise<any> {
|
||||
const cacheKey = this.generateCacheKey(`tv_${showId}_images_full`);
|
||||
async getTvShowImagesFull(showId: number | string, language: string = 'en'): Promise<any> {
|
||||
const cacheKey = this.generateCacheKey(`tv_${showId}_images_full`, { language });
|
||||
|
||||
// Check cache
|
||||
const cached = this.getCachedData<any>(cacheKey);
|
||||
|
|
@ -1067,7 +1072,7 @@ export class TMDBService {
|
|||
const response = await axios.get(`${BASE_URL}/tv/${showId}/images`, {
|
||||
headers: await this.getHeaders(),
|
||||
params: await this.getParams({
|
||||
include_image_language: `en,null`
|
||||
include_image_language: `${language},en,null`
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue