mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
test
This commit is contained in:
parent
b21f13c283
commit
62a2ed0046
20 changed files with 385 additions and 309 deletions
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -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
BIN
src/utils/.logger.ts.swp
Normal file
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue