diff --git a/build-and-publish-app-release.sh b/build-and-publish-app-release.sh index 499ffd8..5a49357 100644 --- a/build-and-publish-app-release.sh +++ b/build-and-publish-app-release.sh @@ -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 .. diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 38974b1..051d642 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -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 = memo(({ isWatched={isWatched} isTrailerPlaying={globalTrailerPlaying} trailerMuted={trailerMuted} + trailerReady={trailerReady} /> {/* Optimized genre display with lazy loading */} diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 506c705..dc60865 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -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 = () => {