mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Fixed an issue with Container Parsing on AndroidVideoPlayer
This commit is contained in:
parent
dce3e5c200
commit
bc4bf5a963
3 changed files with 60 additions and 4 deletions
|
|
@ -74,6 +74,18 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
backdrop
|
||||
} = route.params;
|
||||
|
||||
// Optional hint not yet in typed navigator params
|
||||
const videoType = (route.params as any).videoType as string | undefined;
|
||||
|
||||
const defaultAndroidHeaders = () => {
|
||||
if (Platform.OS !== 'android') return {} as any;
|
||||
return {
|
||||
'User-Agent': 'ExoPlayerLib/2.19.1 (Linux;Android) Nuvio/1.0',
|
||||
'Accept': '*/*',
|
||||
'Connection': 'keep-alive',
|
||||
} as any;
|
||||
};
|
||||
|
||||
// Initialize Trakt autosync
|
||||
const traktAutosync = useTraktAutosync({
|
||||
id: id || '',
|
||||
|
|
@ -177,6 +189,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(false);
|
||||
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: any[]; addonName: string } }>(passedAvailableStreams || {});
|
||||
const [currentStreamUrl, setCurrentStreamUrl] = useState<string>(uri);
|
||||
const [currentVideoType, setCurrentVideoType] = useState<string | undefined>(videoType);
|
||||
// Track a single silent retry per source to avoid loops
|
||||
const retryAttemptRef = useRef<number>(0);
|
||||
const [isChangingSource, setIsChangingSource] = useState<boolean>(false);
|
||||
|
|
@ -1020,6 +1033,29 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
return; // Do not proceed to show error UI
|
||||
}
|
||||
|
||||
// If format unrecognized, try flipping between HLS and MP4 once
|
||||
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}'`);
|
||||
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;
|
||||
}
|
||||
|
||||
// Check for specific AVFoundation server configuration errors (iOS)
|
||||
const isServerConfigError = error?.error?.code === -11850 ||
|
||||
error?.code === -11850 ||
|
||||
|
|
@ -2031,7 +2067,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
<Video
|
||||
ref={videoRef}
|
||||
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
||||
source={{ uri: currentStreamUrl, headers: headers || {} }}
|
||||
source={{ uri: currentStreamUrl, headers: headers || defaultAndroidHeaders(), type: (currentVideoType as any) }}
|
||||
paused={paused}
|
||||
onProgress={handleProgress}
|
||||
onLoad={(e) => {
|
||||
|
|
|
|||
|
|
@ -121,10 +121,9 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
S{season}E{episode} {episodeTitle && `• ${episodeTitle}`}
|
||||
</Text>
|
||||
)}
|
||||
{/* Show year, quality, and provider */}
|
||||
{/* Show year and provider (quality chip removed) */}
|
||||
<View style={styles.metadataRow}>
|
||||
{year && <Text style={styles.metadataText}>{year}</Text>}
|
||||
{quality && <View style={styles.qualityBadge}><Text style={styles.qualityText}>{quality}</Text></View>}
|
||||
{streamName && <Text style={styles.providerText}>via {streamName}</Text>}
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -911,6 +911,25 @@ export const StreamsScreen = () => {
|
|||
|
||||
// Show a quick full-screen black overlay to mask rotation flicker
|
||||
// by setting a transient state that renders a covering View (implementation already supported by dark backgrounds)
|
||||
|
||||
// Infer video type for player (helps Android ExoPlayer choose correct extractor)
|
||||
const inferVideoTypeFromUrl = (u?: string): string | undefined => {
|
||||
if (!u) return undefined;
|
||||
const lower = u.toLowerCase();
|
||||
if (/(\.|ext=)(m3u8)(\b|$)/i.test(lower)) return 'm3u8';
|
||||
if (/(\.|ext=)(mpd)(\b|$)/i.test(lower)) return 'mpd';
|
||||
if (/(\.|ext=)(mp4)(\b|$)/i.test(lower)) return 'mp4';
|
||||
return undefined;
|
||||
};
|
||||
let videoType = inferVideoTypeFromUrl(stream.url);
|
||||
// Heuristic: certain providers (e.g., Xprime) serve HLS without .m3u8 extension
|
||||
try {
|
||||
const providerId = stream.addonId || (stream as any).addon || '';
|
||||
if (!videoType && /xprime/i.test(providerId)) {
|
||||
videoType = 'm3u8';
|
||||
}
|
||||
} catch {}
|
||||
|
||||
navigation.navigate('Player', {
|
||||
uri: stream.url,
|
||||
title: metadata?.name || '',
|
||||
|
|
@ -931,7 +950,9 @@ export const StreamsScreen = () => {
|
|||
imdbId: imdbId || undefined,
|
||||
availableStreams: streamsToPass,
|
||||
backdrop: bannerImage || undefined,
|
||||
});
|
||||
// Hint for Android ExoPlayer/react-native-video
|
||||
videoType: videoType,
|
||||
} as any);
|
||||
}, [metadata, type, currentEpisode, navigation, id, selectedEpisode, imdbId, episodeStreams, groupedStreams, bannerImage]);
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue