improved player detection logic ios

This commit is contained in:
tapframe 2025-09-15 02:18:23 +05:30
parent a241de97f6
commit dc181905e9
4 changed files with 312 additions and 105 deletions

View file

@ -124,14 +124,50 @@ else
fi
echo ""
# Try upload with extended timeout and retry logic
max_retries=3
retry_count=0
# Upload with multiple strategies to handle server issues
echo "🔄 Uploading with extended timeout..."
echo "📊 File size: $(du -h ${timestamp}.zip | cut -f1)"
while [ $retry_count -lt $max_retries ]; do
echo "🔄 Upload attempt $((retry_count + 1))/$max_retries..."
# Strategy 1: Try with HTTP/2 disabled and longer timeouts
echo "🔍 Attempt 1: HTTP/1.1 with extended timeout..."
response=$(curl --http1.1 --max-time 600 --connect-timeout 60 -X POST $serverHost/api/upload \
-F "file=@${timestamp}.zip" \
-F "runtimeVersion=$runtimeVersion" \
-F "commitHash=$commitHash" \
-F "commitMessage=$commitMessage" \
${RELEASE_NOTES:+-F "releaseNotes=$RELEASE_NOTES"} \
--write-out "HTTP_CODE:%{http_code}" \
--silent \
--show-error \
--retry 2 \
--retry-delay 5)
# Extract HTTP code from response
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
# Check if we got a valid HTTP code
if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then
echo "❌ Failed to extract HTTP status code from response"
echo "Response: $response"
http_code="000"
fi
echo "HTTP Status: $http_code"
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo ""
echo "✅ Successfully uploaded to $serverHost/api/upload"
# Extract the response body (everything before HTTP_CODE)
response_body=$(echo "$response" | sed 's/HTTP_CODE:[0-9]*$//')
if [ -n "$response_body" ]; then
echo "📦 Server response: $response_body"
fi
else
echo "❌ Strategy 1 failed, trying alternative approach..."
response=$(curl --http1.1 --max-time 300 --connect-timeout 30 -X POST $serverHost/api/upload \
# Strategy 2: Try with different curl options
echo "🔍 Attempt 2: Alternative curl configuration..."
response=$(curl --http1.1 --max-time 900 --connect-timeout 120 -X POST $serverHost/api/upload \
-F "file=@${timestamp}.zip" \
-F "runtimeVersion=$runtimeVersion" \
-F "commitHash=$commitHash" \
@ -139,12 +175,13 @@ while [ $retry_count -lt $max_retries ]; do
${RELEASE_NOTES:+-F "releaseNotes=$RELEASE_NOTES"} \
--write-out "HTTP_CODE:%{http_code}" \
--silent \
--show-error)
--show-error \
--no-buffer \
--tcp-nodelay)
# Extract HTTP code from response
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
# Check if we got a valid HTTP code
if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then
echo "❌ Failed to extract HTTP status code from response"
echo "Response: $response"
@ -156,28 +193,30 @@ while [ $retry_count -lt $max_retries ]; do
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo ""
echo "✅ Successfully uploaded to $serverHost/api/upload"
break
else
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "⚠️ Upload attempt $retry_count failed, retrying in 5 seconds..."
sleep 5
else
echo "❌ Error: Upload failed after $max_retries attempts"
echo "📊 Final HTTP Status: $http_code"
if [ "$http_code" = "524" ]; then
echo "💡 Error 524: Server timeout - try again later or check server capacity"
elif [ "$http_code" = "413" ]; then
echo "💡 Error 413: File too large - consider reducing bundle size"
elif [ "$http_code" = "500" ]; then
echo "💡 Error 500: Server error - check server logs"
else
echo "💡 Check server status and try again"
fi
exit 1
# Extract the response body (everything before HTTP_CODE)
response_body=$(echo "$response" | sed 's/HTTP_CODE:[0-9]*$//')
if [ -n "$response_body" ]; then
echo "📦 Server response: $response_body"
fi
else
echo "❌ Error: All upload attempts failed"
echo "📊 Final HTTP Status: $http_code"
if [ "$http_code" = "524" ]; then
echo "💡 Error 524: Server timeout - try again later or check server capacity"
elif [ "$http_code" = "413" ]; then
echo "💡 Error 413: File too large - consider reducing bundle size"
elif [ "$http_code" = "500" ]; then
echo "💡 Error 500: Server error - check server logs"
elif [ "$http_code" = "502" ]; then
echo "💡 Error 502: Bad Gateway - server may be overloaded"
echo "💡 Try running the script again in a few minutes"
echo "💡 Or use manual curl: curl -X POST $serverHost/api/upload -F \"file=@${timestamp}.zip\" -F \"runtimeVersion=$runtimeVersion\" -F \"commitHash=$commitHash\" -F \"commitMessage=$commitMessage\""
else
echo "💡 Check server status and try again"
fi
exit 1
fi
done
fi
cd ..

