mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
removed aggressive cache cleaning
This commit is contained in:
parent
714226b6a5
commit
8178dfc215
7 changed files with 38 additions and 389 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { View, StyleSheet, Dimensions } from 'react-native';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { imageCacheService } from '../../services/imageCacheService';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
interface OptimizedImageProps {
|
||||
|
|
@ -59,16 +58,14 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
onLoad,
|
||||
onError,
|
||||
contentFit = 'cover',
|
||||
transition = 200,
|
||||
transition = 0,
|
||||
cachePolicy = 'memory'
|
||||
}) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(!lazy);
|
||||
const [recyclingKey] = useState(() => `${Math.random().toString(36).slice(2)}-${Date.now()}`);
|
||||
const [optimizedUrl, setOptimizedUrl] = useState<string>('');
|
||||
const mountedRef = useRef(true);
|
||||
const loadTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Extract URL from source
|
||||
const sourceUrl = typeof source === 'string' ? source : source?.uri || '';
|
||||
|
|
@ -80,9 +77,6 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
useEffect(() => {
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
if (loadTimeoutRef.current) {
|
||||
clearTimeout(loadTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -97,7 +91,6 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
// Lazy loading intersection observer simulation
|
||||
useEffect(() => {
|
||||
if (lazy && !isVisible) {
|
||||
// Simple lazy loading - load after a short delay to simulate intersection
|
||||
const timer = setTimeout(() => {
|
||||
if (mountedRef.current) {
|
||||
setIsVisible(true);
|
||||
|
|
@ -108,41 +101,22 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
}
|
||||
}, [lazy, isVisible, priority]);
|
||||
|
||||
// Preload image with caching
|
||||
// Preload image via FastImage
|
||||
const preloadImage = useCallback(async () => {
|
||||
if (!optimizedUrl || !isVisible) return;
|
||||
|
||||
try {
|
||||
// Use our cache service to manage the image
|
||||
const cachedUrl = await imageCacheService.getCachedImageUrl(optimizedUrl);
|
||||
|
||||
// Set a timeout for loading
|
||||
loadTimeoutRef.current = setTimeout(() => {
|
||||
if (mountedRef.current && !isLoaded) {
|
||||
logger.warn(`[OptimizedImage] Load timeout for: ${optimizedUrl.substring(0, 50)}...`);
|
||||
setHasError(true);
|
||||
}
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
// Skip prefetch to reduce memory pressure and heating
|
||||
// await ExpoImage.prefetch(cachedUrl);
|
||||
|
||||
if (mountedRef.current) {
|
||||
setIsLoaded(true);
|
||||
if (loadTimeoutRef.current) {
|
||||
clearTimeout(loadTimeoutRef.current);
|
||||
loadTimeoutRef.current = null;
|
||||
}
|
||||
onLoad?.();
|
||||
}
|
||||
await FastImage.preload([{ uri: optimizedUrl }]);
|
||||
if (!mountedRef.current) return;
|
||||
setIsLoaded(true);
|
||||
onLoad?.();
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
logger.error(`[OptimizedImage] Failed to load: ${optimizedUrl.substring(0, 50)}...`, error);
|
||||
setHasError(true);
|
||||
onError?.(error);
|
||||
}
|
||||
if (!mountedRef.current) return;
|
||||
logger.error(`[OptimizedImage] Failed to preload: ${optimizedUrl.substring(0, 50)}...`, error);
|
||||
setHasError(true);
|
||||
onError?.(error);
|
||||
}
|
||||
}, [optimizedUrl, isVisible, isLoaded, onLoad, onError]);
|
||||
}, [optimizedUrl, isVisible, onLoad, onError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && optimizedUrl && !isLoaded && !hasError) {
|
||||
|
|
@ -158,26 +132,23 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
// Show placeholder while loading or on error
|
||||
if (!isLoaded || hasError) {
|
||||
return (
|
||||
<ExpoImage
|
||||
<FastImage
|
||||
source={{ uri: placeholder }}
|
||||
style={style}
|
||||
contentFit={contentFit}
|
||||
transition={0}
|
||||
cachePolicy="memory"
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ExpoImage
|
||||
source={{ uri: optimizedUrl }}
|
||||
<FastImage
|
||||
source={{
|
||||
uri: optimizedUrl,
|
||||
priority: priority === 'high' ? FastImage.priority.high : priority === 'low' ? FastImage.priority.low : FastImage.priority.normal,
|
||||
cache: FastImage.cacheControl.immutable
|
||||
}}
|
||||
style={style}
|
||||
contentFit={contentFit}
|
||||
transition={transition}
|
||||
cachePolicy={cachePolicy}
|
||||
// Use a stable recycling key per component instance to keep textures alive between reuses
|
||||
// This mitigates flicker on fast horizontal scrolls
|
||||
recyclingKey={recyclingKey}
|
||||
resizeMode={contentFit === 'contain' ? FastImage.resizeMode.contain : contentFit === 'cover' ? FastImage.resizeMode.cover : FastImage.resizeMode.cover}
|
||||
onLoad={() => {
|
||||
setIsLoaded(true);
|
||||
onLoad?.();
|
||||
|
|
|
|||
|
|
@ -87,6 +87,12 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
|||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const [isWatched, setIsWatched] = useState(false);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset image error state when item changes, allowing for retry on re-render
|
||||
setImageError(false);
|
||||
}, [item.id, item.poster]);
|
||||
|
||||
const { currentTheme } = useTheme();
|
||||
const { settings, isLoaded } = useSettings();
|
||||
const posterRadius = typeof settings.posterBorderRadius === 'number' ? settings.posterBorderRadius : 12;
|
||||
|
|
@ -245,6 +251,9 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
|||
}}
|
||||
style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1, borderRadius: posterRadius }]}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
onLoad={() => {
|
||||
setImageError(false);
|
||||
}}
|
||||
onError={() => {
|
||||
if (__DEV__) console.warn('Image load error for:', item.poster);
|
||||
setImageError(true);
|
||||
|
|
@ -359,6 +368,8 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export default React.memo(ContentItem, (prev, next) => {
|
||||
// Only re-render when the item ID changes (FastImage handles caching internally)
|
||||
return prev.item.id === next.item.id && prev.item.type === next.item.type;
|
||||
// Re-render when identity or poster changes. Caching is handled by FastImage.
|
||||
if (prev.item.id !== next.item.id) return false;
|
||||
if (prev.item.poster !== next.item.poster) return false;
|
||||
return true;
|
||||
});
|
||||
|
|
@ -32,7 +32,6 @@ import { useSettings } from '../../hooks/useSettings';
|
|||
import { TMDBService } from '../../services/tmdbService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { imageCacheService } from '../../services/imageCacheService';
|
||||
|
||||
interface FeaturedContentProps {
|
||||
featuredContent: StreamingContent | null;
|
||||
|
|
@ -234,7 +233,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
|
|||
});
|
||||
|
||||
await Promise.race([
|
||||
imageCacheService.getCachedImageUrl(url),
|
||||
FastImage.preload([{ uri: url }]),
|
||||
timeout,
|
||||
]);
|
||||
imageCache[url] = true;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Toast } from 'toastify-react-native';
|
||||
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||
import { imageCacheService } from '../services/imageCacheService';
|
||||
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||
|
||||
// Constants
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
|
|||
let isMounted = true;
|
||||
const fetchPoster = async () => {
|
||||
if (item.images) {
|
||||
const url = await TraktService.getTraktPosterUrlCached(item.images);
|
||||
const url = TraktService.getTraktPosterUrl(item.images);
|
||||
if (isMounted && url) {
|
||||
setPosterUrl(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,291 +0,0 @@
|
|||
import { logger } from '../utils/logger';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
|
||||
interface CachedImage {
|
||||
url: string;
|
||||
localPath: string;
|
||||
timestamp: number;
|
||||
expiresAt: number;
|
||||
size?: number; // Track approximate memory usage
|
||||
accessCount: number; // Track usage frequency
|
||||
lastAccessed: number; // Track last access time
|
||||
}
|
||||
|
||||
class ImageCacheService {
|
||||
private cache = new Map<string, CachedImage>();
|
||||
private readonly CACHE_DURATION = Infinity; // Session-only: valid until app close
|
||||
private readonly MAX_CACHE_SIZE = 25; // Further reduced maximum number of cached images
|
||||
private readonly MAX_MEMORY_MB = 40; // Further reduced maximum memory usage in MB
|
||||
private currentMemoryUsage = 0;
|
||||
private cleanupInterval: NodeJS.Timeout | null = null;
|
||||
private appStateSubscription: any = null;
|
||||
|
||||
constructor() {
|
||||
// Start cleanup interval every 15 minutes (more frequent cleanup to reduce memory pressure)
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.performCleanup();
|
||||
}, 15 * 60 * 1000);
|
||||
|
||||
// Reduce memory footprint when app goes to background
|
||||
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached image URL or cache the original if not present
|
||||
*/
|
||||
public async getCachedImageUrl(originalUrl: string): Promise<string> {
|
||||
if (!originalUrl || originalUrl.includes('placeholder')) {
|
||||
return originalUrl; // Don't cache placeholder images
|
||||
}
|
||||
|
||||
// Check if we have a valid cached version
|
||||
const cached = this.cache.get(originalUrl);
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
// Update access tracking
|
||||
cached.accessCount++;
|
||||
cached.lastAccessed = Date.now();
|
||||
// Skip verbose logging to reduce CPU load
|
||||
return cached.localPath;
|
||||
}
|
||||
|
||||
// Check memory pressure before adding new entries (more lenient)
|
||||
if (this.cache.size >= this.MAX_CACHE_SIZE * 0.95) {
|
||||
// Skip verbose logging to reduce CPU load
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
// Estimate image size (rough approximation)
|
||||
const estimatedSize = this.estimateImageSize(originalUrl);
|
||||
|
||||
const cachedImage: CachedImage = {
|
||||
url: originalUrl,
|
||||
localPath: originalUrl, // In production, this would be a local file path
|
||||
timestamp: Date.now(),
|
||||
expiresAt: Date.now() + this.CACHE_DURATION,
|
||||
size: estimatedSize,
|
||||
accessCount: 1,
|
||||
lastAccessed: Date.now()
|
||||
};
|
||||
|
||||
this.cache.set(originalUrl, cachedImage);
|
||||
this.currentMemoryUsage += estimatedSize;
|
||||
this.enforceMemoryLimits();
|
||||
|
||||
// Skip verbose logging to reduce CPU load
|
||||
return cachedImage.localPath;
|
||||
} catch (error) {
|
||||
logger.error('[ImageCache] Failed to cache image:', error);
|
||||
return originalUrl; // Fallback to original URL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an image is cached
|
||||
*/
|
||||
public isCached(url: string): boolean {
|
||||
const cached = this.cache.get(url);
|
||||
return cached !== undefined && cached.expiresAt > Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log cache status (for debugging)
|
||||
*/
|
||||
public logCacheStatus(): void {
|
||||
const stats = this.getCacheStats();
|
||||
logger.log(`[ImageCache] 📊 Cache Status: ${stats.size} total, ${stats.expired} expired`);
|
||||
|
||||
// Log first 5 cached URLs for debugging
|
||||
const entries = Array.from(this.cache.entries()).slice(0, 5);
|
||||
entries.forEach(([url, cached]) => {
|
||||
const isExpired = cached.expiresAt <= Date.now();
|
||||
const timeLeft = Math.max(0, cached.expiresAt - Date.now()) / 1000 / 60; // minutes
|
||||
logger.log(`[ImageCache] - ${url.substring(0, 60)}... (${isExpired ? 'EXPIRED' : `${timeLeft.toFixed(1)}m left`})`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear expired cache entries
|
||||
*/
|
||||
public clearExpiredCache(): void {
|
||||
const now = Date.now();
|
||||
for (const [url, cached] of this.cache.entries()) {
|
||||
if (cached.expiresAt <= now) {
|
||||
this.cache.delete(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached images
|
||||
*/
|
||||
public clearAllCache(): void {
|
||||
this.cache.clear();
|
||||
logger.log('[ImageCache] Cleared all cached images');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
public getCacheStats(): { size: number; expired: number } {
|
||||
const now = Date.now();
|
||||
let expired = 0;
|
||||
|
||||
for (const cached of this.cache.values()) {
|
||||
if (cached.expiresAt <= now) {
|
||||
expired++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
size: this.cache.size,
|
||||
expired,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce maximum cache size by removing oldest entries
|
||||
*/
|
||||
private enforceMaxCacheSize(): void {
|
||||
if (this.cache.size <= this.MAX_CACHE_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to array and sort by timestamp (oldest first)
|
||||
const entries = Array.from(this.cache.entries()).sort(
|
||||
(a, b) => a[1].timestamp - b[1].timestamp
|
||||
);
|
||||
|
||||
// Remove oldest entries
|
||||
const toRemove = this.cache.size - this.MAX_CACHE_SIZE;
|
||||
for (let i = 0; i < toRemove; i++) {
|
||||
this.cache.delete(entries[i][0]);
|
||||
}
|
||||
|
||||
logger.log(`[ImageCache] Removed ${toRemove} old entries to enforce cache size limit`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce memory limits using LRU eviction
|
||||
*/
|
||||
private enforceMemoryLimits(): void {
|
||||
const maxMemoryBytes = this.MAX_MEMORY_MB * 1024 * 1024;
|
||||
|
||||
if (this.currentMemoryUsage <= maxMemoryBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by access frequency and recency (LRU)
|
||||
const entries = Array.from(this.cache.entries()).sort((a, b) => {
|
||||
const scoreA = a[1].accessCount * 0.3 + (Date.now() - a[1].lastAccessed) * 0.7;
|
||||
const scoreB = b[1].accessCount * 0.3 + (Date.now() - b[1].lastAccessed) * 0.7;
|
||||
return scoreB - scoreA; // Higher score = more likely to be evicted
|
||||
});
|
||||
|
||||
let removedCount = 0;
|
||||
for (const [url, cached] of entries) {
|
||||
if (this.currentMemoryUsage <= maxMemoryBytes * 0.8) { // Leave 20% buffer
|
||||
break;
|
||||
}
|
||||
|
||||
this.cache.delete(url);
|
||||
this.currentMemoryUsage -= cached.size || 0;
|
||||
removedCount++;
|
||||
}
|
||||
|
||||
// Skip verbose memory eviction logging to reduce CPU load
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate image size based on URL patterns
|
||||
*/
|
||||
private estimateImageSize(url: string): number {
|
||||
// Rough estimates in bytes based on common image types
|
||||
if (url.includes('poster')) return 150 * 1024; // 150KB for posters
|
||||
if (url.includes('banner') || url.includes('backdrop')) return 300 * 1024; // 300KB for banners
|
||||
if (url.includes('logo')) return 50 * 1024; // 50KB for logos
|
||||
if (url.includes('thumb')) return 75 * 1024; // 75KB for thumbnails
|
||||
return 200 * 1024; // Default 200KB
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should skip caching due to memory pressure
|
||||
*/
|
||||
private shouldSkipCaching(): boolean {
|
||||
const maxMemoryBytes = this.MAX_MEMORY_MB * 1024 * 1024;
|
||||
return this.currentMemoryUsage > maxMemoryBytes * 0.9 || this.cache.size >= this.MAX_CACHE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform comprehensive cleanup
|
||||
*/
|
||||
private performCleanup(): void {
|
||||
const initialSize = this.cache.size;
|
||||
const initialMemory = this.currentMemoryUsage;
|
||||
|
||||
// Remove expired entries
|
||||
this.clearExpiredCache();
|
||||
|
||||
// Recalculate memory usage
|
||||
this.recalculateMemoryUsage();
|
||||
|
||||
// Enforce limits
|
||||
this.enforceMemoryLimits();
|
||||
this.enforceMaxCacheSize();
|
||||
|
||||
// Avoid clearing Expo's global memory cache to prevent re-decode churn
|
||||
|
||||
const finalSize = this.cache.size;
|
||||
const finalMemory = this.currentMemoryUsage;
|
||||
|
||||
// Skip verbose cleanup logging to reduce CPU load
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate memory usage from cache entries
|
||||
*/
|
||||
private recalculateMemoryUsage(): void {
|
||||
this.currentMemoryUsage = 0;
|
||||
for (const cached of this.cache.values()) {
|
||||
this.currentMemoryUsage += cached.size || 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup resources
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
if (this.appStateSubscription) {
|
||||
this.appStateSubscription.remove();
|
||||
this.appStateSubscription = null;
|
||||
}
|
||||
this.clearAllCache();
|
||||
}
|
||||
|
||||
private handleAppStateChange = (nextState: AppStateStatus) => {
|
||||
if (nextState !== 'active') {
|
||||
// On background/inactive, aggressively trim cache to 25% to reduce memory pressure
|
||||
const targetSize = Math.floor(this.MAX_CACHE_SIZE * 0.25);
|
||||
if (this.cache.size > targetSize) {
|
||||
const entries = Array.from(this.cache.entries());
|
||||
const toRemove = this.cache.size - targetSize;
|
||||
for (let i = 0; i < toRemove; i++) {
|
||||
const [url, cached] = entries[i];
|
||||
this.cache.delete(url);
|
||||
this.currentMemoryUsage -= cached.size || 0;
|
||||
}
|
||||
}
|
||||
// Force aggressive memory cleanup
|
||||
this.enforceMemoryLimits();
|
||||
// Clear any remaining memory pressure
|
||||
this.currentMemoryUsage = Math.min(this.currentMemoryUsage, this.MAX_MEMORY_MB * 1024 * 1024 * 0.3);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const imageCacheService = new ImageCacheService();
|
||||
|
|
@ -1176,7 +1176,7 @@ export class TraktService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract poster URL from Trakt images with basic caching
|
||||
* Extract poster URL from Trakt images
|
||||
*/
|
||||
public static getTraktPosterUrl(images?: TraktImages): string | null {
|
||||
if (!images || !images.poster || images.poster.length === 0) {
|
||||
|
|
@ -1185,36 +1185,7 @@ export class TraktService {
|
|||
|
||||
// Get the first poster and add https prefix
|
||||
const posterPath = images.poster[0];
|
||||
const fullUrl = posterPath.startsWith('http') ? posterPath : `https://${posterPath}`;
|
||||
|
||||
// Try to use cached version synchronously (basic cache check)
|
||||
const isCached = imageCacheService.isCached(fullUrl);
|
||||
if (isCached) {
|
||||
logger.log(`[TraktService] 🎯 Using cached poster: ${fullUrl.substring(0, 60)}...`);
|
||||
} else {
|
||||
logger.log(`[TraktService] 📥 New poster URL: ${fullUrl.substring(0, 60)}...`);
|
||||
// Queue for async caching
|
||||
imageCacheService.getCachedImageUrl(fullUrl).catch(error => {
|
||||
logger.error('[TraktService] Background caching failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
return fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract poster URL from Trakt images with async caching
|
||||
*/
|
||||
public static async getTraktPosterUrlCached(images?: TraktImages): Promise<string | null> {
|
||||
const url = this.getTraktPosterUrl(images);
|
||||
if (!url) return null;
|
||||
|
||||
try {
|
||||
return await imageCacheService.getCachedImageUrl(url);
|
||||
} catch (error) {
|
||||
logger.error('[TraktService] Failed to cache image:', error);
|
||||
return url;
|
||||
}
|
||||
return posterPath.startsWith('http') ? posterPath : `https://${posterPath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2029,17 +2000,6 @@ export class TraktService {
|
|||
logger.error('[TraktService] DEBUG: Error fetching playback progress:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Debug image cache status
|
||||
*/
|
||||
public static debugImageCache(): void {
|
||||
try {
|
||||
logger.log('[TraktService] === IMAGE CACHE DEBUG ===');
|
||||
imageCacheService.logCacheStatus();
|
||||
} catch (error) {
|
||||
logger.error('[TraktService] Debug image cache failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a playback progress entry on Trakt by its playback `id`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue