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.
This commit is contained in:
tapframe 2025-06-10 01:42:54 +05:30
parent 9c73af1d47
commit 4deb343c5f
3 changed files with 140 additions and 2 deletions

View file

@ -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';

View file

@ -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}`;

View file

@ -462,6 +462,17 @@ const SettingsScreen: React.FC = () => {
icon="play-arrow"
renderControl={ChevronRight}
onPress={() => navigation.navigate('PlayerSettings')}
/>
<SettingItem
title="Auto-play First Stream"
description="Automatically play the first available stream without showing stream selection"
icon="auto-fix-high"
renderControl={() => (
<CustomSwitch
value={settings.autoPlayFirstStream}
onValueChange={(value) => updateSetting('autoPlayFirstStream', value)}
/>
)}
isLast={true}
/>
</SettingsCard>