Removed extensive logging from production build

This commit is contained in:
tapframe 2025-09-09 21:01:27 +05:30
parent 86cfca4c96
commit 97ba1ef42b
31 changed files with 349 additions and 269 deletions

View file

@ -82,8 +82,8 @@
],
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"checkAutomatically": "ON_ERROR",
"fallbackToCacheTimeout": 30000,
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
},
"runtimeVersion": "0.6.0-beta.10"

View file

@ -85,7 +85,7 @@ export const CalendarSection: React.FC<CalendarSectionProps> = ({
// Process episodes to identify dates with content
useEffect(() => {
console.log(`[CalendarSection] Processing ${episodes.length} episodes for calendar dots`);
if (__DEV__) console.log(`[CalendarSection] Processing ${episodes.length} episodes for calendar dots`);
const dateMap: { [key: string]: boolean } = {};
episodes.forEach(episode => {
@ -96,7 +96,7 @@ export const CalendarSection: React.FC<CalendarSectionProps> = ({
}
});
console.log(`[CalendarSection] Found ${Object.keys(dateMap).length} unique dates with episodes`);
if (__DEV__) console.log(`[CalendarSection] Found ${Object.keys(dateMap).length} unique dates with episodes`);
setDatesWithEpisodes(dateMap);
}, [episodes]);

View file

@ -174,7 +174,7 @@ const ContentItem = ({ item, onPress }: ContentItemProps) => {
setImageError(false);
}}
onError={(error) => {
console.warn('Image load error for:', item.poster, error);
if (__DEV__) console.warn('Image load error for:', item.poster, error);
// Try fallback URL on first error
if (retryCount === 0 && item.poster && !item.poster.includes('metahub.space')) {
setRetryCount(1);

View file

@ -91,7 +91,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
setPersonDetails(details);
setHasFetched(true);
} catch (error) {
console.error('Error fetching person details:', error);
if (__DEV__) console.error('Error fetching person details:', error);
} finally {
setLoading(false);
}

View file

@ -79,7 +79,7 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
throw new Error('Could not find Stremio ID');
}
} catch (error) {
console.error('Error navigating to recommendation:', error);
if (__DEV__) console.error('Error navigating to recommendation:', error);
Alert.alert(
'Error',
'Unable to load this content. Please try again later.',

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native';
import { Image } from 'expo-image';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { FlashList, FlashListRef } from '@shopify/flash-list';
import { useTheme } from '../../contexts/ThemeContext';
@ -70,10 +70,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const savedMode = await AsyncStorage.getItem(`season_view_mode_${metadata.id}`);
if (savedMode === 'text' || savedMode === 'posters') {
setSeasonViewMode(savedMode);
console.log('[SeriesContent] Loaded saved view mode:', savedMode, 'for show:', metadata.id);
if (__DEV__) console.log('[SeriesContent] Loaded saved view mode:', savedMode, 'for show:', metadata.id);
}
} catch (error) {
console.log('[SeriesContent] Error loading view mode preference:', error);
if (__DEV__) console.log('[SeriesContent] Error loading view mode preference:', error);
}
}
};
@ -99,7 +99,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
setSeasonViewMode(newMode);
if (metadata?.id) {
AsyncStorage.setItem(`season_view_mode_${metadata.id}`, newMode).catch(error => {
console.log('[SeriesContent] Error saving view mode preference:', error);
if (__DEV__) console.log('[SeriesContent] Error saving view mode preference:', error);
});
}
};
@ -328,7 +328,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
return null;
}
console.log('[SeriesContent] renderSeasonSelector called, current view mode:', seasonViewMode);
if (__DEV__) console.log('[SeriesContent] renderSeasonSelector called, current view mode:', seasonViewMode);
const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b);
@ -357,7 +357,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
onPress={() => {
const newMode = seasonViewMode === 'posters' ? 'text' : 'posters';
updateViewMode(newMode);
console.log('[SeriesContent] View mode changed to:', newMode, 'Current ref value:', seasonViewMode);
if (__DEV__) console.log('[SeriesContent] View mode changed to:', newMode, 'Current ref value:', seasonViewMode);
}}
activeOpacity={0.7}
>
@ -398,7 +398,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
if (seasonViewMode === 'text') {
// Text-only view
console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
if (__DEV__) console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
return (
<View
key={season}
@ -430,7 +430,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
}
// Poster view (current implementation)
console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
if (__DEV__) console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
return (
<View
key={season}

View file

@ -313,7 +313,7 @@ const AndroidVideoPlayer: React.FC = () => {
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
setZoomScale(newScale);
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
}
};
@ -321,7 +321,7 @@ const AndroidVideoPlayer: React.FC = () => {
if (event.nativeEvent.state === State.END) {
setLastZoomScale(zoomScale);
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
}
}
};
@ -331,7 +331,7 @@ const AndroidVideoPlayer: React.FC = () => {
setZoomScale(targetZoom);
setLastZoomScale(targetZoom);
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
}
};
@ -345,7 +345,7 @@ const AndroidVideoPlayer: React.FC = () => {
);
setCustomVideoStyles(styles);
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
}
}
}, [screenDimensions, videoAspectRatio]);
@ -439,7 +439,7 @@ const AndroidVideoPlayer: React.FC = () => {
// Fallback: ensure animation completes even if something goes wrong
setTimeout(() => {
if (!isOpeningAnimationComplete) {
logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered');
if (__DEV__) logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered');
setIsOpeningAnimationComplete(true);
openingScaleAnim.setValue(1);
openingFadeAnim.setValue(1);
@ -452,40 +452,40 @@ const AndroidVideoPlayer: React.FC = () => {
const loadWatchProgress = async () => {
if (id && type) {
try {
logger.log(`[AndroidVideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress);
if (savedProgress) {
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
logger.log(`[AndroidVideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
if (progressPercent < 85) {
setResumePosition(savedProgress.currentTime);
setSavedDuration(savedProgress.duration);
logger.log(`[AndroidVideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
if (appSettings.alwaysResume) {
// Only prepare auto-resume state and seek when AlwaysResume is enabled
setInitialPosition(savedProgress.currentTime);
initialSeekTargetRef.current = savedProgress.currentTime;
logger.log(`[AndroidVideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
seekToTime(savedProgress.currentTime);
} else {
// Do not set initialPosition; start from beginning with no auto-seek
setShowResumeOverlay(true);
logger.log(`[AndroidVideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
}
} else {
logger.log(`[AndroidVideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
}
} else {
logger.log(`[AndroidVideoPlayer] No saved progress found`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] No saved progress found`);
}
} catch (error) {
logger.error('[AndroidVideoPlayer] Error loading watch progress:', error);
}
} else {
logger.log(`[AndroidVideoPlayer] Missing id or type: id=${id}, type=${type}`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Missing id or type: id=${id}, type=${type}`);
}
};
loadWatchProgress();
@ -545,7 +545,7 @@ const AndroidVideoPlayer: React.FC = () => {
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
if (videoRef.current && duration > 0 && !isSeeking.current) {
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
if (__DEV__) logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
}
isSeeking.current = true;
@ -779,7 +779,7 @@ const AndroidVideoPlayer: React.FC = () => {
NativeModules.StatusBarManager.setHidden(true);
}
} catch (error) {
console.log('Immersive mode error:', error);
if (__DEV__) console.log('Immersive mode error:', error);
}
}
};

View file

@ -61,15 +61,17 @@ const VideoPlayer: React.FC = () => {
// - Xprime streams on any platform
// - Non-MKV files on iOS (unless forceVlc is set)
const shouldUseAndroidPlayer = Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc);
logger.log('[VideoPlayer] Player selection:', {
platform: Platform.OS,
isXprimeStream,
isMkvFile,
forceVlc: !!forceVlc,
selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer',
streamProvider,
uri
});
if (__DEV__) {
logger.log('[VideoPlayer] Player selection:', {
platform: Platform.OS,
isXprimeStream,
isMkvFile,
forceVlc: !!forceVlc,
selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer',
streamProvider,
uri
});
}
if (shouldUseAndroidPlayer) {
return <AndroidVideoPlayer />;
}
@ -335,7 +337,7 @@ const VideoPlayer: React.FC = () => {
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
setZoomScale(newScale);
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
if (__DEV__) logger.log(`[VideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
}
};
@ -343,7 +345,7 @@ const VideoPlayer: React.FC = () => {
if (event.nativeEvent.state === State.END) {
setLastZoomScale(zoomScale);
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
if (__DEV__) logger.log(`[VideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
}
}
};
@ -353,7 +355,7 @@ const VideoPlayer: React.FC = () => {
setZoomScale(targetZoom);
setLastZoomScale(targetZoom);
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
if (__DEV__) logger.log(`[VideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
}
};
@ -367,7 +369,7 @@ const VideoPlayer: React.FC = () => {
);
setCustomVideoStyles(styles);
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
if (__DEV__) logger.log(`[VideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
}
}
}, [effectiveDimensions, videoAspectRatio]);
@ -377,7 +379,7 @@ const VideoPlayer: React.FC = () => {
const lockOrientation = async () => {
try {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
logger.log('[VideoPlayer] Locked to landscape orientation');
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
} catch (error) {
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
}
@ -483,32 +485,36 @@ const VideoPlayer: React.FC = () => {
const loadWatchProgress = async () => {
if (id && type) {
try {
logger.log(`[VideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
if (__DEV__) {
logger.log(`[VideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
}
const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
logger.log(`[VideoPlayer] Saved progress:`, savedProgress);
if (__DEV__) {
logger.log(`[VideoPlayer] Saved progress:`, savedProgress);
}
if (savedProgress) {
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
logger.log(`[VideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
if (__DEV__) logger.log(`[VideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
if (progressPercent < 85) {
setResumePosition(savedProgress.currentTime);
setSavedDuration(savedProgress.duration);
logger.log(`[VideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
if (__DEV__) logger.log(`[VideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
if (appSettings.alwaysResume) {
// Only prepare auto-resume state and seek when AlwaysResume is enabled
setInitialPosition(savedProgress.currentTime);
initialSeekTargetRef.current = savedProgress.currentTime;
logger.log(`[VideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
if (__DEV__) logger.log(`[VideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
// Seek immediately after load
seekToTime(savedProgress.currentTime);
} else {
// Do not set initialPosition; start from beginning with no auto-seek
setShowResumeOverlay(true);
logger.log(`[VideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
if (__DEV__) logger.log(`[VideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
}
} else {
logger.log(`[VideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
if (__DEV__) logger.log(`[VideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
}
} else {
logger.log(`[VideoPlayer] No saved progress found`);
@ -517,7 +523,7 @@ const VideoPlayer: React.FC = () => {
logger.error('[VideoPlayer] Error loading watch progress:', error);
}
} else {
logger.log(`[VideoPlayer] Missing id or type: id=${id}, type=${type}`);
if (__DEV__) logger.log(`[VideoPlayer] Missing id or type: id=${id}, type=${type}`);
}
};
loadWatchProgress();
@ -547,8 +553,7 @@ const VideoPlayer: React.FC = () => {
clearInterval(progressSaveInterval);
}
// HEATING FIX: Increase sync interval to 15 seconds to reduce CPU load
const syncInterval = 15000; // 15 seconds to prevent heating
const syncInterval = 20000; // 20s to further reduce CPU load
const interval = setInterval(() => {
saveWatchProgress();
@ -560,7 +565,7 @@ const VideoPlayer: React.FC = () => {
setProgressSaveInterval(null);
};
}
}, [id, type, paused, currentTime, duration]);
}, [id, type, paused, duration]);
useEffect(() => {
return () => {
@ -597,7 +602,7 @@ const VideoPlayer: React.FC = () => {
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
if (vlcRef.current && duration > 0 && !isSeeking.current) {
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
if (__DEV__) logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
}
isSeeking.current = true;
@ -799,7 +804,7 @@ const VideoPlayer: React.FC = () => {
NativeModules.StatusBarManager.setHidden(true);
}
} catch (error) {
console.log('Immersive mode error:', error);
if (__DEV__) console.log('Immersive mode error:', error);
}
}
};

View file

@ -32,13 +32,13 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
if (u) {
try {
await syncService.migrateLocalScopeToUser();
// Small yield to event loop
await new Promise(resolve => setTimeout(resolve, 50));
// Longer yield to event loop to reduce CPU pressure
await new Promise(resolve => setTimeout(resolve, 100));
await syncService.subscribeRealtime();
await new Promise(resolve => setTimeout(resolve, 50));
await new Promise(resolve => setTimeout(resolve, 100));
// Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull();
await new Promise(resolve => setTimeout(resolve, 50));
await new Promise(resolve => setTimeout(resolve, 100));
await syncService.fullPush();
} catch {}
}

View file

@ -181,7 +181,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
if (theme) setCurrentThemeState(theme);
}
} catch (error) {
console.error('Failed to load themes:', error);
if (__DEV__) console.error('Failed to load themes:', error);
}
};
loadThemes();
@ -241,7 +241,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
await AsyncStorage.setItem(CURRENT_THEME_KEY, id);
// Do not emit global settings sync for themes
} catch (error) {
console.error('Failed to add custom theme:', error);
if (__DEV__) console.error('Failed to add custom theme:', error);
}
};
@ -279,7 +279,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
}
// Do not emit global settings sync for themes
} catch (error) {
console.error('Failed to update custom theme:', error);
if (__DEV__) console.error('Failed to update custom theme:', error);
}
};
@ -316,7 +316,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
}
// Do not emit global settings sync for themes
} catch (error) {
console.error('Failed to delete custom theme:', error);
if (__DEV__) console.error('Failed to delete custom theme:', error);
}
};

View file

@ -142,7 +142,7 @@ const selectBestColor = (result: ImageColorsResult): string => {
export const preloadDominantColor = async (imageUri: string | null) => {
if (!imageUri || colorCache.has(imageUri)) return;
console.log('[useDominantColor] Preloading color for URI:', imageUri);
if (__DEV__) console.log('[useDominantColor] Preloading color for URI:', imageUri);
try {
// Fast first-pass: prioritize speed to avoid visible delay
@ -157,7 +157,7 @@ export const preloadDominantColor = async (imageUri: string | null) => {
const extractedColor = selectBestColor(result);
colorCache.set(imageUri, extractedColor);
} catch (err) {
console.warn('[preloadDominantColor] Failed to preload color:', err);
if (__DEV__) console.warn('[preloadDominantColor] Failed to preload color:', err);
colorCache.set(imageUri, '#1a1a1a');
}
};
@ -236,7 +236,7 @@ export const useDominantColor = (imageUri: string | null): DominantColorResult =
// Ignore refine errors silently
});
} catch (err) {
console.warn('[useDominantColor] Failed to extract color:', err);
if (__DEV__) console.warn('[useDominantColor] Failed to extract color:', err);
setError(err instanceof Error ? err.message : 'Failed to extract color');
const fallbackColor = '#1a1a1a';
colorCache.set(uri, fallbackColor); // Cache fallback to avoid repeated failures

View file

@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { StreamingContent } from '../types/metadata';
import { StreamingContent } from '../services/catalogService';
import { catalogService } from '../services/catalogService';
const LEGACY_LIBRARY_STORAGE_KEY = 'stremio-library';
@ -36,7 +36,7 @@ export const useLibrary = () => {
}
}
} catch (error) {
console.error('Error loading library items:', error);
if (__DEV__) console.error('Error loading library items:', error);
} finally {
setLoading(false);
}
@ -56,7 +56,7 @@ export const useLibrary = () => {
// keep legacy for backward-compat
await AsyncStorage.setItem(LEGACY_LIBRARY_STORAGE_KEY, JSON.stringify(itemsObject));
} catch (error) {
console.error('Error saving library items:', error);
if (__DEV__) console.error('Error saving library items:', error);
}
}, []);
@ -66,7 +66,7 @@ export const useLibrary = () => {
await catalogService.addToLibrary({ ...item, inLibrary: true });
return true;
} catch (e) {
console.error('Error adding to library via catalogService:', e);
if (__DEV__) console.error('Error adding to library via catalogService:', e);
// Fallback local write
const updatedItems = [...libraryItems, { ...item, inLibrary: true }];
setLibraryItems(updatedItems);
@ -82,7 +82,7 @@ export const useLibrary = () => {
await catalogService.removeFromLibrary(type, id);
return true;
} catch (e) {
console.error('Error removing from library via catalogService:', e);
if (__DEV__) console.error('Error removing from library via catalogService:', e);
// Fallback local write
const updatedItems = libraryItems.filter(item => item.id !== id);
setLibraryItems(updatedItems);
@ -115,7 +115,7 @@ export const useLibrary = () => {
// Subscribe to catalogService library updates
useEffect(() => {
const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => {
console.log('[useLibrary] Received library update from catalogService:', items.length, 'items');
if (__DEV__) console.log('[useLibrary] Received library update from catalogService:', items.length, 'items');
setLibraryItems(items);
setLoading(false);
});

View file

@ -143,7 +143,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const logPrefix = isEpisode ? 'loadEpisodeStreams' : 'loadStreams';
const sourceName = 'stremio';
logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`);
if (__DEV__) logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`);
try {
await stremioService.getStreams(type, id,
@ -180,12 +180,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (error) {
logger.error(`❌ [${logPrefix}:${sourceName}] Error for addon ${addonName} (${addonId}):`, error);
} else if (streams && addonId && addonName) {
logger.log(`✅ [${logPrefix}:${sourceName}] Received ${streams.length} streams from ${addonName} (${addonId}) after ${processTime}ms`);
if (__DEV__) logger.log(`✅ [${logPrefix}:${sourceName}] Received ${streams.length} streams from ${addonName} (${addonId}) after ${processTime}ms`);
if (streams.length > 0) {
// Use the streams directly as they are already processed by stremioService
const updateState = (prevState: GroupedStreams): GroupedStreams => {
logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
if (__DEV__) logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
return {
...prevState,
[addonId]: {
@ -205,16 +205,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setLoadingStreams(false);
}
} else {
logger.log(`🤷 [${logPrefix}:${sourceName}] No streams found for addon ${addonName} (${addonId})`);
if (__DEV__) logger.log(`🤷 [${logPrefix}:${sourceName}] No streams found for addon ${addonName} (${addonId})`);
}
} else {
// Handle case where callback provides null streams without error (e.g., empty results)
logger.log(`🏁 [${logPrefix}:${sourceName}] Finished fetching for addon ${addonName} (${addonId}) with no streams after ${processTime}ms`);
if (__DEV__) logger.log(`🏁 [${logPrefix}:${sourceName}] Finished fetching for addon ${addonName} (${addonId}) with no streams after ${processTime}ms`);
}
}
);
// The function now returns void, just await to let callbacks fire
logger.log(`🏁 [${logPrefix}:${sourceName}] Stremio fetching process initiated`);
if (__DEV__) logger.log(`🏁 [${logPrefix}:${sourceName}] Stremio fetching process initiated`);
} catch (error) {
// Catch errors from the initial call to getStreams (e.g., initialization errors)
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
@ -225,13 +225,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
};
const loadCast = async () => {
logger.log('[loadCast] Starting cast fetch for:', id);
if (__DEV__) logger.log('[loadCast] Starting cast fetch for:', id);
setLoadingCast(true);
try {
// Check cache first
const cachedCast = cacheService.getCast(id, type);
if (cachedCast) {
logger.log('[loadCast] Using cached cast data');
if (__DEV__) logger.log('[loadCast] Using cached cast data');
setCast(cachedCast);
setLoadingCast(false);
return;
@ -240,7 +240,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Handle TMDB IDs
if (id.startsWith('tmdb:')) {
const tmdbId = id.split(':')[1];
logger.log('[loadCast] Using TMDB ID directly:', tmdbId);
if (__DEV__) logger.log('[loadCast] Using TMDB ID directly:', tmdbId);
const castData = await tmdbService.getCredits(parseInt(tmdbId), type);
if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({
@ -249,7 +249,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
character: actor.character,
profile_path: actor.profile_path
}));
logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`);
if (__DEV__) logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`);
setCast(formattedCast);
cacheService.setCast(id, type, formattedCast);
setLoadingCast(false);
@ -260,12 +260,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Handle IMDb IDs or convert to TMDB ID
let tmdbId;
if (id.startsWith('tt')) {
logger.log('[loadCast] Converting IMDb ID to TMDB ID');
if (__DEV__) logger.log('[loadCast] Converting IMDb ID to TMDB ID');
tmdbId = await tmdbService.findTMDBIdByIMDB(id);
}
if (tmdbId) {
logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId);
if (__DEV__) logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId);
const castData = await tmdbService.getCredits(tmdbId, type);
if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({
@ -274,12 +274,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
character: actor.character,
profile_path: actor.profile_path
}));
logger.log(`[loadCast] Found ${formattedCast.length} cast members`);
if (__DEV__) logger.log(`[loadCast] Found ${formattedCast.length} cast members`);
setCast(formattedCast);
cacheService.setCast(id, type, formattedCast);
}
} else {
logger.warn('[loadCast] Could not find TMDB ID for cast fetch');
if (__DEV__) logger.warn('[loadCast] Could not find TMDB ID for cast fetch');
}
} catch (error) {
logger.error('[loadCast] Failed to load cast:', error);
@ -325,7 +325,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const tmdbId = id.split(':')[1];
// For TMDB IDs, we need to handle metadata differently
if (type === 'movie') {
logger.log('Fetching movie details from TMDB for:', tmdbId);
if (__DEV__) logger.log('Fetching movie details from TMDB for:', tmdbId);
const movieDetails = await tmdbService.getMovieDetails(tmdbId);
if (movieDetails) {
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id;
@ -388,7 +388,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}
} else if (type === 'series') {
// Handle TV shows with TMDB IDs
logger.log('Fetching TV show details from TMDB for:', tmdbId);
if (__DEV__) logger.log('Fetching TV show details from TMDB for:', tmdbId);
try {
const showDetails = await tmdbService.getTVShowDetails(parseInt(tmdbId));
if (showDetails) {
@ -443,7 +443,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const logoUrl = await tmdbService.getTvShowImages(tmdbId);
if (logoUrl) {
formattedShow.logo = logoUrl;
logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
}
} catch (error) {
logger.error('Failed to fetch logo from TMDB:', error);
@ -455,7 +455,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Load series data (episodes)
setTmdbId(parseInt(tmdbId));
loadSeriesData().catch(console.error);
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
const isInLib = catalogService.getLibraryItems().some(item => item.id === id);
setInLibrary(isInLib);
@ -502,7 +502,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
throw new Error('Content not found');
}
} catch (error) {
console.error('Failed to load metadata:', error);
if (__DEV__) console.error('Failed to load metadata:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to load content';
setError(errorMessage);
@ -522,7 +522,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// First check if we have episode data from the addon
const addonVideos = metadata?.videos;
if (addonVideos && Array.isArray(addonVideos) && addonVideos.length > 0) {
logger.log(`🎬 Found ${addonVideos.length} episodes from addon metadata for ${metadata?.name || id}`);
if (__DEV__) logger.log(`🎬 Found ${addonVideos.length} episodes from addon metadata for ${metadata?.name || id}`);
// Group addon episodes by season
const groupedAddonEpisodes: GroupedEpisodes = {};
@ -562,7 +562,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
groupedAddonEpisodes[parseInt(season)].sort((a, b) => a.episode_number - b.episode_number);
});
logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`);
if (__DEV__) logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`);
// Fetch season posters from TMDB
try {
@ -582,7 +582,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}));
}
});
logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
if (__DEV__) logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
}
}
} catch (error) {
@ -697,7 +697,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
}
} catch (error) {
console.error('Failed to load episodes:', error);
if (__DEV__) console.error('Failed to load episodes:', error);
} finally {
setLoadingSeasons(false);
}
@ -724,7 +724,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const loadStreams = async () => {
const startTime = Date.now();
try {
console.log('🚀 [loadStreams] START - Loading streams for:', id);
if (__DEV__) console.log('🚀 [loadStreams] START - Loading streams for:', id);
updateLoadingState();
// Reset scraper tracking
@ -732,21 +732,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setActiveFetchingScrapers([]);
// Get TMDB ID for external sources and determine the correct ID for Stremio addons
console.log('🔍 [loadStreams] Getting TMDB ID for:', id);
if (__DEV__) console.log('🔍 [loadStreams] Getting TMDB ID for:', id);
let tmdbId;
let stremioId = id; // Default to original ID
if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1];
console.log('✅ [loadStreams] Using TMDB ID from ID:', tmdbId);
if (__DEV__) console.log('✅ [loadStreams] Using TMDB ID from ID:', tmdbId);
// Try to get IMDb ID from metadata first, then convert if needed
if (metadata?.imdb_id) {
stremioId = metadata.imdb_id;
console.log('✅ [loadStreams] Using IMDb ID from metadata for Stremio:', stremioId);
if (__DEV__) console.log('✅ [loadStreams] Using IMDb ID from metadata for Stremio:', stremioId);
} else if (imdbId) {
stremioId = imdbId;
console.log('✅ [loadStreams] Using stored IMDb ID for Stremio:', stremioId);
if (__DEV__) console.log('✅ [loadStreams] Using stored IMDb ID for Stremio:', stremioId);
} else {
// Convert TMDB ID to IMDb ID for Stremio addons (they expect IMDb format)
try {
@ -760,24 +760,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (externalIds?.imdb_id) {
stremioId = externalIds.imdb_id;
console.log('✅ [loadStreams] Converted TMDB to IMDb ID for Stremio:', stremioId);
if (__DEV__) console.log('✅ [loadStreams] Converted TMDB to IMDb ID for Stremio:', stremioId);
} else {
console.log('⚠️ [loadStreams] No IMDb ID found for TMDB ID, using original:', stremioId);
if (__DEV__) console.log('⚠️ [loadStreams] No IMDb ID found for TMDB ID, using original:', stremioId);
}
} catch (error) {
console.log('⚠️ [loadStreams] Failed to convert TMDB to IMDb, using original ID:', error);
if (__DEV__) console.log('⚠️ [loadStreams] Failed to convert TMDB to IMDb, using original ID:', error);
}
}
} else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio
stremioId = id;
console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
if (__DEV__) console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
if (__DEV__) console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
} else {
tmdbId = id;
stremioId = id;
console.log(' [loadStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
if (__DEV__) console.log(' [loadStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
}
// Initialize scraper tracking
@ -851,11 +851,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
} catch (error) {
console.error('Failed to initialize scraper tracking:', error);
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
}
// Start Stremio request using the converted ID format
console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
if (__DEV__) console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
processStremioSource(type, stremioId, false);
// Monitor scraper completion status instead of using fixed timeout
@ -881,7 +881,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}, 30000);
} catch (error) {
console.error('❌ [loadStreams] Failed to load streams:', error);
if (__DEV__) console.error('❌ [loadStreams] Failed to load streams:', error);
setError('Failed to load streams');
setLoadingStreams(false);
}
@ -890,7 +890,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const loadEpisodeStreams = async (episodeId: string) => {
const startTime = Date.now();
try {
console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId);
if (__DEV__) console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId);
updateEpisodeLoadingState();
// Reset scraper tracking for episodes
@ -968,28 +968,28 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
} catch (error) {
console.error('Failed to initialize episode scraper tracking:', error);
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
}
// Get TMDB ID for external sources and determine the correct ID for Stremio addons
console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
if (__DEV__) console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
let tmdbId;
let stremioEpisodeId = episodeId; // Default to original episode ID
if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1];
console.log('✅ [loadEpisodeStreams] Using TMDB ID from ID:', tmdbId);
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using TMDB ID from ID:', tmdbId);
// Try to get IMDb ID from metadata first, then convert if needed
if (metadata?.imdb_id) {
// Replace the series ID in episodeId with the IMDb ID
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${metadata.imdb_id}:${season}:${episode}`;
console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
} else if (imdbId) {
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${imdbId}:${season}:${episode}`;
console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
} else {
// Convert TMDB ID to IMDb ID for Stremio addons
try {
@ -998,33 +998,33 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (externalIds?.imdb_id) {
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${externalIds.imdb_id}:${season}:${episode}`;
console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
} else {
console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
}
} catch (error) {
console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using original episode ID:', error);
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using original episode ID:', error);
}
}
} else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio
console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
} else {
tmdbId = id;
console.log(' [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
if (__DEV__) console.log(' [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
}
// Extract episode info from the episodeId for logging
const [, season, episode] = episodeId.split(':');
const episodeQuery = `?s=${season}&e=${episode}`;
console.log(` [loadEpisodeStreams] Episode query: ${episodeQuery}`);
if (__DEV__) console.log(` [loadEpisodeStreams] Episode query: ${episodeQuery}`);
console.log('🔄 [loadEpisodeStreams] Starting stream requests');
if (__DEV__) console.log('🔄 [loadEpisodeStreams] Starting stream requests');
// Start Stremio request using the converted episode ID format
console.log('🎬 [loadEpisodeStreams] Using episode ID for Stremio addons:', stremioEpisodeId);
if (__DEV__) console.log('🎬 [loadEpisodeStreams] Using episode ID for Stremio addons:', stremioEpisodeId);
processStremioSource('series', stremioEpisodeId, true);
// Monitor scraper completion status instead of using fixed timeout
@ -1040,7 +1040,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
};
// Check completion less frequently to reduce CPU load
const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 2000);
const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 3000);
// Fallback timeout after 30 seconds
const episodeFallbackTimeout = setTimeout(() => {
@ -1050,7 +1050,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}, 30000);
} catch (error) {
console.error('❌ [loadEpisodeStreams] Failed to load episode streams:', error);
if (__DEV__) console.error('❌ [loadEpisodeStreams] Failed to load episode streams:', error);
setError('Failed to load episode streams');
setLoadingEpisodeStreams(false);
}
@ -1103,7 +1103,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => {
if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) {
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
loadSeriesData().catch(console.error);
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
}
}, [metadata?.videos, type]);
@ -1126,7 +1126,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setRecommendations(formattedRecommendations);
} catch (error) {
console.error('Failed to load recommendations:', error);
if (__DEV__) console.error('Failed to load recommendations:', error);
setRecommendations([]);
} finally {
setLoadingRecommendations(false);
@ -1141,24 +1141,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const tmdbService = TMDBService.getInstance();
const fetchedTmdbId = await tmdbService.extractTMDBIdFromStremioId(id);
if (fetchedTmdbId) {
console.log('[useMetadata] extracted TMDB id from content id', { id, fetchedTmdbId });
if (__DEV__) console.log('[useMetadata] extracted TMDB id from content id', { id, fetchedTmdbId });
setTmdbId(fetchedTmdbId);
// Fetch certification
const certification = await tmdbService.getCertification(type, fetchedTmdbId);
if (certification) {
console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
if (__DEV__) console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
setMetadata(prev => prev ? {
...prev,
certification
} : null);
} else {
console.warn('[useMetadata] certification not returned from TMDB (extract path)', { type, fetchedTmdbId });
if (__DEV__) console.warn('[useMetadata] certification not returned from TMDB (extract path)', { type, fetchedTmdbId });
}
} else {
console.warn('[useMetadata] Could not determine TMDB ID for recommendations / certification', { id });
if (__DEV__) console.warn('[useMetadata] Could not determine TMDB ID for recommendations / certification', { id });
}
} catch (error) {
console.error('[useMetadata] Error fetching TMDB ID (extract path):', error);
if (__DEV__) console.error('[useMetadata] Error fetching TMDB ID (extract path):', error);
}
}
};
@ -1168,7 +1168,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => {
if (tmdbId) {
console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
if (__DEV__) console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
loadRecommendations();
// Reset recommendations when tmdbId changes
return () => {
@ -1183,27 +1183,27 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const maybeAttachCertification = async () => {
try {
if (!metadata) {
console.warn('[useMetadata] skip certification attach: metadata not ready');
if (__DEV__) console.warn('[useMetadata] skip certification attach: metadata not ready');
return;
}
if (!tmdbId) {
console.warn('[useMetadata] skip certification attach: tmdbId not available yet');
if (__DEV__) console.warn('[useMetadata] skip certification attach: tmdbId not available yet');
return;
}
if ((metadata as any).certification) {
console.log('[useMetadata] certification already present on metadata; skipping fetch');
if (__DEV__) console.log('[useMetadata] certification already present on metadata; skipping fetch');
return;
}
const tmdbSvc = TMDBService.getInstance();
const cert = await tmdbSvc.getCertification(type, tmdbId);
if (cert) {
console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
if (__DEV__) console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
setMetadata(prev => prev ? { ...prev, certification: cert } : prev);
} else {
console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
if (__DEV__) console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
}
} catch (err) {
console.error('[useMetadata] error attaching certification', err);
if (__DEV__) console.error('[useMetadata] error attaching certification', err);
}
};
maybeAttachCertification();

View file

@ -89,7 +89,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
});
} catch (error) {
// Silently handle any animation errors
console.warn('Animation error in enterAnimations:', error);
if (__DEV__) console.warn('Animation error in enterAnimations:', error);
}
};
@ -97,7 +97,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
try {
runOnUI(enterAnimations)();
} catch (error) {
console.warn('Failed to run enter animations:', error);
if (__DEV__) console.warn('Failed to run enter animations:', error);
}
}, []);
@ -114,14 +114,14 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
easing: easings.fast
});
} catch (error) {
console.warn('Animation error in updateProgress:', error);
if (__DEV__) console.warn('Animation error in updateProgress:', error);
}
};
try {
runOnUI(updateProgress)();
} catch (error) {
console.warn('Failed to run progress animation:', error);
if (__DEV__) console.warn('Failed to run progress animation:', error);
}
}, [watchProgress]);
@ -140,7 +140,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
cancelAnimation(headerProgress);
cancelAnimation(staticHeaderElementsY);
} catch (error) {
console.warn('Error canceling animations:', error);
if (__DEV__) console.warn('Error canceling animations:', error);
}
};
}, []);
@ -166,7 +166,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
});
}
} catch (error) {
console.warn('Animation error in scroll handler:', error);
if (__DEV__) console.warn('Animation error in scroll handler:', error);
}
},
});

View file

@ -172,7 +172,7 @@ export const useSettings = () => {
if (merged) setSettings({ ...DEFAULT_SETTINGS, ...merged });
else setSettings(DEFAULT_SETTINGS);
} catch (error) {
console.error('Failed to load settings:', error);
if (__DEV__) console.error('Failed to load settings:', error);
// Fallback to default settings on error
setSettings(DEFAULT_SETTINGS);
}
@ -195,18 +195,18 @@ export const useSettings = () => {
// Ensure a current scope exists to avoid future loads missing the chosen scope
await AsyncStorage.setItem('@user:current', scope);
setSettings(newSettings);
console.log(`Setting updated: ${key}`, value);
if (__DEV__) console.log(`Setting updated: ${key}`, value);
// Notify all subscribers that settings have changed (if requested)
if (emitEvent) {
console.log('Emitting settings change event');
if (__DEV__) console.log('Emitting settings change event');
settingsEmitter.emit();
}
// If authenticated, push settings to server to prevent overwrite on next pull
try { syncService.pushSettings(); } catch {}
} catch (error) {
console.error('Failed to save settings:', error);
if (__DEV__) console.error('Failed to save settings:', error);
}
}, [settings]);

View file

@ -1,17 +1,8 @@
import { useState, useEffect, useCallback } from 'react';
import { Alert } from 'react-native';
import UpdateService from '../services/updateService';
import UpdateService, { UpdateInfo } from '../services/updateService';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface UpdateInfo {
isAvailable: boolean;
manifest?: {
id: string;
version?: string;
description?: string;
};
}
interface UseUpdatePopupReturn {
showUpdatePopup: boolean;
updateInfo: UpdateInfo;
@ -24,6 +15,7 @@ interface UseUpdatePopupReturn {
const UPDATE_POPUP_STORAGE_KEY = '@update_popup_dismissed';
const UPDATE_LATER_STORAGE_KEY = '@update_later_timestamp';
const UPDATE_LAST_CHECK_TS_KEY = '@update_last_check_ts';
export const useUpdatePopup = (): UseUpdatePopupReturn => {
const [showUpdatePopup, setShowUpdatePopup] = useState(false);
@ -59,7 +51,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setShowUpdatePopup(true);
}
} catch (error) {
console.error('Error checking for updates:', error);
if (__DEV__) console.error('Error checking for updates:', error);
// Don't show popup on error, just log it
}
}, [updateInfo.manifest?.id]);
@ -102,7 +94,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setShowUpdatePopup(true);
}
} catch (error) {
console.error('Error installing update:', error);
if (__DEV__) console.error('Error installing update:', error);
Alert.alert(
'Update Error',
'An error occurred while installing the update. Please try again later.'
@ -120,7 +112,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
await AsyncStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
setShowUpdatePopup(false);
} catch (error) {
console.error('Error storing update later preference:', error);
if (__DEV__) console.error('Error storing update later preference:', error);
setShowUpdatePopup(false);
}
}, []);
@ -134,7 +126,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
}
setShowUpdatePopup(false);
} catch (error) {
console.error('Error storing dismiss preference:', error);
if (__DEV__) console.error('Error storing dismiss preference:', error);
setShowUpdatePopup(false);
}
}, [updateInfo.manifest?.id]);
@ -143,7 +135,21 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
useEffect(() => {
// Add a small delay to ensure the app is fully loaded
const timer = setTimeout(() => {
checkForUpdates();
(async () => {
try {
const lastCheckTs = await AsyncStorage.getItem(UPDATE_LAST_CHECK_TS_KEY);
const last = lastCheckTs ? parseInt(lastCheckTs, 10) : 0;
const now = Date.now();
const twentyFourHours = 24 * 60 * 60 * 1000;
if (now - last < twentyFourHours) {
return; // Throttle: only auto-check once per 24h
}
await checkForUpdates();
await AsyncStorage.setItem(UPDATE_LAST_CHECK_TS_KEY, String(now));
} catch {
// ignore
}
})();
}, 2000); // 2 second delay
return () => clearTimeout(timer);

View file

@ -144,7 +144,7 @@ const CastMoviesScreen: React.FC = () => {
setMovies(allCredits);
}
} catch (error) {
console.error('Error fetching cast credits:', error);
if (__DEV__) console.error('Error fetching cast credits:', error);
} finally {
setLoading(false);
}
@ -206,8 +206,9 @@ const CastMoviesScreen: React.FC = () => {
}, [displayLimit, filteredAndSortedMovies.length, isLoadingMore]);
const handleMoviePress = async (movie: CastMovie) => {
console.log('=== CastMoviesScreen: Movie Press ===');
console.log('Movie data:', {
if (__DEV__) {
console.log('=== CastMoviesScreen: Movie Press ===');
console.log('Movie data:', {
id: movie.id,
title: movie.title,
media_type: movie.media_type,
@ -219,15 +220,15 @@ const CastMoviesScreen: React.FC = () => {
});
try {
console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString());
if (__DEV__) console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString());
// Get Stremio ID using catalogService
const stremioId = await catalogService.getStremioId(movie.media_type, movie.id.toString());
console.log('Stremio ID result:', stremioId);
if (__DEV__) console.log('Stremio ID result:', stremioId);
if (stremioId) {
console.log('Successfully found Stremio ID, navigating to Metadata with:', {
if (__DEV__) console.log('Successfully found Stremio ID, navigating to Metadata with:', {
id: stremioId,
type: movie.media_type
});
@ -235,7 +236,7 @@ const CastMoviesScreen: React.FC = () => {
// Convert TMDB media type to Stremio media type
const stremioType = movie.media_type === 'tv' ? 'series' : movie.media_type;
console.log('Navigating with Stremio type conversion:', {
if (__DEV__) console.log('Navigating with Stremio type conversion:', {
originalType: movie.media_type,
stremioType: stremioType,
id: stremioId
@ -248,15 +249,17 @@ const CastMoviesScreen: React.FC = () => {
})
);
} else {
console.warn('Stremio ID is null/undefined for movie:', movie.title);
if (__DEV__) console.warn('Stremio ID is null/undefined for movie:', movie.title);
throw new Error('Could not find Stremio ID');
}
} catch (error: any) {
console.error('=== Error in handleMoviePress ===');
console.error('Movie:', movie.title);
console.error('Error details:', error);
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
if (__DEV__) {
console.error('=== Error in handleMoviePress ===');
console.error('Movie:', movie.title);
console.error('Error details:', error);
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
}
Alert.alert(
'Error',

View file

@ -119,7 +119,7 @@ const HeroCatalogsScreen: React.FC = () => {
setCatalogs(catalogItems);
} catch (error) {
console.error('Failed to load catalogs:', error);
if (__DEV__) console.error('Failed to load catalogs:', error);
Alert.alert('Error', 'Failed to load catalogs');
} finally {
setLoading(false);

View file

@ -158,7 +158,7 @@ const HomeScreen = () => {
let catalogIndex = 0;
// Limit concurrent catalog loading to prevent overwhelming the system
const MAX_CONCURRENT_CATALOGS = 2; // Very low concurrency to reduce heating
const MAX_CONCURRENT_CATALOGS = 1; // Single catalog at a time to minimize heating
let activeCatalogLoads = 0;
const catalogQueue: (() => Promise<void>)[] = [];
@ -170,7 +170,7 @@ const HomeScreen = () => {
catalogLoader().finally(async () => {
activeCatalogLoads--;
// Yield to event loop to avoid JS thread starvation and reduce heating
await new Promise(resolve => setTimeout(resolve, 50));
await new Promise(resolve => setTimeout(resolve, 100));
processCatalogQueue(); // Process next in queue
});
}
@ -257,7 +257,7 @@ const HomeScreen = () => {
});
}
} catch (error) {
console.error(`[HomeScreen] Failed to load ${catalog.name} from ${addon.name}:`, error);
if (__DEV__) console.error(`[HomeScreen] Failed to load ${catalog.name} from ${addon.name}:`, error);
} finally {
setLoadedCatalogCount(prev => {
const next = prev + 1;
@ -285,7 +285,7 @@ const HomeScreen = () => {
// Start processing the catalog queue (parallel fetching continues in background)
processCatalogQueue();
} catch (error) {
console.error('[HomeScreen] Error in progressive catalog loading:', error);
if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error);
setCatalogsLoading(false);
}
}, []);
@ -414,7 +414,7 @@ const HomeScreen = () => {
try {
ExpoImage.clearMemoryCache();
} catch (error) {
console.warn('Failed to clear image cache:', error);
if (__DEV__) console.warn('Failed to clear image cache:', error);
}
};
}, [currentTheme.colors.darkBackground]);
@ -527,7 +527,7 @@ const HomeScreen = () => {
setHasContinueWatching(hasContent);
} catch (error) {
console.error('[HomeScreen] Error refreshing continue watching:', error);
if (__DEV__) console.error('[HomeScreen] Error refreshing continue watching:', error);
setHasContinueWatching(false);
}
}

View file

@ -605,7 +605,7 @@ const LogoSourceSettings = () => {
try {
await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId);
} catch (e) {
console.error('Error saving selected show:', e);
if (__DEV__) console.error('Error saving selected show:', e);
}
};
@ -621,7 +621,7 @@ const LogoSourceSettings = () => {
}
}
} catch (e) {
console.error('Error loading selected show:', e);
if (__DEV__) console.error('Error loading selected show:', e);
}
};

View file

@ -192,7 +192,7 @@ const MetadataScreen: React.FC = () => {
// Debug logging for color extraction timing
useEffect(() => {
if (__DEV__ && heroImageUri && dominantColor) {
console.log('[MetadataScreen] Dynamic background color:', {
if (__DEV__) console.log('[MetadataScreen] Dynamic background color:', {
dominantColor,
fallback: currentTheme.colors.darkBackground,
finalColor: dynamicBackgroundColor,
@ -242,7 +242,7 @@ const MetadataScreen: React.FC = () => {
const isAuthenticated = await traktService.isAuthenticated();
if (!isAuthenticated) {
console.log(`[MetadataScreen] Not authenticated with Trakt`);
if (__DEV__) console.log(`[MetadataScreen] Not authenticated with Trakt`);
return;
}
@ -283,7 +283,7 @@ const MetadataScreen: React.FC = () => {
}
} catch (error) {
console.error(`[MetadataScreen] Failed to fetch Trakt progress:`, error);
if (__DEV__) console.error(`[MetadataScreen] Failed to fetch Trakt progress:`, error);
}
}, [shouldLoadSecondaryData, metadata, id, type]);
@ -315,7 +315,7 @@ const MetadataScreen: React.FC = () => {
const timer = setTimeout(() => {
const renderTime = Date.now() - startTime;
if (renderTime > 100) {
console.warn(`[MetadataScreen] Slow render detected: ${renderTime}ms for ${metadata.name}`);
if (__DEV__) console.warn(`[MetadataScreen] Slow render detected: ${renderTime}ms for ${metadata.name}`);
}
}, 0);
return () => clearTimeout(timer);
@ -448,7 +448,7 @@ const MetadataScreen: React.FC = () => {
const handleEpisodeSelect = useCallback((episode: Episode) => {
if (!isScreenFocused) return;
console.log('[MetadataScreen] Selected Episode:', episode.episode_number, episode.season_number);
if (__DEV__) console.log('[MetadataScreen] Selected Episode:', episode.episode_number, episode.season_number);
const episodeId = episode.stremioId || `${id}:${episode.season_number}:${episode.episode_number}`;
// Optimize navigation with requestAnimationFrame

View file

@ -117,7 +117,7 @@ const OnboardingScreen = () => {
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
}
} catch (error) {
console.error('Error saving onboarding status:', error);
if (__DEV__) console.error('Error saving onboarding status:', error);
navigation.reset({ index: 0, routes: [{ name: user ? 'MainTabs' : 'Account', params: !user ? ({ fromOnboarding: true } as any) : undefined }] });
}
};

View file

@ -13,7 +13,7 @@ import {
Modal
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { MaterialIcons } from '@expo/vector-icons';
import { useTheme } from '../contexts/ThemeContext';
import { useTraktContext } from '../contexts/TraktContext';
import AsyncStorage from '@react-native-async-storage/async-storage';
@ -58,7 +58,7 @@ const ProfilesScreen: React.FC = () => {
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify([defaultProfile]));
}
} catch (error) {
console.error('Error loading profiles:', error);
if (__DEV__) console.error('Error loading profiles:', error);
Alert.alert('Error', 'Failed to load profiles');
} finally {
setIsLoading(false);
@ -84,7 +84,7 @@ const ProfilesScreen: React.FC = () => {
try {
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
} catch (error) {
console.error('Error saving profiles:', error);
if (__DEV__) console.error('Error saving profiles:', error);
Alert.alert('Error', 'Failed to save profiles');
}
}, []);

View file

@ -244,7 +244,7 @@ const SettingsScreen: React.FC = () => {
// Refresh auth status
if (isAuthenticated || userProfile) {
// Just to be cautious, log the current state
console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
if (__DEV__) console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
}
refreshAuthStatus();
});
@ -291,7 +291,7 @@ const SettingsScreen: React.FC = () => {
setMdblistKeySet(!!mdblistKey);
} catch (error) {
console.error('Error loading settings data:', error);
if (__DEV__) console.error('Error loading settings data:', error);
}
}, []);
@ -343,7 +343,7 @@ const SettingsScreen: React.FC = () => {
Alert.alert("Success", "MDBList cache has been cleared.");
} catch (error) {
Alert.alert("Error", "Could not clear MDBList cache.");
console.error('Error clearing MDBList cache:', error);
if (__DEV__) console.error('Error clearing MDBList cache:', error);
}
}
}

View file

@ -403,7 +403,7 @@ export const StreamsScreen = () => {
}, []);
useEffect(() => {
console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail);
if (__DEV__) console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail);
}, [episodeThumbnail]);
// Pause trailer when StreamsScreen is opened
@ -1081,29 +1081,29 @@ export const StreamsScreen = () => {
return;
}
console.log(`Attempting to open stream in ${settings.preferredPlayer}`);
if (__DEV__) console.log(`Attempting to open stream in ${settings.preferredPlayer}`);
// Try each URL format in sequence
const tryNextUrl = (index: number) => {
if (index >= externalPlayerUrls.length) {
console.log(`All ${settings.preferredPlayer} formats failed, falling back to direct URL`);
if (__DEV__) console.log(`All ${settings.preferredPlayer} formats failed, falling back to direct URL`);
// Try direct URL as last resort
Linking.openURL(stream.url)
.then(() => console.log('Opened with direct URL'))
.then(() => { if (__DEV__) console.log('Opened with direct URL'); })
.catch(() => {
console.log('Direct URL failed, falling back to built-in player');
if (__DEV__) console.log('Direct URL failed, falling back to built-in player');
navigateToPlayer(stream);
});
return;
}
const url = externalPlayerUrls[index];
console.log(`Trying ${settings.preferredPlayer} URL format ${index + 1}: ${url}`);
if (__DEV__) console.log(`Trying ${settings.preferredPlayer} URL format ${index + 1}: ${url}`);
Linking.openURL(url)
.then(() => console.log(`Successfully opened stream with ${settings.preferredPlayer} format ${index + 1}`))
.then(() => { if (__DEV__) console.log(`Successfully opened stream with ${settings.preferredPlayer} format ${index + 1}`); })
.catch(err => {
console.log(`Format ${index + 1} failed: ${err.message}`, err);
if (__DEV__) console.log(`Format ${index + 1} failed: ${err.message}`, err);
tryNextUrl(index + 1);
});
};
@ -1112,7 +1112,7 @@ export const StreamsScreen = () => {
tryNextUrl(0);
} catch (error) {
console.error(`Error with ${settings.preferredPlayer}:`, error);
if (__DEV__) console.error(`Error with ${settings.preferredPlayer}:`, error);
// Fallback to the built-in player
navigateToPlayer(stream);
}
@ -1120,18 +1120,18 @@ export const StreamsScreen = () => {
// For Android with external player preference
else if (Platform.OS === 'android' && settings.useExternalPlayer) {
try {
console.log('Opening stream with Android native app chooser');
if (__DEV__) console.log('Opening stream with Android native app chooser');
// For Android, determine if the URL is a direct http/https URL or a magnet link
const isMagnet = stream.url.startsWith('magnet:');
if (isMagnet) {
// For magnet links, open directly which will trigger the torrent app chooser
console.log('Opening magnet link directly');
if (__DEV__) console.log('Opening magnet link directly');
Linking.openURL(stream.url)
.then(() => console.log('Successfully opened magnet link'))
.catch(err => {
console.error('Failed to open magnet link:', err);
.then(() => { if (__DEV__) console.log('Successfully opened magnet link'); })
.catch(err => {
if (__DEV__) console.error('Failed to open magnet link:', err);
// No good fallback for magnet links
navigateToPlayer(stream);
});
@ -1145,12 +1145,12 @@ export const StreamsScreen = () => {
});
if (!success) {
console.log('VideoPlayerService failed, falling back to built-in player');
if (__DEV__) console.log('VideoPlayerService failed, falling back to built-in player');
navigateToPlayer(stream);
}
}
} catch (error) {
console.error('Error with external player:', error);
if (__DEV__) console.error('Error with external player:', error);
// Fallback to the built-in player
navigateToPlayer(stream);
}
@ -1161,7 +1161,7 @@ export const StreamsScreen = () => {
}
}
} catch (error) {
console.error('Error in handleStreamPress:', error);
if (__DEV__) console.error('Error in handleStreamPress:', error);
// Final fallback: Use built-in player
navigateToPlayer(stream);
}

View file

@ -99,7 +99,7 @@ const UpdateScreen: React.FC = () => {
setLastOperation('No updates available');
}
} catch (error) {
console.error('Error checking for updates:', error);
if (__DEV__) console.error('Error checking for updates:', error);
setUpdateStatus('error');
setLastOperation(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
Alert.alert('Error', 'Failed to check for updates');
@ -144,7 +144,7 @@ const UpdateScreen: React.FC = () => {
Alert.alert('No Update', 'No update available to install');
}
} catch (error) {
console.error('Error installing update:', error);
if (__DEV__) console.error('Error installing update:', error);
setUpdateStatus('error');
setLastOperation(`Installation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
Alert.alert('Error', 'Failed to install update');
@ -226,7 +226,7 @@ const UpdateScreen: React.FC = () => {
setLastOperation('Update server is not reachable');
}
} catch (error) {
console.error('Error testing connectivity:', error);
if (__DEV__) console.error('Error testing connectivity:', error);
setLastOperation(`Connectivity test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
const logs = UpdateService.getLogs();
setLogs(logs);
@ -241,7 +241,7 @@ const UpdateScreen: React.FC = () => {
setLogs(logs);
setLastOperation('Asset URL testing completed');
} catch (error) {
console.error('Error testing asset URLs:', error);
if (__DEV__) console.error('Error testing asset URLs:', error);
setLastOperation(`Asset URL test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
const logs = UpdateService.getLogs();
setLogs(logs);

View file

@ -137,7 +137,7 @@ class SyncService {
logger.log('[Sync] fullPull (debounced) start');
this.fullPull()
.then(() => logger.log('[Sync] fullPull (debounced) done'))
.catch((e) => console.warn('[Sync] fullPull (debounced) error', e));
.catch((e) => { if (__DEV__) console.warn('[Sync] fullPull (debounced) error', e); });
}, 300);
};
@ -168,7 +168,7 @@ class SyncService {
logger.log(`[Sync][rt] user_library ${payload.eventType} ${mediaType}:${mediaId}`);
}
} catch (e) {
console.warn('[Sync][rt] user_library handler error', e);
if (__DEV__) console.warn('[Sync][rt] user_library handler error', e);
} finally {
this.suppressLibraryPush = false;
}
@ -455,7 +455,7 @@ class SyncService {
.select('media_type, media_id, title, poster_url, year, deleted_at, updated_at')
.eq('user_id', userId);
if (error) {
console.warn('[SyncService] pull library error', error);
if (__DEV__) console.warn('[SyncService] pull library error', error);
return;
}
const obj: Record<string, any> = {};
@ -475,7 +475,7 @@ class SyncService {
await AsyncStorage.setItem('stremio-library', JSON.stringify(obj));
logger.log(`[Sync] pull user_library wrote items=${Object.keys(obj).length}`);
} catch (e) {
console.warn('[SyncService] pullLibrary exception', e);
if (__DEV__) console.warn('[SyncService] pullLibrary exception', e);
}
}
@ -516,12 +516,12 @@ class SyncService {
const { error: upErr } = await supabase
.from('user_library')
.upsert(rows, { onConflict: 'user_id,media_type,media_id' });
if (upErr) console.warn('[SyncService] push library upsert error', upErr);
if (upErr && __DEV__) console.warn('[SyncService] push library upsert error', upErr);
else await AsyncStorage.setItem(`@user:${user.id}:library_initialized`, 'true');
}
// No computed deletions; removals happen only via explicit user action (soft delete)
} catch (e) {
console.warn('[SyncService] pushLibrary exception', e);
if (__DEV__) console.warn('[SyncService] pushLibrary exception', e);
}
}
@ -540,9 +540,9 @@ class SyncService {
updated_at: new Date().toISOString(),
};
const { error } = await supabase.from('user_library').upsert(row, { onConflict: 'user_id,media_type,media_id' });
if (error) console.warn('[SyncService] pushLibraryAdd error', error);
if (error && __DEV__) console.warn('[SyncService] pushLibraryAdd error', error);
} catch (e) {
console.warn('[SyncService] pushLibraryAdd exception', e);
if (__DEV__) console.warn('[SyncService] pushLibraryAdd exception', e);
}
}
@ -556,9 +556,9 @@ class SyncService {
.eq('user_id', user.id)
.eq('media_type', type === 'movie' ? 'movie' : 'series')
.eq('media_id', id);
if (error) console.warn('[SyncService] pushLibraryRemove error', error);
if (error && __DEV__) console.warn('[SyncService] pushLibraryRemove error', error);
} catch (e) {
console.warn('[SyncService] pushLibraryRemove exception', e);
if (__DEV__) console.warn('[SyncService] pushLibraryRemove exception', e);
}
}
@ -570,7 +570,7 @@ class SyncService {
.eq('user_id', userId)
.order('position', { ascending: true });
if (addonsErr) {
console.warn('[SyncService] pull addons error', addonsErr);
if (__DEV__) console.warn('[SyncService] pull addons error', addonsErr);
return;
}
if (!(addons && Array.isArray(addons))) return;
@ -603,7 +603,7 @@ class SyncService {
manifest.id = a.addon_id;
map.set(a.addon_id, manifest);
} catch (e) {
console.warn('[SyncService] failed to fetch manifest for', a.addon_id, e);
if (__DEV__) console.warn('[SyncService] failed to fetch manifest for', a.addon_id, e);
}
}
@ -715,7 +715,7 @@ class SyncService {
const { error } = await supabase
.from('watch_progress')
.upsert(filteredRows, { onConflict: 'user_id,media_type,media_id,episode_id' });
if (error) console.warn('[SyncService] push watch_progress error', error);
if (error && __DEV__) console.warn('[SyncService] push watch_progress error', error);
else logger.log('[Sync] push watch_progress upsert ok');
}
} catch (e) {
@ -723,7 +723,7 @@ class SyncService {
const { error } = await supabase
.from('watch_progress')
.upsert(rows, { onConflict: 'user_id,media_type,media_id,episode_id' });
if (error) console.warn('[SyncService] push watch_progress error', error);
if (error && __DEV__) console.warn('[SyncService] push watch_progress error', error);
else logger.log('[Sync] push watch_progress upsert ok');
}
}
@ -742,9 +742,9 @@ class SyncService {
.eq('media_type', type)
.eq('media_id', id)
.eq('episode_id', episodeId || '');
if (error) console.warn('[SyncService] softDeleteWatchProgress error', error);
if (error && __DEV__) console.warn('[SyncService] softDeleteWatchProgress error', error);
} catch (e) {
console.warn('[SyncService] softDeleteWatchProgress exception', e);
if (__DEV__) console.warn('[SyncService] softDeleteWatchProgress exception', e);
}
}
@ -767,7 +767,7 @@ class SyncService {
app_settings: appSettings,
subtitle_settings: subtitleSettings,
});
if (error) console.warn('[SyncService] push settings error', error);
if (error && __DEV__) console.warn('[SyncService] push settings error', error);
else logger.log('[Sync] push user_settings ok');
}
@ -807,14 +807,14 @@ class SyncService {
.delete()
.eq('user_id', userId)
.in('addon_id', toDelete);
if (del.error) console.warn('[SyncService] delete addons error', del.error);
if (del.error && __DEV__) console.warn('[SyncService] delete addons error', del.error);
}
}
} catch (e) {
console.warn('[SyncService] deletion sync for addons failed', e);
if (__DEV__) console.warn('[SyncService] deletion sync for addons failed', e);
}
const { error } = await supabase.from('installed_addons').upsert(rows, { onConflict: 'user_id,addon_id' });
if (error) console.warn('[SyncService] push addons error', error);
if (error && __DEV__) console.warn('[SyncService] push addons error', error);
}
// Excluded: pushLocalScrapers (local scrapers are device-local only)

