potential fix

This commit is contained in:
tapframe 2025-09-04 17:07:31 +05:30
parent a562e45ffd
commit e8d7eab2e4
2 changed files with 37 additions and 27 deletions

View file

@ -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<any>}> = ({ 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

View file

@ -66,6 +66,27 @@ const { width, height } = Dimensions.get('window');
const scraperLogoCache = new Map<string, string>();
let scraperLogoCachePromise: Promise<void> | 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<string, string>) => {
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<boolean>([
detectMkvViaHead(stream.url, (stream.headers as any) || undefined),
new Promise<boolean>(res => setTimeout(() => res(false), MKV_HEAD_TIMEOUT_MS)),
]);
if (mkvDetected) {
const mergedHeaders = {
...(stream.headers || {}),
'Content-Type': 'video/x-matroska',
} as Record<string, string>;
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);
}
}
}