mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Removed extensive logging from production build
This commit is contained in:
parent
86cfca4c96
commit
97ba1ef42b
31 changed files with 349 additions and 269 deletions
4
app.json
4
app.json
|
|
@ -82,8 +82,8 @@
|
|||
],
|
||||
"updates": {
|
||||
"enabled": true,
|
||||
"checkAutomatically": "ON_LOAD",
|
||||
"fallbackToCacheTimeout": 0,
|
||||
"checkAutomatically": "ON_ERROR",
|
||||
"fallbackToCacheTimeout": 30000,
|
||||
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
|
||||
},
|
||||
"runtimeVersion": "0.6.0-beta.10"
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export const CalendarSection: React.FC<CalendarSectionProps> = ({
|
|||
|
||||
// Process episodes to identify dates with content
|
||||
useEffect(() => {
|
||||
console.log(`[CalendarSection] Processing ${episodes.length} episodes for calendar dots`);
|
||||
if (__DEV__) console.log(`[CalendarSection] Processing ${episodes.length} episodes for calendar dots`);
|
||||
const dateMap: { [key: string]: boolean } = {};
|
||||
|
||||
episodes.forEach(episode => {
|
||||
|
|
@ -96,7 +96,7 @@ export const CalendarSection: React.FC<CalendarSectionProps> = ({
|
|||
}
|
||||
});
|
||||
|
||||
console.log(`[CalendarSection] Found ${Object.keys(dateMap).length} unique dates with episodes`);
|
||||
if (__DEV__) console.log(`[CalendarSection] Found ${Object.keys(dateMap).length} unique dates with episodes`);
|
||||
setDatesWithEpisodes(dateMap);
|
||||
}, [episodes]);
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ const ContentItem = ({ item, onPress }: ContentItemProps) => {
|
|||
setImageError(false);
|
||||
}}
|
||||
onError={(error) => {
|
||||
console.warn('Image load error for:', item.poster, error);
|
||||
if (__DEV__) console.warn('Image load error for:', item.poster, error);
|
||||
// Try fallback URL on first error
|
||||
if (retryCount === 0 && item.poster && !item.poster.includes('metahub.space')) {
|
||||
setRetryCount(1);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
|
|||
setPersonDetails(details);
|
||||
setHasFetched(true);
|
||||
} catch (error) {
|
||||
console.error('Error fetching person details:', error);
|
||||
if (__DEV__) console.error('Error fetching person details:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
|||
throw new Error('Could not find Stremio ID');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error navigating to recommendation:', error);
|
||||
if (__DEV__) console.error('Error navigating to recommendation:', error);
|
||||
Alert.alert(
|
||||
'Error',
|
||||
'Unable to load this content. Please try again later.',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native';
|
||||
import { Image } from 'expo-image';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { FlashList, FlashListRef } from '@shopify/flash-list';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
|
@ -70,10 +70,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
const savedMode = await AsyncStorage.getItem(`season_view_mode_${metadata.id}`);
|
||||
if (savedMode === 'text' || savedMode === 'posters') {
|
||||
setSeasonViewMode(savedMode);
|
||||
console.log('[SeriesContent] Loaded saved view mode:', savedMode, 'for show:', metadata.id);
|
||||
if (__DEV__) console.log('[SeriesContent] Loaded saved view mode:', savedMode, 'for show:', metadata.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[SeriesContent] Error loading view mode preference:', error);
|
||||
if (__DEV__) console.log('[SeriesContent] Error loading view mode preference:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -99,7 +99,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
setSeasonViewMode(newMode);
|
||||
if (metadata?.id) {
|
||||
AsyncStorage.setItem(`season_view_mode_${metadata.id}`, newMode).catch(error => {
|
||||
console.log('[SeriesContent] Error saving view mode preference:', error);
|
||||
if (__DEV__) console.log('[SeriesContent] Error saving view mode preference:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -328,7 +328,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
console.log('[SeriesContent] renderSeasonSelector called, current view mode:', seasonViewMode);
|
||||
if (__DEV__) console.log('[SeriesContent] renderSeasonSelector called, current view mode:', seasonViewMode);
|
||||
|
||||
const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b);
|
||||
|
||||
|
|
@ -357,7 +357,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
onPress={() => {
|
||||
const newMode = seasonViewMode === 'posters' ? 'text' : 'posters';
|
||||
updateViewMode(newMode);
|
||||
console.log('[SeriesContent] View mode changed to:', newMode, 'Current ref value:', seasonViewMode);
|
||||
if (__DEV__) console.log('[SeriesContent] View mode changed to:', newMode, 'Current ref value:', seasonViewMode);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
|
|
@ -398,7 +398,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
|
||||
if (seasonViewMode === 'text') {
|
||||
// Text-only view
|
||||
console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
if (__DEV__) console.log('[SeriesContent] Rendering text view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
return (
|
||||
<View
|
||||
key={season}
|
||||
|
|
@ -430,7 +430,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
}
|
||||
|
||||
// Poster view (current implementation)
|
||||
console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
if (__DEV__) console.log('[SeriesContent] Rendering poster view for season:', season, 'View mode ref:', seasonViewMode);
|
||||
return (
|
||||
<View
|
||||
key={season}
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
|
||||
setZoomScale(newScale);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
if (event.nativeEvent.state === State.END) {
|
||||
setLastZoomScale(zoomScale);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -331,7 +331,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
setZoomScale(targetZoom);
|
||||
setLastZoomScale(targetZoom);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
);
|
||||
setCustomVideoStyles(styles);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
||||
}
|
||||
}
|
||||
}, [screenDimensions, videoAspectRatio]);
|
||||
|
|
@ -439,7 +439,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
// Fallback: ensure animation completes even if something goes wrong
|
||||
setTimeout(() => {
|
||||
if (!isOpeningAnimationComplete) {
|
||||
logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered');
|
||||
if (__DEV__) logger.warn('[AndroidVideoPlayer] Opening animation fallback triggered');
|
||||
setIsOpeningAnimationComplete(true);
|
||||
openingScaleAnim.setValue(1);
|
||||
openingFadeAnim.setValue(1);
|
||||
|
|
@ -452,40 +452,40 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const loadWatchProgress = async () => {
|
||||
if (id && type) {
|
||||
try {
|
||||
logger.log(`[AndroidVideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
|
||||
logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Saved progress:`, savedProgress);
|
||||
|
||||
if (savedProgress) {
|
||||
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
|
||||
logger.log(`[AndroidVideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
|
||||
|
||||
if (progressPercent < 85) {
|
||||
setResumePosition(savedProgress.currentTime);
|
||||
setSavedDuration(savedProgress.duration);
|
||||
logger.log(`[AndroidVideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
|
||||
if (appSettings.alwaysResume) {
|
||||
// Only prepare auto-resume state and seek when AlwaysResume is enabled
|
||||
setInitialPosition(savedProgress.currentTime);
|
||||
initialSeekTargetRef.current = savedProgress.currentTime;
|
||||
logger.log(`[AndroidVideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
|
||||
seekToTime(savedProgress.currentTime);
|
||||
} else {
|
||||
// Do not set initialPosition; start from beginning with no auto-seek
|
||||
setShowResumeOverlay(true);
|
||||
logger.log(`[AndroidVideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[AndroidVideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[AndroidVideoPlayer] No saved progress found`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] No saved progress found`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[AndroidVideoPlayer] Error loading watch progress:', error);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[AndroidVideoPlayer] Missing id or type: id=${id}, type=${type}`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Missing id or type: id=${id}, type=${type}`);
|
||||
}
|
||||
};
|
||||
loadWatchProgress();
|
||||
|
|
@ -545,7 +545,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
|
||||
if (videoRef.current && duration > 0 && !isSeeking.current) {
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||
}
|
||||
|
||||
isSeeking.current = true;
|
||||
|
|
@ -779,7 +779,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
NativeModules.StatusBarManager.setHidden(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Immersive mode error:', error);
|
||||
if (__DEV__) console.log('Immersive mode error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -61,15 +61,17 @@ const VideoPlayer: React.FC = () => {
|
|||
// - Xprime streams on any platform
|
||||
// - Non-MKV files on iOS (unless forceVlc is set)
|
||||
const shouldUseAndroidPlayer = Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc);
|
||||
logger.log('[VideoPlayer] Player selection:', {
|
||||
platform: Platform.OS,
|
||||
isXprimeStream,
|
||||
isMkvFile,
|
||||
forceVlc: !!forceVlc,
|
||||
selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer',
|
||||
streamProvider,
|
||||
uri
|
||||
});
|
||||
if (__DEV__) {
|
||||
logger.log('[VideoPlayer] Player selection:', {
|
||||
platform: Platform.OS,
|
||||
isXprimeStream,
|
||||
isMkvFile,
|
||||
forceVlc: !!forceVlc,
|
||||
selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer',
|
||||
streamProvider,
|
||||
uri
|
||||
});
|
||||
}
|
||||
if (shouldUseAndroidPlayer) {
|
||||
return <AndroidVideoPlayer />;
|
||||
}
|
||||
|
|
@ -335,7 +337,7 @@ const VideoPlayer: React.FC = () => {
|
|||
const newScale = Math.max(1, Math.min(lastZoomScale * scale, 1.1));
|
||||
setZoomScale(newScale);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Center Zoom: ${newScale.toFixed(2)}x`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -343,7 +345,7 @@ const VideoPlayer: React.FC = () => {
|
|||
if (event.nativeEvent.state === State.END) {
|
||||
setLastZoomScale(zoomScale);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Pinch ended - saved scale: ${zoomScale.toFixed(2)}x`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -353,7 +355,7 @@ const VideoPlayer: React.FC = () => {
|
|||
setZoomScale(targetZoom);
|
||||
setLastZoomScale(targetZoom);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Zoom reset to ${targetZoom}x (16:9: ${is16by9Content})`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -367,7 +369,7 @@ const VideoPlayer: React.FC = () => {
|
|||
);
|
||||
setCustomVideoStyles(styles);
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
||||
}
|
||||
}
|
||||
}, [effectiveDimensions, videoAspectRatio]);
|
||||
|
|
@ -377,7 +379,7 @@ const VideoPlayer: React.FC = () => {
|
|||
const lockOrientation = async () => {
|
||||
try {
|
||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||
logger.log('[VideoPlayer] Locked to landscape orientation');
|
||||
if (__DEV__) logger.log('[VideoPlayer] Locked to landscape orientation');
|
||||
} catch (error) {
|
||||
logger.warn('[VideoPlayer] Failed to lock orientation:', error);
|
||||
}
|
||||
|
|
@ -483,32 +485,36 @@ const VideoPlayer: React.FC = () => {
|
|||
const loadWatchProgress = async () => {
|
||||
if (id && type) {
|
||||
try {
|
||||
logger.log(`[VideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
if (__DEV__) {
|
||||
logger.log(`[VideoPlayer] Loading watch progress for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
}
|
||||
const savedProgress = await storageService.getWatchProgress(id, type, episodeId);
|
||||
logger.log(`[VideoPlayer] Saved progress:`, savedProgress);
|
||||
if (__DEV__) {
|
||||
logger.log(`[VideoPlayer] Saved progress:`, savedProgress);
|
||||
}
|
||||
|
||||
if (savedProgress) {
|
||||
const progressPercent = (savedProgress.currentTime / savedProgress.duration) * 100;
|
||||
logger.log(`[VideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Progress: ${progressPercent.toFixed(1)}% (${savedProgress.currentTime}/${savedProgress.duration})`);
|
||||
|
||||
if (progressPercent < 85) {
|
||||
setResumePosition(savedProgress.currentTime);
|
||||
setSavedDuration(savedProgress.duration);
|
||||
logger.log(`[VideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Set resume position to: ${savedProgress.currentTime} of ${savedProgress.duration}`);
|
||||
if (appSettings.alwaysResume) {
|
||||
// Only prepare auto-resume state and seek when AlwaysResume is enabled
|
||||
setInitialPosition(savedProgress.currentTime);
|
||||
initialSeekTargetRef.current = savedProgress.currentTime;
|
||||
logger.log(`[VideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] AlwaysResume enabled. Auto-seeking to ${savedProgress.currentTime}`);
|
||||
// Seek immediately after load
|
||||
seekToTime(savedProgress.currentTime);
|
||||
} else {
|
||||
// Do not set initialPosition; start from beginning with no auto-seek
|
||||
setShowResumeOverlay(true);
|
||||
logger.log(`[VideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] AlwaysResume disabled. Not auto-seeking; overlay shown (if enabled)`);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[VideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Progress too high (${progressPercent.toFixed(1)}%), not showing resume overlay`);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[VideoPlayer] No saved progress found`);
|
||||
|
|
@ -517,7 +523,7 @@ const VideoPlayer: React.FC = () => {
|
|||
logger.error('[VideoPlayer] Error loading watch progress:', error);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[VideoPlayer] Missing id or type: id=${id}, type=${type}`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Missing id or type: id=${id}, type=${type}`);
|
||||
}
|
||||
};
|
||||
loadWatchProgress();
|
||||
|
|
@ -547,8 +553,7 @@ const VideoPlayer: React.FC = () => {
|
|||
clearInterval(progressSaveInterval);
|
||||
}
|
||||
|
||||
// HEATING FIX: Increase sync interval to 15 seconds to reduce CPU load
|
||||
const syncInterval = 15000; // 15 seconds to prevent heating
|
||||
const syncInterval = 20000; // 20s to further reduce CPU load
|
||||
|
||||
const interval = setInterval(() => {
|
||||
saveWatchProgress();
|
||||
|
|
@ -560,7 +565,7 @@ const VideoPlayer: React.FC = () => {
|
|||
setProgressSaveInterval(null);
|
||||
};
|
||||
}
|
||||
}, [id, type, paused, currentTime, duration]);
|
||||
}, [id, type, paused, duration]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -597,7 +602,7 @@ const VideoPlayer: React.FC = () => {
|
|||
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
|
||||
if (vlcRef.current && duration > 0 && !isSeeking.current) {
|
||||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||
if (__DEV__) logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||
}
|
||||
|
||||
isSeeking.current = true;
|
||||
|
|
@ -799,7 +804,7 @@ const VideoPlayer: React.FC = () => {
|
|||
NativeModules.StatusBarManager.setHidden(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Immersive mode error:', error);
|
||||
if (__DEV__) console.log('Immersive mode error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
|||
if (u) {
|
||||
try {
|
||||
await syncService.migrateLocalScopeToUser();
|
||||
// Small yield to event loop
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
// Longer yield to event loop to reduce CPU pressure
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await syncService.subscribeRealtime();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
// Pull first to hydrate local state, then push to avoid wiping server with empty local
|
||||
await syncService.fullPull();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await syncService.fullPush();
|
||||
} catch {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
if (theme) setCurrentThemeState(theme);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load themes:', error);
|
||||
if (__DEV__) console.error('Failed to load themes:', error);
|
||||
}
|
||||
};
|
||||
loadThemes();
|
||||
|
|
@ -241,7 +241,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
await AsyncStorage.setItem(CURRENT_THEME_KEY, id);
|
||||
// Do not emit global settings sync for themes
|
||||
} catch (error) {
|
||||
console.error('Failed to add custom theme:', error);
|
||||
if (__DEV__) console.error('Failed to add custom theme:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
// Do not emit global settings sync for themes
|
||||
} catch (error) {
|
||||
console.error('Failed to update custom theme:', error);
|
||||
if (__DEV__) console.error('Failed to update custom theme:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -316,7 +316,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
// Do not emit global settings sync for themes
|
||||
} catch (error) {
|
||||
console.error('Failed to delete custom theme:', error);
|
||||
if (__DEV__) console.error('Failed to delete custom theme:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ const selectBestColor = (result: ImageColorsResult): string => {
|
|||
export const preloadDominantColor = async (imageUri: string | null) => {
|
||||
if (!imageUri || colorCache.has(imageUri)) return;
|
||||
|
||||
console.log('[useDominantColor] Preloading color for URI:', imageUri);
|
||||
if (__DEV__) console.log('[useDominantColor] Preloading color for URI:', imageUri);
|
||||
|
||||
try {
|
||||
// Fast first-pass: prioritize speed to avoid visible delay
|
||||
|
|
@ -157,7 +157,7 @@ export const preloadDominantColor = async (imageUri: string | null) => {
|
|||
const extractedColor = selectBestColor(result);
|
||||
colorCache.set(imageUri, extractedColor);
|
||||
} catch (err) {
|
||||
console.warn('[preloadDominantColor] Failed to preload color:', err);
|
||||
if (__DEV__) console.warn('[preloadDominantColor] Failed to preload color:', err);
|
||||
colorCache.set(imageUri, '#1a1a1a');
|
||||
}
|
||||
};
|
||||
|
|
@ -236,7 +236,7 @@ export const useDominantColor = (imageUri: string | null): DominantColorResult =
|
|||
// Ignore refine errors silently
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[useDominantColor] Failed to extract color:', err);
|
||||
if (__DEV__) console.warn('[useDominantColor] Failed to extract color:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to extract color');
|
||||
const fallbackColor = '#1a1a1a';
|
||||
colorCache.set(uri, fallbackColor); // Cache fallback to avoid repeated failures
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { StreamingContent } from '../types/metadata';
|
||||
import { StreamingContent } from '../services/catalogService';
|
||||
import { catalogService } from '../services/catalogService';
|
||||
|
||||
const LEGACY_LIBRARY_STORAGE_KEY = 'stremio-library';
|
||||
|
|
@ -36,7 +36,7 @@ export const useLibrary = () => {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading library items:', error);
|
||||
if (__DEV__) console.error('Error loading library items:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ export const useLibrary = () => {
|
|||
// keep legacy for backward-compat
|
||||
await AsyncStorage.setItem(LEGACY_LIBRARY_STORAGE_KEY, JSON.stringify(itemsObject));
|
||||
} catch (error) {
|
||||
console.error('Error saving library items:', error);
|
||||
if (__DEV__) console.error('Error saving library items:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export const useLibrary = () => {
|
|||
await catalogService.addToLibrary({ ...item, inLibrary: true });
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Error adding to library via catalogService:', e);
|
||||
if (__DEV__) console.error('Error adding to library via catalogService:', e);
|
||||
// Fallback local write
|
||||
const updatedItems = [...libraryItems, { ...item, inLibrary: true }];
|
||||
setLibraryItems(updatedItems);
|
||||
|
|
@ -82,7 +82,7 @@ export const useLibrary = () => {
|
|||
await catalogService.removeFromLibrary(type, id);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Error removing from library via catalogService:', e);
|
||||
if (__DEV__) console.error('Error removing from library via catalogService:', e);
|
||||
// Fallback local write
|
||||
const updatedItems = libraryItems.filter(item => item.id !== id);
|
||||
setLibraryItems(updatedItems);
|
||||
|
|
@ -115,7 +115,7 @@ export const useLibrary = () => {
|
|||
// Subscribe to catalogService library updates
|
||||
useEffect(() => {
|
||||
const unsubscribe = catalogService.subscribeToLibraryUpdates((items) => {
|
||||
console.log('[useLibrary] Received library update from catalogService:', items.length, 'items');
|
||||
if (__DEV__) console.log('[useLibrary] Received library update from catalogService:', items.length, 'items');
|
||||
setLibraryItems(items);
|
||||
setLoading(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const logPrefix = isEpisode ? 'loadEpisodeStreams' : 'loadStreams';
|
||||
const sourceName = 'stremio';
|
||||
|
||||
logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`);
|
||||
if (__DEV__) logger.log(`🔍 [${logPrefix}:${sourceName}] Starting fetch`);
|
||||
|
||||
try {
|
||||
await stremioService.getStreams(type, id,
|
||||
|
|
@ -180,12 +180,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (error) {
|
||||
logger.error(`❌ [${logPrefix}:${sourceName}] Error for addon ${addonName} (${addonId}):`, error);
|
||||
} else if (streams && addonId && addonName) {
|
||||
logger.log(`✅ [${logPrefix}:${sourceName}] Received ${streams.length} streams from ${addonName} (${addonId}) after ${processTime}ms`);
|
||||
if (__DEV__) logger.log(`✅ [${logPrefix}:${sourceName}] Received ${streams.length} streams from ${addonName} (${addonId}) after ${processTime}ms`);
|
||||
|
||||
if (streams.length > 0) {
|
||||
// Use the streams directly as they are already processed by stremioService
|
||||
const updateState = (prevState: GroupedStreams): GroupedStreams => {
|
||||
logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
|
||||
if (__DEV__) logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
|
||||
return {
|
||||
...prevState,
|
||||
[addonId]: {
|
||||
|
|
@ -205,16 +205,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setLoadingStreams(false);
|
||||
}
|
||||
} else {
|
||||
logger.log(`🤷 [${logPrefix}:${sourceName}] No streams found for addon ${addonName} (${addonId})`);
|
||||
if (__DEV__) logger.log(`🤷 [${logPrefix}:${sourceName}] No streams found for addon ${addonName} (${addonId})`);
|
||||
}
|
||||
} else {
|
||||
// Handle case where callback provides null streams without error (e.g., empty results)
|
||||
logger.log(`🏁 [${logPrefix}:${sourceName}] Finished fetching for addon ${addonName} (${addonId}) with no streams after ${processTime}ms`);
|
||||
if (__DEV__) logger.log(`🏁 [${logPrefix}:${sourceName}] Finished fetching for addon ${addonName} (${addonId}) with no streams after ${processTime}ms`);
|
||||
}
|
||||
}
|
||||
);
|
||||
// The function now returns void, just await to let callbacks fire
|
||||
logger.log(`🏁 [${logPrefix}:${sourceName}] Stremio fetching process initiated`);
|
||||
if (__DEV__) logger.log(`🏁 [${logPrefix}:${sourceName}] Stremio fetching process initiated`);
|
||||
} catch (error) {
|
||||
// Catch errors from the initial call to getStreams (e.g., initialization errors)
|
||||
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
|
||||
|
|
@ -225,13 +225,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
};
|
||||
|
||||
const loadCast = async () => {
|
||||
logger.log('[loadCast] Starting cast fetch for:', id);
|
||||
if (__DEV__) logger.log('[loadCast] Starting cast fetch for:', id);
|
||||
setLoadingCast(true);
|
||||
try {
|
||||
// Check cache first
|
||||
const cachedCast = cacheService.getCast(id, type);
|
||||
if (cachedCast) {
|
||||
logger.log('[loadCast] Using cached cast data');
|
||||
if (__DEV__) logger.log('[loadCast] Using cached cast data');
|
||||
setCast(cachedCast);
|
||||
setLoadingCast(false);
|
||||
return;
|
||||
|
|
@ -240,7 +240,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Handle TMDB IDs
|
||||
if (id.startsWith('tmdb:')) {
|
||||
const tmdbId = id.split(':')[1];
|
||||
logger.log('[loadCast] Using TMDB ID directly:', tmdbId);
|
||||
if (__DEV__) logger.log('[loadCast] Using TMDB ID directly:', tmdbId);
|
||||
const castData = await tmdbService.getCredits(parseInt(tmdbId), type);
|
||||
if (castData && castData.cast) {
|
||||
const formattedCast = castData.cast.map((actor: any) => ({
|
||||
|
|
@ -249,7 +249,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
character: actor.character,
|
||||
profile_path: actor.profile_path
|
||||
}));
|
||||
logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`);
|
||||
if (__DEV__) logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`);
|
||||
setCast(formattedCast);
|
||||
cacheService.setCast(id, type, formattedCast);
|
||||
setLoadingCast(false);
|
||||
|
|
@ -260,12 +260,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Handle IMDb IDs or convert to TMDB ID
|
||||
let tmdbId;
|
||||
if (id.startsWith('tt')) {
|
||||
logger.log('[loadCast] Converting IMDb ID to TMDB ID');
|
||||
if (__DEV__) logger.log('[loadCast] Converting IMDb ID to TMDB ID');
|
||||
tmdbId = await tmdbService.findTMDBIdByIMDB(id);
|
||||
}
|
||||
|
||||
if (tmdbId) {
|
||||
logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId);
|
||||
if (__DEV__) logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId);
|
||||
const castData = await tmdbService.getCredits(tmdbId, type);
|
||||
if (castData && castData.cast) {
|
||||
const formattedCast = castData.cast.map((actor: any) => ({
|
||||
|
|
@ -274,12 +274,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
character: actor.character,
|
||||
profile_path: actor.profile_path
|
||||
}));
|
||||
logger.log(`[loadCast] Found ${formattedCast.length} cast members`);
|
||||
if (__DEV__) logger.log(`[loadCast] Found ${formattedCast.length} cast members`);
|
||||
setCast(formattedCast);
|
||||
cacheService.setCast(id, type, formattedCast);
|
||||
}
|
||||
} else {
|
||||
logger.warn('[loadCast] Could not find TMDB ID for cast fetch');
|
||||
if (__DEV__) logger.warn('[loadCast] Could not find TMDB ID for cast fetch');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[loadCast] Failed to load cast:', error);
|
||||
|
|
@ -325,7 +325,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const tmdbId = id.split(':')[1];
|
||||
// For TMDB IDs, we need to handle metadata differently
|
||||
if (type === 'movie') {
|
||||
logger.log('Fetching movie details from TMDB for:', tmdbId);
|
||||
if (__DEV__) logger.log('Fetching movie details from TMDB for:', tmdbId);
|
||||
const movieDetails = await tmdbService.getMovieDetails(tmdbId);
|
||||
if (movieDetails) {
|
||||
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id;
|
||||
|
|
@ -388,7 +388,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}
|
||||
} else if (type === 'series') {
|
||||
// Handle TV shows with TMDB IDs
|
||||
logger.log('Fetching TV show details from TMDB for:', tmdbId);
|
||||
if (__DEV__) logger.log('Fetching TV show details from TMDB for:', tmdbId);
|
||||
try {
|
||||
const showDetails = await tmdbService.getTVShowDetails(parseInt(tmdbId));
|
||||
if (showDetails) {
|
||||
|
|
@ -443,7 +443,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const logoUrl = await tmdbService.getTvShowImages(tmdbId);
|
||||
if (logoUrl) {
|
||||
formattedShow.logo = logoUrl;
|
||||
logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch logo from TMDB:', error);
|
||||
|
|
@ -455,7 +455,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Load series data (episodes)
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
loadSeriesData().catch(console.error);
|
||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||
|
||||
const isInLib = catalogService.getLibraryItems().some(item => item.id === id);
|
||||
setInLibrary(isInLib);
|
||||
|
|
@ -502,7 +502,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
throw new Error('Content not found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load metadata:', error);
|
||||
if (__DEV__) console.error('Failed to load metadata:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to load content';
|
||||
setError(errorMessage);
|
||||
|
||||
|
|
@ -522,7 +522,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// First check if we have episode data from the addon
|
||||
const addonVideos = metadata?.videos;
|
||||
if (addonVideos && Array.isArray(addonVideos) && addonVideos.length > 0) {
|
||||
logger.log(`🎬 Found ${addonVideos.length} episodes from addon metadata for ${metadata?.name || id}`);
|
||||
if (__DEV__) logger.log(`🎬 Found ${addonVideos.length} episodes from addon metadata for ${metadata?.name || id}`);
|
||||
|
||||
// Group addon episodes by season
|
||||
const groupedAddonEpisodes: GroupedEpisodes = {};
|
||||
|
|
@ -562,7 +562,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
groupedAddonEpisodes[parseInt(season)].sort((a, b) => a.episode_number - b.episode_number);
|
||||
});
|
||||
|
||||
logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`);
|
||||
if (__DEV__) logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`);
|
||||
|
||||
// Fetch season posters from TMDB
|
||||
try {
|
||||
|
|
@ -582,7 +582,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}));
|
||||
}
|
||||
});
|
||||
logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
|
||||
if (__DEV__) logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -697,7 +697,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load episodes:', error);
|
||||
if (__DEV__) console.error('Failed to load episodes:', error);
|
||||
} finally {
|
||||
setLoadingSeasons(false);
|
||||
}
|
||||
|
|
@ -724,7 +724,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const loadStreams = async () => {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
console.log('🚀 [loadStreams] START - Loading streams for:', id);
|
||||
if (__DEV__) console.log('🚀 [loadStreams] START - Loading streams for:', id);
|
||||
updateLoadingState();
|
||||
|
||||
// Reset scraper tracking
|
||||
|
|
@ -732,21 +732,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setActiveFetchingScrapers([]);
|
||||
|
||||
// Get TMDB ID for external sources and determine the correct ID for Stremio addons
|
||||
console.log('🔍 [loadStreams] Getting TMDB ID for:', id);
|
||||
if (__DEV__) console.log('🔍 [loadStreams] Getting TMDB ID for:', id);
|
||||
let tmdbId;
|
||||
let stremioId = id; // Default to original ID
|
||||
|
||||
if (id.startsWith('tmdb:')) {
|
||||
tmdbId = id.split(':')[1];
|
||||
console.log('✅ [loadStreams] Using TMDB ID from ID:', tmdbId);
|
||||
if (__DEV__) console.log('✅ [loadStreams] Using TMDB ID from ID:', tmdbId);
|
||||
|
||||
// Try to get IMDb ID from metadata first, then convert if needed
|
||||
if (metadata?.imdb_id) {
|
||||
stremioId = metadata.imdb_id;
|
||||
console.log('✅ [loadStreams] Using IMDb ID from metadata for Stremio:', stremioId);
|
||||
if (__DEV__) console.log('✅ [loadStreams] Using IMDb ID from metadata for Stremio:', stremioId);
|
||||
} else if (imdbId) {
|
||||
stremioId = imdbId;
|
||||
console.log('✅ [loadStreams] Using stored IMDb ID for Stremio:', stremioId);
|
||||
if (__DEV__) console.log('✅ [loadStreams] Using stored IMDb ID for Stremio:', stremioId);
|
||||
} else {
|
||||
// Convert TMDB ID to IMDb ID for Stremio addons (they expect IMDb format)
|
||||
try {
|
||||
|
|
@ -760,24 +760,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
if (externalIds?.imdb_id) {
|
||||
stremioId = externalIds.imdb_id;
|
||||
console.log('✅ [loadStreams] Converted TMDB to IMDb ID for Stremio:', stremioId);
|
||||
if (__DEV__) console.log('✅ [loadStreams] Converted TMDB to IMDb ID for Stremio:', stremioId);
|
||||
} else {
|
||||
console.log('⚠️ [loadStreams] No IMDb ID found for TMDB ID, using original:', stremioId);
|
||||
if (__DEV__) console.log('⚠️ [loadStreams] No IMDb ID found for TMDB ID, using original:', stremioId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ [loadStreams] Failed to convert TMDB to IMDb, using original ID:', error);
|
||||
if (__DEV__) console.log('⚠️ [loadStreams] Failed to convert TMDB to IMDb, using original ID:', error);
|
||||
}
|
||||
}
|
||||
} else if (id.startsWith('tt')) {
|
||||
// This is already an IMDB ID, perfect for Stremio
|
||||
stremioId = id;
|
||||
console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
|
||||
if (__DEV__) console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
|
||||
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
|
||||
console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
|
||||
if (__DEV__) console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
|
||||
} else {
|
||||
tmdbId = id;
|
||||
stremioId = id;
|
||||
console.log('ℹ️ [loadStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
|
||||
if (__DEV__) console.log('ℹ️ [loadStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
|
||||
}
|
||||
|
||||
// Initialize scraper tracking
|
||||
|
|
@ -851,11 +851,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize scraper tracking:', error);
|
||||
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
|
||||
}
|
||||
|
||||
// Start Stremio request using the converted ID format
|
||||
console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
|
||||
if (__DEV__) console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
|
||||
processStremioSource(type, stremioId, false);
|
||||
|
||||
// Monitor scraper completion status instead of using fixed timeout
|
||||
|
|
@ -881,7 +881,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}, 30000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [loadStreams] Failed to load streams:', error);
|
||||
if (__DEV__) console.error('❌ [loadStreams] Failed to load streams:', error);
|
||||
setError('Failed to load streams');
|
||||
setLoadingStreams(false);
|
||||
}
|
||||
|
|
@ -890,7 +890,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const loadEpisodeStreams = async (episodeId: string) => {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId);
|
||||
if (__DEV__) console.log('🚀 [loadEpisodeStreams] START - Loading episode streams for:', episodeId);
|
||||
updateEpisodeLoadingState();
|
||||
|
||||
// Reset scraper tracking for episodes
|
||||
|
|
@ -968,28 +968,28 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize episode scraper tracking:', error);
|
||||
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
|
||||
}
|
||||
|
||||
// Get TMDB ID for external sources and determine the correct ID for Stremio addons
|
||||
console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
|
||||
if (__DEV__) console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
|
||||
let tmdbId;
|
||||
let stremioEpisodeId = episodeId; // Default to original episode ID
|
||||
|
||||
if (id.startsWith('tmdb:')) {
|
||||
tmdbId = id.split(':')[1];
|
||||
console.log('✅ [loadEpisodeStreams] Using TMDB ID from ID:', tmdbId);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using TMDB ID from ID:', tmdbId);
|
||||
|
||||
// Try to get IMDb ID from metadata first, then convert if needed
|
||||
if (metadata?.imdb_id) {
|
||||
// Replace the series ID in episodeId with the IMDb ID
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${metadata.imdb_id}:${season}:${episode}`;
|
||||
console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
|
||||
} else if (imdbId) {
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${imdbId}:${season}:${episode}`;
|
||||
console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
} else {
|
||||
// Convert TMDB ID to IMDb ID for Stremio addons
|
||||
try {
|
||||
|
|
@ -998,33 +998,33 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (externalIds?.imdb_id) {
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${externalIds.imdb_id}:${season}:${episode}`;
|
||||
console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
} else {
|
||||
console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
|
||||
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using original episode ID:', error);
|
||||
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using original episode ID:', error);
|
||||
}
|
||||
}
|
||||
} else if (id.startsWith('tt')) {
|
||||
// This is already an IMDB ID, perfect for Stremio
|
||||
console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
|
||||
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
|
||||
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
|
||||
console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
|
||||
} else {
|
||||
tmdbId = id;
|
||||
console.log('ℹ️ [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
|
||||
if (__DEV__) console.log('ℹ️ [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
|
||||
}
|
||||
|
||||
// Extract episode info from the episodeId for logging
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
const episodeQuery = `?s=${season}&e=${episode}`;
|
||||
console.log(`ℹ️ [loadEpisodeStreams] Episode query: ${episodeQuery}`);
|
||||
if (__DEV__) console.log(`ℹ️ [loadEpisodeStreams] Episode query: ${episodeQuery}`);
|
||||
|
||||
console.log('🔄 [loadEpisodeStreams] Starting stream requests');
|
||||
if (__DEV__) console.log('🔄 [loadEpisodeStreams] Starting stream requests');
|
||||
|
||||
// Start Stremio request using the converted episode ID format
|
||||
console.log('🎬 [loadEpisodeStreams] Using episode ID for Stremio addons:', stremioEpisodeId);
|
||||
if (__DEV__) console.log('🎬 [loadEpisodeStreams] Using episode ID for Stremio addons:', stremioEpisodeId);
|
||||
processStremioSource('series', stremioEpisodeId, true);
|
||||
|
||||
// Monitor scraper completion status instead of using fixed timeout
|
||||
|
|
@ -1040,7 +1040,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
};
|
||||
|
||||
// Check completion less frequently to reduce CPU load
|
||||
const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 2000);
|
||||
const episodeCompletionInterval = setInterval(checkEpisodeScrapersCompletion, 3000);
|
||||
|
||||
// Fallback timeout after 30 seconds
|
||||
const episodeFallbackTimeout = setTimeout(() => {
|
||||
|
|
@ -1050,7 +1050,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}, 30000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [loadEpisodeStreams] Failed to load episode streams:', error);
|
||||
if (__DEV__) console.error('❌ [loadEpisodeStreams] Failed to load episode streams:', error);
|
||||
setError('Failed to load episode streams');
|
||||
setLoadingEpisodeStreams(false);
|
||||
}
|
||||
|
|
@ -1103,7 +1103,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
useEffect(() => {
|
||||
if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) {
|
||||
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
|
||||
loadSeriesData().catch(console.error);
|
||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||
}
|
||||
}, [metadata?.videos, type]);
|
||||
|
||||
|
|
@ -1126,7 +1126,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
setRecommendations(formattedRecommendations);
|
||||
} catch (error) {
|
||||
console.error('Failed to load recommendations:', error);
|
||||
if (__DEV__) console.error('Failed to load recommendations:', error);
|
||||
setRecommendations([]);
|
||||
} finally {
|
||||
setLoadingRecommendations(false);
|
||||
|
|
@ -1141,24 +1141,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const tmdbService = TMDBService.getInstance();
|
||||
const fetchedTmdbId = await tmdbService.extractTMDBIdFromStremioId(id);
|
||||
if (fetchedTmdbId) {
|
||||
console.log('[useMetadata] extracted TMDB id from content id', { id, fetchedTmdbId });
|
||||
if (__DEV__) console.log('[useMetadata] extracted TMDB id from content id', { id, fetchedTmdbId });
|
||||
setTmdbId(fetchedTmdbId);
|
||||
// Fetch certification
|
||||
const certification = await tmdbService.getCertification(type, fetchedTmdbId);
|
||||
if (certification) {
|
||||
console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
|
||||
if (__DEV__) console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
|
||||
setMetadata(prev => prev ? {
|
||||
...prev,
|
||||
certification
|
||||
} : null);
|
||||
} else {
|
||||
console.warn('[useMetadata] certification not returned from TMDB (extract path)', { type, fetchedTmdbId });
|
||||
if (__DEV__) console.warn('[useMetadata] certification not returned from TMDB (extract path)', { type, fetchedTmdbId });
|
||||
}
|
||||
} else {
|
||||
console.warn('[useMetadata] Could not determine TMDB ID for recommendations / certification', { id });
|
||||
if (__DEV__) console.warn('[useMetadata] Could not determine TMDB ID for recommendations / certification', { id });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[useMetadata] Error fetching TMDB ID (extract path):', error);
|
||||
if (__DEV__) console.error('[useMetadata] Error fetching TMDB ID (extract path):', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1168,7 +1168,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
useEffect(() => {
|
||||
if (tmdbId) {
|
||||
console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
|
||||
if (__DEV__) console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
|
||||
loadRecommendations();
|
||||
// Reset recommendations when tmdbId changes
|
||||
return () => {
|
||||
|
|
@ -1183,27 +1183,27 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const maybeAttachCertification = async () => {
|
||||
try {
|
||||
if (!metadata) {
|
||||
console.warn('[useMetadata] skip certification attach: metadata not ready');
|
||||
if (__DEV__) console.warn('[useMetadata] skip certification attach: metadata not ready');
|
||||
return;
|
||||
}
|
||||
if (!tmdbId) {
|
||||
console.warn('[useMetadata] skip certification attach: tmdbId not available yet');
|
||||
if (__DEV__) console.warn('[useMetadata] skip certification attach: tmdbId not available yet');
|
||||
return;
|
||||
}
|
||||
if ((metadata as any).certification) {
|
||||
console.log('[useMetadata] certification already present on metadata; skipping fetch');
|
||||
if (__DEV__) console.log('[useMetadata] certification already present on metadata; skipping fetch');
|
||||
return;
|
||||
}
|
||||
const tmdbSvc = TMDBService.getInstance();
|
||||
const cert = await tmdbSvc.getCertification(type, tmdbId);
|
||||
if (cert) {
|
||||
console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
|
||||
if (__DEV__) console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
|
||||
setMetadata(prev => prev ? { ...prev, certification: cert } : prev);
|
||||
} else {
|
||||
console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
|
||||
if (__DEV__) console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useMetadata] error attaching certification', err);
|
||||
if (__DEV__) console.error('[useMetadata] error attaching certification', err);
|
||||
}
|
||||
};
|
||||
maybeAttachCertification();
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
});
|
||||
} catch (error) {
|
||||
// Silently handle any animation errors
|
||||
console.warn('Animation error in enterAnimations:', error);
|
||||
if (__DEV__) console.warn('Animation error in enterAnimations:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
try {
|
||||
runOnUI(enterAnimations)();
|
||||
} catch (error) {
|
||||
console.warn('Failed to run enter animations:', error);
|
||||
if (__DEV__) console.warn('Failed to run enter animations:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -114,14 +114,14 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
easing: easings.fast
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Animation error in updateProgress:', error);
|
||||
if (__DEV__) console.warn('Animation error in updateProgress:', error);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
runOnUI(updateProgress)();
|
||||
} catch (error) {
|
||||
console.warn('Failed to run progress animation:', error);
|
||||
if (__DEV__) console.warn('Failed to run progress animation:', error);
|
||||
}
|
||||
}, [watchProgress]);
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
cancelAnimation(headerProgress);
|
||||
cancelAnimation(staticHeaderElementsY);
|
||||
} catch (error) {
|
||||
console.warn('Error canceling animations:', error);
|
||||
if (__DEV__) console.warn('Error canceling animations:', error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
|
@ -166,7 +166,7 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Animation error in scroll handler:', error);
|
||||
if (__DEV__) console.warn('Animation error in scroll handler:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export const useSettings = () => {
|
|||
if (merged) setSettings({ ...DEFAULT_SETTINGS, ...merged });
|
||||
else setSettings(DEFAULT_SETTINGS);
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error);
|
||||
if (__DEV__) console.error('Failed to load settings:', error);
|
||||
// Fallback to default settings on error
|
||||
setSettings(DEFAULT_SETTINGS);
|
||||
}
|
||||
|
|
@ -195,18 +195,18 @@ export const useSettings = () => {
|
|||
// Ensure a current scope exists to avoid future loads missing the chosen scope
|
||||
await AsyncStorage.setItem('@user:current', scope);
|
||||
setSettings(newSettings);
|
||||
console.log(`Setting updated: ${key}`, value);
|
||||
if (__DEV__) console.log(`Setting updated: ${key}`, value);
|
||||
|
||||
// Notify all subscribers that settings have changed (if requested)
|
||||
if (emitEvent) {
|
||||
console.log('Emitting settings change event');
|
||||
if (__DEV__) console.log('Emitting settings change event');
|
||||
settingsEmitter.emit();
|
||||
}
|
||||
|
||||
// If authenticated, push settings to server to prevent overwrite on next pull
|
||||
try { syncService.pushSettings(); } catch {}
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error);
|
||||
if (__DEV__) console.error('Failed to save settings:', error);
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,8 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import UpdateService from '../services/updateService';
|
||||
import UpdateService, { UpdateInfo } from '../services/updateService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
interface UpdateInfo {
|
||||
isAvailable: boolean;
|
||||
manifest?: {
|
||||
id: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UseUpdatePopupReturn {
|
||||
showUpdatePopup: boolean;
|
||||
updateInfo: UpdateInfo;
|
||||
|
|
@ -24,6 +15,7 @@ interface UseUpdatePopupReturn {
|
|||
|
||||
const UPDATE_POPUP_STORAGE_KEY = '@update_popup_dismissed';
|
||||
const UPDATE_LATER_STORAGE_KEY = '@update_later_timestamp';
|
||||
const UPDATE_LAST_CHECK_TS_KEY = '@update_last_check_ts';
|
||||
|
||||
export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
||||
const [showUpdatePopup, setShowUpdatePopup] = useState(false);
|
||||
|
|
@ -59,7 +51,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
setShowUpdatePopup(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
if (__DEV__) console.error('Error checking for updates:', error);
|
||||
// Don't show popup on error, just log it
|
||||
}
|
||||
}, [updateInfo.manifest?.id]);
|
||||
|
|
@ -102,7 +94,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
setShowUpdatePopup(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error installing update:', error);
|
||||
if (__DEV__) console.error('Error installing update:', error);
|
||||
Alert.alert(
|
||||
'Update Error',
|
||||
'An error occurred while installing the update. Please try again later.'
|
||||
|
|
@ -120,7 +112,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
await AsyncStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
|
||||
setShowUpdatePopup(false);
|
||||
} catch (error) {
|
||||
console.error('Error storing update later preference:', error);
|
||||
if (__DEV__) console.error('Error storing update later preference:', error);
|
||||
setShowUpdatePopup(false);
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -134,7 +126,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
}
|
||||
setShowUpdatePopup(false);
|
||||
} catch (error) {
|
||||
console.error('Error storing dismiss preference:', error);
|
||||
if (__DEV__) console.error('Error storing dismiss preference:', error);
|
||||
setShowUpdatePopup(false);
|
||||
}
|
||||
}, [updateInfo.manifest?.id]);
|
||||
|
|
@ -143,7 +135,21 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
useEffect(() => {
|
||||
// Add a small delay to ensure the app is fully loaded
|
||||
const timer = setTimeout(() => {
|
||||
checkForUpdates();
|
||||
(async () => {
|
||||
try {
|
||||
const lastCheckTs = await AsyncStorage.getItem(UPDATE_LAST_CHECK_TS_KEY);
|
||||
const last = lastCheckTs ? parseInt(lastCheckTs, 10) : 0;
|
||||
const now = Date.now();
|
||||
const twentyFourHours = 24 * 60 * 60 * 1000;
|
||||
if (now - last < twentyFourHours) {
|
||||
return; // Throttle: only auto-check once per 24h
|
||||
}
|
||||
await checkForUpdates();
|
||||
await AsyncStorage.setItem(UPDATE_LAST_CHECK_TS_KEY, String(now));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
})();
|
||||
}, 2000); // 2 second delay
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ const CastMoviesScreen: React.FC = () => {
|
|||
setMovies(allCredits);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching cast credits:', error);
|
||||
if (__DEV__) console.error('Error fetching cast credits:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -206,8 +206,9 @@ const CastMoviesScreen: React.FC = () => {
|
|||
}, [displayLimit, filteredAndSortedMovies.length, isLoadingMore]);
|
||||
|
||||
const handleMoviePress = async (movie: CastMovie) => {
|
||||
console.log('=== CastMoviesScreen: Movie Press ===');
|
||||
console.log('Movie data:', {
|
||||
if (__DEV__) {
|
||||
console.log('=== CastMoviesScreen: Movie Press ===');
|
||||
console.log('Movie data:', {
|
||||
id: movie.id,
|
||||
title: movie.title,
|
||||
media_type: movie.media_type,
|
||||
|
|
@ -219,15 +220,15 @@ const CastMoviesScreen: React.FC = () => {
|
|||
});
|
||||
|
||||
try {
|
||||
console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString());
|
||||
if (__DEV__) console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString());
|
||||
|
||||
// Get Stremio ID using catalogService
|
||||
const stremioId = await catalogService.getStremioId(movie.media_type, movie.id.toString());
|
||||
|
||||
console.log('Stremio ID result:', stremioId);
|
||||
if (__DEV__) console.log('Stremio ID result:', stremioId);
|
||||
|
||||
if (stremioId) {
|
||||
console.log('Successfully found Stremio ID, navigating to Metadata with:', {
|
||||
if (__DEV__) console.log('Successfully found Stremio ID, navigating to Metadata with:', {
|
||||
id: stremioId,
|
||||
type: movie.media_type
|
||||
});
|
||||
|
|
@ -235,7 +236,7 @@ const CastMoviesScreen: React.FC = () => {
|
|||
// Convert TMDB media type to Stremio media type
|
||||
const stremioType = movie.media_type === 'tv' ? 'series' : movie.media_type;
|
||||
|
||||
console.log('Navigating with Stremio type conversion:', {
|
||||
if (__DEV__) console.log('Navigating with Stremio type conversion:', {
|
||||
originalType: movie.media_type,
|
||||
stremioType: stremioType,
|
||||
id: stremioId
|
||||
|
|
@ -248,15 +249,17 @@ const CastMoviesScreen: React.FC = () => {
|
|||
})
|
||||
);
|
||||
} else {
|
||||
console.warn('Stremio ID is null/undefined for movie:', movie.title);
|
||||
if (__DEV__) console.warn('Stremio ID is null/undefined for movie:', movie.title);
|
||||
throw new Error('Could not find Stremio ID');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('=== Error in handleMoviePress ===');
|
||||
console.error('Movie:', movie.title);
|
||||
console.error('Error details:', error);
|
||||
console.error('Error message:', error.message);
|
||||
console.error('Error stack:', error.stack);
|
||||
if (__DEV__) {
|
||||
console.error('=== Error in handleMoviePress ===');
|
||||
console.error('Movie:', movie.title);
|
||||
console.error('Error details:', error);
|
||||
console.error('Error message:', error.message);
|
||||
console.error('Error stack:', error.stack);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Error',
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ const HeroCatalogsScreen: React.FC = () => {
|
|||
|
||||
setCatalogs(catalogItems);
|
||||
} catch (error) {
|
||||
console.error('Failed to load catalogs:', error);
|
||||
if (__DEV__) console.error('Failed to load catalogs:', error);
|
||||
Alert.alert('Error', 'Failed to load catalogs');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ const HomeScreen = () => {
|
|||
let catalogIndex = 0;
|
||||
|
||||
// Limit concurrent catalog loading to prevent overwhelming the system
|
||||
const MAX_CONCURRENT_CATALOGS = 2; // Very low concurrency to reduce heating
|
||||
const MAX_CONCURRENT_CATALOGS = 1; // Single catalog at a time to minimize heating
|
||||
let activeCatalogLoads = 0;
|
||||
const catalogQueue: (() => Promise<void>)[] = [];
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ const HomeScreen = () => {
|
|||
catalogLoader().finally(async () => {
|
||||
activeCatalogLoads--;
|
||||
// Yield to event loop to avoid JS thread starvation and reduce heating
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
processCatalogQueue(); // Process next in queue
|
||||
});
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ const HomeScreen = () => {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[HomeScreen] Failed to load ${catalog.name} from ${addon.name}:`, error);
|
||||
if (__DEV__) console.error(`[HomeScreen] Failed to load ${catalog.name} from ${addon.name}:`, error);
|
||||
} finally {
|
||||
setLoadedCatalogCount(prev => {
|
||||
const next = prev + 1;
|
||||
|
|
@ -285,7 +285,7 @@ const HomeScreen = () => {
|
|||
// Start processing the catalog queue (parallel fetching continues in background)
|
||||
processCatalogQueue();
|
||||
} catch (error) {
|
||||
console.error('[HomeScreen] Error in progressive catalog loading:', error);
|
||||
if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error);
|
||||
setCatalogsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -414,7 +414,7 @@ const HomeScreen = () => {
|
|||
try {
|
||||
ExpoImage.clearMemoryCache();
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear image cache:', error);
|
||||
if (__DEV__) console.warn('Failed to clear image cache:', error);
|
||||
}
|
||||
};
|
||||
}, [currentTheme.colors.darkBackground]);
|
||||
|
|
@ -527,7 +527,7 @@ const HomeScreen = () => {
|
|||
setHasContinueWatching(hasContent);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[HomeScreen] Error refreshing continue watching:', error);
|
||||
if (__DEV__) console.error('[HomeScreen] Error refreshing continue watching:', error);
|
||||
setHasContinueWatching(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ const LogoSourceSettings = () => {
|
|||
try {
|
||||
await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId);
|
||||
} catch (e) {
|
||||
console.error('Error saving selected show:', e);
|
||||
if (__DEV__) console.error('Error saving selected show:', e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -621,7 +621,7 @@ const LogoSourceSettings = () => {
|
|||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading selected show:', e);
|
||||
if (__DEV__) console.error('Error loading selected show:', e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ const MetadataScreen: React.FC = () => {
|
|||
// Debug logging for color extraction timing
|
||||
useEffect(() => {
|
||||
if (__DEV__ && heroImageUri && dominantColor) {
|
||||
console.log('[MetadataScreen] Dynamic background color:', {
|
||||
if (__DEV__) console.log('[MetadataScreen] Dynamic background color:', {
|
||||
dominantColor,
|
||||
fallback: currentTheme.colors.darkBackground,
|
||||
finalColor: dynamicBackgroundColor,
|
||||
|
|
@ -242,7 +242,7 @@ const MetadataScreen: React.FC = () => {
|
|||
const isAuthenticated = await traktService.isAuthenticated();
|
||||
|
||||
if (!isAuthenticated) {
|
||||
console.log(`[MetadataScreen] Not authenticated with Trakt`);
|
||||
if (__DEV__) console.log(`[MetadataScreen] Not authenticated with Trakt`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ const MetadataScreen: React.FC = () => {
|
|||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[MetadataScreen] Failed to fetch Trakt progress:`, error);
|
||||
if (__DEV__) console.error(`[MetadataScreen] Failed to fetch Trakt progress:`, error);
|
||||
}
|
||||
}, [shouldLoadSecondaryData, metadata, id, type]);
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ const MetadataScreen: React.FC = () => {
|
|||
const timer = setTimeout(() => {
|
||||
const renderTime = Date.now() - startTime;
|
||||
if (renderTime > 100) {
|
||||
console.warn(`[MetadataScreen] Slow render detected: ${renderTime}ms for ${metadata.name}`);
|
||||
if (__DEV__) console.warn(`[MetadataScreen] Slow render detected: ${renderTime}ms for ${metadata.name}`);
|
||||
}
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
|
|
@ -448,7 +448,7 @@ const MetadataScreen: React.FC = () => {
|
|||
const handleEpisodeSelect = useCallback((episode: Episode) => {
|
||||
if (!isScreenFocused) return;
|
||||
|
||||
console.log('[MetadataScreen] Selected Episode:', episode.episode_number, episode.season_number);
|
||||
if (__DEV__) console.log('[MetadataScreen] Selected Episode:', episode.episode_number, episode.season_number);
|
||||
const episodeId = episode.stremioId || `${id}:${episode.season_number}:${episode.episode_number}`;
|
||||
|
||||
// Optimize navigation with requestAnimationFrame
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ const OnboardingScreen = () => {
|
|||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving onboarding status:', error);
|
||||
if (__DEV__) console.error('Error saving onboarding status:', error);
|
||||
navigation.reset({ index: 0, routes: [{ name: user ? 'MainTabs' : 'Account', params: !user ? ({ fromOnboarding: true } as any) : undefined }] });
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
Modal
|
||||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useTraktContext } from '../contexts/TraktContext';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
|
@ -58,7 +58,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify([defaultProfile]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading profiles:', error);
|
||||
if (__DEV__) console.error('Error loading profiles:', error);
|
||||
Alert.alert('Error', 'Failed to load profiles');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
|
@ -84,7 +84,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
try {
|
||||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
|
||||
} catch (error) {
|
||||
console.error('Error saving profiles:', error);
|
||||
if (__DEV__) console.error('Error saving profiles:', error);
|
||||
Alert.alert('Error', 'Failed to save profiles');
|
||||
}
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ const SettingsScreen: React.FC = () => {
|
|||
// Refresh auth status
|
||||
if (isAuthenticated || userProfile) {
|
||||
// Just to be cautious, log the current state
|
||||
console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
|
||||
if (__DEV__) console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
|
||||
}
|
||||
refreshAuthStatus();
|
||||
});
|
||||
|
|
@ -291,7 +291,7 @@ const SettingsScreen: React.FC = () => {
|
|||
setMdblistKeySet(!!mdblistKey);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading settings data:', error);
|
||||
if (__DEV__) console.error('Error loading settings data:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -343,7 +343,7 @@ const SettingsScreen: React.FC = () => {
|
|||
Alert.alert("Success", "MDBList cache has been cleared.");
|
||||
} catch (error) {
|
||||
Alert.alert("Error", "Could not clear MDBList cache.");
|
||||
console.error('Error clearing MDBList cache:', error);
|
||||
if (__DEV__) console.error('Error clearing MDBList cache:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ export const StreamsScreen = () => {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail);
|
||||
if (__DEV__) console.log('[StreamsScreen] Received thumbnail from params:', episodeThumbnail);
|
||||
}, [episodeThumbnail]);
|
||||
|
||||
// Pause trailer when StreamsScreen is opened
|
||||
|
|
@ -1081,29 +1081,29 @@ export const StreamsScreen = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log(`Attempting to open stream in ${settings.preferredPlayer}`);
|
||||
if (__DEV__) console.log(`Attempting to open stream in ${settings.preferredPlayer}`);
|
||||
|
||||
// Try each URL format in sequence
|
||||
const tryNextUrl = (index: number) => {
|
||||
if (index >= externalPlayerUrls.length) {
|
||||
console.log(`All ${settings.preferredPlayer} formats failed, falling back to direct URL`);
|
||||
if (__DEV__) console.log(`All ${settings.preferredPlayer} formats failed, falling back to direct URL`);
|
||||
// Try direct URL as last resort
|
||||
Linking.openURL(stream.url)
|
||||
.then(() => console.log('Opened with direct URL'))
|
||||
.then(() => { if (__DEV__) console.log('Opened with direct URL'); })
|
||||
.catch(() => {
|
||||
console.log('Direct URL failed, falling back to built-in player');
|
||||
if (__DEV__) console.log('Direct URL failed, falling back to built-in player');
|
||||
navigateToPlayer(stream);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const url = externalPlayerUrls[index];
|
||||
console.log(`Trying ${settings.preferredPlayer} URL format ${index + 1}: ${url}`);
|
||||
if (__DEV__) console.log(`Trying ${settings.preferredPlayer} URL format ${index + 1}: ${url}`);
|
||||
|
||||
Linking.openURL(url)
|
||||
.then(() => console.log(`Successfully opened stream with ${settings.preferredPlayer} format ${index + 1}`))
|
||||
.then(() => { if (__DEV__) console.log(`Successfully opened stream with ${settings.preferredPlayer} format ${index + 1}`); })
|
||||
.catch(err => {
|
||||
console.log(`Format ${index + 1} failed: ${err.message}`, err);
|
||||
if (__DEV__) console.log(`Format ${index + 1} failed: ${err.message}`, err);
|
||||
tryNextUrl(index + 1);
|
||||
});
|
||||
};
|
||||
|
|
@ -1112,7 +1112,7 @@ export const StreamsScreen = () => {
|
|||
tryNextUrl(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error with ${settings.preferredPlayer}:`, error);
|
||||
if (__DEV__) console.error(`Error with ${settings.preferredPlayer}:`, error);
|
||||
// Fallback to the built-in player
|
||||
navigateToPlayer(stream);
|
||||
}
|
||||
|
|
@ -1120,18 +1120,18 @@ export const StreamsScreen = () => {
|
|||
// For Android with external player preference
|
||||
else if (Platform.OS === 'android' && settings.useExternalPlayer) {
|
||||
try {
|
||||
console.log('Opening stream with Android native app chooser');
|
||||
if (__DEV__) console.log('Opening stream with Android native app chooser');
|
||||
|
||||
// For Android, determine if the URL is a direct http/https URL or a magnet link
|
||||
const isMagnet = stream.url.startsWith('magnet:');
|
||||
|
||||
if (isMagnet) {
|
||||
// For magnet links, open directly which will trigger the torrent app chooser
|
||||
console.log('Opening magnet link directly');
|
||||
if (__DEV__) console.log('Opening magnet link directly');
|
||||
Linking.openURL(stream.url)
|
||||
.then(() => console.log('Successfully opened magnet link'))
|
||||
.catch(err => {
|
||||
console.error('Failed to open magnet link:', err);
|
||||
.then(() => { if (__DEV__) console.log('Successfully opened magnet link'); })
|
||||
.catch(err => {
|
||||
if (__DEV__) console.error('Failed to open magnet link:', err);
|
||||
// No good fallback for magnet links
|
||||
navigateToPlayer(stream);
|
||||
});
|
||||
|
|
@ -1145,12 +1145,12 @@ export const StreamsScreen = () => {
|
|||
});
|
||||
|
||||
if (!success) {
|
||||
console.log('VideoPlayerService failed, falling back to built-in player');
|
||||
if (__DEV__) console.log('VideoPlayerService failed, falling back to built-in player');
|
||||
navigateToPlayer(stream);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error with external player:', error);
|
||||
if (__DEV__) console.error('Error with external player:', error);
|
||||
// Fallback to the built-in player
|
||||
navigateToPlayer(stream);
|
||||
}
|
||||
|
|
@ -1161,7 +1161,7 @@ export const StreamsScreen = () => {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in handleStreamPress:', error);
|
||||
if (__DEV__) console.error('Error in handleStreamPress:', error);
|
||||
// Final fallback: Use built-in player
|
||||
navigateToPlayer(stream);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const UpdateScreen: React.FC = () => {
|
|||
setLastOperation('No updates available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
if (__DEV__) console.error('Error checking for updates:', error);
|
||||
setUpdateStatus('error');
|
||||
setLastOperation(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
Alert.alert('Error', 'Failed to check for updates');
|
||||
|
|
@ -144,7 +144,7 @@ const UpdateScreen: React.FC = () => {
|
|||
Alert.alert('No Update', 'No update available to install');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error installing update:', error);
|
||||
if (__DEV__) console.error('Error installing update:', error);
|
||||
setUpdateStatus('error');
|
||||
setLastOperation(`Installation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
Alert.alert('Error', 'Failed to install update');
|
||||
|
|
@ -226,7 +226,7 @@ const UpdateScreen: React.FC = () => {
|
|||
setLastOperation('Update server is not reachable');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error testing connectivity:', error);
|
||||
if (__DEV__) console.error('Error testing connectivity:', error);
|
||||
setLastOperation(`Connectivity test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
const logs = UpdateService.getLogs();
|
||||
setLogs(logs);
|
||||
|
|
@ -241,7 +241,7 @@ const UpdateScreen: React.FC = () => {
|
|||
setLogs(logs);
|
||||
setLastOperation('Asset URL testing completed');
|
||||
} catch (error) {
|
||||
console.error('Error testing asset URLs:', error);
|
||||
if (__DEV__) console.error('Error testing asset URLs:', error);
|
||||
setLastOperation(`Asset URL test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
const logs = UpdateService.getLogs();
|
||||
setLogs(logs);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class SyncService {
|
|||
logger.log('[Sync] fullPull (debounced) start');
|
||||
this.fullPull()
|
||||
.then(() => logger.log('[Sync] fullPull (debounced) done'))
|
||||
.catch((e) => console.warn('[Sync] fullPull (debounced) error', e));
|
||||
.catch((e) => { if (__DEV__) console.warn('[Sync] fullPull (debounced) error', e); });
|
||||
}, 300);
|
||||
};
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ class SyncService {
|
|||
logger.log(`[Sync][rt] user_library ${payload.eventType} ${mediaType}:${mediaId}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Sync][rt] user_library handler error', e);
|
||||
if (__DEV__) console.warn('[Sync][rt] user_library handler error', e);
|
||||
} finally {
|
||||
this.suppressLibraryPush = false;
|
||||
}
|
||||
|
|
@ -455,7 +455,7 @@ class SyncService {
|
|||
.select('media_type, media_id, title, poster_url, year, deleted_at, updated_at')
|
||||
.eq('user_id', userId);
|
||||
if (error) {
|
||||
console.warn('[SyncService] pull library error', error);
|
||||
if (__DEV__) console.warn('[SyncService] pull library error', error);
|
||||
return;
|
||||
}
|
||||
const obj: Record<string, any> = {};
|
||||
|
|
@ -475,7 +475,7 @@ class SyncService {
|
|||
await AsyncStorage.setItem('stremio-library', JSON.stringify(obj));
|
||||
logger.log(`[Sync] pull user_library wrote items=${Object.keys(obj).length}`);
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] pullLibrary exception', e);
|
||||
if (__DEV__) console.warn('[SyncService] pullLibrary exception', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -516,12 +516,12 @@ class SyncService {
|
|||
const { error: upErr } = await supabase
|
||||
.from('user_library')
|
||||
.upsert(rows, { onConflict: 'user_id,media_type,media_id' });
|
||||
if (upErr) console.warn('[SyncService] push library upsert error', upErr);
|
||||
if (upErr && __DEV__) console.warn('[SyncService] push library upsert error', upErr);
|
||||
else await AsyncStorage.setItem(`@user:${user.id}:library_initialized`, 'true');
|
||||
}
|
||||
// No computed deletions; removals happen only via explicit user action (soft delete)
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] pushLibrary exception', e);
|
||||
if (__DEV__) console.warn('[SyncService] pushLibrary exception', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -540,9 +540,9 @@ class SyncService {
|
|||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
const { error } = await supabase.from('user_library').upsert(row, { onConflict: 'user_id,media_type,media_id' });
|
||||
if (error) console.warn('[SyncService] pushLibraryAdd error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] pushLibraryAdd error', error);
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] pushLibraryAdd exception', e);
|
||||
if (__DEV__) console.warn('[SyncService] pushLibraryAdd exception', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -556,9 +556,9 @@ class SyncService {
|
|||
.eq('user_id', user.id)
|
||||
.eq('media_type', type === 'movie' ? 'movie' : 'series')
|
||||
.eq('media_id', id);
|
||||
if (error) console.warn('[SyncService] pushLibraryRemove error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] pushLibraryRemove error', error);
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] pushLibraryRemove exception', e);
|
||||
if (__DEV__) console.warn('[SyncService] pushLibraryRemove exception', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +570,7 @@ class SyncService {
|
|||
.eq('user_id', userId)
|
||||
.order('position', { ascending: true });
|
||||
if (addonsErr) {
|
||||
console.warn('[SyncService] pull addons error', addonsErr);
|
||||
if (__DEV__) console.warn('[SyncService] pull addons error', addonsErr);
|
||||
return;
|
||||
}
|
||||
if (!(addons && Array.isArray(addons))) return;
|
||||
|
|
@ -603,7 +603,7 @@ class SyncService {
|
|||
manifest.id = a.addon_id;
|
||||
map.set(a.addon_id, manifest);
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] failed to fetch manifest for', a.addon_id, e);
|
||||
if (__DEV__) console.warn('[SyncService] failed to fetch manifest for', a.addon_id, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -715,7 +715,7 @@ class SyncService {
|
|||
const { error } = await supabase
|
||||
.from('watch_progress')
|
||||
.upsert(filteredRows, { onConflict: 'user_id,media_type,media_id,episode_id' });
|
||||
if (error) console.warn('[SyncService] push watch_progress error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] push watch_progress error', error);
|
||||
else logger.log('[Sync] push watch_progress upsert ok');
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -723,7 +723,7 @@ class SyncService {
|
|||
const { error } = await supabase
|
||||
.from('watch_progress')
|
||||
.upsert(rows, { onConflict: 'user_id,media_type,media_id,episode_id' });
|
||||
if (error) console.warn('[SyncService] push watch_progress error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] push watch_progress error', error);
|
||||
else logger.log('[Sync] push watch_progress upsert ok');
|
||||
}
|
||||
}
|
||||
|
|
@ -742,9 +742,9 @@ class SyncService {
|
|||
.eq('media_type', type)
|
||||
.eq('media_id', id)
|
||||
.eq('episode_id', episodeId || '');
|
||||
if (error) console.warn('[SyncService] softDeleteWatchProgress error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] softDeleteWatchProgress error', error);
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] softDeleteWatchProgress exception', e);
|
||||
if (__DEV__) console.warn('[SyncService] softDeleteWatchProgress exception', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -767,7 +767,7 @@ class SyncService {
|
|||
app_settings: appSettings,
|
||||
subtitle_settings: subtitleSettings,
|
||||
});
|
||||
if (error) console.warn('[SyncService] push settings error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] push settings error', error);
|
||||
else logger.log('[Sync] push user_settings ok');
|
||||
}
|
||||
|
||||
|
|
@ -807,14 +807,14 @@ class SyncService {
|
|||
.delete()
|
||||
.eq('user_id', userId)
|
||||
.in('addon_id', toDelete);
|
||||
if (del.error) console.warn('[SyncService] delete addons error', del.error);
|
||||
if (del.error && __DEV__) console.warn('[SyncService] delete addons error', del.error);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[SyncService] deletion sync for addons failed', e);
|
||||
if (__DEV__) console.warn('[SyncService] deletion sync for addons failed', e);
|
||||
}
|
||||
const { error } = await supabase.from('installed_addons').upsert(rows, { onConflict: 'user_id,addon_id' });
|
||||
if (error) console.warn('[SyncService] push addons error', error);
|
||||
if (error && __DEV__) console.warn('[SyncService] push addons error', error);
|
||||
}
|
||||
|
||||
// Excluded: pushLocalScrapers (local scrapers are device-local only)
|
||||
|
|
|
|||
|
|
@ -844,18 +844,20 @@ class CatalogService {
|
|||
}
|
||||
|
||||
async getStremioId(type: string, tmdbId: string): Promise<string | null> {
|
||||
console.log('=== CatalogService.getStremioId ===');
|
||||
console.log('Input type:', type);
|
||||
console.log('Input tmdbId:', tmdbId);
|
||||
if (__DEV__) {
|
||||
console.log('=== CatalogService.getStremioId ===');
|
||||
console.log('Input type:', type);
|
||||
console.log('Input tmdbId:', tmdbId);
|
||||
}
|
||||
|
||||
try {
|
||||
// For movies, use the tt prefix with IMDb ID
|
||||
if (type === 'movie') {
|
||||
console.log('Processing movie - fetching TMDB details...');
|
||||
if (__DEV__) console.log('Processing movie - fetching TMDB details...');
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const movieDetails = await tmdbService.getMovieDetails(tmdbId);
|
||||
|
||||
console.log('Movie details result:', {
|
||||
if (__DEV__) console.log('Movie details result:', {
|
||||
id: movieDetails?.id,
|
||||
title: movieDetails?.title,
|
||||
imdb_id: movieDetails?.imdb_id,
|
||||
|
|
@ -863,7 +865,7 @@ class CatalogService {
|
|||
});
|
||||
|
||||
if (movieDetails?.imdb_id) {
|
||||
console.log('Successfully found IMDb ID:', movieDetails.imdb_id);
|
||||
if (__DEV__) console.log('Successfully found IMDb ID:', movieDetails.imdb_id);
|
||||
return movieDetails.imdb_id;
|
||||
} else {
|
||||
console.warn('No IMDb ID found for movie:', tmdbId);
|
||||
|
|
@ -872,25 +874,25 @@ class CatalogService {
|
|||
}
|
||||
// For TV shows, get the IMDb ID like movies
|
||||
else if (type === 'tv' || type === 'series') {
|
||||
console.log('Processing TV show - fetching TMDB details for IMDb ID...');
|
||||
if (__DEV__) console.log('Processing TV show - fetching TMDB details for IMDb ID...');
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
|
||||
// Get TV show external IDs to find IMDb ID
|
||||
const externalIds = await tmdbService.getShowExternalIds(parseInt(tmdbId));
|
||||
|
||||
console.log('TV show external IDs result:', {
|
||||
if (__DEV__) console.log('TV show external IDs result:', {
|
||||
tmdbId: tmdbId,
|
||||
imdb_id: externalIds?.imdb_id,
|
||||
hasImdbId: !!externalIds?.imdb_id
|
||||
});
|
||||
|
||||
if (externalIds?.imdb_id) {
|
||||
console.log('Successfully found IMDb ID for TV show:', externalIds.imdb_id);
|
||||
if (__DEV__) console.log('Successfully found IMDb ID for TV show:', externalIds.imdb_id);
|
||||
return externalIds.imdb_id;
|
||||
} else {
|
||||
console.warn('No IMDb ID found for TV show, falling back to kitsu format:', tmdbId);
|
||||
const fallbackId = `kitsu:${tmdbId}`;
|
||||
console.log('Generated fallback Stremio ID for TV:', fallbackId);
|
||||
if (__DEV__) console.log('Generated fallback Stremio ID for TV:', fallbackId);
|
||||
return fallbackId;
|
||||
}
|
||||
}
|
||||
|
|
@ -899,11 +901,13 @@ class CatalogService {
|
|||
return null;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('=== Error in getStremioId ===');
|
||||
console.error('Type:', type);
|
||||
console.error('TMDB ID:', tmdbId);
|
||||
console.error('Error details:', error);
|
||||
console.error('Error message:', error.message);
|
||||
if (__DEV__) {
|
||||
console.error('=== Error in getStremioId ===');
|
||||
console.error('Type:', type);
|
||||
console.error('TMDB ID:', tmdbId);
|
||||
console.error('Error details:', error);
|
||||
console.error('Error message:', error.message);
|
||||
}
|
||||
logger.error('Error getting Stremio ID:', error);
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { logger } from '../utils/logger';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
|
||||
interface CachedImage {
|
||||
url: string;
|
||||
|
|
@ -14,16 +15,20 @@ interface CachedImage {
|
|||
class ImageCacheService {
|
||||
private cache = new Map<string, CachedImage>();
|
||||
private readonly CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
||||
private readonly MAX_CACHE_SIZE = 100; // Increased maximum number of cached images
|
||||
private readonly MAX_MEMORY_MB = 150; // Increased maximum memory usage in MB
|
||||
private readonly MAX_CACHE_SIZE = 25; // Further reduced maximum number of cached images
|
||||
private readonly MAX_MEMORY_MB = 40; // Further reduced maximum memory usage in MB
|
||||
private currentMemoryUsage = 0;
|
||||
private cleanupInterval: NodeJS.Timeout | null = null;
|
||||
private appStateSubscription: any = null;
|
||||
|
||||
constructor() {
|
||||
// Start cleanup interval every 30 minutes (less churn)
|
||||
// Start cleanup interval every 15 minutes (more frequent cleanup to reduce memory pressure)
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.performCleanup();
|
||||
}, 30 * 60 * 1000);
|
||||
}, 15 * 60 * 1000);
|
||||
|
||||
// Reduce memory footprint when app goes to background
|
||||
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -255,8 +260,32 @@ class ImageCacheService {
|
|||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
if (this.appStateSubscription) {
|
||||
this.appStateSubscription.remove();
|
||||
this.appStateSubscription = null;
|
||||
}
|
||||
this.clearAllCache();
|
||||
}
|
||||
|
||||
private handleAppStateChange = (nextState: AppStateStatus) => {
|
||||
if (nextState !== 'active') {
|
||||
// On background/inactive, aggressively trim cache to 25% to reduce memory pressure
|
||||
const targetSize = Math.floor(this.MAX_CACHE_SIZE * 0.25);
|
||||
if (this.cache.size > targetSize) {
|
||||
const entries = Array.from(this.cache.entries());
|
||||
const toRemove = this.cache.size - targetSize;
|
||||
for (let i = 0; i < toRemove; i++) {
|
||||
const [url, cached] = entries[i];
|
||||
this.cache.delete(url);
|
||||
this.currentMemoryUsage -= cached.size || 0;
|
||||
}
|
||||
}
|
||||
// Force aggressive memory cleanup
|
||||
this.enforceMemoryLimits();
|
||||
// Clear any remaining memory pressure
|
||||
this.currentMemoryUsage = Math.min(this.currentMemoryUsage, this.MAX_MEMORY_MB * 1024 * 1024 * 0.3);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const imageCacheService = new ImageCacheService();
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { logger } from '../utils/logger';
|
||||
import { imageCacheService } from './imageCacheService';
|
||||
|
||||
|
|
@ -260,7 +261,7 @@ export class TraktService {
|
|||
|
||||
// Rate limiting
|
||||
private lastApiCall: number = 0;
|
||||
private readonly MIN_API_INTERVAL = 2000; // Minimum 2 seconds between API calls (reduce heating)
|
||||
private readonly MIN_API_INTERVAL = 3000; // Minimum 3 seconds between API calls (further reduce heating)
|
||||
private requestQueue: Array<() => Promise<any>> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
|
||||
|
|
@ -272,7 +273,7 @@ export class TraktService {
|
|||
// Track currently watching sessions to avoid duplicate starts// Sync debouncing
|
||||
private currentlyWatching: Set<string> = new Set();
|
||||
private lastSyncTimes: Map<string, number> = new Map();
|
||||
private readonly SYNC_DEBOUNCE_MS = 15000; // 15 seconds to align with player save interval
|
||||
private readonly SYNC_DEBOUNCE_MS = 20000; // 20 seconds to further reduce API calls
|
||||
|
||||
// Debounce for stop calls
|
||||
private lastStopCalls: Map<string, number> = new Map();
|
||||
|
|
@ -285,6 +286,9 @@ export class TraktService {
|
|||
// Increased cleanup interval from 5 minutes to 15 minutes to reduce heating
|
||||
setInterval(() => this.cleanupOldStopCalls(), 15 * 60 * 1000); // Clean up every 15 minutes
|
||||
|
||||
// Add AppState cleanup to reduce memory pressure
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
|
||||
// Load user settings
|
||||
this.loadCompletionThreshold();
|
||||
}
|
||||
|
|
@ -1566,6 +1570,24 @@ export class TraktService {
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle app state changes to reduce memory pressure
|
||||
*/
|
||||
private handleAppStateChange = (nextState: AppStateStatus) => {
|
||||
if (nextState !== 'active') {
|
||||
// Clear tracking maps to reduce memory pressure when app goes to background
|
||||
this.scrobbledItems.clear();
|
||||
this.scrobbledTimestamps.clear();
|
||||
this.currentlyWatching.clear();
|
||||
this.lastSyncTimes.clear();
|
||||
this.lastStopCalls.clear();
|
||||
|
||||
// Clear request queue to prevent background processing
|
||||
this.requestQueue = [];
|
||||
this.isProcessingQueue = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
|
|
|
|||
|
|
@ -38,8 +38,17 @@ export class UpdateService {
|
|||
this.logs = this.logs.slice(0, this.MAX_LOGS);
|
||||
}
|
||||
|
||||
// Always log to console - this will be visible in adb logcat for production builds
|
||||
// Use different console methods for better filtering in logcat
|
||||
// Console logging policy:
|
||||
// - Development: log INFO/WARN/ERROR for visibility
|
||||
// - Production: only log ERROR to reduce JS<->native bridge traffic and CPU usage
|
||||
if (!__DEV__) {
|
||||
if (level === 'ERROR') {
|
||||
console.error(`[UpdateService] ${logEntry}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Development detailed logging
|
||||
if (level === 'ERROR') {
|
||||
console.error(`[UpdateService] ${logEntry}`);
|
||||
} else if (level === 'WARN') {
|
||||
|
|
@ -47,9 +56,11 @@ export class UpdateService {
|
|||
} else {
|
||||
console.log(`[UpdateService] ${logEntry}`);
|
||||
}
|
||||
|
||||
// Also log with a consistent prefix for easy filtering
|
||||
console.log(`UpdateService: ${logEntry}`);
|
||||
|
||||
// Additional prefixed line for easier filtering during development only
|
||||
if (__DEV__) {
|
||||
console.log(`UpdateService: ${logEntry}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue