From e8d7eab2e4392106be36f96ae03f73a681109ced Mon Sep 17 00:00:00 2001 From: tapframe Date: Thu, 4 Sep 2025 17:07:31 +0530 Subject: [PATCH] potential fix --- src/navigation/AppNavigator.tsx | 12 +++++--- src/screens/StreamsScreen.tsx | 52 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 912594a2..2ea62624 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react'; import { NavigationContainer, DefaultTheme as NavigationDefaultTheme, DarkTheme as NavigationDarkTheme, Theme, NavigationProp } from '@react-navigation/native'; import { createNativeStackNavigator, NativeStackNavigationOptions, NativeStackNavigationProp } from '@react-navigation/native-stack'; import { createBottomTabNavigator, BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; -import { useColorScheme, Platform, Animated, StatusBar, TouchableOpacity, View, Text, AppState, Easing, Dimensions } from 'react-native'; +import { useColorScheme, Platform, Animated, StatusBar, TouchableOpacity, View, Text, AppState, Easing, Dimensions, useWindowDimensions } from 'react-native'; import { PaperProvider, MD3DarkTheme, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper'; import type { MD3Theme } from 'react-native-paper'; import type { BottomTabBarProps } from '@react-navigation/bottom-tabs'; @@ -358,7 +358,9 @@ const TabIcon = React.memo(({ focused, color, iconName }: { // Update the TabScreenWrapper component with fixed layout dimensions const TabScreenWrapper: React.FC<{children: React.ReactNode}> = ({ children }) => { - const isTablet = Dimensions.get('window').width >= 768; + const { width, height } = useWindowDimensions(); + const smallestDimension = Math.min(width, height); + const isTablet = (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768); const insets = useSafeAreaInsets(); // Force consistent status bar settings useEffect(() => { @@ -421,7 +423,9 @@ const WrappedScreen: React.FC<{Screen: React.ComponentType}> = ({ Screen }) const MainTabs = () => { const { currentTheme } = useTheme(); const { isHomeLoading } = useLoading(); - const isTablet = Dimensions.get('window').width >= 768; + const { width, height } = useWindowDimensions(); + const smallestDimension = Math.min(width, height); + const isTablet = (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768); const insets = useSafeAreaInsets(); const isIosTablet = Platform.OS === 'ios' && isTablet; const [hidden, setHidden] = React.useState(HeaderVisibility.isHidden()); @@ -927,7 +931,7 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta component={VideoPlayer as any} options={{ animation: Platform.OS === 'android' ? 'none' : 'default', - animationDuration: Platform.OS === 'android' ? 0 : 300, + animationDuration: Platform.OS === 'android' ? 0 : 0, // Force fullscreen presentation on iPad presentation: Platform.OS === 'ios' ? 'fullScreenModal' : 'card', // Disable gestures during video playback diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index ea632df6..2e8f0e8e 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -66,6 +66,27 @@ const { width, height } = Dimensions.get('window'); const scraperLogoCache = new Map(); let scraperLogoCachePromise: Promise | null = null; +// Short-budget HEAD detection to avoid long delays before navigation +const MKV_HEAD_TIMEOUT_MS = 600; + +const detectMkvViaHead = async (url: string, headers?: Record) => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), MKV_HEAD_TIMEOUT_MS); + try { + const res = await fetch(url, { + method: 'HEAD', + headers, + signal: controller.signal as any, + } as any); + const contentType = res.headers.get('content-type') || ''; + return /matroska|x-matroska/i.test(contentType); + } catch (_e) { + return false; + } finally { + clearTimeout(timeout); + } +}; + // Extracted Components const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos }: { stream: Stream; @@ -973,43 +994,28 @@ export const StreamsScreen = () => { logger.warn('[StreamsScreen] MKV pre-check failed:', err); } - // On iOS, for installed addons where URL may not include .mkv, send a HEAD request - // to detect MKV via Content-Type before opening the player + // iOS: very short MKV detection race; never block longer than MKV_HEAD_TIMEOUT_MS if (Platform.OS === 'ios') { const lowerUrl = (stream.url || '').toLowerCase(); - const isMkvByPath = lowerUrl.includes('.mkv') || /[?&]ext=mkv\b/.test(lowerUrl) || /format=mkv\b/.test(lowerUrl) || /container=mkv\b/.test(lowerUrl); + const isMkvByPath = lowerUrl.includes('.mkv') || /[?&]ext=mkv\b/i.test(lowerUrl) || /format=mkv\b/i.test(lowerUrl) || /container=mkv\b/i.test(lowerUrl); const isHttp = lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://'); if (!isMkvByPath && isHttp) { try { - const mkvDetected = await (async () => { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 4000); - try { - const res = await fetch(stream.url, { - method: 'HEAD', - // Pass along any known headers to improve odds of correct response - headers: (stream.headers as any) || undefined, - signal: controller.signal as any, - } as any); - const contentType = res.headers.get('content-type') || ''; - return typeof contentType === 'string' && /matroska|x-matroska/i.test(contentType); - } catch (_e) { - return false; - } finally { - clearTimeout(timeout); - } - })(); + const mkvDetected = await Promise.race([ + detectMkvViaHead(stream.url, (stream.headers as any) || undefined), + new Promise(res => setTimeout(() => res(false), MKV_HEAD_TIMEOUT_MS)), + ]); if (mkvDetected) { const mergedHeaders = { ...(stream.headers || {}), 'Content-Type': 'video/x-matroska', } as Record; - logger.log('[StreamsScreen] HEAD detected MKV via Content-Type, forcing in-app VLC on iOS'); + logger.log('[StreamsScreen] HEAD detected MKV via Content-Type quickly, forcing in-app VLC on iOS'); navigateToPlayer(stream, { forceVlc: true, headers: mergedHeaders }); return; } } catch (e) { - logger.warn('[StreamsScreen] HEAD MKV detection failed:', e); + logger.warn('[StreamsScreen] Short MKV detection failed:', e); } } }