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": { "updates": {
"enabled": true, "enabled": true,
"checkAutomatically": "ON_LOAD", "checkAutomatically": "ON_ERROR",
"fallbackToCacheTimeout": 0, "fallbackToCacheTimeout": 30000,
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest" "url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
}, },
"runtimeVersion": "0.6.0-beta.10" "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 // Process episodes to identify dates with content
useEffect(() => { 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 } = {}; const dateMap: { [key: string]: boolean } = {};
episodes.forEach(episode => { 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); setDatesWithEpisodes(dateMap);
}, [episodes]); }, [episodes]);

View file

@ -174,7 +174,7 @@ const ContentItem = ({ item, onPress }: ContentItemProps) => {
setImageError(false); setImageError(false);
}} }}
onError={(error) => { 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 // Try fallback URL on first error
if (retryCount === 0 && item.poster && !item.poster.includes('metahub.space')) { if (retryCount === 0 && item.poster && !item.poster.includes('metahub.space')) {
setRetryCount(1); setRetryCount(1);

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native';
import { Image } from 'expo-image'; 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 { LinearGradient } from 'expo-linear-gradient';
import { FlashList, FlashListRef } from '@shopify/flash-list'; import { FlashList, FlashListRef } from '@shopify/flash-list';
import { useTheme } from '../../contexts/ThemeContext'; 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}`); const savedMode = await AsyncStorage.getItem(`season_view_mode_${metadata.id}`);
if (savedMode === 'text' || savedMode === 'posters') { if (savedMode === 'text' || savedMode === 'posters') {
setSeasonViewMode(savedMode); 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) { } 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); setSeasonViewMode(newMode);
if (metadata?.id) { if (metadata?.id) {
AsyncStorage.setItem(`season_view_mode_${metadata.id}`, newMode).catch(error => { 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; 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); const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b);
@ -357,7 +357,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
onPress={() => { onPress={() => {
const newMode = seasonViewMode === 'posters' ? 'text' : 'posters'; const newMode = seasonViewMode === 'posters' ? 'text' : 'posters';
updateViewMode(newMode); 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} activeOpacity={0.7}
> >
@ -398,7 +398,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
if (seasonViewMode === 'text') { if (seasonViewMode === 'text') {
// Text-only view // 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 ( return (
<View <View
key={season} key={season}
@ -430,7 +430,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
} }
// Poster view (current implementation) // 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 ( return (
<View <View
key={season} key={season}

View file

@ -313,7 +313,7 @@ const AndroidVideoPlayer: React.FC = () => {
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1)); const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
setZoomScale(newScale); setZoomScale(newScale);
if (DEBUG_MODE) { 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) { if (event.nativeEvent.state === State.END) {
setLastZoomScale(zoomScale); setLastZoomScale(zoomScale);
if (DEBUG_MODE) { 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); setZoomScale(targetZoom);
setLastZoomScale(targetZoom); setLastZoomScale(targetZoom);
if (DEBUG_MODE) { 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); setCustomVideoStyles(styles);
if (DEBUG_MODE) { 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]); }, [screenDimensions, videoAspectRatio]);
@ -439,7 +439,7 @@ const AndroidVideoPlayer: React.FC = () => {
// Fallback: ensure animation completes even if something goes wrong // Fallback: ensure animation completes even if something goes wrong
setTimeout(() => { setTimeout(() => {
if (!isOpeningAnimationComplete) { if (!isOpeningAnimationComplete) {
logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered'); if (__DEV__) logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered');
setIsOpeningAnimationComplete(true); setIsOpeningAnimationComplete(true);
openingScaleAnim.setValue(1); openingScaleAnim.setValue(1);
openingFadeAnim.setValue(1); openingFadeAnim.setValue(1);
@ -452,40 +452,40 @@ const AndroidVideoPlayer: React.FC = () => {
const loadWatchProgress = async () => { const loadWatchProgress = async () => {
if (id && type) { if (id && type) {
try { 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); const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress); if (__DEV__) logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress);
if (savedProgress) { if (savedProgress) {
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100; 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) { if (progressPercent < 85) {
setResumePosition(savedProgress.currentTime); setResumePosition(savedProgress.currentTime);
setSavedDuration(savedProgress.duration); 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) { if (appSettings.alwaysResume) {
// Only prepare auto-resume state and seek when AlwaysResume is enabled // Only prepare auto-resume state and seek when AlwaysResume is enabled
setInitialPosition(savedProgress.currentTime); setInitialPosition(savedProgress.currentTime);
initialSeekTargetRef.current = 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); seekToTime(savedProgress.currentTime);
} else { } else {
// Do not set initialPosition; start from beginning with no auto-seek // Do not set initialPosition; start from beginning with no auto-seek
setShowResumeOverlay(true); 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 { } 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 { } else {
logger.log(`[AndroidVideoPlayer] No saved progress found`); if (__DEV__) logger.log(`[AndroidVideoPlayer] No saved progress found`);
} }
} catch (error) { } catch (error) {
logger.error('[AndroidVideoPlayer] Error loading watch progress:', error); logger.error('[AndroidVideoPlayer] Error loading watch progress:', error);
} }
} else { } 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(); loadWatchProgress();
@ -545,7 +545,7 @@ const AndroidVideoPlayer: React.FC = () => {
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds)); const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
if (videoRef.current && duration > 0 && !isSeeking.current) { if (videoRef.current && duration > 0 && !isSeeking.current) {
if (DEBUG_MODE) { 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; isSeeking.current = true;
@ -779,7 +779,7 @@ const AndroidVideoPlayer: React.FC = () => {
NativeModules.StatusBarManager.setHidden(true); NativeModules.StatusBarManager.setHidden(true);
} }
} catch (error) { } 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 // - Xprime streams on any platform
// - Non-MKV files on iOS (unless forceVlc is set) // - Non-MKV files on iOS (unless forceVlc is set)
const shouldUseAndroidPlayer = Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc); const shouldUseAndroidPlayer = Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc);
logger.log('[VideoPlayer] Player selection:', { if (__DEV__) {
platform: Platform.OS, logger.log('[VideoPlayer] Player selection:', {
isXprimeStream, platform: Platform.OS,
isMkvFile, isXprimeStream,
forceVlc: !!forceVlc, isMkvFile,
selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer', forceVlc: !!forceVlc,
streamProvider, selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer',
uri streamProvider,
}); uri
});
}
if (shouldUseAndroidPlayer) { if (shouldUseAndroidPlayer) {
return <AndroidVideoPlayer />; return <AndroidVideoPlayer />;
} }
@ -335,7 +337,7 @@ const VideoPlayer: React.FC = () => {
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1)); const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
setZoomScale(newScale); setZoomScale(newScale);
if (DEBUG_MODE) { 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) { if (event.nativeEvent.state === State.END) {
setLastZoomScale(zoomScale); setLastZoomScale(zoomScale);
if (DEBUG_MODE) { 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); setZoomScale(targetZoom);
setLastZoomScale(targetZoom); setLastZoomScale(targetZoom);
if (DEBUG_MODE) { 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); setCustomVideoStyles(styles);
if (DEBUG_MODE) { 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]); }, [effectiveDimensions, videoAspectRatio]);
@ -377,7 +379,7 @@ const VideoPlayer: React.FC = () => {
const lockOrientation = async () => { const lockOrientation = async () => {
try { try {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
logger.log('[VideoPlayer] Locked to landscape orientation'); if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
} catch (error) { } catch (error) {
logger.warn('[VideoPlayer] Failed to lock orientation:', error); logger.warn('[VideoPlayer] Failed to lock orientation:', error);
} }
@ -483,32 +485,36 @@ const VideoPlayer: React.FC = () => {
const loadWatchProgress = async () => { const loadWatchProgress = async () => {
if (id && type) { if (id && type) {
try { 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); const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
logger.log(`[VideoPlayer] Saved progress:`, savedProgress); if (__DEV__) {
logger.log(`[VideoPlayer] Saved progress:`, savedProgress);
}
if (savedProgress) { if (savedProgress) {
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100; 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) { if (progressPercent < 85) {
setResumePosition(savedProgress.currentTime); setResumePosition(savedProgress.currentTime);
setSavedDuration(savedProgress.duration); 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) { if (appSettings.alwaysResume) {
// Only prepare auto-resume state and seek when AlwaysResume is enabled // Only prepare auto-resume state and seek when AlwaysResume is enabled
setInitialPosition(savedProgress.currentTime); setInitialPosition(savedProgress.currentTime);
initialSeekTargetRef.current = 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 // Seek immediately after load
seekToTime(savedProgress.currentTime); seekToTime(savedProgress.currentTime);
} else { } else {
// Do not set initialPosition; start from beginning with no auto-seek // Do not set initialPosition; start from beginning with no auto-seek
setShowResumeOverlay(true); 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 { } 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 { } else {
logger.log(`[VideoPlayer] No saved progress found`); logger.log(`[VideoPlayer] No saved progress found`);
@ -517,7 +523,7 @@ const VideoPlayer: React.FC = () => {
logger.error('[VideoPlayer] Error loading watch progress:', error); logger.error('[VideoPlayer] Error loading watch progress:', error);
} }
} else { } 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(); loadWatchProgress();
@ -547,8 +553,7 @@ const VideoPlayer: React.FC = () => {
clearInterval(progressSaveInterval); clearInterval(progressSaveInterval);
} }
// HEATING FIX: Increase sync interval to 15 seconds to reduce CPU load const syncInterval = 20000; // 20s to further reduce CPU load
const syncInterval = 15000; // 15 seconds to prevent heating
const interval = setInterval(() => { const interval = setInterval(() => {
saveWatchProgress(); saveWatchProgress();
@ -560,7 +565,7 @@ const VideoPlayer: React.FC = () => {
setProgressSaveInterval(null); setProgressSaveInterval(null);
}; };
} }
}, [id, type, paused, currentTime, duration]); }, [id, type, paused, duration]);
useEffect(() => { useEffect(() => {
return () => { return () => {
@ -597,7 +602,7 @@ const VideoPlayer: React.FC = () => {
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds)); const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
if (vlcRef.current && duration > 0 && !isSeeking.current) { if (vlcRef.current && duration > 0 && !isSeeking.current) {
if (DEBUG_MODE) { 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; isSeeking.current = true;
@ -799,7 +804,7 @@ const VideoPlayer: React.FC = () => {
NativeModules.StatusBarManager.setHidden(true); NativeModules.StatusBarManager.setHidden(true);
} }
} catch (error) { } 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) { if (u) {
try { try {
await syncService.migrateLocalScopeToUser(); await syncService.migrateLocalScopeToUser();
// Small yield to event loop // Longer yield to event loop to reduce CPU pressure
await new Promise(resolve => setTimeout(resolve, 50)); await new Promise(resolve => setTimeout(resolve, 100));
await syncService.subscribeRealtime(); 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 // Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull(); await syncService.fullPull();
await new Promise(resolve => setTimeout(resolve, 50)); await new Promise(resolve => setTimeout(resolve, 100));
await syncService.fullPush(); await syncService.fullPush();
} catch {} } catch {}
} }

View file

@ -181,7 +181,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
if (theme) setCurrentThemeState(theme); if (theme) setCurrentThemeState(theme);
} }
} catch (error) { } catch (error) {
console.error('Failed to load themes:', error); if (__DEV__) console.error('Failed to load themes:', error);
} }
}; };
loadThemes(); loadThemes();
@ -241,7 +241,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
await AsyncStorage.setItem(CURRENT_THEME_KEY, id); await AsyncStorage.setItem(CURRENT_THEME_KEY, id);
// Do not emit global settings sync for themes // Do not emit global settings sync for themes
} catch (error) { } 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 // Do not emit global settings sync for themes
} catch (error) { } 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 // Do not emit global settings sync for themes
} catch (error) { } 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) => { export const preloadDominantColor = async (imageUri: string | null) => {
if (!imageUri || colorCache.has(imageUri)) return; 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 { try {
// Fast first-pass: prioritize speed to avoid visible delay // Fast first-pass: prioritize speed to avoid visible delay
@ -157,7 +157,7 @@ export const preloadDominantColor = async (imageUri: string | null) => {
const extractedColor = selectBestColor(result); const extractedColor = selectBestColor(result);
colorCache.set(imageUri, extractedColor); colorCache.set(imageUri, extractedColor);
} catch (err) { } catch (err) {
console.warn('[preloadDominantColor] Failed to preload color:', err); if (__DEV__) console.warn('[preloadDominantColor] Failed to preload color:', err);
colorCache.set(imageUri, '#1a1a1a'); colorCache.set(imageUri, '#1a1a1a');
} }
}; };
@ -236,7 +236,7 @@ export const useDominantColor = (imageUri: string | null): DominantColorResult =
// Ignore refine errors silently // Ignore refine errors silently
}); });
} catch (err) { } 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'); setError(err instanceof Error ? err.message : 'Failed to extract color');
const fallbackColor = '#1a1a1a'; const fallbackColor = '#1a1a1a';
colorCache.set(uri, fallbackColor); // Cache fallback to avoid repeated failures colorCache.set(uri, fallbackColor); // Cache fallback to avoid repeated failures

View file

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

View file

@ -143,7 +143,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const logPrefix = isEpisode ? 'loadEpisodeStreams' : 'loadStreams'; const logPrefix = isEpisode ? 'loadEpisodeStreams' : 'loadStreams';
const sourceName = 'stremio'; const sourceName = 'stremio';
logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`); if (__DEV__) logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`);
try { try {
await stremioService.getStreams(type, id, await stremioService.getStreams(type, id,
@ -180,12 +180,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (error) { if (error) {
logger.error(`❌ [${logPrefix}:${sourceName}] Error for addon ${addonName} (${addonId}):`, error); logger.error(`❌ [${logPrefix}:${sourceName}] Error for addon ${addonName} (${addonId}):`, error);
} else if (streams && addonId && addonName) { } 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) { if (streams.length > 0) {
// Use the streams directly as they are already processed by stremioService // Use the streams directly as they are already processed by stremioService
const updateState = (prevState: GroupedStreams): GroupedStreams => { 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 { return {
...prevState, ...prevState,
[addonId]: { [addonId]: {
@ -205,16 +205,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setLoadingStreams(false); setLoadingStreams(false);
} }
} else { } 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 { } else {
// Handle case where callback provides null streams without error (e.g., empty results) // 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 // 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 (error) {
// Catch errors from the initial call to getStreams (e.g., initialization errors) // Catch errors from the initial call to getStreams (e.g., initialization errors)
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error); logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
@ -225,13 +225,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}; };
const loadCast = async () => { const loadCast = async () => {
logger.log('[loadCast] Starting cast fetch for:', id); if (__DEV__) logger.log('[loadCast] Starting cast fetch for:', id);
setLoadingCast(true); setLoadingCast(true);
try { try {
// Check cache first // Check cache first
const cachedCast = cacheService.getCast(id, type); const cachedCast = cacheService.getCast(id, type);
if (cachedCast) { if (cachedCast) {
logger.log('[loadCast] Using cached cast data'); if (__DEV__) logger.log('[loadCast] Using cached cast data');
setCast(cachedCast); setCast(cachedCast);
setLoadingCast(false); setLoadingCast(false);
return; return;
@ -240,7 +240,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Handle TMDB IDs // Handle TMDB IDs
if (id.startsWith('tmdb:')) { if (id.startsWith('tmdb:')) {
const tmdbId = id.split(':')[1]; 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); const castData = await tmdbService.getCredits(parseInt(tmdbId), type);
if (castData && castData.cast) { if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({ const formattedCast = castData.cast.map((actor: any) => ({
@ -249,7 +249,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
character: actor.character, character: actor.character,
profile_path: actor.profile_path 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); setCast(formattedCast);
cacheService.setCast(id, type, formattedCast); cacheService.setCast(id, type, formattedCast);
setLoadingCast(false); setLoadingCast(false);
@ -260,12 +260,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Handle IMDb IDs or convert to TMDB ID // Handle IMDb IDs or convert to TMDB ID
let tmdbId; let tmdbId;
if (id.startsWith('tt')) { 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); tmdbId = await tmdbService.findTMDBIdByIMDB(id);
} }
if (tmdbId) { 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); const castData = await tmdbService.getCredits(tmdbId, type);
if (castData && castData.cast) { if (castData && castData.cast) {
const formattedCast = castData.cast.map((actor: any) => ({ const formattedCast = castData.cast.map((actor: any) => ({
@ -274,12 +274,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
character: actor.character, character: actor.character,
profile_path: actor.profile_path 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); setCast(formattedCast);
cacheService.setCast(id, type, formattedCast); cacheService.setCast(id, type, formattedCast);
} }
} else { } 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) { } catch (error) {
logger.error('[loadCast] Failed to load cast:', 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]; const tmdbId = id.split(':')[1];
// For TMDB IDs, we need to handle metadata differently // For TMDB IDs, we need to handle metadata differently
if (type === 'movie') { 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); const movieDetails = await tmdbService.getMovieDetails(tmdbId);
if (movieDetails) { if (movieDetails) {
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id; 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') { } else if (type === 'series') {
// Handle TV shows with TMDB IDs // 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 { try {
const showDetails = await tmdbService.getTVShowDetails(parseInt(tmdbId)); const showDetails = await tmdbService.getTVShowDetails(parseInt(tmdbId));
if (showDetails) { if (showDetails) {
@ -443,7 +443,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const logoUrl = await tmdbService.getTvShowImages(tmdbId); const logoUrl = await tmdbService.getTvShowImages(tmdbId);
if (logoUrl) { if (logoUrl) {
formattedShow.logo = 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) { } catch (error) {
logger.error('Failed to fetch logo from TMDB:', 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) // Load series data (episodes)
setTmdbId(parseInt(tmdbId)); setTmdbId(parseInt(tmdbId));
loadSeriesData().catch(console.error); loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
const isInLib = catalogService.getLibraryItems().some(item => item.id === id); const isInLib = catalogService.getLibraryItems().some(item => item.id === id);
setInLibrary(isInLib); setInLibrary(isInLib);
@ -502,7 +502,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
throw new Error('Content not found'); throw new Error('Content not found');
} }
} catch (error) { } 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'; const errorMessage = error instanceof Error ? error.message : 'Failed to load content';
setError(errorMessage); setError(errorMessage);
@ -522,7 +522,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// First check if we have episode data from the addon // First check if we have episode data from the addon
const addonVideos = metadata?.videos; const addonVideos = metadata?.videos;
if (addonVideos && Array.isArray(addonVideos) && addonVideos.length > 0) { 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 // Group addon episodes by season
const groupedAddonEpisodes: GroupedEpisodes = {}; 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); 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 // Fetch season posters from TMDB
try { 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) { } catch (error) {
@ -697,7 +697,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setEpisodes(transformedEpisodes[selectedSeasonNumber] || []); setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
} }
} catch (error) { } catch (error) {
console.error('Failed to load episodes:', error); if (__DEV__) console.error('Failed to load episodes:', error);
} finally { } finally {
setLoadingSeasons(false); setLoadingSeasons(false);
} }
@ -724,7 +724,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const loadStreams = async () => { const loadStreams = async () => {
const startTime = Date.now(); const startTime = Date.now();
try { try {
console.log('🚀 [loadStreams] START - Loading streams for:', id); if (__DEV__) console.log('🚀 [loadStreams] START - Loading streams for:', id);
updateLoadingState(); updateLoadingState();
// Reset scraper tracking // Reset scraper tracking
@ -732,21 +732,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setActiveFetchingScrapers([]); setActiveFetchingScrapers([]);
// Get TMDB ID for external sources and determine the correct ID for Stremio addons // 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 tmdbId;
let stremioId = id; // Default to original ID let stremioId = id; // Default to original ID
if (id.startsWith('tmdb:')) { if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1]; 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 // Try to get IMDb ID from metadata first, then convert if needed
if (metadata?.imdb_id) { if (metadata?.imdb_id) {
stremioId = 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) { } else if (imdbId) {
stremioId = 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 { } else {
// Convert TMDB ID to IMDb ID for Stremio addons (they expect IMDb format) // Convert TMDB ID to IMDb ID for Stremio addons (they expect IMDb format)
try { try {
@ -760,24 +760,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (externalIds?.imdb_id) { if (externalIds?.imdb_id) {
stremioId = 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 { } 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) { } 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')) { } else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio // This is already an IMDB ID, perfect for Stremio
stremioId = id; 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); 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 { } else {
tmdbId = id; tmdbId = id;
stremioId = 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 // Initialize scraper tracking
@ -851,11 +851,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses); setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching); setActiveFetchingScrapers(initialActiveFetching);
} catch (error) { } 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 // 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); processStremioSource(type, stremioId, false);
// Monitor scraper completion status instead of using fixed timeout // Monitor scraper completion status instead of using fixed timeout
@ -881,7 +881,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}, 30000); }, 30000);
} catch (error) { } 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'); setError('Failed to load streams');
setLoadingStreams(false); setLoadingStreams(false);
} }
@ -890,7 +890,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const loadEpisodeStreams = async (episodeId: string) => { const loadEpisodeStreams = async (episodeId: string) => {
const startTime = Date.now(); const startTime = Date.now();
try { try {
console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId); if (__DEV__) console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId);
updateEpisodeLoadingState(); updateEpisodeLoadingState();
// Reset scraper tracking for episodes // Reset scraper tracking for episodes
@ -968,28 +968,28 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses); setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching); setActiveFetchingScrapers(initialActiveFetching);
} catch (error) { } 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 // 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 tmdbId;
let stremioEpisodeId = episodeId; // Default to original episode ID let stremioEpisodeId = episodeId; // Default to original episode ID
if (id.startsWith('tmdb:')) { if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1]; 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 // Try to get IMDb ID from metadata first, then convert if needed
if (metadata?.imdb_id) { if (metadata?.imdb_id) {
// Replace the series ID in episodeId with the IMDb ID // Replace the series ID in episodeId with the IMDb ID
const [, season, episode] = episodeId.split(':'); const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${metadata.imdb_id}:${season}:${episode}`; 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) { } else if (imdbId) {
const [, season, episode] = episodeId.split(':'); const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${imdbId}:${season}:${episode}`; 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 { } else {
// Convert TMDB ID to IMDb ID for Stremio addons // Convert TMDB ID to IMDb ID for Stremio addons
try { try {
@ -998,33 +998,33 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (externalIds?.imdb_id) { if (externalIds?.imdb_id) {
const [, season, episode] = episodeId.split(':'); const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `series:${externalIds.imdb_id}:${season}:${episode}`; 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 { } 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) { } 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')) { } else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio // 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); 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 { } else {
tmdbId = id; 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 // Extract episode info from the episodeId for logging
const [, season, episode] = episodeId.split(':'); const [, season, episode] = episodeId.split(':');
const episodeQuery = `?s=${season}&e=${episode}`; 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 // 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); processStremioSource('series', stremioEpisodeId, true);
// Monitor scraper completion status instead of using fixed timeout // 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 // Check completion less frequently to reduce CPU load
const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 2000); const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 3000);
// Fallback timeout after 30 seconds // Fallback timeout after 30 seconds
const episodeFallbackTimeout = setTimeout(() => { const episodeFallbackTimeout = setTimeout(() => {
@ -1050,7 +1050,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}, 30000); }, 30000);
} catch (error) { } 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'); setError('Failed to load episode streams');
setLoadingEpisodeStreams(false); setLoadingEpisodeStreams(false);
} }
@ -1103,7 +1103,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => { useEffect(() => {
if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) { if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) {
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`); 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]); }, [metadata?.videos, type]);
@ -1126,7 +1126,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setRecommendations(formattedRecommendations); setRecommendations(formattedRecommendations);
} catch (error) { } catch (error) {
console.error('Failed to load recommendations:', error); if (__DEV__) console.error('Failed to load recommendations:', error);
setRecommendations([]); setRecommendations([]);
} finally { } finally {
setLoadingRecommendations(false); setLoadingRecommendations(false);
@ -1141,24 +1141,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const tmdbService = TMDBService.getInstance(); const tmdbService = TMDBService.getInstance();
const fetchedTmdbId = await tmdbService.extractTMDBIdFromStremioId(id); const fetchedTmdbId = await tmdbService.extractTMDBIdFromStremioId(id);
if (fetchedTmdbId) { 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); setTmdbId(fetchedTmdbId);
// Fetch certification // Fetch certification
const certification = await tmdbService.getCertification(type, fetchedTmdbId); const certification = await tmdbService.getCertification(type, fetchedTmdbId);
if (certification) { 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 ? { setMetadata(prev => prev ? {
...prev, ...prev,
certification certification
} : null); } : null);
} else { } 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 { } 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) { } 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(() => { useEffect(() => {
if (tmdbId) { 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(); loadRecommendations();
// Reset recommendations when tmdbId changes // Reset recommendations when tmdbId changes
return () => { return () => {
@ -1183,27 +1183,27 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const maybeAttachCertification = async () => { const maybeAttachCertification = async () => {
try { try {
if (!metadata) { if (!metadata) {
console.warn('[useMetadata] skip certification attach: metadata not ready'); if (__DEV__) console.warn('[useMetadata] skip certification attach: metadata not ready');
return; return;
} }
if (!tmdbId) { 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; return;
} }
if ((metadata as any).certification) { 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; return;
} }
const tmdbSvc = TMDBService.getInstance(); const tmdbSvc = TMDBService.getInstance();
const cert = await tmdbSvc.getCertification(type, tmdbId); const cert = await tmdbSvc.getCertification(type, tmdbId);
if (cert) { 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); setMetadata(prev => prev ? { ...prev, certification: cert } : prev);
} else { } 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) { } catch (err) {
console.error('[useMetadata] error attaching certification', err); if (__DEV__) console.error('[useMetadata] error attaching certification', err);
} }
}; };
maybeAttachCertification(); maybeAttachCertification();

View file

@ -89,7 +89,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
}); });
} catch (error) { } catch (error) {
// Silently handle any animation errors // 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 { try {
runOnUI(enterAnimations)(); runOnUI(enterAnimations)();
} catch (error) { } 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 easing: easings.fast
}); });
} catch (error) { } catch (error) {
console.warn('Animation error in updateProgress:', error); if (__DEV__) console.warn('Animation error in updateProgress:', error);
} }
}; };
try { try {
runOnUI(updateProgress)(); runOnUI(updateProgress)();
} catch (error) { } catch (error) {
console.warn('Failed to run progress animation:', error); if (__DEV__) console.warn('Failed to run progress animation:', error);
} }
}, [watchProgress]); }, [watchProgress]);
@ -140,7 +140,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
cancelAnimation(headerProgress); cancelAnimation(headerProgress);
cancelAnimation(staticHeaderElementsY); cancelAnimation(staticHeaderElementsY);
} catch (error) { } 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) { } 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 }); if (merged) setSettings({ ...DEFAULT_SETTINGS, ...merged });
else setSettings(DEFAULT_SETTINGS); else setSettings(DEFAULT_SETTINGS);
} catch (error) { } catch (error) {
console.error('Failed to load settings:', error); if (__DEV__) console.error('Failed to load settings:', error);
// Fallback to default settings on error // Fallback to default settings on error
setSettings(DEFAULT_SETTINGS); setSettings(DEFAULT_SETTINGS);
} }
@ -195,18 +195,18 @@ export const useSettings = () => {
// Ensure a current scope exists to avoid future loads missing the chosen scope // Ensure a current scope exists to avoid future loads missing the chosen scope
await AsyncStorage.setItem('@user:current', scope); await AsyncStorage.setItem('@user:current', scope);
setSettings(newSettings); 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) // Notify all subscribers that settings have changed (if requested)
if (emitEvent) { if (emitEvent) {
console.log('Emitting settings change event'); if (__DEV__) console.log('Emitting settings change event');
settingsEmitter.emit(); settingsEmitter.emit();
} }
// If authenticated, push settings to server to prevent overwrite on next pull // If authenticated, push settings to server to prevent overwrite on next pull
try { syncService.pushSettings(); } catch {} try { syncService.pushSettings(); } catch {}
} catch (error) { } catch (error) {
console.error('Failed to save settings:', error); if (__DEV__) console.error('Failed to save settings:', error);
} }
}, [settings]); }, [settings]);

