From 62a2ed0046e0ca4b8e8e0e1e03c43cd88f9f4319 Mon Sep 17 00:00:00 2001 From: tapframe Date: Wed, 6 Aug 2025 20:45:51 +0530 Subject: [PATCH] test --- package-lock.json | 15 ++ package.json | 1 + src/components/home/CatalogSection.tsx | 7 +- src/components/home/ContentItem.tsx | 25 +- src/components/metadata/HeroSection.tsx | 4 +- src/components/player/AndroidVideoPlayer.tsx | 122 +++++----- src/components/player/VideoPlayer.tsx | 88 +++---- .../player/modals/ResumeOverlay.tsx | 8 +- .../player/modals/SubtitleModals.tsx | 42 +++- src/components/player/utils/playerTypes.ts | 16 +- src/hooks/useTraktAutosync.ts | 16 +- src/hooks/useTraktIntegration.ts | 16 +- src/hooks/useWatchProgress.ts | 4 +- src/screens/HomeScreen.tsx | 217 +++++++++++++----- src/screens/StreamsScreen.tsx | 27 +-- src/services/storageService.ts | 12 +- src/services/stremioService.ts | 6 +- src/services/traktService.ts | 37 +-- src/utils/.logger.ts.swp | Bin 0 -> 12288 bytes src/utils/httpInterceptor.ts | 31 +-- 20 files changed, 385 insertions(+), 309 deletions(-) create mode 100644 src/utils/.logger.ts.swp diff --git a/package-lock.json b/package-lock.json index 2daad366..d7d345b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@react-navigation/native-stack": "^7.3.10", "@react-navigation/stack": "^7.2.10", "@sentry/react-native": "^6.15.1", + "@shopify/flash-list": "^2.0.2", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.11.0", @@ -4309,6 +4310,20 @@ "node": ">=14.18" } }, + "node_modules/@shopify/flash-list": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.2.tgz", + "integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "peerDependencies": { + "@babel/runtime": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", diff --git a/package.json b/package.json index bff8c85d..5bda5e4a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@react-navigation/native-stack": "^7.3.10", "@react-navigation/stack": "^7.2.10", "@sentry/react-native": "^6.15.1", + "@shopify/flash-list": "^2.0.2", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.11.0", diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index b7edc37e..8102e09a 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -109,7 +109,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { decelerationRate="fast" snapToAlignment="start" ItemSeparatorComponent={() => } - initialNumToRender={3} + initialNumToRender={4} maxToRenderPerBatch={2} windowSize={3} removeClippedSubviews={Platform.OS === 'android'} @@ -122,7 +122,8 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { maintainVisibleContentPosition={{ minIndexForVisible: 0 }} - onEndReachedThreshold={1} + onEndReachedThreshold={0.5} + scrollEventThrottle={16} /> ); @@ -177,4 +178,4 @@ const styles = StyleSheet.create({ }, }); -export default React.memo(CatalogSection); \ No newline at end of file +export default React.memo(CatalogSection); \ No newline at end of file diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index 1c7728cd..d65e379f 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text } from 'react-native'; import { Image as ExpoImage } from 'expo-image'; import { MaterialIcons } from '@expo/vector-icons'; @@ -53,6 +53,8 @@ const POSTER_WIDTH = posterLayout.posterWidth; const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => { const [menuVisible, setMenuVisible] = useState(false); const [isWatched, setIsWatched] = useState(false); + const [imageLoaded, setImageLoaded] = useState(false); + const [imageError, setImageError] = useState(false); const { currentTheme } = useTheme(); const handleLongPress = useCallback(() => { @@ -101,12 +103,26 @@ const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => { source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }} style={styles.poster} contentFit="cover" - cachePolicy="memory" + cachePolicy="memory-disk" transition={200} placeholder={{ uri: 'https://via.placeholder.com/300x450' }} placeholderContentFit="cover" recyclingKey={item.id} + onLoad={() => { + setImageLoaded(true); + setImageError(false); + }} + onError={() => { + setImageError(true); + setImageLoaded(false); + }} + priority="low" /> + {imageError && ( + + + + )} {isWatched && ( @@ -191,12 +207,11 @@ const styles = StyleSheet.create({ padding: 4, }, title: { - fontSize: 14, + fontSize: 13, fontWeight: '500', marginTop: 4, textAlign: 'center', - fontFamily: 'SpaceMono-Regular', } }); -export default ContentItem; \ No newline at end of file +export default ContentItem; \ No newline at end of file diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 85cc74e8..59c1cd45 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -799,7 +799,7 @@ const HeroSection: React.FC = ({ // Check Trakt progress first if available and user is authenticated if (isTraktAuthenticated && watchProgress.traktProgress !== undefined) { const traktWatched = watchProgress.traktProgress >= 95; - logger.log(`[HeroSection] Trakt authenticated: ${isTraktAuthenticated}, Trakt progress: ${watchProgress.traktProgress}%, Watched: ${traktWatched}`); + // Removed excessive logging for Trakt progress return traktWatched; } @@ -807,7 +807,7 @@ const HeroSection: React.FC = ({ if (watchProgress.duration === 0) return false; const progressPercent = (watchProgress.currentTime / watchProgress.duration) * 100; const localWatched = progressPercent >= 85; - logger.log(`[HeroSection] Local progress: ${progressPercent.toFixed(1)}%, Watched: ${localWatched}`); + // Removed excessive logging for local progress return localWatched; }, [watchProgress, isTraktAuthenticated]); diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index cac81863..a6e3b812 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -16,13 +16,13 @@ import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings'; import { useMetadata } from '../../hooks/useMetadata'; import { useSettings } from '../../hooks/useSettings'; import { testVideoStreamUrl } from '../../utils/httpInterceptor'; +import { stremioService, Subtitle } from '../../services/stremioService'; import { DEFAULT_SUBTITLE_SIZE, AudioTrack, TextTrack, ResizeModeType, - WyzieSubtitle, SubtitleCue, RESUME_PREF_KEY, RESUME_PREF, @@ -149,7 +149,7 @@ const AndroidVideoPlayer: React.FC = () => { const [subtitleBackground, setSubtitleBackground] = useState(true); const [useCustomSubtitles, setUseCustomSubtitles] = useState(false); const [isLoadingSubtitles, setIsLoadingSubtitles] = useState(false); - const [availableSubtitles, setAvailableSubtitles] = useState([]); + const [availableSubtitles, setAvailableSubtitles] = useState([]); const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState(false); const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState(false); const [showSourcesModal, setShowSourcesModal] = useState(false); @@ -403,7 +403,7 @@ const AndroidVideoPlayer: React.FC = () => { saveWatchProgress(); }, syncInterval); - logger.log(`[AndroidVideoPlayer] Watch progress save interval set to ${syncInterval}ms (immediate sync mode)`); + // Removed excessive logging for watch progress save interval setProgressSaveInterval(interval); return () => { @@ -498,16 +498,7 @@ const AndroidVideoPlayer: React.FC = () => { const onLoad = (data: any) => { try { - // Enhanced HTTP response logging - console.log('\nāœ… [AndroidVideoPlayer] HTTP RESPONSE SUCCESS:'); - console.log('šŸ“ URL:', currentStreamUrl); - console.log('šŸ“Š Status: 200 OK (Video Stream Loaded)'); - console.log('šŸ“ŗ Duration:', data?.duration ? `${data.duration.toFixed(2)}s` : 'Unknown'); - console.log('šŸ“ Resolution:', data?.naturalSize ? `${data.naturalSize.width}x${data.naturalSize.height}` : 'Unknown'); - console.log('šŸŽµ Audio Tracks:', data?.audioTracks?.length || 0); - console.log('šŸ“ Text Tracks:', data?.textTracks?.length || 0); - console.log('ā° Response Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP response logging removed if (DEBUG_MODE) { logger.log('[AndroidVideoPlayer] Video loaded:', data); @@ -724,15 +715,7 @@ const AndroidVideoPlayer: React.FC = () => { const handleError = (error: any) => { try { - // Enhanced HTTP error response logging - console.log('\nāŒ [AndroidVideoPlayer] HTTP RESPONSE ERROR:'); - console.log('šŸ“ URL:', currentStreamUrl); - console.log('šŸ“Š Status:', error?.error?.code ? `${error.error.code} (${error.error.domain || 'Unknown Domain'})` : 'Unknown Error Code'); - console.log('šŸ’¬ Error Message:', error?.error?.localizedDescription || error?.message || 'Unknown error'); - console.log('šŸ” Error Type:', error?.error?.domain || 'Unknown'); - console.log('šŸ“‹ Full Error Object:', JSON.stringify(error, null, 2)); - console.log('ā° Error Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP error response logging removed logger.error('AndroidVideoPlayer error: ', error); @@ -908,56 +891,89 @@ const AndroidVideoPlayer: React.FC = () => { } setIsLoadingSubtitleList(true); try { - let searchUrl = `https://sub.wyzie.ru/search?id=${targetImdbId}&encoding=utf-8&source=all`; + // Determine content type and ID format for Stremio + let contentType = 'movie'; + let contentId = targetImdbId; + let videoId: string | undefined; + if (season && episode) { - searchUrl += `&season=${season}&episode=${episode}`; + contentType = 'series'; + videoId = `series:${targetImdbId}:${season}:${episode}`; } - const response = await fetch(searchUrl); - const subtitles: WyzieSubtitle[] = await response.json(); - const uniqueSubtitles = subtitles.reduce((acc, current) => { - const exists = acc.find(item => item.language === current.language); + + logger.log(`[AndroidVideoPlayer] Fetching subtitles for ${contentType}: ${contentId}${videoId ? ` (${videoId})` : ''}`); + + const subtitles = await stremioService.getSubtitles(contentType, contentId, videoId); + + // Remove duplicates based on language + const uniqueSubtitles = subtitles.reduce((acc: Subtitle[], current: Subtitle) => { + const exists = acc.find((item: Subtitle) => item.lang === current.lang); if (!exists) { acc.push(current); } return acc; - }, [] as WyzieSubtitle[]); - uniqueSubtitles.sort((a, b) => a.display.localeCompare(b.display)); + }, [] as Subtitle[]); + + // Sort by language + uniqueSubtitles.sort((a: Subtitle, b: Subtitle) => a.lang.localeCompare(b.lang)); setAvailableSubtitles(uniqueSubtitles); + if (autoSelectEnglish) { - const englishSubtitle = uniqueSubtitles.find(sub => - sub.language.toLowerCase() === 'eng' || - sub.language.toLowerCase() === 'en' || - sub.display.toLowerCase().includes('english') + const englishSubtitle = uniqueSubtitles.find((sub: Subtitle) => + sub.lang.toLowerCase() === 'eng' || + sub.lang.toLowerCase() === 'en' || + sub.lang.toLowerCase().includes('english') ); if (englishSubtitle) { - loadWyzieSubtitle(englishSubtitle); + loadStremioSubtitle(englishSubtitle); return; } } - if (!autoSelectEnglish) { + + if (!autoSelectEnglish && uniqueSubtitles.length > 0) { setShowSubtitleLanguageModal(true); } } catch (error) { - logger.error('[AndroidVideoPlayer] Error fetching subtitles from Wyzie API:', error); + logger.error('[AndroidVideoPlayer] Error fetching subtitles from Stremio addons:', error); } finally { setIsLoadingSubtitleList(false); } }; - const loadWyzieSubtitle = async (subtitle: WyzieSubtitle) => { + const loadStremioSubtitle = async (subtitle: Subtitle) => { + console.log('[AndroidVideoPlayer] Starting subtitle load, setting isLoadingSubtitles to true'); setShowSubtitleLanguageModal(false); setIsLoadingSubtitles(true); try { const response = await fetch(subtitle.url); const srtContent = await response.text(); const parsedCues = parseSRT(srtContent); + + // Force a microtask delay before updating subtitle state + await new Promise(resolve => setTimeout(resolve, 50)); + setCustomSubtitles(parsedCues); setUseCustomSubtitles(true); setSelectedTextTrack(-1); + logger.log(`[AndroidVideoPlayer] Loaded subtitle: ${subtitle.lang} from ${subtitle.addonName}`); + console.log('[AndroidVideoPlayer] Subtitle loaded successfully'); + + // Force a state update by triggering a seek + if (videoRef.current && duration > 0) { + const currentPos = currentTime; + console.log('[AndroidVideoPlayer] Forcing a micro-seek to refresh subtitle state'); + videoRef.current.seek(currentPos); + } } catch (error) { - logger.error('[AndroidVideoPlayer] Error loading Wyzie subtitle:', error); + logger.error('[AndroidVideoPlayer] Error loading Stremio subtitle:', error); + console.log('[AndroidVideoPlayer] Subtitle loading failed:', error); } finally { - setIsLoadingSubtitles(false); + console.log('[AndroidVideoPlayer] Setting isLoadingSubtitles to false'); + // Add a small delay to ensure state updates are processed + setTimeout(() => { + setIsLoadingSubtitles(false); + console.log('[AndroidVideoPlayer] isLoadingSubtitles set to false'); + }, 100); } }; @@ -1004,8 +1020,10 @@ const AndroidVideoPlayer: React.FC = () => { currentTime >= cue.start && currentTime <= cue.end ); const newSubtitle = currentCue ? currentCue.text : ''; - setCurrentSubtitle(newSubtitle); - }, [currentTime, customSubtitles, useCustomSubtitles]); + if (newSubtitle !== currentSubtitle) { + setCurrentSubtitle(newSubtitle); + } + }, [currentTime, customSubtitles, useCustomSubtitles, currentSubtitle]); useEffect(() => { loadSubtitleSize(); @@ -1073,11 +1091,10 @@ const AndroidVideoPlayer: React.FC = () => { if (currentStreamUrl && currentStreamUrl.trim() !== '') { const testStream = async () => { try { - console.log('\nšŸ” [AndroidVideoPlayer] Testing video stream URL...'); - const isValid = await testVideoStreamUrl(currentStreamUrl, headers || {}); - console.log(`āœ… [AndroidVideoPlayer] Stream test result: ${isValid ? 'VALID' : 'INVALID'}`); + // Stream testing without verbose logging + await testVideoStreamUrl(currentStreamUrl, headers || {}); } catch (error) { - console.log('āŒ [AndroidVideoPlayer] Stream test failed:', error); + // Stream test failed silently } }; @@ -1287,16 +1304,7 @@ const AndroidVideoPlayer: React.FC = () => { headers: headers } : { uri: currentStreamUrl }; - // Enhanced HTTP request logging - console.log('\n🌐 [AndroidVideoPlayer] HTTP REQUEST DETAILS:'); - console.log('šŸ“ URL:', currentStreamUrl); - console.log('šŸ”§ Method: GET (Video Stream)'); - console.log('šŸ“‹ Headers:', headers ? JSON.stringify(headers, null, 2) : 'No headers'); - console.log('šŸŽ¬ Stream Provider:', currentStreamProvider || 'Unknown'); - console.log('šŸ“ŗ Stream Name:', currentStreamName || 'Unknown'); - console.log('šŸŽÆ Quality:', currentQuality || 'Unknown'); - console.log('ā° Timestamp:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP request logging removed return sourceWithHeaders; })()} @@ -1399,7 +1407,7 @@ const AndroidVideoPlayer: React.FC = () => { subtitleSize={subtitleSize} subtitleBackground={subtitleBackground} fetchAvailableSubtitles={fetchAvailableSubtitles} - loadWyzieSubtitle={loadWyzieSubtitle} + loadStremioSubtitle={loadStremioSubtitle} selectTextTrack={selectTextTrack} increaseSubtitleSize={increaseSubtitleSize} decreaseSubtitleSize={decreaseSubtitleSize} diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index c3744db8..70a44fb5 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -17,13 +17,13 @@ import { useTraktAutosync } from '../../hooks/useTraktAutosync'; import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings'; import { useMetadata } from '../../hooks/useMetadata'; import { useSettings } from '../../hooks/useSettings'; +import { stremioService, Subtitle } from '../../services/stremioService'; import { DEFAULT_SUBTITLE_SIZE, AudioTrack, TextTrack, ResizeModeType, - WyzieSubtitle, SubtitleCue, RESUME_PREF_KEY, RESUME_PREF, @@ -160,7 +160,7 @@ const VideoPlayer: React.FC = () => { const [subtitleBackground, setSubtitleBackground] = useState(true); const [useCustomSubtitles, setUseCustomSubtitles] = useState(false); const [isLoadingSubtitles, setIsLoadingSubtitles] = useState(false); - const [availableSubtitles, setAvailableSubtitles] = useState([]); + const [availableSubtitles, setAvailableSubtitles] = useState([]); const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState(false); const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState(false); const [showSourcesModal, setShowSourcesModal] = useState(false); @@ -434,7 +434,7 @@ const VideoPlayer: React.FC = () => { saveWatchProgress(); }, syncInterval); - logger.log(`[VideoPlayer] Watch progress save interval set to ${syncInterval}ms (immediate sync mode)`); + // Removed excessive logging for watch progress save interval setProgressSaveInterval(interval); return () => { @@ -797,15 +797,7 @@ const VideoPlayer: React.FC = () => { }; const handleError = (error: any) => { - // Enhanced HTTP error response logging - console.log('\nāŒ [VideoPlayer] HTTP RESPONSE ERROR:'); - console.log('šŸ“ URL:', currentStreamUrl); - console.log('šŸ“Š Status:', error?.error?.code ? `${error.error.code} (${error.error.domain || 'Unknown Domain'})` : 'Unknown Error Code'); - console.log('šŸ’¬ Error Message:', error?.error?.localizedDescription || error?.message || 'Unknown error'); - console.log('šŸ” Error Type:', error?.error?.domain || 'Unknown'); - console.log('šŸ“‹ Full Error Object:', JSON.stringify(error, null, 2)); - console.log('ā° Error Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP error response logging removed logger.error('[VideoPlayer] Playback Error:', error); @@ -915,43 +907,56 @@ const VideoPlayer: React.FC = () => { } setIsLoadingSubtitleList(true); try { - let searchUrl = `https://sub.wyzie.ru/search?id=${targetImdbId}&encoding=utf-8&source=all`; + // Determine content type and ID format for Stremio + let contentType = 'movie'; + let contentId = targetImdbId; + let videoId: string | undefined; + if (season && episode) { - searchUrl += `&season=${season}&episode=${episode}`; + contentType = 'series'; + videoId = `series:${targetImdbId}:${season}:${episode}`; } - const response = await fetch(searchUrl); - const subtitles: WyzieSubtitle[] = await response.json(); - const uniqueSubtitles = subtitles.reduce((acc, current) => { - const exists = acc.find(item => item.language === current.language); + + logger.log(`[VideoPlayer] Fetching subtitles for ${contentType}: ${contentId}${videoId ? ` (${videoId})` : ''}`); + + const subtitles = await stremioService.getSubtitles(contentType, contentId, videoId); + + // Remove duplicates based on language + const uniqueSubtitles = subtitles.reduce((acc: Subtitle[], current: Subtitle) => { + const exists = acc.find((item: Subtitle) => item.lang === current.lang); if (!exists) { acc.push(current); } return acc; - }, [] as WyzieSubtitle[]); - uniqueSubtitles.sort((a, b) => a.display.localeCompare(b.display)); + }, [] as Subtitle[]); + + // Sort by language + uniqueSubtitles.sort((a: Subtitle, b: Subtitle) => a.lang.localeCompare(b.lang)); setAvailableSubtitles(uniqueSubtitles); + if (autoSelectEnglish) { - const englishSubtitle = uniqueSubtitles.find(sub => - sub.language.toLowerCase() === 'eng' || - sub.language.toLowerCase() === 'en' || - sub.display.toLowerCase().includes('english') + const englishSubtitle = uniqueSubtitles.find((sub: Subtitle) => + sub.lang.toLowerCase() === 'eng' || + sub.lang.toLowerCase() === 'en' || + sub.lang.toLowerCase().includes('english') ); if (englishSubtitle) { - loadWyzieSubtitle(englishSubtitle); + loadStremioSubtitle(englishSubtitle); return; } } - if (!autoSelectEnglish) { + + if (!autoSelectEnglish && uniqueSubtitles.length > 0) { setShowSubtitleLanguageModal(true); } } catch (error) { - logger.error('[VideoPlayer] Error fetching subtitles from Wyzie API:', error); + logger.error('[VideoPlayer] Error fetching subtitles from Stremio addons:', error); } finally { setIsLoadingSubtitleList(false); } }; - const loadWyzieSubtitle = async (subtitle: WyzieSubtitle) => { + const loadStremioSubtitle = async (subtitle: Subtitle) => { setShowSubtitleLanguageModal(false); setIsLoadingSubtitles(true); try { @@ -961,10 +966,14 @@ const VideoPlayer: React.FC = () => { setCustomSubtitles(parsedCues); setUseCustomSubtitles(true); setSelectedTextTrack(-1); + logger.log(`[VideoPlayer] Loaded subtitle: ${subtitle.lang} from ${subtitle.addonName}`); } catch (error) { - logger.error('[VideoPlayer] Error loading Wyzie subtitle:', error); + logger.error('[VideoPlayer] Error loading Stremio subtitle:', error); } finally { - setIsLoadingSubtitles(false); + // Add a small delay to ensure state updates are processed + setTimeout(() => { + setIsLoadingSubtitles(false); + }, 100); } }; @@ -1004,8 +1013,10 @@ const VideoPlayer: React.FC = () => { currentTime >= cue.start && currentTime <= cue.end ); const newSubtitle = currentCue ? currentCue.text : ''; - setCurrentSubtitle(newSubtitle); - }, [currentTime, customSubtitles, useCustomSubtitles]); + if (newSubtitle !== currentSubtitle) { + setCurrentSubtitle(newSubtitle); + } + }, [currentTime, customSubtitles, useCustomSubtitles, currentSubtitle]); useEffect(() => { loadSubtitleSize(); @@ -1281,16 +1292,7 @@ const VideoPlayer: React.FC = () => { headers: headers } : { uri: currentStreamUrl }; - // Enhanced HTTP request logging - console.log('\n🌐 [VideoPlayer] HTTP REQUEST DETAILS:'); - console.log('šŸ“ URL:', currentStreamUrl); - console.log('šŸ”§ Method: GET (Video Stream)'); - console.log('šŸ“‹ Headers:', headers ? JSON.stringify(headers, null, 2) : 'No headers'); - console.log('šŸŽ¬ Stream Provider:', currentStreamProvider || streamProvider || 'Unknown'); - console.log('šŸ“ŗ Stream Name:', currentStreamName || streamName || 'Unknown'); - console.log('šŸŽÆ Quality:', currentQuality || quality || 'Unknown'); - console.log('ā° Timestamp:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP request logging removed return sourceWithHeaders; })()} @@ -1386,7 +1388,7 @@ const VideoPlayer: React.FC = () => { subtitleSize={subtitleSize} subtitleBackground={subtitleBackground} fetchAvailableSubtitles={fetchAvailableSubtitles} - loadWyzieSubtitle={loadWyzieSubtitle} + loadStremioSubtitle={loadStremioSubtitle} selectTextTrack={selectTextTrack} increaseSubtitleSize={increaseSubtitleSize} decreaseSubtitleSize={decreaseSubtitleSize} diff --git a/src/components/player/modals/ResumeOverlay.tsx b/src/components/player/modals/ResumeOverlay.tsx index 724a57fe..a5ff1837 100644 --- a/src/components/player/modals/ResumeOverlay.tsx +++ b/src/components/player/modals/ResumeOverlay.tsx @@ -28,15 +28,15 @@ export const ResumeOverlay: React.FC = ({ handleStartFromBeginning, }) => { useEffect(() => { - logger.log(`[ResumeOverlay] Props changed: showOverlay=${showResumeOverlay}, resumePosition=${resumePosition}, duration=${duration}, title=${title}`); + // Removed excessive logging for props changes }, [showResumeOverlay, resumePosition, duration, title]); if (!showResumeOverlay || resumePosition === null) { - logger.log(`[ResumeOverlay] Not showing overlay: showOverlay=${showResumeOverlay}, resumePosition=${resumePosition}`); + // Removed excessive logging for overlay visibility return null; } - logger.log(`[ResumeOverlay] Rendering overlay for ${title} at ${resumePosition}s`); + // Removed excessive logging for overlay rendering return ( @@ -91,4 +91,4 @@ export const ResumeOverlay: React.FC = ({ ); }; -export default ResumeOverlay; \ No newline at end of file +export default ResumeOverlay; \ No newline at end of file diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx index c2aa2ba9..5ecc5e3e 100644 --- a/src/components/player/modals/SubtitleModals.tsx +++ b/src/components/player/modals/SubtitleModals.tsx @@ -8,7 +8,8 @@ import Animated, { SlideOutRight, } from 'react-native-reanimated'; import { styles } from '../utils/playerStyles'; -import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes'; +import { SubtitleCue } from '../utils/playerTypes'; +import { Subtitle } from '../../../services/stremioService'; import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils'; interface SubtitleModalsProps { @@ -19,14 +20,14 @@ interface SubtitleModalsProps { isLoadingSubtitleList: boolean; isLoadingSubtitles: boolean; customSubtitles: SubtitleCue[]; - availableSubtitles: WyzieSubtitle[]; + availableSubtitles: Subtitle[]; vlcTextTracks: Array<{id: number, name: string, language?: string}>; selectedTextTrack: number; useCustomSubtitles: boolean; subtitleSize: number; subtitleBackground: boolean; fetchAvailableSubtitles: () => void; - loadWyzieSubtitle: (subtitle: WyzieSubtitle) => void; + loadStremioSubtitle: (subtitle: Subtitle) => void; selectTextTrack: (trackId: number) => void; increaseSubtitleSize: () => void; decreaseSubtitleSize: () => void; @@ -51,7 +52,7 @@ export const SubtitleModals: React.FC = ({ subtitleSize, subtitleBackground, fetchAvailableSubtitles, - loadWyzieSubtitle, + loadStremioSubtitle, selectTextTrack, increaseSubtitleSize, decreaseSubtitleSize, @@ -59,6 +60,8 @@ export const SubtitleModals: React.FC = ({ }) => { // Track which specific online subtitle is currently loaded const [selectedOnlineSubtitleId, setSelectedOnlineSubtitleId] = React.useState(null); + // Track which subtitle is currently being loaded + const [loadingSubtitleId, setLoadingSubtitleId] = React.useState(null); React.useEffect(() => { if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) { @@ -77,11 +80,26 @@ export const SubtitleModals: React.FC = ({ setShowSubtitleModal(false); }; - const handleLoadWyzieSubtitle = (subtitle: WyzieSubtitle) => { + const handleLoadStremioSubtitle = (subtitle: Subtitle) => { + console.log('[SubtitleModals] Starting to load subtitle:', subtitle.id); + setLoadingSubtitleId(subtitle.id); setSelectedOnlineSubtitleId(subtitle.id); - loadWyzieSubtitle(subtitle); + loadStremioSubtitle(subtitle); }; + // Clear loading state when subtitle loading is complete + React.useEffect(() => { + console.log('[SubtitleModals] isLoadingSubtitles changed:', isLoadingSubtitles); + if (!isLoadingSubtitles) { + console.log('[SubtitleModals] Clearing loadingSubtitleId'); + // Force clear loading state with a small delay to ensure proper re-render + setTimeout(() => { + setLoadingSubtitleId(null); + console.log('[SubtitleModals] loadingSubtitleId cleared'); + }, 50); + } + }, [isLoadingSubtitles]); + // Main subtitle menu const renderSubtitleMenu = () => { if (!showSubtitleModal) return null; @@ -396,10 +414,10 @@ export const SubtitleModals: React.FC = ({ borderColor: isSelected ? 'rgba(34, 197, 94, 0.3)' : 'rgba(255, 255, 255, 0.1)', }} onPress={() => { - handleLoadWyzieSubtitle(sub); + handleLoadStremioSubtitle(sub); }} activeOpacity={0.7} - disabled={isLoadingSubtitles} + disabled={loadingSubtitleId === sub.id} > @@ -409,16 +427,16 @@ export const SubtitleModals: React.FC = ({ fontWeight: '500', marginBottom: 4, }}> - {sub.display} + {formatLanguage(sub.lang)} - {formatLanguage(sub.language)} + {sub.addonName || 'Stremio Addon'} - {isLoadingSubtitles ? ( + {loadingSubtitleId === sub.id ? ( ) : isSelected ? ( @@ -539,4 +557,4 @@ export const SubtitleModals: React.FC = ({ ); }; -export default SubtitleModals; \ No newline at end of file +export default SubtitleModals; \ No newline at end of file diff --git a/src/components/player/utils/playerTypes.ts b/src/components/player/utils/playerTypes.ts index 3f2c5d82..1ee195a6 100644 --- a/src/components/player/utils/playerTypes.ts +++ b/src/components/player/utils/playerTypes.ts @@ -71,18 +71,4 @@ export interface SubtitleCue { start: number; end: number; text: string; -} - -// Add interface for Wyzie subtitle API response -export interface WyzieSubtitle { - id: string; - url: string; - flagUrl: string; - format: string; - encoding: string; - media: string; - display: string; - language: string; - isHearingImpaired: boolean; - source: string; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/hooks/useTraktAutosync.ts b/src/hooks/useTraktAutosync.ts index 8c14ea90..013a6d07 100644 --- a/src/hooks/useTraktAutosync.ts +++ b/src/hooks/useTraktAutosync.ts @@ -190,7 +190,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) { currentTime ); - logger.log(`[TraktAutosync] Synced progress ${progressPercent.toFixed(1)}%: ${contentData.title}`); + // Progress sync logging removed } } catch (error) { logger.error('[TraktAutosync] Error syncing progress:', error); @@ -201,7 +201,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) { const handlePlaybackEnd = useCallback(async (currentTime: number, duration: number, reason: 'ended' | 'unmount' = 'ended') => { const now = Date.now(); - logger.log(`[TraktAutosync] handlePlaybackEnd called: reason=${reason}, time=${currentTime}, duration=${duration}, authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}, started=${hasStartedWatching.current}, stopped=${hasStopped.current}, complete=${isSessionComplete.current}, session=${sessionKey.current}, unmountCount=${unmountCount.current}`); + // Removed excessive logging for handlePlaybackEnd calls if (!isAuthenticated || !autosyncSettings.enabled) { logger.log(`[TraktAutosync] Skipping handlePlaybackEnd: authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}`); @@ -227,7 +227,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) { hasStopped.current = false; isSignificantUpdate = true; } else { - logger.log(`[TraktAutosync] Already stopped this session, skipping duplicate call (reason: ${reason})`); + // Already stopped this session, skipping duplicate call return; } } @@ -247,7 +247,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) { try { let progressPercent = duration > 0 ? (currentTime / duration) * 100 : 0; - logger.log(`[TraktAutosync] Initial progress calculation: ${progressPercent.toFixed(1)}%`); + // Initial progress calculation logging removed // For unmount calls, always use the highest available progress // Check current progress, last synced progress, and local storage progress @@ -278,28 +278,26 @@ export function useTraktAutosync(options: TraktAutosyncOptions) { } if (maxProgress !== progressPercent) { - logger.log(`[TraktAutosync] Using highest available progress for unmount: ${maxProgress.toFixed(1)}% (current: ${progressPercent.toFixed(1)}%, last synced: ${lastSyncProgress.current.toFixed(1)}%)`); + // Highest progress logging removed progressPercent = maxProgress; } else { - logger.log(`[TraktAutosync] Current progress is already highest: ${progressPercent.toFixed(1)}%`); + // Current progress logging removed } } // If we have valid progress but no started session, force start one first if (!hasStartedWatching.current && progressPercent > 1) { - logger.log(`[TraktAutosync] Force starting session for progress: ${progressPercent.toFixed(1)}%`); const contentData = buildContentData(); const success = await startWatching(contentData, progressPercent); if (success) { hasStartedWatching.current = true; - logger.log(`[TraktAutosync] Force started watching: ${contentData.title}`); } } // Only stop if we have meaningful progress (>= 0.5%) or it's a natural video end // Lower threshold for unmount calls to catch more edge cases if (reason === 'unmount' && progressPercent < 0.5) { - logger.log(`[TraktAutosync] Skipping unmount stop for ${options.title} - too early (${progressPercent.toFixed(1)}%)`); + // Early unmount stop logging removed return; } diff --git a/src/hooks/useTraktIntegration.ts b/src/hooks/useTraktIntegration.ts index 1e818a0e..ccd62647 100644 --- a/src/hooks/useTraktIntegration.ts +++ b/src/hooks/useTraktIntegration.ts @@ -243,7 +243,7 @@ export function useTraktIntegration() { // Get playback progress from Trakt const getTraktPlaybackProgress = useCallback(async (type?: 'movies' | 'shows'): Promise => { - logger.log(`[useTraktIntegration] getTraktPlaybackProgress called - isAuthenticated: ${isAuthenticated}, type: ${type || 'all'}`); + // getTraktPlaybackProgress call logging removed if (!isAuthenticated) { logger.log('[useTraktIntegration] getTraktPlaybackProgress: Not authenticated'); @@ -251,9 +251,9 @@ export function useTraktIntegration() { } try { - logger.log('[useTraktIntegration] Calling traktService.getPlaybackProgress...'); + // traktService.getPlaybackProgress call logging removed const result = await traktService.getPlaybackProgress(type); - logger.log(`[useTraktIntegration] traktService.getPlaybackProgress returned ${result.length} items`); + // Playback progress logging removed return result; } catch (error) { logger.error('[useTraktIntegration] Error getting playback progress:', error); @@ -335,7 +335,7 @@ export function useTraktIntegration() { traktService.getWatchedMovies() ]); - logger.log(`[useTraktIntegration] Retrieved ${traktProgress.length} progress items, ${watchedMovies.length} watched movies`); + // Progress retrieval logging removed // Batch process all updates to reduce storage notifications const updatePromises: Promise[] = []; @@ -406,7 +406,7 @@ export function useTraktIntegration() { // Execute all updates in parallel await Promise.all(updatePromises); - logger.log(`[useTraktIntegration] Successfully merged ${updatePromises.length} items from Trakt`); + // Trakt merge logging removed return true; } catch (error) { logger.error('[useTraktIntegration] Error fetching and merging Trakt progress:', error); @@ -431,9 +431,7 @@ export function useTraktIntegration() { if (isAuthenticated) { // Fetch Trakt progress and merge with local fetchAndMergeTraktProgress().then((success) => { - if (success) { - logger.log('[useTraktIntegration] Trakt progress merged successfully'); - } + // Trakt progress merge success logging removed }); } }, [isAuthenticated, fetchAndMergeTraktProgress]); @@ -503,4 +501,4 @@ export function useTraktIntegration() { fetchAndMergeTraktProgress, forceSyncTraktProgress // For manual testing }; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/hooks/useWatchProgress.ts b/src/hooks/useWatchProgress.ts index 63427bc3..7e0f5a05 100644 --- a/src/hooks/useWatchProgress.ts +++ b/src/hooks/useWatchProgress.ts @@ -114,7 +114,7 @@ export const useWatchProgress = ( const mostRecentProgress = sortedProgresses[0]; const progress = mostRecentProgress.progress; - logger.log(`[useWatchProgress] Using most recent progress for ${mostRecentProgress.episodeId}, updated at ${new Date(progress.lastUpdated).toLocaleString()}`); + // Removed excessive logging for most recent progress setWatchProgress({ ...progress, @@ -204,4 +204,4 @@ export const useWatchProgress = ( getPlayButtonText, loadWatchProgress }; -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 2048dd59..7e4bef87 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -85,7 +85,8 @@ type HomeScreenListItem = | { type: 'continueWatching'; key: string } | { type: 'catalog'; catalog: CatalogContent; key: string } | { type: 'placeholder'; key: string } - | { type: 'welcome'; key: string }; + | { type: 'welcome'; key: string } + | { type: 'loadMore'; key: string }; // Sample categories (real app would get these from API) const SAMPLE_CATEGORIES: Category[] = [ @@ -122,6 +123,7 @@ const HomeScreen = () => { const [loadedCatalogCount, setLoadedCatalogCount] = useState(0); const [hasAddons, setHasAddons] = useState(null); const totalCatalogsRef = useRef(0); + const [visibleCatalogCount, setVisibleCatalogCount] = useState(8); // Moderate number of visible catalogs const { featuredContent, @@ -131,7 +133,7 @@ const HomeScreen = () => { refreshFeatured } = useFeaturedContent(); - // Progressive catalog loading function + // Progressive catalog loading function with performance optimizations const loadCatalogsProgressively = useCallback(async () => { setCatalogsLoading(true); setCatalogs([]); @@ -155,6 +157,24 @@ const HomeScreen = () => { const catalogPromises: Promise[] = []; let catalogIndex = 0; + // Limit concurrent catalog loading to prevent overwhelming the system + const MAX_CONCURRENT_CATALOGS = 5; + let activeCatalogLoads = 0; + const catalogQueue: (() => Promise)[] = []; + + const processCatalogQueue = async () => { + while (catalogQueue.length > 0 && activeCatalogLoads < MAX_CONCURRENT_CATALOGS) { + const catalogLoader = catalogQueue.shift(); + if (catalogLoader) { + activeCatalogLoads++; + catalogLoader().finally(() => { + activeCatalogLoads--; + processCatalogQueue(); // Process next in queue + }); + } + } + }; + for (const addon of addons) { if (addon.catalogs) { for (const catalog of addon.catalogs) { @@ -167,29 +187,29 @@ const HomeScreen = () => { const currentIndex = catalogIndex; catalogPlaceholders.push(null); // Reserve position - const catalogPromise = (async () => { + const catalogLoader = async () => { try { const manifest = addonManifests.find((a: any) => a.id === addon.id); if (!manifest) return; const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1); if (metas && metas.length > 0) { - const items = metas.map((meta: any) => ({ + // Limit items per catalog to reduce memory usage + const limitedMetas = metas.slice(0, 20); // Moderate limit for better content variety + + const items = limitedMetas.map((meta: any) => ({ id: meta.id, type: meta.type, name: meta.name, poster: meta.poster, posterShape: meta.posterShape, - banner: meta.background, - logo: meta.logo, + // Remove banner and logo to reduce memory usage imdbRating: meta.imdbRating, year: meta.year, genres: meta.genres, description: meta.description, runtime: meta.runtime, released: meta.released, - trailerStreams: meta.trailerStreams, - videos: meta.videos, directors: meta.director, creators: meta.creator, certification: meta.certification @@ -221,9 +241,9 @@ const HomeScreen = () => { } finally { setLoadedCatalogCount(prev => prev + 1); } - })(); + }; - catalogPromises.push(catalogPromise); + catalogQueue.push(catalogLoader); catalogIndex++; } } @@ -235,11 +255,28 @@ const HomeScreen = () => { // Initialize catalogs array with proper length setCatalogs(new Array(catalogIndex).fill(null)); - // Start all catalog loading promises but don't wait for them - // They will update the state progressively as they complete - await Promise.allSettled(catalogPromises); + // Start processing the catalog queue + processCatalogQueue(); - // Only set catalogsLoading to false after all promises have settled + // Wait for all catalogs to load with a timeout + const checkAllLoaded = () => { + return new Promise((resolve) => { + const interval = setInterval(() => { + if (loadedCatalogCount >= catalogIndex || catalogQueue.length === 0) { + clearInterval(interval); + resolve(); + } + }, 100); + + // Timeout after 30 seconds + setTimeout(() => { + clearInterval(interval); + resolve(); + }, 30000); + }); + }; + + await checkAllLoaded(); setCatalogsLoading(false); } catch (error) { console.error('[HomeScreen] Error in progressive catalog loading:', error); @@ -322,48 +359,67 @@ const HomeScreen = () => { if (refreshTimeoutRef.current) { clearTimeout(refreshTimeoutRef.current); } + + // Clear image cache when component unmounts to free memory + try { + ExpoImage.clearMemoryCache(); + } catch (error) { + console.warn('Failed to clear image cache:', error); + } }; }, [currentTheme.colors.darkBackground]); - // Optimized preload images function with better memory management + // Periodic memory cleanup when many catalogs are loaded + useEffect(() => { + if (catalogs.filter(c => c).length > 15) { + const cleanup = setTimeout(() => { + try { + ExpoImage.clearMemoryCache(); + } catch (error) { + console.warn('Failed to clear image cache:', error); + } + }, 60000); // Clean every 60 seconds when many catalogs are loaded + + return () => clearTimeout(cleanup); + } + }, [catalogs]); + + // Balanced preload images function const preloadImages = useCallback(async (content: StreamingContent[]) => { if (!content.length) return; try { - // Significantly reduced concurrent prefetching to prevent heating - const BATCH_SIZE = 2; // Reduced from 3 to 2 - const MAX_IMAGES = 5; // Reduced from 10 to 5 + // Moderate prefetching for better performance balance + const MAX_IMAGES = 6; // Preload 6 most important images - // Only preload the most important images (poster and banner, skip logo) - const allImages = content.slice(0, MAX_IMAGES) - .map(item => [item.poster, item.banner]) - .flat() + // Only preload poster images (skip banner and logo entirely) + const posterImages = content.slice(0, MAX_IMAGES) + .map(item => item.poster) .filter(Boolean) as string[]; - // Process in smaller batches with longer delays - for (let i = 0; i < allImages.length; i += BATCH_SIZE) { - const batch = allImages.slice(i, i + BATCH_SIZE); + // Process in batches of 2 with moderate delays + for (let i = 0; i < posterImages.length; i += 2) { + const batch = posterImages.slice(i, i + 2); - try { - await Promise.all( - batch.map(async (imageUrl) => { - try { - // Use our cache service instead of direct prefetch - await imageCacheService.getCachedImageUrl(imageUrl); - // Increased delay between prefetches to reduce CPU load - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - // Silently handle individual prefetch errors - } - }) - ); - - // Longer delay between batches to allow GC and reduce heating - if (i + BATCH_SIZE < allImages.length) { - await new Promise(resolve => setTimeout(resolve, 200)); + await Promise.all(batch.map(async (imageUrl) => { + try { + // Use our cache service with timeout + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), 2000) + ); + + await Promise.race([ + imageCacheService.getCachedImageUrl(imageUrl), + timeoutPromise + ]); + } catch (error) { + // Skip failed images and continue } - } catch (error) { - // Continue with next batch if current batch fails + })); + + // Moderate delay between batches + if (i + 2 < posterImages.length) { + await new Promise(resolve => setTimeout(resolve, 200)); } } } catch (error) { @@ -490,7 +546,10 @@ const HomeScreen = () => { data.push({ type: 'thisWeek', key: 'thisWeek' }); data.push({ type: 'continueWatching', key: 'continueWatching' }); - catalogs.forEach((catalog, index) => { + // Only show a limited number of catalogs initially for performance + const catalogsToShow = catalogs.slice(0, visibleCatalogCount); + + catalogsToShow.forEach((catalog, index) => { if (catalog) { data.push({ type: 'catalog', catalog, key: `${catalog.addon}-${catalog.id}-${index}` }); } else { @@ -499,8 +558,17 @@ const HomeScreen = () => { } }); + // Add a "Load More" button if there are more catalogs to show + if (catalogs.length > visibleCatalogCount && catalogs.filter(c => c).length > visibleCatalogCount) { + data.push({ type: 'loadMore', key: 'load-more' } as any); + } + return data; - }, [hasAddons, showHeroSection, catalogs]); + }, [hasAddons, showHeroSection, catalogs, visibleCatalogCount]); + + const handleLoadMoreCatalogs = useCallback(() => { + setVisibleCatalogCount(prev => Math.min(prev + 5, catalogs.length)); + }, [catalogs.length]); const renderListItem = useCallback(({ item }: { item: HomeScreenListItem }) => { switch (item.type) { @@ -535,7 +603,7 @@ const HomeScreen = () => { horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.placeholderPosters}> - {[...Array(5)].map((_, posterIndex) => ( + {[...Array(3)].map((_, posterIndex) => ( { ); + case 'loadMore': + return ( + + + + + + Load More Catalogs + + + + + ); case 'welcome': return ; default: @@ -556,7 +640,8 @@ const HomeScreen = () => { featuredContent, isSaved, handleSaveToLibrary, - currentTheme.colors + currentTheme.colors, + handleLoadMoreCatalogs ]); const ListFooterComponent = useMemo(() => ( @@ -608,9 +693,9 @@ const HomeScreen = () => { ]} showsVerticalScrollIndicator={false} ListFooterComponent={ListFooterComponent} - initialNumToRender={5} - maxToRenderPerBatch={5} - windowSize={11} + initialNumToRender={4} + maxToRenderPerBatch={3} + windowSize={7} removeClippedSubviews={Platform.OS === 'android'} onEndReachedThreshold={0.5} updateCellsBatchingPeriod={50} @@ -618,11 +703,8 @@ const HomeScreen = () => { minIndexForVisible: 0, autoscrollToTopThreshold: 10 }} - getItemLayout={(data, index) => ({ - length: index === 0 ? 400 : 280, // Approximate heights for different item types - offset: index === 0 ? 0 : 400 + (index - 1) * 280, - index, - })} + disableIntervalMomentum={true} + scrollEventThrottle={16} /> ); @@ -757,6 +839,27 @@ const styles = StyleSheet.create({ fontWeight: '600', marginLeft: 8, }, + loadMoreContainer: { + padding: 16, + alignItems: 'center', + }, + loadMoreButton: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 25, + elevation: 2, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 4, + }, + loadMoreText: { + marginLeft: 8, + fontSize: 14, + fontWeight: '600', + }, loadingContainer: { flex: 1, justifyContent: 'center', @@ -1131,4 +1234,4 @@ const styles = StyleSheet.create({ }, }); -export default React.memo(HomeScreen); \ No newline at end of file +export default React.memo(HomeScreen); \ No newline at end of file diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 1d01a7c8..a181971f 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -839,32 +839,9 @@ export const StreamsScreen = () => { const streamName = stream.name || stream.title || 'Unnamed Stream'; const streamProvider = stream.addonId || stream.addonName || stream.name; - // Debug logging for stream provider identification - console.log('[StreamsScreen] Stream Provider Debug:'); - console.log(' stream.addonId:', stream.addonId); - console.log(' stream.addonName:', stream.addonName); - console.log(' stream.name:', stream.name); - console.log(' final streamProvider:', streamProvider); - console.log(' stream.url:', stream.url); + // Stream provider debug logging removed - // Enhanced logging for NetMirror streams - if (streamProvider && (streamProvider.toLowerCase().includes('netmirror') || stream.url?.includes('nm-cdn'))) { - console.log('\n[StreamsScreen] šŸŽ¬ NETMIRROR STREAM DETAILS:'); - console.log(' šŸ“ŗ Stream Name:', streamName); - console.log(' šŸ”— Final Extracted URL:', stream.url); - console.log(' šŸ“‹ Headers:', JSON.stringify(stream.headers, null, 2)); - console.log(' šŸŽÆ Quality:', stream.title?.match(/(\d+)p/)?.[1] || 'Unknown'); - console.log(' šŸ“± Platform:', Platform.OS); - console.log(' āš™ļø Stream Object:', JSON.stringify({ - name: stream.name, - title: stream.title, - url: stream.url, - headers: stream.headers, - addonId: stream.addonId, - addonName: stream.addonName - }, null, 2)); - console.log('\n'); - } + // NetMirror stream logging removed // Navigate to player immediately without waiting for orientation lock // This prevents delay in player opening diff --git a/src/services/storageService.ts b/src/services/storageService.ts index 9ef38bc8..75c296c6 100644 --- a/src/services/storageService.ts +++ b/src/services/storageService.ts @@ -350,9 +350,7 @@ class StorageService { }; await this.setWatchProgress(id, type, newProgress, episodeId); - const timeSource = exactTime ? 'exact' : 'calculated'; - const durationSource = await this.getContentDuration(id, type, episodeId) ? 'stored' : 'estimated'; - logger.log(`[StorageService] Created progress from Trakt: ${(currentTime/60).toFixed(1)}min (${timeSource}) of ${(duration/60).toFixed(0)}min (${durationSource})`); + // Progress creation logging removed } else { // Local progress exists - merge intelligently const localProgressPercent = (localProgress.currentTime / localProgress.duration) * 100; @@ -416,11 +414,7 @@ class StorageService { }; await this.setWatchProgress(id, type, updatedProgress, episodeId); - // Only log significant changes - if (progressDiff > 10 || traktProgress === 100) { - const timeSource = exactTime ? 'exact' : 'calculated'; - logger.log(`[StorageService] Updated progress: ${(currentTime/60).toFixed(1)}min (${timeSource}) = ${traktProgress}%`); - } + // Progress update logging removed } } catch (error) { logger.error('Error merging with Trakt progress:', error); @@ -428,4 +422,4 @@ class StorageService { } } -export const storageService = StorageService.getInstance(); \ No newline at end of file +export const storageService = StorageService.getInstance(); \ No newline at end of file diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index 075614f2..3feb5f9b 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -1121,8 +1121,10 @@ class StremioService { // For series episodes, use the videoId directly which includes series ID + episode info let url = ''; if (type === 'series' && videoId) { - // For series, the format should be /subtitles/series/tt12345:1:2.json - url = `${baseUrl}/subtitles/${type}/${videoId}.json`; + // For series, extract the IMDB ID and episode info from videoId (series:tt12345:1:2) + // and construct the proper URL format: /subtitles/series/tt12345:1:2.json + const episodeInfo = videoId.replace('series:', ''); + url = `${baseUrl}/subtitles/series/${episodeInfo}.json`; } else { // For movies, the format is /subtitles/movie/tt12345.json url = `${baseUrl}/subtitles/${type}/${id}.json`; diff --git a/src/services/traktService.ts b/src/services/traktService.ts index cbd3c32f..3bce8074 100644 --- a/src/services/traktService.ts +++ b/src/services/traktService.ts @@ -598,14 +598,7 @@ export class TraktService { const response = await fetch(`${TRAKT_API_URL}${endpoint}`, options); - // Debug log API responses for scrobble endpoints - if (endpoint.includes('/scrobble/')) { - logger.log(`[TraktService] DEBUG API Response for ${endpoint}:`, { - status: response.status, - statusText: response.statusText, - headers: Object.fromEntries(response.headers.entries()) - }); - } + // Debug logging removed to reduce terminal noise // Handle rate limiting with exponential backoff if (response.status === 429) { @@ -693,7 +686,7 @@ export class TraktService { // Debug log successful scrobble responses if (endpoint.includes('/scrobble/')) { - logger.log(`[TraktService] DEBUG API Success for ${endpoint}:`, responseData); + // API success logging removed } return responseData; @@ -1150,7 +1143,7 @@ export class TraktService { progress: Math.round(progress * 100) / 100 // Round to 2 decimal places }; - logger.log('[TraktService] DEBUG movie payload:', JSON.stringify(payload, null, 2)); + // Movie payload logging removed return payload; } else if (contentData.type === 'episode') { if (!contentData.season || !contentData.episode || !contentData.showTitle || !contentData.showYear) { @@ -1192,7 +1185,7 @@ export class TraktService { payload.episode.ids.imdb = cleanEpisodeImdbId; } - logger.log('[TraktService] DEBUG episode payload:', JSON.stringify(payload, null, 2)); + // Episode payload logging removed return payload; } @@ -1287,17 +1280,7 @@ export class TraktService { return true; } - // Debug log the content data being sent - logger.log(`[TraktService] DEBUG scrobbleStart payload:`, { - type: contentData.type, - title: contentData.title, - year: contentData.year, - imdbId: contentData.imdbId, - season: contentData.season, - episode: contentData.episode, - showTitle: contentData.showTitle, - progress: progress - }); + // Debug log removed to reduce terminal noise // Only start if not already watching this content if (this.currentlyWatching.has(watchingKey)) { @@ -1485,23 +1468,23 @@ export class TraktService { public async debugPlaybackProgress(): Promise { try { if (!await this.isAuthenticated()) { - logger.log('[TraktService] DEBUG: Not authenticated'); + // Debug logging removed return; } const progress = await this.getPlaybackProgress(); - logger.log(`[TraktService] DEBUG: Found ${progress.length} items in Trakt playback progress:`); + // Progress logging removed progress.forEach((item, index) => { if (item.type === 'movie' && item.movie) { - logger.log(`[TraktService] DEBUG ${index + 1}: Movie "${item.movie.title}" (${item.movie.year}) - ${item.progress.toFixed(1)}% - Paused: ${item.paused_at}`); + // Movie progress logging removed } else if (item.type === 'episode' && item.episode && item.show) { - logger.log(`[TraktService] DEBUG ${index + 1}: Episode "${item.show.title}" S${item.episode.season}E${item.episode.number} - ${item.progress.toFixed(1)}% - Paused: ${item.paused_at}`); + // Episode progress logging removed } }); if (progress.length === 0) { - logger.log('[TraktService] DEBUG: No items found in Trakt playback progress'); + // No progress logging removed } } catch (error) { logger.error('[TraktService] DEBUG: Error fetching playback progress:', error); diff --git a/src/utils/.logger.ts.swp b/src/utils/.logger.ts.swp new file mode 100644 index 0000000000000000000000000000000000000000..9c23798eadc29e2d48628ee02a5461e295068a90 GIT binary patch literal 12288 zcmeI2F>4e-7>4IkDH@H6mCdIj+!gL7T8KHsCMg0MR5U0Oj@iAr%_Qs2GBbBc2vL84 zzraGVu(H&`O0?5fTMI$|fQ_BvyO%w)T3h+{?Sw$eONKcK3>hgTQc3xixR{5cR7n z_Tgf;r%mW=9Cp1@$a#u>m%~ z2G{@_U;}J`4X^<=FpdU%UK5Y7@riQt^>VEJGnThJumLu}2G{@_U;}J`4X^<=zy{a= z8(;%tXh61w_;6T=%}Hz?|Njr3|9>78;tTi;-h(F~1FJxTo8S^S1x|uvU<&*?BE(nl z4!i|#z-#anJO(jX2KT^Sa21>b2fzgQgMIu4-@zyF3cLg_z;o~n6notOe6ayGzy{a= z8(;%$fDNz#HoykP!hqVyjrA0p%z4^*-#EshB-nS|xg2 z+N@&qaA}ojWv#Ilvqx&Hgrh|jqlYT@3Lb3`dfsZy%`dfDJ1q>u!98h5l9JR!GNqoh zx@airjBY6zds>uU_Y^wVKS8z|d;IpYskzlX=}|m52D20y92%K5MVw0K$}@%|@E13Y B|2zNy literal 0 HcmV?d00001 diff --git a/src/utils/httpInterceptor.ts b/src/utils/httpInterceptor.ts index 1933adf5..e7a2143b 100644 --- a/src/utils/httpInterceptor.ts +++ b/src/utils/httpInterceptor.ts @@ -5,13 +5,7 @@ export const logHttpRequest = async (url: string, options: RequestInit = {}): Pr const method = options.method || 'GET'; const headers = options.headers || {}; - // Log HTTP request - console.log('\n🌐 [AndroidVideoPlayer] HTTP REQUEST:'); - console.log('šŸ“ URL:', url); - console.log('šŸ”§ Method:', method); - console.log('šŸ“‹ Headers:', JSON.stringify(headers, null, 2)); - console.log('ā° Request Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP request logging removed const startTime = Date.now(); @@ -21,33 +15,14 @@ export const logHttpRequest = async (url: string, options: RequestInit = {}): Pr const endTime = Date.now(); const duration = endTime - startTime; - // Log HTTP response success - console.log('\nāœ… [AndroidVideoPlayer] HTTP RESPONSE SUCCESS:'); - console.log('šŸ“ URL:', url); - console.log('šŸ“Š Status:', `${response.status} ${response.statusText}`); - console.log('šŸ“‹ Response Headers:', JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)); - console.log('ā±ļø Duration:', `${duration}ms`); - console.log('šŸ“¦ Content-Type:', response.headers.get('content-type') || 'Unknown'); - console.log('šŸ“ Content-Length:', response.headers.get('content-length') || 'Unknown'); - console.log('šŸ”’ CORS:', response.headers.get('access-control-allow-origin') || 'Not specified'); - console.log('ā° Response Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP response success logging removed return response; } catch (error: any) { const endTime = Date.now(); const duration = endTime - startTime; - // Log HTTP response error - console.log('\nāŒ [AndroidVideoPlayer] HTTP RESPONSE ERROR:'); - console.log('šŸ“ URL:', url); - console.log('šŸ“Š Status: Network Error'); - console.log('šŸ’¬ Error Message:', error.message || 'Unknown error'); - console.log('šŸ” Error Type:', error.name || 'Unknown'); - console.log('ā±ļø Duration:', `${duration}ms`); - console.log('šŸ“‹ Full Error:', JSON.stringify(error, null, 2)); - console.log('ā° Error Time:', new Date().toISOString()); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + // HTTP response error logging removed throw error; }