NuvioStreaming/src/hooks/useStreamNavigation.ts

221 lines
No EOL
8.6 KiB
TypeScript

import { useCallback } from 'react';
import { Platform, Linking } from 'react-native';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParamList } from '../navigation/AppNavigator';
import { Stream } from '../types/metadata';
import { logger } from '../utils/logger';
interface UseStreamNavigationProps {
metadata: {
name?: string;
year?: number;
} | null;
currentEpisode?: {
name?: string;
season_number?: number;
episode_number?: number;
} | null;
id: string;
type: string;
selectedEpisode?: string;
useExternalPlayer?: boolean;
preferredPlayer?: 'internal' | 'vlc' | 'outplayer' | 'infuse' | 'vidhub' | 'external';
}
export const useStreamNavigation = ({
metadata,
currentEpisode,
id,
type,
selectedEpisode,
useExternalPlayer,
preferredPlayer
}: UseStreamNavigationProps) => {
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const navigateToPlayer = useCallback((stream: Stream) => {
navigation.navigate('Player', {
uri: stream.url,
title: metadata?.name || '',
episodeTitle: type === 'series' ? currentEpisode?.name : undefined,
season: type === 'series' ? currentEpisode?.season_number : undefined,
episode: type === 'series' ? currentEpisode?.episode_number : undefined,
quality: stream.title?.match(/(\d+)p/)?.[1] || undefined,
year: metadata?.year,
streamProvider: stream.name,
id,
type,
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined
});
}, [metadata, type, currentEpisode, navigation, id, selectedEpisode]);
const handleStreamPress = useCallback(async (stream: Stream) => {
try {
if (stream.url) {
logger.log('handleStreamPress called with stream:', {
url: stream.url,
behaviorHints: stream.behaviorHints,
useExternalPlayer,
preferredPlayer
});
// For iOS, try to open with the preferred external player
if (Platform.OS === 'ios' && preferredPlayer !== 'internal') {
try {
// Format the URL for the selected player
const streamUrl = encodeURIComponent(stream.url);
let externalPlayerUrls: string[] = [];
// Configure URL formats based on the selected player
switch (preferredPlayer) {
case 'vlc':
externalPlayerUrls = [
`vlc://${stream.url}`,
`vlc-x-callback://x-callback-url/stream?url=${streamUrl}`,
`vlc://${streamUrl}`
];
break;
case 'outplayer':
externalPlayerUrls = [
`outplayer://${stream.url}`,
`outplayer://${streamUrl}`,
`outplayer://play?url=${streamUrl}`,
`outplayer://stream?url=${streamUrl}`,
`outplayer://play/browser?url=${streamUrl}`
];
break;
case 'infuse':
externalPlayerUrls = [
`infuse://x-callback-url/play?url=${streamUrl}`,
`infuse://play?url=${streamUrl}`,
`infuse://${streamUrl}`
];
break;
case 'vidhub':
externalPlayerUrls = [
`vidhub://play?url=${streamUrl}`,
`vidhub://${streamUrl}`
];
break;
default:
// If no matching player or the setting is somehow invalid, use internal player
navigateToPlayer(stream);
return;
}
console.log(`Attempting to open stream in ${preferredPlayer}`);
// Try each URL format in sequence
const tryNextUrl = (index: number) => {
if (index >= externalPlayerUrls.length) {
console.log(`All ${preferredPlayer} formats failed, falling back to direct URL`);
// Try direct URL as last resort
Linking.openURL(stream.url)
.then(() => console.log('Opened with direct URL'))
.catch(() => {
console.log('Direct URL failed, falling back to built-in player');
navigateToPlayer(stream);
});
return;
}
const url = externalPlayerUrls[index];
console.log(`Trying ${preferredPlayer} URL format ${index + 1}: ${url}`);
Linking.openURL(url)
.then(() => console.log(`Successfully opened stream with ${preferredPlayer} format ${index + 1}`))
.catch(err => {
console.log(`Format ${index + 1} failed: ${err.message}`, err);
tryNextUrl(index + 1);
});
};
// Start with the first URL format
tryNextUrl(0);
} catch (error) {
console.error(`Error with ${preferredPlayer}:`, error);
// Fallback to the built-in player
navigateToPlayer(stream);
}
}
// For Android with external player preference
else if (Platform.OS === 'android' && useExternalPlayer) {
try {
console.log('Opening stream with Android native app chooser');
// For Android, determine if the URL is a direct http/https URL or a magnet link
const isMagnet = stream.url.startsWith('magnet:');
if (isMagnet) {
// For magnet links, open directly which will trigger the torrent app chooser
console.log('Opening magnet link directly');
Linking.openURL(stream.url)
.then(() => console.log('Successfully opened magnet link'))
.catch(err => {
console.error('Failed to open magnet link:', err);
// No good fallback for magnet links
navigateToPlayer(stream);
});
} else {
// For direct video URLs, use the S.Browser.ACTION_VIEW approach
// This is a more reliable way to force Android to show all video apps
// Strip query parameters if they exist as they can cause issues with some apps
let cleanUrl = stream.url;
if (cleanUrl.includes('?')) {
cleanUrl = cleanUrl.split('?')[0];
}
// Create an Android intent URL that forces the chooser
// Set component=null to ensure chooser is shown
// Set action=android.intent.action.VIEW to open the content
const intentUrl = `intent:${cleanUrl}#Intent;action=android.intent.action.VIEW;category=android.intent.category.DEFAULT;component=;type=video/*;launchFlags=0x10000000;end`;
console.log(`Using intent URL: ${intentUrl}`);
Linking.openURL(intentUrl)
.then(() => console.log('Successfully opened with intent URL'))
.catch(err => {
console.error('Failed to open with intent URL:', err);
// First fallback: Try direct URL with regular Linking API
console.log('Trying plain URL as fallback');
Linking.openURL(stream.url)
.then(() => console.log('Opened with direct URL'))
.catch(directErr => {
console.error('Failed to open direct URL:', directErr);
// Final fallback: Use built-in player
console.log('All external player attempts failed, using built-in player');
navigateToPlayer(stream);
});
});
}
} catch (error) {
console.error('Error with external player:', error);
// Fallback to the built-in player
navigateToPlayer(stream);
}
}
else {
// For internal player or if other options failed, use the built-in player
navigateToPlayer(stream);
}
}
} catch (error) {
console.error('Error in handleStreamPress:', error);
// Final fallback: Use built-in player
navigateToPlayer(stream);
}
}, [navigateToPlayer, preferredPlayer, useExternalPlayer]);
return {
handleStreamPress,
navigateToPlayer
};
};