From 52740c26de96afd6154fd74ab6d51d802bdfbd31 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 21 Sep 2025 14:00:37 +0530 Subject: [PATCH 1/4] AI fix,. changes model to - x-ai/grok-4-fast:free --- src/services/aiService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/aiService.ts b/src/services/aiService.ts index ff66249e..24fe8b01 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -304,7 +304,7 @@ Answer questions about this movie using only the verified database information a 'X-Title': 'Nuvio - AI Chat', }, body: JSON.stringify({ - model: 'openrouter/sonoma-dusk-alpha', + model: 'x-ai/grok-4-fast:free', messages, max_tokens: 1000, temperature: 0.7, From c1503f0614f29ba11faa8b87ec3378d8e055c869 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 21 Sep 2025 16:35:36 +0530 Subject: [PATCH 2/4] homescreen optimization --- src/screens/HomeScreen.tsx | 48 +++++++++++------- src/screens/StreamsScreen.tsx | 96 ++++++++++++++++++++++++----------- 2 files changed, 95 insertions(+), 49 deletions(-) diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 9137b58e..d6b305f8 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -15,7 +15,8 @@ import { Image, Modal, Pressable, - Alert + Alert, + InteractionManager } from 'react-native'; import { FlashList } from '@shopify/flash-list'; import { useNavigation, useFocusEffect } from '@react-navigation/native'; @@ -146,8 +147,10 @@ const HomeScreen = () => { stremioService.getInstalledAddonsAsync() ]); - // Set hasAddons state based on whether we have any addons - setHasAddons(addons.length > 0); + // Set hasAddons state based on whether we have any addons - ensure on main thread + InteractionManager.runAfterInteractions(() => { + setHasAddons(addons.length > 0); + }); const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {}; @@ -245,23 +248,28 @@ const HomeScreen = () => { items }; - // Update the catalog at its specific position - setCatalogs(prevCatalogs => { - const newCatalogs = [...prevCatalogs]; - newCatalogs[currentIndex] = catalogContent; - return newCatalogs; + // Update the catalog at its specific position - ensure on main thread + InteractionManager.runAfterInteractions(() => { + setCatalogs(prevCatalogs => { + const newCatalogs = [...prevCatalogs]; + newCatalogs[currentIndex] = catalogContent; + return newCatalogs; + }); }); } } catch (error) { if (__DEV__) console.error(`[HomeScreen] Failed to load ${catalog.name} from ${addon.name}:`, error); } finally { - setLoadedCatalogCount(prev => { - const next = prev + 1; - // Exit loading screen as soon as first catalog finishes - if (prev === 0) { - setCatalogsLoading(false); - } - return next; + // Update loading count - ensure on main thread + InteractionManager.runAfterInteractions(() => { + setLoadedCatalogCount(prev => { + const next = prev + 1; + // Exit loading screen as soon as first catalog finishes + if (prev === 0) { + setCatalogsLoading(false); + } + return next; + }); }); } }; @@ -275,14 +283,18 @@ const HomeScreen = () => { totalCatalogsRef.current = catalogIndex; - // Initialize catalogs array with proper length - setCatalogs(new Array(catalogIndex).fill(null)); + // Initialize catalogs array with proper length - ensure on main thread + InteractionManager.runAfterInteractions(() => { + setCatalogs(new Array(catalogIndex).fill(null)); + }); // Start processing the catalog queue processCatalogQueue(); } catch (error) { if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error); - setCatalogsLoading(false); + InteractionManager.runAfterInteractions(() => { + setCatalogsLoading(false); + }); } }, []); diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 0e1da92d..98b6caa1 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -80,29 +80,38 @@ const detectMkvViaHead = async (url: string, headers?: Record) = }; // Animated Components -const AnimatedImage = memo(({ - source, - style, - contentFit, - onLoad -}: { - source: { uri: string } | undefined; - style: any; - contentFit: any; +const AnimatedImage = memo(({ + source, + style, + contentFit, + onLoad +}: { + source: { uri: string } | undefined; + style: any; + contentFit: any; onLoad?: () => void; }) => { const opacity = useSharedValue(0); - + const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, })); - + useEffect(() => { if (source?.uri) { opacity.value = withTiming(1, { duration: 300 }); + } else { + opacity.value = 0; } }, [source?.uri]); - + + // Cleanup on unmount + useEffect(() => { + return () => { + opacity.value = 0; + }; + }, []); + return ( { const opacity = useSharedValue(0); const translateY = useSharedValue(20); - + const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [{ translateY: translateY.value }], })); - + useEffect(() => { opacity.value = withDelay(delay, withTiming(1, { duration: 250 })); translateY.value = withDelay(delay, withTiming(0, { duration: 250 })); + }, [delay]); + + // Cleanup on unmount + useEffect(() => { + return () => { + opacity.value = 0; + translateY.value = 20; + }; }, []); - + return ( {children} @@ -146,28 +163,36 @@ const AnimatedText = memo(({ ); }); -const AnimatedView = memo(({ - children, - style, - delay = 0 -}: { - children: React.ReactNode; - style?: any; +const AnimatedView = memo(({ + children, + style, + delay = 0 +}: { + children: React.ReactNode; + style?: any; delay?: number; }) => { const opacity = useSharedValue(0); const translateY = useSharedValue(20); - + const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [{ translateY: translateY.value }], })); - + useEffect(() => { opacity.value = withDelay(delay, withTiming(1, { duration: 250 })); translateY.value = withDelay(delay, withTiming(0, { duration: 250 })); + }, [delay]); + + // Cleanup on unmount + useEffect(() => { + return () => { + opacity.value = 0; + translateY.value = 20; + }; }, []); - + return ( {children} @@ -388,6 +413,7 @@ const ProviderFilter = memo(({ initialNumToRender={5} maxToRenderPerBatch={3} windowSize={3} + removeClippedSubviews={true} getItemLayout={(data, index) => ({ length: 100, // Approximate width of each item offset: 100 * index, @@ -1598,6 +1624,9 @@ export const StreamsScreen = () => { useEffect(() => { return () => { isMounted.current = false; + // Clear scraper logo cache to free memory + scraperLogoCache.clear(); + scraperLogoCachePromise = null; }; }, []); @@ -1856,6 +1885,11 @@ export const StreamsScreen = () => { windowSize={3} removeClippedSubviews={true} showsVerticalScrollIndicator={false} + getItemLayout={(data, index) => ({ + length: 78, // Approximate height of StreamCard (68 minHeight + 10 marginBottom) + offset: 78 * index, + index, + })} /> ) : ( // Empty section placeholder From 0b3a36c76f94d60c6483d6fd0ae6eea38e58c3a5 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 21 Sep 2025 16:39:08 +0530 Subject: [PATCH 3/4] catalogscreen optimziation --- src/screens/CatalogScreen.tsx | 57 +++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/screens/CatalogScreen.tsx b/src/screens/CatalogScreen.tsx index 3e594d9f..b6f47712 100644 --- a/src/screens/CatalogScreen.tsx +++ b/src/screens/CatalogScreen.tsx @@ -10,6 +10,7 @@ import { RefreshControl, Dimensions, Platform, + InteractionManager } from 'react-native'; import { FlashList } from '@shopify/flash-list'; import { RouteProp } from '@react-navigation/native'; @@ -226,6 +227,7 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); + const [hasMore, setHasMore] = useState(false); const [dataSource, setDataSource] = useState(DataSource.STREMIO_ADDONS); const [actualCatalogName, setActualCatalogName] = useState(null); const [screenData, setScreenData] = useState(() => { @@ -403,24 +405,30 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { index === self.findIndex((t) => t.id === item.id) ); - setItems(uniqueItems); - setHasMore(false); // TMDB already returns a full set - setLoading(false); - setRefreshing(false); + InteractionManager.runAfterInteractions(() => { + setItems(uniqueItems); + setHasMore(false); // TMDB already returns a full set + setLoading(false); + setRefreshing(false); + }); return; } else { - setError("No content found for the selected filters"); - setItems([]); - setLoading(false); - setRefreshing(false); + InteractionManager.runAfterInteractions(() => { + setError("No content found for the selected filters"); + setItems([]); + setLoading(false); + setRefreshing(false); + }); return; } } catch (error) { logger.error('Failed to get TMDB catalog:', error); - setError('Failed to load content from TMDB'); - setItems([]); - setLoading(false); - setRefreshing(false); + InteractionManager.runAfterInteractions(() => { + setError('Failed to load content from TMDB'); + setItems([]); + setLoading(false); + setRefreshing(false); + }); return; } } @@ -448,7 +456,9 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { if (catalogItems.length > 0) { foundItems = true; - setItems(catalogItems); + InteractionManager.runAfterInteractions(() => { + setItems(catalogItems); + }); } } else if (effectiveGenreFilter) { // Get all addons that have catalogs of the specified type @@ -527,19 +537,27 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { if (uniqueItems.length > 0) { foundItems = true; - setItems(uniqueItems); + InteractionManager.runAfterInteractions(() => { + setItems(uniqueItems); + }); } } if (!foundItems) { - setError("No content found for the selected filters"); + InteractionManager.runAfterInteractions(() => { + setError("No content found for the selected filters"); + }); } } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load catalog items'); + InteractionManager.runAfterInteractions(() => { + setError(err instanceof Error ? err.message : 'Failed to load catalog items'); + }); logger.error('Failed to load catalog:', err); } finally { - setLoading(false); - setRefreshing(false); + InteractionManager.runAfterInteractions(() => { + setLoading(false); + setRefreshing(false); + }); } }, [addonId, type, id, genreFilter, dataSource]); @@ -651,7 +669,7 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { loadItems(1)} + onPress={() => loadItems(true)} > Retry @@ -736,7 +754,6 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { } contentContainerStyle={styles.list} showsVerticalScrollIndicator={false} - estimatedItemSize={effectiveItemWidth * 1.5 + SPACING.lg} /> ) : renderEmptyState()} From 53b439f1fdc044a8f3a9871305cdeb7a907e0e4c Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 21 Sep 2025 17:00:30 +0530 Subject: [PATCH 4/4] small fix --- src/components/player/AndroidVideoPlayer.tsx | 27 +++----------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 9024f284..23c41051 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, Image, StyleSheet, Modal, AppState } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType } from 'react-native-video'; +import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video'; import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native'; import { RootStackParamList } from '../../navigation/AppNavigator'; import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; @@ -1135,27 +1135,6 @@ const AndroidVideoPlayer: React.FC = () => { }; }); setRnVideoAudioTracks(formattedAudioTracks); - - // Auto-select audio track if none is selected (similar to iOS behavior) - if (selectedAudioTrack?.type === SelectedTrackType.SYSTEM && formattedAudioTracks.length > 0) { - // Look for English track first - const englishTrack = formattedAudioTracks.find((track: {id: number, name: string, language?: string}) => { - const lang = (track.language || '').toLowerCase(); - return lang === 'english' || lang === 'en' || lang === 'eng' || - (track.name && track.name.toLowerCase().includes('english')); - }); - - const selectedTrack = englishTrack || formattedAudioTracks[0]; - setSelectedAudioTrack({ type: SelectedTrackType.INDEX, value: selectedTrack.id }); - - if (DEBUG_MODE) { - if (englishTrack) { - logger.log(`[AndroidVideoPlayer] Auto-selected English audio track: ${selectedTrack.name} (ID: ${selectedTrack.id})`); - } else { - logger.log(`[AndroidVideoPlayer] No English track found, auto-selected first audio track: ${selectedTrack.name} (ID: ${selectedTrack.id})`); - } - } - } if (DEBUG_MODE) { logger.log(`[AndroidVideoPlayer] Formatted audio tracks:`, formattedAudioTracks); @@ -2770,8 +2749,8 @@ const AndroidVideoPlayer: React.FC = () => { allowsExternalPlayback={false as any} preventsDisplaySleepDuringVideoPlayback={true as any} // ExoPlayer HLS optimization - let the player use optimal defaults - // Use SurfaceView on Android to lower memory pressure with 4K/high-bitrate content - useTextureView={Platform.OS === 'android' ? false : (undefined as any)} + // Use textureView on Android: allows 3D mapping but DRM not supported + viewType={Platform.OS === 'android' ? ViewType.TEXTURE : undefined} />