View file

@ -1,17 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { Alert } from 'react-native'; 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'; import AsyncStorage from '@react-native-async-storage/async-storage';
interface UpdateInfo {
isAvailable: boolean;
manifest?: {
id: string;
version?: string;
description?: string;
};
}
interface UseUpdatePopupReturn { interface UseUpdatePopupReturn {
showUpdatePopup: boolean; showUpdatePopup: boolean;
updateInfo: UpdateInfo; updateInfo: UpdateInfo;
@ -24,6 +15,7 @@ interface UseUpdatePopupReturn {
const UPDATE_POPUP_STORAGE_KEY = '@update_popup_dismissed'; const UPDATE_POPUP_STORAGE_KEY = '@update_popup_dismissed';
const UPDATE_LATER_STORAGE_KEY = '@update_later_timestamp'; const UPDATE_LATER_STORAGE_KEY = '@update_later_timestamp';
const UPDATE_LAST_CHECK_TS_KEY = '@update_last_check_ts';
export const useUpdatePopup = (): UseUpdatePopupReturn => { export const useUpdatePopup = (): UseUpdatePopupReturn => {
const [showUpdatePopup, setShowUpdatePopup] = useState(false); const [showUpdatePopup, setShowUpdatePopup] = useState(false);
@ -59,7 +51,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setShowUpdatePopup(true); setShowUpdatePopup(true);
} }
} catch (error) { } 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 // Don't show popup on error, just log it
} }
}, [updateInfo.manifest?.id]); }, [updateInfo.manifest?.id]);
@ -102,7 +94,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setShowUpdatePopup(true); setShowUpdatePopup(true);
} }
} catch (error) { } catch (error) {
console.error('Error installing update:', error); if (__DEV__) console.error('Error installing update:', error);
Alert.alert( Alert.alert(
'Update Error', 'Update Error',
'An error occurred while installing the update. Please try again later.' '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()); await AsyncStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
setShowUpdatePopup(false); setShowUpdatePopup(false);
} catch (error) { } catch (error) {
console.error('Error storing update later preference:', error); if (__DEV__) console.error('Error storing update later preference:', error);
setShowUpdatePopup(false); setShowUpdatePopup(false);
} }
}, []); }, []);
@ -134,7 +126,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
} }
setShowUpdatePopup(false); setShowUpdatePopup(false);
} catch (error) { } catch (error) {
console.error('Error storing dismiss preference:', error); if (__DEV__) console.error('Error storing dismiss preference:', error);
setShowUpdatePopup(false); setShowUpdatePopup(false);
} }
}, [updateInfo.manifest?.id]); }, [updateInfo.manifest?.id]);
@ -143,7 +135,21 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
useEffect(() => { useEffect(() => {
// Add a small delay to ensure the app is fully loaded // Add a small delay to ensure the app is fully loaded
const timer = setTimeout(() => { 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 }, 2000); // 2 second delay
return () => clearTimeout(timer); return () => clearTimeout(timer);

View file

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

View file

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

View file

@ -158,7 +158,7 @@ const HomeScreen = () => {
let catalogIndex = 0; let catalogIndex = 0;
// Limit concurrent catalog loading to prevent overwhelming the system // 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; let activeCatalogLoads = 0;
const catalogQueue: (() => Promise<void>)[] = []; const catalogQueue: (() => Promise<void>)[] = [];
@ -170,7 +170,7 @@ const HomeScreen = () => {
catalogLoader().finally(async () => { catalogLoader().finally(async () => {
activeCatalogLoads--; activeCatalogLoads--;
// Yield to event loop to avoid JS thread starvation and reduce heating // 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 processCatalogQueue(); // Process next in queue
}); });
} }
@ -257,7 +257,7 @@ const HomeScreen = () => {
}); });
} }
} catch (error) { } 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 { } finally {
setLoadedCatalogCount(prev => { setLoadedCatalogCount(prev => {
const next = prev + 1; const next = prev + 1;
@ -285,7 +285,7 @@ const HomeScreen = () => {
// Start processing the catalog queue (parallel fetching continues in background) // Start processing the catalog queue (parallel fetching continues in background)
processCatalogQueue(); processCatalogQueue();
} catch (error) { } catch (error) {
console.error('[HomeScreen] Error in progressive catalog loading:', error); if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error);
setCatalogsLoading(false); setCatalogsLoading(false);
} }
}, []); }, []);
@ -414,7 +414,7 @@ const HomeScreen = () => {
try { try {
ExpoImage.clearMemoryCache(); ExpoImage.clearMemoryCache();
} catch (error) { } catch (error) {
console.warn('Failed to clear image cache:', error); if (__DEV__) console.warn('Failed to clear image cache:', error);
} }
}; };
}, [currentTheme.colors.darkBackground]); }, [currentTheme.colors.darkBackground]);
@ -527,7 +527,7 @@ const HomeScreen = () => {
setHasContinueWatching(hasContent); setHasContinueWatching(hasContent);
} catch (error) { } catch (error) {
console.error('[HomeScreen] Error refreshing continue watching:', error); if (__DEV__) console.error('[HomeScreen] Error refreshing continue watching:', error);
setHasContinueWatching(false); setHasContinueWatching(false);
} }
} }

View file

@ -605,7 +605,7 @@ const LogoSourceSettings = () => {
try { try {
await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId); await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId);
} catch (e) { } 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) { } 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 // Debug logging for color extraction timing
useEffect(() => { useEffect(() => {
if (__DEV__ && heroImageUri && dominantColor) { if (__DEV__ && heroImageUri && dominantColor) {
console.log('[MetadataScreen] Dynamic background color:', { if (__DEV__) console.log('[MetadataScreen] Dynamic background color:', {
dominantColor, dominantColor,
fallback: currentTheme.colors.darkBackground, fallback: currentTheme.colors.darkBackground,
finalColor: dynamicBackgroundColor, finalColor: dynamicBackgroundColor,
@ -242,7 +242,7 @@ const MetadataScreen: React.FC = () => {
const isAuthenticated = await traktService.isAuthenticated(); const isAuthenticated = await traktService.isAuthenticated();
if (!isAuthenticated) { if (!isAuthenticated) {
console.log(`[MetadataScreen] Not authenticated with Trakt`); if (__DEV__) console.log(`[MetadataScreen] Not authenticated with Trakt`);
return; return;
} }
@ -283,7 +283,7 @@ const MetadataScreen: React.FC = () => {
} }
} catch (error) { } 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]); }, [shouldLoadSecondaryData, metadata, id, type]);
@ -315,7 +315,7 @@ const MetadataScreen: React.FC = () => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
const renderTime = Date.now() - startTime; const renderTime = Date.now() - startTime;
if (renderTime > 100) { 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); }, 0);
return () => clearTimeout(timer); return () => clearTimeout(timer);
@ -448,7 +448,7 @@ const MetadataScreen: React.FC = () => {
const handleEpisodeSelect = useCallback((episode: Episode) => { const handleEpisodeSelect = useCallback((episode: Episode) => {
if (!isScreenFocused) return; 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}`; const episodeId = episode.stremioId || `${id}:${episode.season_number}:${episode.episode_number}`;
// Optimize navigation with requestAnimationFrame // Optimize navigation with requestAnimationFrame

View file

@ -117,7 +117,7 @@ const OnboardingScreen = () => {
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] }); navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
} }
} catch (error) { } 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 }] }); navigation.reset({ index: 0, routes: [{ name: user ? 'MainTabs' : 'Account', params: !user ? ({ fromOnboarding: true } as any) : undefined }] });
} }
}; };

View file

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

View file

@ -244,7 +244,7 @@ const SettingsScreen: React.FC = () => {
// Refresh auth status // Refresh auth status
if (isAuthenticated || userProfile) { if (isAuthenticated || userProfile) {
// Just to be cautious, log the current state // 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(); refreshAuthStatus();
}); });
@ -291,7 +291,7 @@ const SettingsScreen: React.FC = () => {
setMdblistKeySet(!!mdblistKey); setMdblistKeySet(!!mdblistKey);
} catch (error) { } 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."); Alert.alert("Success", "MDBList cache has been cleared.");
} catch (error) { } catch (error) {
Alert.alert("Error", "Could not clear MDBList cache."); 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(() => { useEffect(() => {
console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail); if (__DEV__) console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail);
}, [episodeThumbnail]); }, [episodeThumbnail]);
// Pause trailer when StreamsScreen is opened // Pause trailer when StreamsScreen is opened
@ -1081,29 +1081,29 @@ export const StreamsScreen = () => {
return; 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 // Try each URL format in sequence
const tryNextUrl = (index: number) => { const tryNextUrl = (index: number) => {
if (index >= externalPlayerUrls.length) { 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 // Try direct URL as last resort
Linking.openURL(stream.url) Linking.openURL(stream.url)
.then(() => console.log('Opened with direct URL')) .then(() => { if (__DEV__) console.log('Opened with direct URL'); })
.catch(() => { .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); navigateToPlayer(stream);
}); });
return; return;
} }
const url = externalPlayerUrls[index]; 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) 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 => { .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); tryNextUrl(index + 1);
}); });
}; };
@ -1112,7 +1112,7 @@ export const StreamsScreen = () => {
tryNextUrl(0); tryNextUrl(0);
} catch (error) { } catch (error) {
console.error(`Error with ${settings.preferredPlayer}:`, error); if (__DEV__) console.error(`Error with ${settings.preferredPlayer}:`, error);
// Fallback to the built-in player // Fallback to the built-in player
navigateToPlayer(stream); navigateToPlayer(stream);
} }
@ -1120,18 +1120,18 @@ export const StreamsScreen = () => {
// For Android with external player preference // For Android with external player preference
else if (Platform.OS === 'android' && settings.useExternalPlayer) { else if (Platform.OS === 'android' && settings.useExternalPlayer) {
try { 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 // For Android, determine if the URL is a direct http/https URL or a magnet link
const isMagnet = stream.url.startsWith('magnet:'); const isMagnet = stream.url.startsWith('magnet:');
if (isMagnet) { if (isMagnet) {
// For magnet links, open directly which will trigger the torrent app chooser // 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) Linking.openURL(stream.url)
.then(() => console.log('Successfully opened magnet link')) .then(() => { if (__DEV__) console.log('Successfully opened magnet link'); })
.catch(err => { .catch(err => {
console.error('Failed to open magnet link:', err); if (__DEV__) console.error('Failed to open magnet link:', err);
// No good fallback for magnet links // No good fallback for magnet links
navigateToPlayer(stream); navigateToPlayer(stream);
}); });
@ -1145,12 +1145,12 @@ export const StreamsScreen = () => {
}); });
if (!success) { 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); navigateToPlayer(stream);
} }
} }
} catch (error) { } catch (error) {
console.error('Error with external player:', error); if (__DEV__) console.error('Error with external player:', error);
// Fallback to the built-in player // Fallback to the built-in player
navigateToPlayer(stream); navigateToPlayer(stream);
} }
@ -1161,7 +1161,7 @@ export const StreamsScreen = () => {
} }
} }
} catch (error) { } catch (error) {
console.error('Error in handleStreamPress:', error); if (__DEV__) console.error('Error in handleStreamPress:', error);
// Final fallback: Use built-in player // Final fallback: Use built-in player
navigateToPlayer(stream); navigateToPlayer(stream);
} }

View file

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

View file

@ -137,7 +137,7 @@ class SyncService {
logger.log('[Sync] fullPull (debounced) start'); logger.log('[Sync] fullPull (debounced) start');
this.fullPull() this.fullPull()
.then(() => logger.log('[Sync] fullPull (debounced) done')) .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); }, 300);
}; };
@ -168,7 +168,7 @@ class SyncService {
logger.log(`[Sync][rt] user_library ${payload.eventType} ${mediaType}:${mediaId}`); logger.log(`[Sync][rt] user_library ${payload.eventType} ${mediaType}:${mediaId}`);
} }
} catch (e) { } catch (e) {
console.warn('[Sync][rt] user_library handler error', e); if (__DEV__) console.warn('[Sync][rt] user_library handler error', e);
} finally { } finally {
this.suppressLibraryPush = false; this.suppressLibraryPush = false;
} }
@ -455,7 +455,7 @@ class SyncService {
.select('media_type, media_id, title, poster_url, year, deleted_at, updated_at') .select('media_type, media_id, title, poster_url, year, deleted_at, updated_at')
.eq('user_id', userId); .eq('user_id', userId);
if (error) { if (error) {
console.warn('[SyncService] pull library error', error); if (__DEV__) console.warn('[SyncService] pull library error', error);
return; return;
} }
const obj: Record<string, any> = {}; const obj: Record<string, any> = {};
@ -475,7 +475,7 @@ class SyncService {
await AsyncStorage.setItem('stremio-library', JSON.stringify(obj)); await AsyncStorage.setItem('stremio-library', JSON.stringify(obj));
logger.log(`[Sync] pull user_library wrote items=${Object.keys(obj).length}`); logger.log(`[Sync] pull user_library wrote items=${Object.keys(obj).length}`);
} catch (e) { } 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 const { error: upErr } = await supabase
.from('user_library') .from('user_library')
.upsert(rows, { onConflict: 'user_id,media_type,media_id' }); .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'); else await AsyncStorage.setItem(`@user:${user.id}:library_initialized`, 'true');
} }
// No computed deletions; removals happen only via explicit user action (soft delete) // No computed deletions; removals happen only via explicit user action (soft delete)
} catch (e) { } 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(), updated_at: new Date().toISOString(),
}; };
const { error } = await supabase.from('user_library').upsert(row, { onConflict: 'user_id,media_type,media_id' }); 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) { } 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('user_id', user.id)
.eq('media_type', type === 'movie' ? 'movie' : 'series') .eq('media_type', type === 'movie' ? 'movie' : 'series')
.eq('media_id', id); .eq('media_id', id);
if (error) console.warn('[SyncService] pushLibraryRemove error', error); if (error && __DEV__) console.warn('[SyncService] pushLibraryRemove error', error);
} catch (e) { } 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) .eq('user_id', userId)
.order('position', { ascending: true }); .order('position', { ascending: true });
if (addonsErr) { if (addonsErr) {
console.warn('[SyncService] pull addons error', addonsErr); if (__DEV__) console.warn('[SyncService] pull addons error', addonsErr);
return; return;
} }
if (!(addons && Array.isArray(addons))) return; if (!(addons && Array.isArray(addons))) return;
@ -603,7 +603,7 @@ class SyncService {
manifest.id = a.addon_id; manifest.id = a.addon_id;
map.set(a.addon_id, manifest); map.set(a.addon_id, manifest);
} catch (e) { } 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 const { error } = await supabase
.from('watch_progress') .from('watch_progress')
.upsert(filteredRows, { onConflict: 'user_id,media_type,media_id,episode_id' }); .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'); else logger.log('[Sync] push watch_progress upsert ok');
} }
} catch (e) { } catch (e) {
@ -723,7 +723,7 @@ class SyncService {
const { error } = await supabase const { error } = await supabase
.from('watch_progress') .from('watch_progress')
.upsert(rows, { onConflict: 'user_id,media_type,media_id,episode_id' }); .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'); else logger.log('[Sync] push watch_progress upsert ok');
} }
} }
@ -742,9 +742,9 @@ class SyncService {
.eq('media_type', type) .eq('media_type', type)
.eq('media_id', id) .eq('media_id', id)
.eq('episode_id', episodeId || ''); .eq('episode_id', episodeId || '');
if (error) console.warn('[SyncService] softDeleteWatchProgress error', error); if (error && __DEV__) console.warn('[SyncService] softDeleteWatchProgress error', error);
} catch (e) { } 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, app_settings: appSettings,
subtitle_settings: subtitleSettings, 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'); else logger.log('[Sync] push user_settings ok');
} }
@ -807,14 +807,14 @@ class SyncService {
.delete() .delete()
.eq('user_id', userId) .eq('user_id', userId)
.in('addon_id', toDelete); .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) { } 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' }); 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) // 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> { async getStremioId(type: string, tmdbId: string): Promise<string | null> {
console.log('=== CatalogService.getStremioId ==='); if (__DEV__) {
console.log('Input type:', type); console.log('=== CatalogService.getStremioId ===');
console.log('Input tmdbId:', tmdbId); console.log('Input type:', type);
console.log('Input tmdbId:', tmdbId);
}
try { try {
// For movies, use the tt prefix with IMDb ID // For movies, use the tt prefix with IMDb ID
if (type === 'movie') { if (type === 'movie') {
console.log('Processing movie - fetching TMDB details...'); if (__DEV__) console.log('Processing movie - fetching TMDB details...');
const tmdbService = TMDBService.getInstance(); const tmdbService = TMDBService.getInstance();
const movieDetails = await tmdbService.getMovieDetails(tmdbId); const movieDetails = await tmdbService.getMovieDetails(tmdbId);
console.log('Movie details result:', { if (__DEV__) console.log('Movie details result:', {
id: movieDetails?.id, id: movieDetails?.id,
title: movieDetails?.title, title: movieDetails?.title,
imdb_id: movieDetails?.imdb_id, imdb_id: movieDetails?.imdb_id,
@ -863,7 +865,7 @@ class CatalogService {
}); });
if (movieDetails?.imdb_id) { 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; return movieDetails.imdb_id;
} else { } else {
console.warn('No IMDb ID found for movie:', tmdbId); console.warn('No IMDb ID found for movie:', tmdbId);
@ -872,25 +874,25 @@ class CatalogService {
} }
// For TV shows, get the IMDb ID like movies // For TV shows, get the IMDb ID like movies
else if (type === 'tv' || type === 'series') { 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(); const tmdbService = TMDBService.getInstance();
// Get TV show external IDs to find IMDb ID // Get TV show external IDs to find IMDb ID
const externalIds = await tmdbService.getShowExternalIds(parseInt(tmdbId)); 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, tmdbId: tmdbId,
imdb_id: externalIds?.imdb_id, imdb_id: externalIds?.imdb_id,
hasImdbId: !!externalIds?.imdb_id hasImdbId: !!externalIds?.imdb_id
}); });
if (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; return externalIds.imdb_id;
} else { } else {
console.warn('No IMDb ID found for TV show, falling back to kitsu format:', tmdbId); console.warn('No IMDb ID found for TV show, falling back to kitsu format:', tmdbId);
const fallbackId = `kitsu:${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; return fallbackId;
} }
} }
@ -899,11 +901,13 @@ class CatalogService {
return null; return null;
} }
} catch (error: any) { } catch (error: any) {
console.error('=== Error in getStremioId ==='); if (__DEV__) {
console.error('Type:', type); console.error('=== Error in getStremioId ===');
console.error('TMDB ID:', tmdbId); console.error('Type:', type);
console.error('Error details:', error); console.error('TMDB ID:', tmdbId);
console.error('Error message:', error.message); console.error('Error details:', error);
console.error('Error message:', error.message);
}
logger.error('Error getting Stremio ID:', error); logger.error('Error getting Stremio ID:', error);
return null; return null;
} }

View file

@ -1,5 +1,6 @@
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
import { Image as ExpoImage } from 'expo-image'; import { Image as ExpoImage } from 'expo-image';
import { AppState, AppStateStatus } from 'react-native';
interface CachedImage { interface CachedImage {
url: string; url: string;
@ -14,16 +15,20 @@ interface CachedImage {
class ImageCacheService { class ImageCacheService {
private cache = new Map<string, CachedImage>(); private cache = new Map<string, CachedImage>();
private readonly CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours 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_CACHE_SIZE = 25; // Further reduced maximum number of cached images
private readonly MAX_MEMORY_MB = 150; // Increased maximum memory usage in MB private readonly MAX_MEMORY_MB = 40; // Further reduced maximum memory usage in MB
private currentMemoryUsage = 0; private currentMemoryUsage = 0;
private cleanupInterval: NodeJS.Timeout | null = null; private cleanupInterval: NodeJS.Timeout | null = null;
private appStateSubscription: any = null;
constructor() { 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.cleanupInterval = setInterval(() => {
this.performCleanup(); 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); clearInterval(this.cleanupInterval);
this.cleanupInterval = null; this.cleanupInterval = null;
} }
if (this.appStateSubscription) {
this.appStateSubscription.remove();
this.appStateSubscription = null;
}
this.clearAllCache(); 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(); export const imageCacheService = new ImageCacheService();

View file

@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState, AppStateStatus } from 'react-native';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
import { imageCacheService } from './imageCacheService'; import { imageCacheService } from './imageCacheService';
@ -260,7 +261,7 @@ export class TraktService {
// Rate limiting // Rate limiting
private lastApiCall: number = 0; 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 requestQueue: Array<() => Promise<any>> = [];
private isProcessingQueue: boolean = false; private isProcessingQueue: boolean = false;
@ -272,7 +273,7 @@ export class TraktService {
// Track currently watching sessions to avoid duplicate starts// Sync debouncing // Track currently watching sessions to avoid duplicate starts// Sync debouncing
private currentlyWatching: Set<string> = new Set(); private currentlyWatching: Set<string> = new Set();
private lastSyncTimes: Map<string, number> = new Map(); 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 // Debounce for stop calls
private lastStopCalls: Map<string, number> = new Map(); 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 // Increased cleanup interval from 5 minutes to 15 minutes to reduce heating
setInterval(() => this.cleanupOldStopCalls(), 15 * 60 * 1000); // Clean up every 15 minutes 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 // Load user settings
this.loadCompletionThreshold(); this.loadCompletionThreshold();
} }
@ -1566,6 +1570,24 @@ export class TraktService {
return []; 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 // Export a singleton instance

View file

@ -38,8 +38,17 @@ export class UpdateService {
this.logs = this.logs.slice(0, this.MAX_LOGS); this.logs = this.logs.slice(0, this.MAX_LOGS);
} }
// Always log to console - this will be visible in adb logcat for production builds // Console logging policy:
// Use different console methods for better filtering in logcat // - 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') { if (level === 'ERROR') {
console.error(`[UpdateService] ${logEntry}`); console.error(`[UpdateService] ${logEntry}`);
} else if (level === 'WARN') { } else if (level === 'WARN') {
@ -47,9 +56,11 @@ export class UpdateService {
} else { } else {
console.log(`[UpdateService] ${logEntry}`); console.log(`[UpdateService] ${logEntry}`);
} }
// Also log with a consistent prefix for easy filtering // Additional prefixed line for easier filtering during development only
console.log(`UpdateService: ${logEntry}`); if (__DEV__) {
console.log(`UpdateService: ${logEntry}`);
}
} }
/** /**