Ios #14

Merged
tapframe merged 88 commits from ios into main 2025-06-20 13:54:29 +00:00
3 changed files with 140 additions and 2 deletions
Showing only changes of commit 4deb343c5f - Show all commits

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>