diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index be5bf6fc..e4f748d1 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -164,10 +164,11 @@ const ContentItem = ({ item, onPress }: ContentItemProps) => { style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1, borderRadius: posterRadius }]} contentFit="cover" cachePolicy="memory-disk" // Use both memory and disk cache - transition={200} // Add smooth transition + transition={0} // Disable transition to reduce GPU work placeholder={{ blurhash: PLACEHOLDER_BLURHASH } as any} placeholderContentFit="cover" allowDownscaling + priority="low" // Deprioritize decode for long lists onLoad={() => { setImageLoaded(true); setImageError(false); @@ -183,7 +184,6 @@ const ContentItem = ({ item, onPress }: ContentItemProps) => { setImageError(true); setImageLoaded(false); }} - priority="normal" // Increase priority for better loading recyclingKey={item.id} // Add recycling key for better performance /> ) : ( @@ -239,11 +239,11 @@ const styles = StyleSheet.create({ borderRadius: 12, overflow: 'hidden', position: 'relative', - elevation: Platform.OS === 'android' ? 2 : 0, + elevation: Platform.OS === 'android' ? 1 : 0, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, - shadowOpacity: 0.1, - shadowRadius: 2, + shadowOpacity: 0.05, + shadowRadius: 1, borderWidth: 0.5, borderColor: 'rgba(255,255,255,0.12)', marginBottom: 8, diff --git a/src/components/home/HeroCarousel.tsx b/src/components/home/HeroCarousel.tsx index e9b68e92..701497e5 100644 --- a/src/components/home/HeroCarousel.tsx +++ b/src/components/home/HeroCarousel.tsx @@ -157,10 +157,10 @@ const HeroCarousel: React.FC = ({ items, loading = false }) = source={{ uri: item.banner || item.poster }} style={styles.backgroundImage as ImageStyle} contentFit="cover" - blurRadius={Platform.OS === 'android' ? 12 : 20} + blurRadius={Platform.OS === 'android' ? 8 : 12} cachePolicy="memory-disk" - transition={200} - priority="high" + transition={0} + priority="low" /> = memo(({ item, colors, logoFail source={{ uri: item.banner || item.poster }} style={styles.banner as ImageStyle} contentFit="cover" - transition={300} + transition={0} cachePolicy="memory-disk" /> = memo(({ item, colors, logoFail source={{ uri: item.logo }} style={styles.logo as ImageStyle} contentFit="contain" - transition={250} + transition={0} cachePolicy="memory-disk" onError={onLogoError} /> @@ -360,11 +360,11 @@ const styles = StyleSheet.create({ height: CARD_HEIGHT, borderRadius: 16, overflow: 'hidden', - elevation: 6, + elevation: 2, shadowColor: '#000', - shadowOffset: { width: 0, height: 6 }, - shadowOpacity: 0.3, - shadowRadius: 12, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.15, + shadowRadius: 4, }, skeletonCard: { width: CARD_WIDTH, diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index bf268d63..87ae9335 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -515,8 +515,8 @@ const AndroidVideoPlayer: React.FC = () => { clearInterval(progressSaveInterval); } - // IMMEDIATE SYNC: Reduce sync interval to 5 seconds for near real-time sync - const syncInterval = 5000; // 5 seconds for immediate sync + // HEATING FIX: Increase sync interval to 15 seconds to reduce CPU load + const syncInterval = 15000; // 15 seconds to prevent heating const interval = setInterval(() => { saveWatchProgress(); @@ -2032,7 +2032,7 @@ const AndroidVideoPlayer: React.FC = () => { playWhenInactive={false} ignoreSilentSwitch="ignore" mixWithOthers="inherit" - progressUpdateInterval={250} + progressUpdateInterval={1000} bufferConfig={{ minBufferMs: 15000, maxBufferMs: 50000, diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index 3d39177e..9325d753 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -547,8 +547,8 @@ const VideoPlayer: React.FC = () => { clearInterval(progressSaveInterval); } - // IMMEDIATE SYNC: Reduce sync interval to 5 seconds for near real-time sync - const syncInterval = 5000; // 5 seconds for immediate sync + // HEATING FIX: Increase sync interval to 15 seconds to reduce CPU load + const syncInterval = 15000; // 15 seconds to prevent heating const interval = setInterval(() => { saveWatchProgress(); diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index 1c51613a..538e48f3 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -870,8 +870,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat }); }; - // Check completion periodically - const completionInterval = setInterval(checkScrapersCompletion, 1000); + // Check completion less frequently to reduce CPU load + const completionInterval = setInterval(checkScrapersCompletion, 2000); // Fallback timeout after 30 seconds const fallbackTimeout = setTimeout(() => { @@ -1039,8 +1039,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat }); }; - // Check completion periodically - const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 1000); + // Check completion less frequently to reduce CPU load + const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 2000); // Fallback timeout after 30 seconds const episodeFallbackTimeout = setTimeout(() => { diff --git a/src/hooks/useMetadataAnimations.ts b/src/hooks/useMetadataAnimations.ts index 671e4008..d72a7e55 100644 --- a/src/hooks/useMetadataAnimations.ts +++ b/src/hooks/useMetadataAnimations.ts @@ -161,7 +161,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) = // Use single progress value for all header animations if (headerProgress.value !== progress) { headerProgress.value = withTiming(progress, { - duration: progress ? 200 : 150, + duration: progress ? 150 : 100, easing: easings.ultraFast }); } diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 92fda409..5319586b 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -158,7 +158,7 @@ const HomeScreen = () => { let catalogIndex = 0; // Limit concurrent catalog loading to prevent overwhelming the system - const MAX_CONCURRENT_CATALOGS = 3; // Lower concurrency to reduce CPU/network spikes + const MAX_CONCURRENT_CATALOGS = 2; // Very low concurrency to reduce heating let activeCatalogLoads = 0; const catalogQueue: (() => Promise)[] = []; @@ -169,8 +169,8 @@ const HomeScreen = () => { activeCatalogLoads++; catalogLoader().finally(async () => { activeCatalogLoads--; - // Yield to event loop to avoid JS thread starvation - await new Promise(resolve => setTimeout(resolve, 10)); + // Yield to event loop to avoid JS thread starvation and reduce heating + await new Promise(resolve => setTimeout(resolve, 50)); processCatalogQueue(); // Process next in queue }); } @@ -605,14 +605,7 @@ const HomeScreen = () => { // Add memory cleanup on scroll end const handleScrollEnd = useCallback(() => { - // Clear memory cache after scroll settles to free up RAM - setTimeout(() => { - try { - ExpoImage.clearMemoryCache(); - } catch (error) { - // Ignore errors - } - }, 1000); + // No-op; avoid clearing image memory cache here to prevent decode thrash/heating }, []); // Memoize individual section components to prevent re-renders @@ -765,7 +758,6 @@ const HomeScreen = () => { showsVerticalScrollIndicator={false} ListHeaderComponent={memoizedHeader} ListFooterComponent={ListFooterComponent} - onMomentumScrollEnd={handleScrollEnd} onEndReached={handleLoadMoreCatalogs} onEndReachedThreshold={0.6} scrollEventThrottle={32} diff --git a/src/services/imageCacheService.ts b/src/services/imageCacheService.ts index 50fd117c..ecd1d3ac 100644 --- a/src/services/imageCacheService.ts +++ b/src/services/imageCacheService.ts @@ -20,10 +20,10 @@ class ImageCacheService { private cleanupInterval: NodeJS.Timeout | null = null; constructor() { - // Start cleanup interval every 10 minutes + // Start cleanup interval every 30 minutes (less churn) this.cleanupInterval = setInterval(() => { this.performCleanup(); - }, 10 * 60 * 1000); + }, 30 * 60 * 1000); } /** @@ -40,13 +40,13 @@ class ImageCacheService { // Update access tracking cached.accessCount++; cached.lastAccessed = Date.now(); - logger.log(`[ImageCache] Retrieved from cache: ${originalUrl.substring(0, 50)}...`); + // 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) { - logger.log(`[ImageCache] Skipping cache due to size limit`); + // Skip verbose logging to reduce CPU load return originalUrl; } @@ -68,7 +68,7 @@ class ImageCacheService { this.currentMemoryUsage += estimatedSize; this.enforceMemoryLimits(); - logger.log(`[ImageCache] ✅ NEW CACHE ENTRY: ${originalUrl.substring(0, 50)}... (Cache: ${this.cache.size}/${this.MAX_CACHE_SIZE}, Memory: ${(this.currentMemoryUsage / 1024 / 1024).toFixed(1)}MB)`); + // Skip verbose logging to reduce CPU load return cachedImage.localPath; } catch (error) { logger.error('[ImageCache] Failed to cache image:', error); @@ -231,12 +231,7 @@ class ImageCacheService { this.enforceMemoryLimits(); this.enforceMaxCacheSize(); - // Clear Expo image memory cache periodically - try { - ExpoImage.clearMemoryCache(); - } catch (error) { - // Ignore errors from clearing memory cache - } + // Avoid clearing Expo's global memory cache to prevent re-decode churn const finalSize = this.cache.size; const finalMemory = this.currentMemoryUsage;