View file

@ -844,18 +844,20 @@ class CatalogService {
}
async getStremioId(type: string, tmdbId: string): Promise<string | null> {
console.log('=== CatalogService.getStremioId ===');
console.log('Input type:', type);
console.log('Input tmdbId:', tmdbId);
if (__DEV__) {
console.log('=== CatalogService.getStremioId ===');
console.log('Input type:', type);
console.log('Input tmdbId:', tmdbId);
}
try {
// For movies, use the tt prefix with IMDb ID
if (type === 'movie') {
console.log('Processing movie - fetching TMDB details...');
if (__DEV__) console.log('Processing movie - fetching TMDB details...');
const tmdbService = TMDBService.getInstance();
const movieDetails = await tmdbService.getMovieDetails(tmdbId);
console.log('Movie details result:', {
if (__DEV__) console.log('Movie details result:', {
id: movieDetails?.id,
title: movieDetails?.title,
imdb_id: movieDetails?.imdb_id,
@ -863,7 +865,7 @@ class CatalogService {
});
if (movieDetails?.imdb_id) {
console.log('Successfully found IMDb ID:', movieDetails.imdb_id);
if (__DEV__) console.log('Successfully found IMDb ID:', movieDetails.imdb_id);
return movieDetails.imdb_id;
} else {
console.warn('No IMDb ID found for movie:', tmdbId);
@ -872,25 +874,25 @@ class CatalogService {
}
// For TV shows, get the IMDb ID like movies
else if (type === 'tv' || type === 'series') {
console.log('Processing TV show - fetching TMDB details for IMDb ID...');
if (__DEV__) console.log('Processing TV show - fetching TMDB details for IMDb ID...');
const tmdbService = TMDBService.getInstance();
// Get TV show external IDs to find IMDb ID
const externalIds = await tmdbService.getShowExternalIds(parseInt(tmdbId));
console.log('TV show external IDs result:', {
if (__DEV__) console.log('TV show external IDs result:', {
tmdbId: tmdbId,
imdb_id: externalIds?.imdb_id,
hasImdbId: !!externalIds?.imdb_id
});
if (externalIds?.imdb_id) {
console.log('Successfully found IMDb ID for TV show:', externalIds.imdb_id);
if (__DEV__) console.log('Successfully found IMDb ID for TV show:', externalIds.imdb_id);
return externalIds.imdb_id;
} else {
console.warn('No IMDb ID found for TV show, falling back to kitsu format:', tmdbId);
const fallbackId = `kitsu:${tmdbId}`;
console.log('Generated fallback Stremio ID for TV:', fallbackId);
if (__DEV__) console.log('Generated fallback Stremio ID for TV:', fallbackId);
return fallbackId;
}
}
@ -899,11 +901,13 @@ class CatalogService {
return null;
}
} catch (error: any) {
console.error('=== Error in getStremioId ===');
console.error('Type:', type);
console.error('TMDB ID:', tmdbId);
console.error('Error details:', error);
console.error('Error message:', error.message);
if (__DEV__) {
console.error('=== Error in getStremioId ===');
console.error('Type:', type);
console.error('TMDB ID:', tmdbId);
console.error('Error details:', error);
console.error('Error message:', error.message);
}
logger.error('Error getting Stremio ID:', error);
return null;
}

View file

@ -1,5 +1,6 @@
import { logger } from '../utils/logger';
import { Image as ExpoImage } from 'expo-image';
import { AppState, AppStateStatus } from 'react-native';
interface CachedImage {
url: string;
@ -14,16 +15,20 @@ interface CachedImage {
class ImageCacheService {
private cache = new Map<string, CachedImage>();
private readonly CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
private readonly MAX_CACHE_SIZE = 100; // Increased maximum number of cached images
private readonly MAX_MEMORY_MB = 150; // Increased maximum memory usage in MB
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 30 minutes (less churn)
// Start cleanup interval every 15 minutes (more frequent cleanup to reduce memory pressure)
this.cleanupInterval = setInterval(() => {
this.performCleanup();
}, 30 * 60 * 1000);
}, 15 * 60 * 1000);
// Reduce memory footprint when app goes to background
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
}
/**
@ -255,8 +260,32 @@ class ImageCacheService {
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();

View file

@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState, AppStateStatus } from 'react-native';
import { logger } from '../utils/logger';
import { imageCacheService } from './imageCacheService';
@ -260,7 +261,7 @@ export class TraktService {
// Rate limiting
private lastApiCall: number = 0;
private readonly MIN_API_INTERVAL = 2000; // Minimum 2 seconds between API calls (reduce heating)
private readonly MIN_API_INTERVAL = 3000; // Minimum 3 seconds between API calls (further reduce heating)
private requestQueue: Array<() => Promise<any>> = [];
private isProcessingQueue: boolean = false;
@ -272,7 +273,7 @@ export class TraktService {
// Track currently watching sessions to avoid duplicate starts// Sync debouncing
private currentlyWatching: Set<string> = new Set();
private lastSyncTimes: Map<string, number> = new Map();
private readonly SYNC_DEBOUNCE_MS = 15000; // 15 seconds to align with player save interval
private readonly SYNC_DEBOUNCE_MS = 20000; // 20 seconds to further reduce API calls
// Debounce for stop calls
private lastStopCalls: Map<string, number> = new Map();
@ -285,6 +286,9 @@ export class TraktService {
// Increased cleanup interval from 5 minutes to 15 minutes to reduce heating
setInterval(() => this.cleanupOldStopCalls(), 15 * 60 * 1000); // Clean up every 15 minutes
// Add AppState cleanup to reduce memory pressure
AppState.addEventListener('change', this.handleAppStateChange);
// Load user settings
this.loadCompletionThreshold();
}
@ -1566,6 +1570,24 @@ export class TraktService {
return [];
}
}
/**
* Handle app state changes to reduce memory pressure
*/
private handleAppStateChange = (nextState: AppStateStatus) => {
if (nextState !== 'active') {
// Clear tracking maps to reduce memory pressure when app goes to background
this.scrobbledItems.clear();
this.scrobbledTimestamps.clear();
this.currentlyWatching.clear();
this.lastSyncTimes.clear();
this.lastStopCalls.clear();
// Clear request queue to prevent background processing
this.requestQueue = [];
this.isProcessingQueue = false;
}
};
}
// Export a singleton instance

View file

@ -38,8 +38,17 @@ export class UpdateService {
this.logs = this.logs.slice(0, this.MAX_LOGS);
}
// Always log to console - this will be visible in adb logcat for production builds
// Use different console methods for better filtering in logcat
// Console logging policy:
// - Development: log INFO/WARN/ERROR for visibility
// - Production: only log ERROR to reduce JS<->native bridge traffic and CPU usage
if (!__DEV__) {
if (level === 'ERROR') {
console.error(`[UpdateService] ${logEntry}`);
}
return;
}
// Development detailed logging
if (level === 'ERROR') {
console.error(`[UpdateService] ${logEntry}`);
} else if (level === 'WARN') {
@ -47,9 +56,11 @@ export class UpdateService {
} else {
console.log(`[UpdateService] ${logEntry}`);
}
// Also log with a consistent prefix for easy filtering
console.log(`UpdateService: ${logEntry}`);
// Additional prefixed line for easier filtering during development only
if (__DEV__) {
console.log(`UpdateService: ${logEntry}`);
}
}
/**