This commit is contained in:
tapframe 2025-08-06 20:45:51 +05:30
parent b21f13c283
commit 62a2ed0046
20 changed files with 385 additions and 309 deletions

15
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -109,7 +109,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
decelerationRate="fast"
snapToAlignment="start"
ItemSeparatorComponent={() => <View style={{ width: 8 }} />}
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}
/>
</Animated.View>
);
@ -177,4 +178,4 @@ const styles = StyleSheet.create({
},
});
export default React.memo(CatalogSection);
export default React.memo(CatalogSection);

View file

@ -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 && (
<View style={[styles.loadingOverlay, { backgroundColor: currentTheme.colors.elevation1 }]}>
<MaterialIcons name="broken-image" size={24} color={currentTheme.colors.textMuted} />
</View>
)}
{isWatched && (
<View style={styles.watchedIndicator}>
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success} />
@ -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;
export default ContentItem;

View file

@ -799,7 +799,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({
// 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<HeroSectionProps> = ({
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]);

View file

@ -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<boolean>(true);
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
const [availableSubtitles, setAvailableSubtitles] = useState<Subtitle[]>([]);
const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState<boolean>(false);
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(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}

View file

@ -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<boolean>(true);
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
const [availableSubtitles, setAvailableSubtitles] = useState<Subtitle[]>([]);
const [showSubtitleLanguageModal, setShowSubtitleLanguageModal] = useState<boolean>(false);
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(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}

View file

@ -28,15 +28,15 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
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 (
<View style={styles.resumeOverlay}>
@ -91,4 +91,4 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
);
};
export default ResumeOverlay;
export default ResumeOverlay;

View file

@ -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<SubtitleModalsProps> = ({
subtitleSize,
subtitleBackground,
fetchAvailableSubtitles,
loadWyzieSubtitle,
loadStremioSubtitle,
selectTextTrack,
increaseSubtitleSize,
decreaseSubtitleSize,
@ -59,6 +60,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
}) => {
// Track which specific online subtitle is currently loaded
const [selectedOnlineSubtitleId, setSelectedOnlineSubtitleId] = React.useState<string | null>(null);
// Track which subtitle is currently being loaded
const [loadingSubtitleId, setLoadingSubtitleId] = React.useState<string | null>(null);
React.useEffect(() => {
if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) {
@ -77,11 +80,26 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
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<SubtitleModalsProps> = ({
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}
>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
<View style={{ flex: 1 }}>
@ -409,16 +427,16 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
fontWeight: '500',
marginBottom: 4,
}}>
{sub.display}
{formatLanguage(sub.lang)}
</Text>
<Text style={{
color: 'rgba(255, 255, 255, 0.6)',
fontSize: 13,
}}>
{formatLanguage(sub.language)}
{sub.addonName || 'Stremio Addon'}
</Text>
</View>
{isLoadingSubtitles ? (
{loadingSubtitleId === sub.id ? (
<ActivityIndicator size="small" color="#22C55E" />
) : isSelected ? (
<MaterialIcons name="check" size={20} color="#22C55E" />
@ -539,4 +557,4 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
);
};
export default SubtitleModals;
export default SubtitleModals;

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -243,7 +243,7 @@ export function useTraktIntegration() {
// Get playback progress from Trakt
const getTraktPlaybackProgress = useCallback(async (type?: 'movies' | 'shows'): Promise<TraktPlaybackItem[]> => {
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<void>[] = [];
@ -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
};
}
}

View file

@ -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
};
};
};

View file

@ -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<boolean | null>(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<void>[] = [];
let catalogIndex = 0;
// Limit concurrent catalog loading to prevent overwhelming the system
const MAX_CONCURRENT_CATALOGS = 5;
let activeCatalogLoads = 0;
const catalogQueue: (() => Promise<void>)[] = [];
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<void>((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) => (
<View
key={posterIndex}
style={[styles.placeholderPoster, { backgroundColor: currentTheme.colors.elevation1 }]}
@ -545,6 +613,22 @@ const HomeScreen = () => {
</View>
</Animated.View>
);
case 'loadMore':
return (
<Animated.View entering={FadeIn.duration(300)}>
<View style={styles.loadMoreContainer}>
<TouchableOpacity
style={[styles.loadMoreButton, { backgroundColor: currentTheme.colors.primary }]}
onPress={handleLoadMoreCatalogs}
>
<MaterialIcons name="expand-more" size={20} color={currentTheme.colors.white} />
<Text style={[styles.loadMoreText, { color: currentTheme.colors.white }]}>
Load More Catalogs
</Text>
</TouchableOpacity>
</View>
</Animated.View>
);
case 'welcome':
return <FirstTimeWelcome />;
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}
/>
</View>
);
@ -757,6 +839,27 @@ const styles = StyleSheet.create<any>({
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<any>({
},
});
export default React.memo(HomeScreen);
export default React.memo(HomeScreen);

View file

@ -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

View file

@ -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();
export const storageService = StorageService.getInstance();

View file

@ -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`;

View file

@ -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<void> {
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);

BIN
src/utils/.logger.ts.swp Normal file

Binary file not shown.

View file

@ -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;
}