From 4deb343c5f40b1d56df3871d54cd24d399bfa3cd Mon Sep 17 00:00:00 2001 From: tapframe Date: Tue, 10 Jun 2025 01:42:54 +0530 Subject: [PATCH] Add auto-play feature for first available stream in MetadataScreen This update introduces a new setting in the app settings to enable auto-playing the first available stream without showing the stream selection screen. The MetadataScreen has been modified to check this setting and automatically load the first stream if enabled, enhancing user experience by streamlining the viewing process. Additionally, the SettingsScreen has been updated to include a toggle for this new feature. --- src/hooks/useSettings.ts | 2 + src/screens/MetadataScreen.tsx | 129 ++++++++++++++++++++++++++++++++- src/screens/SettingsScreen.tsx | 11 +++ 3 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index aa63dce1..e25b1d6c 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -35,6 +35,7 @@ export interface AppSettings { logoSourcePreference: 'metahub' | 'tmdb'; // Preferred source for title logos tmdbLanguagePreference: string; // Preferred language for TMDB logos (ISO 639-1 code) enableInternalProviders: boolean; // Toggle for internal providers like HDRezka + autoPlayFirstStream: boolean; // Auto-play first stream without showing streams selection } export const DEFAULT_SETTINGS: AppSettings = { @@ -52,6 +53,7 @@ export const DEFAULT_SETTINGS: AppSettings = { logoSourcePreference: 'metahub', // Default to Metahub as first source tmdbLanguagePreference: 'en', // Default to English enableInternalProviders: true, // Enable internal providers by default + autoPlayFirstStream: false, // Default to false to maintain existing behavior }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 29a690ba..e5d9252b 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -12,6 +12,7 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { useRoute, useNavigation } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import * as Haptics from 'expo-haptics'; +import * as ScreenOrientation from 'expo-screen-orientation'; import { useTheme } from '../contexts/ThemeContext'; import { useMetadata } from '../hooks/useMetadata'; import { CastSection } from '../components/metadata/CastSection'; @@ -20,6 +21,7 @@ import { MovieContent } from '../components/metadata/MovieContent'; import { MoreLikeThisSection } from '../components/metadata/MoreLikeThisSection'; import { RatingsSection } from '../components/metadata/RatingsSection'; import { RouteParams, Episode } from '../types/metadata'; +import { Stream, GroupedStreams } from '../types/streams'; import Animated, { useAnimatedStyle, interpolate, @@ -75,6 +77,10 @@ const MetadataScreen: React.FC = () => { loadingRecommendations, setMetadata, imdbId, + loadStreams, + loadEpisodeStreams, + groupedStreams, + episodeStreams, } = useMetadata({ id, type }); // Optimized hooks with memoization @@ -107,8 +113,127 @@ const MetadataScreen: React.FC = () => { handleSeasonChange(seasonNumber); }, [handleSeasonChange]); - const handleShowStreams = useCallback(() => { + // Helper function to get the first available stream from grouped streams + const getFirstAvailableStream = useCallback((streams: GroupedStreams): Stream | null => { + const providers = Object.values(streams); + for (const provider of providers) { + if (provider.streams && provider.streams.length > 0) { + // Try to find a cached stream first + const cachedStream = provider.streams.find(stream => + stream.behaviorHints?.cached === true + ); + if (cachedStream) { + return cachedStream; + } + + // Otherwise return the first stream + return provider.streams[0]; + } + } + return null; + }, []); + + const handleShowStreams = useCallback(async () => { const { watchProgress } = watchProgressData; + + // Check if auto-play is enabled + if (settings.autoPlayFirstStream) { + try { + console.log('Auto-play enabled, attempting to load streams...'); + + // Determine the target episode for series + let targetEpisodeId: string | undefined; + if (type === 'series') { + targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? + (episodes[0].stremioId || `${id}:${episodes[0].season_number}:${episodes[0].episode_number}`) : undefined); + } + + // Load streams without locking orientation yet + let streamsLoaded = false; + if (type === 'series' && targetEpisodeId) { + console.log('Loading episode streams for:', targetEpisodeId); + await loadEpisodeStreams(targetEpisodeId); + streamsLoaded = true; + } else if (type === 'movie') { + console.log('Loading movie streams...'); + await loadStreams(); + streamsLoaded = true; + } + + if (streamsLoaded) { + // Wait a bit longer for streams to be processed and state to update + console.log('Waiting for streams to be processed...'); + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Check if we have any streams available + const availableStreams = type === 'series' ? episodeStreams : groupedStreams; + console.log('Available streams:', Object.keys(availableStreams)); + + const firstStream = getFirstAvailableStream(availableStreams); + + if (firstStream) { + console.log('Found stream, navigating to player:', firstStream); + + // Now lock orientation to landscape before navigation + await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); + await new Promise(resolve => setTimeout(resolve, 200)); + + if (type === 'series' && targetEpisodeId) { + // Get episode details for navigation + const targetEpisode = episodes.find(ep => + ep.stremioId === targetEpisodeId || + `${id}:${ep.season_number}:${ep.episode_number}` === targetEpisodeId + ); + + // Navigate directly to player with the first stream + navigation.navigate('Player', { + uri: firstStream.url, + title: metadata?.name || 'Unknown', + season: targetEpisode?.season_number, + episode: targetEpisode?.episode_number, + episodeTitle: targetEpisode?.name, + quality: firstStream.title?.match(/(\d+)p/)?.[1] || 'Unknown', + year: metadata?.year, + streamProvider: firstStream.name || 'Unknown', + id, + type, + episodeId: targetEpisodeId, + imdbId: imdbId || id, + }); + return; + } else if (type === 'movie') { + // Navigate directly to player with the first stream + navigation.navigate('Player', { + uri: firstStream.url, + title: metadata?.name || 'Unknown', + quality: firstStream.title?.match(/(\d+)p/)?.[1] || 'Unknown', + year: metadata?.year, + streamProvider: firstStream.name || 'Unknown', + id, + type, + imdbId: imdbId || id, + }); + return; + } + } else { + console.log('No streams found after waiting, disabling auto-play for this session'); + // Don't fall back to streams screen, just show an alert + alert('No streams available for auto-play. Please try selecting streams manually.'); + return; + } + } + + console.log('Auto-play failed, falling back to manual selection'); + } catch (error) { + console.error('Auto-play failed with error:', error); + // Don't fall back on error, just show alert + alert('Auto-play failed. Please try selecting streams manually.'); + return; + } + } + + // Normal behavior: navigate to streams screen (only if auto-play is disabled or not attempted) + console.log('Navigating to streams screen (normal flow)'); if (type === 'series') { const targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? (episodes[0].stremioId || `${id}:${episodes[0].season_number}:${episodes[0].episode_number}`) : undefined); @@ -119,7 +244,7 @@ const MetadataScreen: React.FC = () => { } } navigation.navigate('Streams', { id, type, episodeId }); - }, [navigation, id, type, episodes, episodeId, watchProgressData.watchProgress]); + }, [settings.autoPlayFirstStream, navigation, id, type, episodes, episodeId, watchProgressData, metadata, loadEpisodeStreams, loadStreams, episodeStreams, groupedStreams, imdbId, getFirstAvailableStream]); const handleEpisodeSelect = useCallback((episode: Episode) => { const episodeId = episode.stremioId || `${id}:${episode.season_number}:${episode.episode_number}`; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 9dbe1026..d4bd7c96 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -462,6 +462,17 @@ const SettingsScreen: React.FC = () => { icon="play-arrow" renderControl={ChevronRight} onPress={() => navigation.navigate('PlayerSettings')} + /> + ( + updateSetting('autoPlayFirstStream', value)} + /> + )} isLast={true} />