VideoPlayer code cleanup

This commit is contained in:
tapframe 2025-09-17 20:23:46 +05:30
parent d0719fabec
commit c767de12aa
4 changed files with 87 additions and 89 deletions

View file

@ -75,9 +75,6 @@ const AndroidVideoPlayer: React.FC = () => {
backdrop backdrop
} = route.params; } = route.params;
// Check if the stream is from Xprime (by provider name or URL pattern)
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) // Check if the stream is HLS (m3u8 playlist)
const isHlsStream = (url: string) => { const isHlsStream = (url: string) => {
@ -99,37 +96,11 @@ const AndroidVideoPlayer: React.FC = () => {
} as any; } as any;
}; };
// Xprime-specific headers for better compatibility (from local-scrapers-repo)
const getXprimeHeaders = () => {
if (!isXprimeStream) return {};
const xprimeHeaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'identity',
'Origin': 'https://xprime.tv',
'Referer': 'https://xprime.tv/',
'Sec-Fetch-Dest': 'video',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'cross-site',
'DNT': '1'
} as any;
logger.log('[AndroidVideoPlayer] Applying Xprime headers for stream:', uri);
return xprimeHeaders;
};
// Get appropriate headers based on stream type // Get appropriate headers based on stream type
const getStreamHeaders = () => { const getStreamHeaders = () => {
// For Xprime streams, be more flexible - only use HLS headers if it actually looks like HLS // Use HLS headers for HLS streams, default headers for everything else
if (isXprimeStream) { if (isHlsStream(currentStreamUrl)) {
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'); logger.log('[AndroidVideoPlayer] Detected HLS stream, applying HLS headers');
return getHlsHeaders(); return getHlsHeaders();
} }
@ -1535,12 +1506,8 @@ const AndroidVideoPlayer: React.FC = () => {
return; return;
} }
// Detect Xprime provider to enable a one-shot silent retry (warms upstream/cache)
const providerName = ((currentStreamProvider || streamProvider || '') as string).toLowerCase();
const isXprimeProvider = providerName.includes('xprime');
// One-shot, silent retry without showing error UI // One-shot, silent retry without showing error UI
if (isXprimeProvider && retryAttemptRef.current < 1) { if (retryAttemptRef.current < 1) {
retryAttemptRef.current = 1; retryAttemptRef.current = 1;
// Cache-bust to force a fresh fetch and warm upstream // Cache-bust to force a fresh fetch and warm upstream
const addRetryParam = (url: string) => { const addRetryParam = (url: string) => {
@ -1548,7 +1515,7 @@ const AndroidVideoPlayer: React.FC = () => {
return `${url}${sep}rn_retry_ts=${Date.now()}`; return `${url}${sep}rn_retry_ts=${Date.now()}`;
}; };
const bustedUrl = addRetryParam(currentStreamUrl); const bustedUrl = addRetryParam(currentStreamUrl);
logger.warn('[AndroidVideoPlayer] Silent retry for Xprime with cache-busted URL'); logger.warn('[AndroidVideoPlayer] Silent retry with cache-busted URL');
// Ensure no modal is visible // Ensure no modal is visible
if (errorTimeoutRef.current) { if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current); clearTimeout(errorTimeoutRef.current);

View file

@ -46,57 +46,23 @@ import * as Brightness from 'expo-brightness';
const VideoPlayer: React.FC = () => { const VideoPlayer: React.FC = () => {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>(); const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
const { streamProvider, uri, headers, forceVlc } = route.params as any; const { uri, headers, forceVlc, streamProvider } = route.params as any;
// Check if the stream is from Xprime (by provider name or URL pattern)
const isXprimeStream = streamProvider === 'xprime' || streamProvider === 'Xprime' ||
(uri && /flutch.*\.workers\.dev|fsl\.fastcloud\.casa|xprime/i.test(uri));
safeDebugLog("Stream detection", {
uri,
streamProvider,
isXprimeStream,
platform: Platform.OS
});
// Xprime-specific headers for better compatibility (from local-scrapers-repo)
const getXprimeHeaders = () => {
if (!isXprimeStream) return {};
const xprimeHeaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'identity',
'Origin': 'https://xprime.tv',
'Referer': 'https://xprime.tv/',
'Sec-Fetch-Dest': 'video',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'cross-site',
'DNT': '1'
} as any;
logger.log('[VideoPlayer] Applying Xprime headers for stream:', uri);
return xprimeHeaders;
};
// Detect if stream is MKV format // Detect if stream is MKV format
const isMkvFile = isMkvStream(uri, headers); const isMkvFile = isMkvStream(uri, headers);
// Use AndroidVideoPlayer for: // Use AndroidVideoPlayer for:
// - Android devices // - Android devices
// - Xprime streams (unless it's MKV on iOS - allow VLC for better compatibility)
// - Non-MKV files on iOS (unless forceVlc is set) // - Non-MKV files on iOS (unless forceVlc is set)
// Use VideoPlayer (VLC) for: // Use VideoPlayer (VLC) for:
// - MKV files on iOS (unless forceVlc is set, even for Xprime) // - MKV files on iOS (unless forceVlc is set)
const shouldUseAndroidPlayer = Platform.OS === 'android' || const shouldUseAndroidPlayer = Platform.OS === 'android' ||
(isXprimeStream && !(Platform.OS === 'ios' && isMkvFile)) ||
(Platform.OS === 'ios' && !isMkvFile && !forceVlc); (Platform.OS === 'ios' && !isMkvFile && !forceVlc);
safeDebugLog("Player selection logic", { safeDebugLog("Player selection logic", {
platform: Platform.OS, platform: Platform.OS,
isXprimeStream,
isMkvFile, isMkvFile,
forceVlc, forceVlc,
xprimeException: isXprimeStream && Platform.OS === 'ios' && isMkvFile,
shouldUseAndroidPlayer shouldUseAndroidPlayer
}); });
if (shouldUseAndroidPlayer) { if (shouldUseAndroidPlayer) {
@ -1176,6 +1142,38 @@ const VideoPlayer: React.FC = () => {
} }
if (data.textTracks && data.textTracks.length > 0) { if (data.textTracks && data.textTracks.length > 0) {
setVlcTextTracks(data.textTracks); setVlcTextTracks(data.textTracks);
// Auto-select English subtitle track if available
if (selectedTextTrack === -1 && !useCustomSubtitles && data.textTracks.length > 0) {
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Available subtitle tracks:`, data.textTracks.map((track: any) => ({
id: track.id,
index: track.index,
name: track.name,
language: track.language
})));
}
// Look for English track first
const englishTrack = data.textTracks.find((track: any) => {
const lang = (track.language || '').toLowerCase();
const name = (track.name || '').toLowerCase();
return lang === 'english' || lang === 'en' || lang === 'eng' ||
name.includes('english') || name.includes('en');
});
if (englishTrack) {
// Try different ID fields that VLC might use
const trackId = englishTrack.id !== undefined ? englishTrack.id :
englishTrack.index !== undefined ? englishTrack.index : 0;
setSelectedTextTrack(trackId);
if (DEBUG_MODE) {
logger.log(`[VideoPlayer] Auto-selected English subtitle track: ${englishTrack.name || 'Unknown'} (ID: ${trackId})`);
}
} else if (DEBUG_MODE) {
logger.log(`[VideoPlayer] No English subtitle track found, keeping subtitles disabled`);
}
}
} }
setIsVideoLoaded(true); setIsVideoLoaded(true);
@ -2557,13 +2555,12 @@ const VideoPlayer: React.FC = () => {
ref={vlcRef} ref={vlcRef}
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]} style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
source={(() => { source={(() => {
// Use Xprime headers if detected, otherwise use headers from route params // Use route headers for VLC streams
const finalHeaders = getXprimeHeaders() || headers; const sourceWithHeaders = headers && Object.keys(headers).length > 0 ? {
const sourceWithHeaders = finalHeaders ? {
uri: currentStreamUrl, uri: currentStreamUrl,
headers: finalHeaders headers: headers
} : { uri: currentStreamUrl }; } : { uri: currentStreamUrl };
return sourceWithHeaders; return sourceWithHeaders;
})()} })()}
paused={paused} paused={paused}
@ -2576,7 +2573,7 @@ const VideoPlayer: React.FC = () => {
onPaused={onPaused} onPaused={onPaused}
resizeMode={resizeMode as any} resizeMode={resizeMode as any}
audioTrack={selectedAudioTrack ?? undefined} audioTrack={selectedAudioTrack ?? undefined}
textTrack={useCustomSubtitles ? -1 : (selectedTextTrack ?? undefined)} textTrack={useCustomSubtitles ? -1 : (selectedTextTrack >= 0 ? selectedTextTrack : -1)}
autoAspectRatio autoAspectRatio
volume={volume / 100} volume={volume / 100}
/> />

View file

@ -288,6 +288,10 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
<View style={{ gap: 8 }}> <View style={{ gap: 8 }}>
{vlcTextTracks.map((track) => { {vlcTextTracks.map((track) => {
const isSelected = selectedTextTrack === track.id && !useCustomSubtitles; const isSelected = selectedTextTrack === track.id && !useCustomSubtitles;
// Debug logging for subtitle selection
if (__DEV__ && vlcTextTracks.length > 0) {
console.log('[SubtitleModals] Track:', track.id, track.name, 'Selected:', selectedTextTrack, 'isSelected:', isSelected, 'useCustom:', useCustomSubtitles);
}
return ( return (
<TouchableOpacity <TouchableOpacity
key={track.id} key={track.id}

View file

@ -18,6 +18,7 @@ import { RootStackParamList } from '../navigation/AppNavigator';
import { Meta, stremioService } from '../services/stremioService'; import { Meta, stremioService } from '../services/stremioService';
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
import { Image } from 'expo-image'; import { Image } from 'expo-image';
import { BlurView } from 'expo-blur';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
import { useCustomCatalogNames } from '../hooks/useCustomCatalogNames'; import { useCustomCatalogNames } from '../hooks/useCustomCatalogNames';
@ -193,12 +194,25 @@ const createStyles = (colors: any) => StyleSheet.create({
top: 10, top: 10,
right: 10, right: 10,
backgroundColor: 'rgba(0,0,0,0.7)', backgroundColor: 'rgba(0,0,0,0.7)',
borderRadius: 0, borderRadius: 10,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 6, paddingVertical: 6,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
}, },
badgeBlur: {
position: 'absolute',
top: 10,
right: 10,
borderRadius: 10,
overflow: 'hidden',
},
badgeContent: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10,
paddingVertical: 6,
},
badgeText: { badgeText: {
fontSize: 11, fontSize: 11,
fontWeight: '600', fontWeight: '600',
@ -584,15 +598,31 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
/> />
{type === 'movie' && nowPlayingMovies.has(item.id) && ( {type === 'movie' && nowPlayingMovies.has(item.id) && (
<View style={styles.badgeContainer}> Platform.OS === 'ios' ? (
<MaterialIcons <View style={styles.badgeBlur}>
name="theaters" <BlurView intensity={40} tint={isDarkMode ? 'dark' : 'light'} style={{ borderRadius: 10 }}>
size={12} <View style={styles.badgeContent}>
color={colors.white} <MaterialIcons
style={{ marginRight: 4 }} name="theaters"
/> size={12}
<Text style={styles.badgeText}>In Theaters</Text> color={colors.white}
</View> style={{ marginRight: 4 }}
/>
<Text style={styles.badgeText}>In Theaters</Text>
</View>
</BlurView>
</View>
) : (
<View style={styles.badgeContainer}>
<MaterialIcons
name="theaters"
size={12}
color={colors.white}
style={{ marginRight: 4 }}
/>
<Text style={styles.badgeText}>In Theaters</Text>
</View>
)
)} )}
</TouchableOpacity> </TouchableOpacity>
); );