View file

@ -363,7 +363,8 @@ const WatchProgressDisplay = memo(({
animatedStyle,
isWatched,
isTrailerPlaying,
trailerMuted
trailerMuted,
trailerReady
}: {
watchProgress: {
currentTime: number;
@ -379,6 +380,7 @@ const WatchProgressDisplay = memo(({
isWatched: boolean;
isTrailerPlaying: boolean;
trailerMuted: boolean;
trailerReady: boolean;
}) => {
const { currentTheme } = useTheme();
const { isAuthenticated: isTraktAuthenticated, forceSyncTraktProgress } = useTraktContext();
@ -589,8 +591,8 @@ const WatchProgressDisplay = memo(({
if (!progressData) return null;
// Hide watch progress when trailer is playing AND unmuted
if (isTrailerPlaying && !trailerMuted) return null;
// Hide watch progress when trailer is playing AND unmuted AND trailer is ready
if (isTrailerPlaying && !trailerMuted && trailerReady) return null;
const isCompleted = progressData.isWatched || progressData.progressPercent >= 85;
@ -1488,6 +1490,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
isWatched={isWatched}
isTrailerPlaying={globalTrailerPlaying}
trailerMuted={trailerMuted}
trailerReady={trailerReady}
/>
{/* Optimized genre display with lazy loading */}

View file

@ -79,6 +79,26 @@ const AndroidVideoPlayer: React.FC = () => {
const isXprimeStream = streamProvider === 'xprime' || streamProvider === 'Xprime' ||
(uri && /flutch.*\.workers\.dev|fsl\.fastcloud\.casa|xprime/i.test(uri));
// Check if the stream is HLS (m3u8 playlist)
const isHlsStream = (url: string) => {
return url.includes('.m3u8') || url.includes('m3u8') ||
url.includes('hls') || url.includes('playlist') ||
(currentVideoType && currentVideoType.toLowerCase() === 'm3u8');
};
// HLS-specific headers for better ExoPlayer compatibility
const getHlsHeaders = () => {
return {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
'Accept': 'application/vnd.apple.mpegurl, application/x-mpegurl, application/vnd.apple.mpegurl, video/mp2t, video/mp4, */*',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'identity',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
} as any;
};
// Xprime-specific headers for better compatibility (from local-scrapers-repo)
const getXprimeHeaders = () => {
if (!isXprimeStream) return {};
@ -98,6 +118,24 @@ const AndroidVideoPlayer: React.FC = () => {
return xprimeHeaders;
};
// Get appropriate headers based on stream type
const getStreamHeaders = () => {
// For Xprime streams, be more flexible - only use HLS headers if it actually looks like HLS
if (isXprimeStream) {
if (isHlsStream(currentStreamUrl)) {
logger.log('[AndroidVideoPlayer] Xprime HLS stream detected, applying HLS headers');
return getXprimeHeaders();
} else {
logger.log('[AndroidVideoPlayer] Xprime MP4 stream detected, using default headers');
return Platform.OS === 'android' ? defaultAndroidHeaders() : defaultIosHeaders();
}
} else if (isHlsStream(currentStreamUrl)) {
logger.log('[AndroidVideoPlayer] Detected HLS stream, applying HLS headers');
return getHlsHeaders();
}
return Platform.OS === 'android' ? defaultAndroidHeaders() : defaultIosHeaders();
};
// Optional hint not yet in typed navigator params
const videoType = (route.params as any).videoType as string | undefined;
@ -1263,12 +1301,64 @@ const AndroidVideoPlayer: React.FC = () => {
return; // Do not proceed to show error UI
}
// If format unrecognized, try flipping between HLS and MP4 once
// If format unrecognized, try different approaches for HLS streams
const isUnrecognized = !!(error?.error?.errorString && String(error.error.errorString).includes('UnrecognizedInputFormatException'));
if (isUnrecognized && retryAttemptRef.current < 1) {
retryAttemptRef.current = 1;
const nextType = currentVideoType === 'm3u8' ? 'mp4' : 'm3u8';
logger.warn(`[AndroidVideoPlayer] Format not recognized. Retrying with type='${nextType}'`);
// Check if this might be an HLS stream that needs different handling
const mightBeHls = currentStreamUrl.includes('.m3u8') || currentStreamUrl.includes('playlist') ||
currentStreamUrl.includes('hls') || currentStreamUrl.includes('stream');
if (mightBeHls) {
logger.warn(`[AndroidVideoPlayer] HLS stream format not recognized. Retrying with explicit HLS type and headers`);
if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current);
errorTimeoutRef.current = null;
}
safeSetState(() => setShowErrorModal(false));
setPaused(true);
setTimeout(() => {
if (!isMounted.current) return;
// Force HLS type and add cache-busting
setCurrentVideoType('m3u8');
const sep = currentStreamUrl.includes('?') ? '&' : '?';
setCurrentStreamUrl(`${currentStreamUrl}${sep}hls_retry=${Date.now()}`);
setPaused(false);
}, 120);
return;
} else {
// For non-HLS streams, try flipping between HLS and MP4
const nextType = currentVideoType === 'm3u8' ? 'mp4' : 'm3u8';
logger.warn(`[AndroidVideoPlayer] Format not recognized. Retrying with type='${nextType}'`);
if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current);
errorTimeoutRef.current = null;
}
safeSetState(() => setShowErrorModal(false));
setPaused(true);
setTimeout(() => {
if (!isMounted.current) return;
setCurrentVideoType(nextType);
// Force re-mount of source by tweaking URL param
const sep = currentStreamUrl.includes('?') ? '&' : '?';
setCurrentStreamUrl(`${currentStreamUrl}${sep}rn_type_retry=${Date.now()}`);
setPaused(false);
}, 120);
return;
}
}
// Handle HLS manifest parsing errors (when content isn't actually M3U8)
const isManifestParseError = error?.error?.errorCode === '23002' ||
error?.errorCode === '23002' ||
(error?.error?.errorString &&
error.error.errorString.includes('ERROR_CODE_PARSING_MANIFEST_MALFORMED'));
if (isManifestParseError && retryAttemptRef.current < 2) {
retryAttemptRef.current = 2;
logger.warn('[AndroidVideoPlayer] HLS manifest parsing failed, likely not M3U8. Retrying as MP4');
if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current);
errorTimeoutRef.current = null;
@ -1277,10 +1367,10 @@ const AndroidVideoPlayer: React.FC = () => {
setPaused(true);
setTimeout(() => {
if (!isMounted.current) return;
setCurrentVideoType(nextType);
setCurrentVideoType('mp4');
// Force re-mount of source by tweaking URL param
const sep = currentStreamUrl.includes('?') ? '&' : '?';
setCurrentStreamUrl(`${currentStreamUrl}${sep}rn_type_retry=${Date.now()}`);
setCurrentStreamUrl(`${currentStreamUrl}${sep}manifest_fix_retry=${Date.now()}`);
setPaused(false);
}, 120);
return;
@ -2437,11 +2527,25 @@ const AndroidVideoPlayer: React.FC = () => {
<Video
ref={videoRef}
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
source={{ uri: currentStreamUrl, headers: getXprimeHeaders() || headers || (Platform.OS === 'android' ? defaultAndroidHeaders() : defaultIosHeaders()), type: (currentVideoType as any) }}
source={{
uri: currentStreamUrl,
headers: headers || getStreamHeaders(),
type: isHlsStream(currentStreamUrl) ? 'm3u8' : (currentVideoType as any)
}}
paused={paused}
onLoadStart={() => {
loadStartAtRef.current = Date.now();
logger.log('[AndroidVideoPlayer] onLoadStart');
// Log stream information for debugging
const streamInfo = {
url: currentStreamUrl,
isHls: isHlsStream(currentStreamUrl),
videoType: currentVideoType,
headers: headers || getStreamHeaders(),
provider: currentStreamProvider || streamProvider
};
logger.log('[AndroidVideoPlayer] Stream info:', streamInfo);
}}
onProgress={handleProgress}
onLoad={(e) => {
@ -2487,6 +2591,13 @@ const AndroidVideoPlayer: React.FC = () => {
preferredForwardBufferDuration={1 as any}
allowsExternalPlayback={false as any}
preventsDisplaySleepDuringVideoPlayback={true as any}
// ExoPlayer HLS optimization
bufferConfig={{
minBufferMs: 15000,
maxBufferMs: 50000,
bufferForPlaybackMs: 2500,
bufferForPlaybackAfterRebufferMs: 5000,
} as any}
/>
</TouchableOpacity>
</View>

View file

@ -929,7 +929,7 @@ export const StreamsScreen = () => {
const streamName = stream.name || stream.title || 'Unnamed Stream';
const streamProvider = stream.addonId || stream.addonName || stream.name;
// Determine if we should force VLC on iOS based on actual stream format (not provider capability)
// Determine if we should force VLC on iOS based on actual stream format and provider capability
let forceVlc = !!options?.forceVlc;
try {
if (Platform.OS === 'ios' && !forceVlc) {
@ -940,9 +940,22 @@ export const StreamsScreen = () => {
const isMkvByPath = lowerUri.includes('.mkv') || /[?&]ext=mkv\b/.test(lowerUri) || /format=mkv\b/.test(lowerUri) || /container=mkv\b/.test(lowerUri);
const isMkvFile = Boolean(isMkvByHeader || isMkvByPath);
if (isMkvFile) {
// Also check if the provider declares MKV format support
let providerSupportsMkv = false;
try {
const availableScrapers = await localScraperService.getAvailableScrapers();
const provider = availableScrapers.find(scraper => scraper.id === streamProvider);
if (provider && provider.formats) {
providerSupportsMkv = provider.formats.includes('mkv');
logger.log(`[StreamsScreen] Provider ${streamProvider} formats:`, provider.formats, 'supports MKV:', providerSupportsMkv);
}
} catch (providerError) {
logger.warn('[StreamsScreen] Failed to check provider formats:', providerError);
}
if (isMkvFile || providerSupportsMkv) {
forceVlc = true;
logger.log(`[StreamsScreen] Stream is MKV format -> forcing VLC`);
logger.log(`[StreamsScreen] Stream is MKV format (detected: ${isMkvFile}, provider supports: ${providerSupportsMkv}) -> forcing VLC`);
}
}
} catch (e) {
@ -1199,7 +1212,22 @@ export const StreamsScreen = () => {
useFocusEffect(
useCallback(() => {
if (Platform.OS === 'ios') {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {});
// Add delay before locking orientation to prevent background glitches
const orientationTimer = setTimeout(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {});
}, 200); // Small delay to let the screen render properly
// iOS-specific: Force a re-render to prevent background glitches
// This helps ensure the background is properly rendered when returning from player
const renderTimer = setTimeout(() => {
// Trigger a small state update to force re-render
setStreamsLoadStart(prev => prev);
}, 100);
return () => {
clearTimeout(orientationTimer);
clearTimeout(renderTimer);
};
}
return () => {};
}, [])
@ -1509,43 +1537,6 @@ export const StreamsScreen = () => {
const showStillFetching = streamsEmpty && loadElapsed >= 10000;
const renderItem = useCallback(({ item, index, section }: { item: any; index: number; section: any }) => {
// Handle empty sections due to quality filtering
if (item.isEmptyPlaceholder && section.isEmptyDueToQualityFilter) {
return (
<View style={styles.emptySectionContainer}>
<View style={styles.emptySectionContent}>
<MaterialIcons name="filter-list-off" size={32} color={colors.mediumEmphasis} />
<Text style={[styles.emptySectionTitle, { color: colors.mediumEmphasis }]}>
No streams available
</Text>
<Text style={[styles.emptySectionSubtitle, { color: colors.textMuted }]}>
All streams were filtered by your quality settings
</Text>
</View>
</View>
);
}
const stream = item as Stream;
// Don't show loading for individual streams that are already available and displayed
const isLoading = false; // If streams are being rendered, they're available and shouldn't be loading
return (
<View>
<StreamCard
stream={stream}
onPress={() => handleStreamPress(stream)}
index={index}
isLoading={isLoading}
statusMessage={undefined}
theme={currentTheme}
showLogos={settings.showScraperLogos}
scraperLogo={(stream.addonId && scraperLogos[stream.addonId]) || (stream as any).addon ? scraperLogoCache.get((stream.addonId || (stream as any).addon) as string) || null : null}
/>
</View>
);
}, [handleStreamPress, currentTheme, settings.showScraperLogos, scraperLogos, colors.mediumEmphasis, colors.textMuted, styles.emptySectionContainer, styles.emptySectionContent, styles.emptySectionTitle, styles.emptySectionSubtitle]);
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string; isEmptyDueToQualityFilter?: boolean } }) => {
const isProviderLoading = loadingProviders[section.addonId];
@ -1777,37 +1768,84 @@ export const StreamsScreen = () => {
</View>
)}
<SectionList
sections={sections}
keyExtractor={(item, index) => {
if (item && item.url) {
return item.url;
}
// For empty sections, use a special key
return `empty-${index}`;
}}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
stickySectionHeadersEnabled={false}
initialNumToRender={6}
maxToRenderPerBatch={2}
windowSize={3}
removeClippedSubviews={true}
contentContainerStyle={styles.streamsContainer}
<ScrollView
style={styles.streamsContent}
contentContainerStyle={[
styles.streamsContainer,
{ paddingBottom: insets.bottom + 100 } // Add safe area + extra padding
]}
showsVerticalScrollIndicator={false}
bounces={true}
overScrollMode="never"
ListEmptyComponent={null}
ListFooterComponent={
(loadingStreams || loadingEpisodeStreams) && hasStremioStreamProviders ? (
<View style={styles.footerLoading}>
<ActivityIndicator size="small" color={colors.primary} />
<Text style={styles.footerLoadingText}>Loading more sources...</Text>
</View>
) : null
}
/>
// iOS-specific fixes for navigation transition glitches
{...(Platform.OS === 'ios' && {
// Ensure proper rendering during transitions
removeClippedSubviews: false, // Prevent iOS from clipping views during transitions
// Force hardware acceleration for smoother transitions
scrollEventThrottle: 16,
})}
>
{sections.map((section, sectionIndex) => (
<View key={section.addonId || sectionIndex}>
{/* Section Header */}
{renderSectionHeader({ section })}
{/* Stream Cards using FlatList */}
{section.data && section.data.length > 0 ? (
<FlatList
data={section.data}
keyExtractor={(item, index) => {
if (item && item.url) {
return item.url;
}
return `empty-${sectionIndex}-${index}`;
}}
renderItem={({ item, index }) => (
<View>
<StreamCard
stream={item}
onPress={() => handleStreamPress(item)}
index={index}
isLoading={false}
statusMessage={undefined}
theme={currentTheme}
showLogos={settings.showScraperLogos}
scraperLogo={(item.addonId && scraperLogos[item.addonId]) || (item as any).addon ? scraperLogoCache.get((item.addonId || (item as any).addon) as string) || null : null}
/>
</View>
)}
scrollEnabled={false}
initialNumToRender={6}
maxToRenderPerBatch={2}
windowSize={3}
removeClippedSubviews={true}
showsVerticalScrollIndicator={false}
/>
) : (
// Empty section placeholder
<View style={styles.emptySectionContainer}>
<View style={styles.emptySectionContent}>
<MaterialIcons name="filter-list-off" size={32} color={colors.mediumEmphasis} />
<Text style={[styles.emptySectionTitle, { color: colors.mediumEmphasis }]}>
No streams available
</Text>
<Text style={[styles.emptySectionSubtitle, { color: colors.textMuted }]}>
All streams were filtered by your quality settings
</Text>
</View>
</View>
)}
</View>
))}
{/* Footer Loading */}
{(loadingStreams || loadingEpisodeStreams) && hasStremioStreamProviders && (
<View style={styles.footerLoading}>
<ActivityIndicator size="small" color={colors.primary} />
<Text style={styles.footerLoadingText}>Loading more sources...</Text>
</View>
)}
</ScrollView>
</View>
)}
</View>
@ -1820,6 +1858,15 @@ const createStyles = (colors: any) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.darkBackground,
// iOS-specific fixes for navigation transition glitches
...(Platform.OS === 'ios' && {
// Ensure the background is properly rendered during transitions
opacity: 1,
// Prevent iOS from trying to optimize the background during transitions
shouldRasterizeIOS: false,
// Ensure the view is properly composited
renderToHardwareTextureAndroid: false,
}),
},
backButtonContainer: {
position: 'absolute',
@ -1848,6 +1895,13 @@ const createStyles = (colors: any) => StyleSheet.create({
backgroundColor: colors.darkBackground,
paddingTop: 12,
zIndex: 1,
// iOS-specific fixes for navigation transition glitches
...(Platform.OS === 'ios' && {
// Ensure proper rendering during transitions
opacity: 1,
// Prevent iOS optimization that can cause glitches
shouldRasterizeIOS: false,
}),
},
streamsMainContentMovie: {
paddingTop: Platform.OS === 'android' ? 10 : 15,