some fixes

This commit is contained in:
tapframe 2025-06-20 14:53:00 +05:30
parent 8e8353635b
commit b953e99e3d
3 changed files with 229 additions and 2 deletions

View file

@ -36,6 +36,7 @@ export interface AppSettings {
tmdbLanguagePreference: string; // Preferred language for TMDB logos (ISO 639-1 code)
enableInternalProviders: boolean; // Toggle for internal providers like HDRezka
episodeLayoutStyle: 'vertical' | 'horizontal'; // Layout style for episode cards
autoplayBestStream: boolean; // Automatically play the best available stream
}
export const DEFAULT_SETTINGS: AppSettings = {
@ -54,6 +55,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
tmdbLanguagePreference: 'en', // Default to English
enableInternalProviders: true, // Enable internal providers by default
episodeLayoutStyle: 'horizontal', // Default to the new horizontal layout
autoplayBestStream: false, // Disabled by default for user choice
};
const SETTINGS_STORAGE_KEY = 'app_settings';

View file

@ -8,6 +8,7 @@ import {
Platform,
TouchableOpacity,
StatusBar,
Switch,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useSettings, AppSettings } from '../hooks/useSettings';
@ -219,6 +220,68 @@ const PlayerSettingsScreen: React.FC = () => {
))}
</View>
</View>
<View style={styles.section}>
<Text
style={[
styles.sectionTitle,
{ color: currentTheme.colors.textMuted },
]}
>
PLAYBACK OPTIONS
</Text>
<View
style={[
styles.card,
{
backgroundColor: currentTheme.colors.elevation2,
},
]}
>
<View style={styles.settingItem}>
<View style={styles.settingContent}>
<View style={[
styles.settingIconContainer,
{ backgroundColor: 'rgba(255,255,255,0.1)' }
]}>
<MaterialIcons
name="play-arrow"
size={20}
color={currentTheme.colors.primary}
/>
</View>
<View style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
Auto-play Best Stream
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
Automatically play the highest quality stream when available
</Text>
</View>
<Switch
value={settings.autoplayBestStream}
onValueChange={(value) => updateSetting('autoplayBestStream', value)}
trackColor={{
false: 'rgba(255,255,255,0.2)',
true: currentTheme.colors.primary + '40'
}}
thumbColor={settings.autoplayBestStream ? currentTheme.colors.primary : 'rgba(255,255,255,0.8)'}
ios_backgroundColor="rgba(255,255,255,0.2)"
/>
</View>
</View>
</View>
</View>
</ScrollView>
</SafeAreaView>
);

View file

@ -302,6 +302,10 @@ export const StreamsScreen = () => {
}
}>({});
// Add state for autoplay functionality
const [autoplayTriggered, setAutoplayTriggered] = useState(false);
const [isAutoplayWaiting, setIsAutoplayWaiting] = useState(false);
// Monitor streams loading start and completion - FIXED to prevent loops
useEffect(() => {
// Skip processing if component is unmounting
@ -438,6 +442,8 @@ export const StreamsScreen = () => {
}
}, [type, groupedStreams, episodeStreams, loadingStreams, loadingEpisodeStreams, selectedProvider]);
React.useEffect(() => {
if (type === 'series' && episodeId) {
logger.log(`🎬 Loading episode streams for: ${episodeId}`);
@ -455,7 +461,16 @@ export const StreamsScreen = () => {
// });
loadStreams();
}
}, [type, episodeId]);
// Reset autoplay state when content changes
setAutoplayTriggered(false);
if (settings.autoplayBestStream) {
setIsAutoplayWaiting(true);
logger.log('🔄 Autoplay enabled, waiting for best stream...');
} else {
setIsAutoplayWaiting(false);
}
}, [type, episodeId, settings.autoplayBestStream]);
React.useEffect(() => {
// Trigger entrance animations
@ -500,6 +515,101 @@ export const StreamsScreen = () => {
setSelectedProvider(provider);
}, []);
// Function to determine the best stream based on quality, provider priority, and other factors
const getBestStream = useCallback((streamsData: typeof groupedStreams): Stream | null => {
if (!streamsData || Object.keys(streamsData).length === 0) {
return null;
}
// Helper function to extract quality as number
const getQualityNumeric = (title: string | undefined): number => {
if (!title) return 0;
const matchWithP = title.match(/(\d+)p/i);
if (matchWithP) return parseInt(matchWithP[1], 10);
const qualityPatterns = [
/\b(240|360|480|720|1080|1440|2160|4320|8000)\b/i
];
for (const pattern of qualityPatterns) {
const match = title.match(pattern);
if (match) {
const quality = parseInt(match[1], 10);
if (quality >= 240 && quality <= 8000) return quality;
}
}
return 0;
};
// Provider priority (higher number = higher priority)
const getProviderPriority = (addonId: string): number => {
if (addonId === 'hdrezka') return 100; // HDRezka highest priority
// Get Stremio addon installation order (earlier = higher priority)
const installedAddons = stremioService.getInstalledAddons();
const addonIndex = installedAddons.findIndex(addon => addon.id === addonId);
if (addonIndex !== -1) {
// Higher priority for addons installed earlier (reverse index)
return 50 - addonIndex;
}
return 0; // Unknown providers get lowest priority
};
// Collect all streams with metadata
const allStreams: Array<{
stream: Stream;
quality: number;
providerPriority: number;
isDebrid: boolean;
isCached: boolean;
}> = [];
Object.entries(streamsData).forEach(([addonId, { streams }]) => {
streams.forEach(stream => {
const quality = getQualityNumeric(stream.name || stream.title);
const providerPriority = getProviderPriority(addonId);
const isDebrid = stream.behaviorHints?.cached || false;
const isCached = isDebrid;
allStreams.push({
stream,
quality,
providerPriority,
isDebrid,
isCached,
});
});
});
if (allStreams.length === 0) return null;
// Sort streams by multiple criteria (best first)
allStreams.sort((a, b) => {
// 1. Prioritize cached/debrid streams
if (a.isCached !== b.isCached) {
return a.isCached ? -1 : 1;
}
// 2. Prioritize higher quality
if (a.quality !== b.quality) {
return b.quality - a.quality;
}
// 3. Prioritize better providers
if (a.providerPriority !== b.providerPriority) {
return b.providerPriority - a.providerPriority;
}
return 0;
});
logger.log(`🎯 Best stream selected: ${allStreams[0].stream.name || allStreams[0].stream.title} (Quality: ${allStreams[0].quality}p, Provider Priority: ${allStreams[0].providerPriority}, Cached: ${allStreams[0].isCached})`);
return allStreams[0].stream;
}, []);
const currentEpisode = useMemo(() => {
if (!selectedEpisode) return null;
@ -710,6 +820,48 @@ export const StreamsScreen = () => {
}
}, [settings.preferredPlayer, settings.useExternalPlayer, navigateToPlayer]);
// Autoplay effect - triggers when streams are available and autoplay is enabled
useEffect(() => {
if (
settings.autoplayBestStream &&
!autoplayTriggered &&
!loadingStreams &&
!loadingEpisodeStreams &&
isAutoplayWaiting
) {
const streams = type === 'series' ? episodeStreams : groupedStreams;
if (Object.keys(streams).length > 0) {
const bestStream = getBestStream(streams);
if (bestStream) {
logger.log('🚀 Autoplay: Best stream found, starting playback...');
setAutoplayTriggered(true);
setIsAutoplayWaiting(false);
// Add a small delay to let the UI settle
setTimeout(() => {
handleStreamPress(bestStream);
}, 500);
} else {
logger.log('⚠️ Autoplay: No suitable stream found');
setIsAutoplayWaiting(false);
}
}
}
}, [
settings.autoplayBestStream,
autoplayTriggered,
loadingStreams,
loadingEpisodeStreams,
isAutoplayWaiting,
type,
episodeStreams,
groupedStreams,
getBestStream,
handleStreamPress
]);
const filterItems = useMemo(() => {
const installedAddons = stremioService.getInstalledAddons();
const streams = type === 'series' ? episodeStreams : groupedStreams;
@ -1140,7 +1292,17 @@ export const StreamsScreen = () => {
style={styles.loadingContainer}
>
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.loadingText}>Finding available streams...</Text>
<Text style={styles.loadingText}>
{isAutoplayWaiting ? 'Finding best stream for autoplay...' : 'Finding available streams...'}
</Text>
</Animated.View>
) : isAutoplayWaiting && !autoplayTriggered ? (
<Animated.View
entering={FadeIn.duration(300)}
style={styles.loadingContainer}
>
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.loadingText}>Starting best stream...</Text>
</Animated.View>
) : Object.keys(streams).length === 0 && !loadingStreams && !loadingEpisodeStreams ? (
<Animated.View