added prefered quality and language autoplaing

This commit is contained in:
meilluer 2026-02-17 15:22:27 +05:30
parent dee6bd3f52
commit 622315f944
3 changed files with 184 additions and 180 deletions

View file

@ -261,180 +261,7 @@ const PlayerSettingsScreen: React.FC = () => {
}, },
]} ]}
> >
<View style={styles.settingItem}> {/* Video Player Engine for Android */}
<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 },
]}
>
{t('player.autoplay_title')}
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
{t('player.autoplay_desc')}
</Text>
</View>
<Switch
value={settings.autoplayBestStream}
onValueChange={(value) => updateSetting('autoplayBestStream', value)}
trackColor={{ false: '#767577', true: currentTheme.colors.primary }}
thumbColor={settings.autoplayBestStream ? '#ffffff' : '#f4f3f4'}
ios_backgroundColor="#3e3e3e"
/>
</View>
</View>
{/* Preferred Quality for Autoplay */}
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
<View style={styles.settingContent}>
<View style={[
styles.settingIconContainer,
{ backgroundColor: 'rgba(255,255,255,0.1)' }
]}>
<MaterialIcons
name="high-quality"
size={20}
color={currentTheme.colors.primary}
/>
</View>
<View style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
{t('player.preferred_quality_title') || 'Preferred Quality'}
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
{t('player.preferred_quality_desc') || 'Select preferred quality for autoplay'}
</Text>
</View>
</View>
<View style={styles.optionButtonsRow}>
{([
{ id: '4K', label: '4K' },
{ id: '1080p', label: '1080p' },
{ id: '720p', label: '720p' },
{ id: '480p', label: '480p' },
] as const).map((option) => (
<TouchableOpacity
key={option.id}
onPress={() => updateSetting('autoplayPreferredQuality', option.id)}
style={[
styles.optionButton,
settings.autoplayPreferredQuality === option.id && { backgroundColor: currentTheme.colors.primary },
]}
>
<Text
style={[
styles.optionButtonText,
{ color: settings.autoplayPreferredQuality === option.id ? '#fff' : currentTheme.colors.text },
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
{/* Preferred Language for Autoplay */}
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
<View style={styles.settingContent}>
<View style={[
styles.settingIconContainer,
{ backgroundColor: 'rgba(255,255,255,0.1)' }
]}>
<MaterialIcons
name="language"
size={20}
color={currentTheme.colors.primary}
/>
</View>
<View style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
{t('player.preferred_language_title') || 'Preferred Language'}
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
{t('player.preferred_language_desc') || 'Select preferred language for autoplay'}
</Text>
</View>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.optionButtonsRowScroll}
>
{([
{ id: 'Any', label: t('common.any') || 'Any' },
{ id: 'English', label: t('settings.english') || 'English' },
{ id: 'Spanish', label: t('settings.spanish') || 'Spanish' },
{ id: 'French', label: t('settings.french') || 'French' },
{ id: 'German', label: t('settings.german') || 'German' },
{ id: 'Italian', label: t('settings.italian') || 'Italian' },
{ id: 'Portuguese', label: t('settings.portuguese') || 'Portuguese' },
{ id: 'Russian', label: t('settings.russian') || 'Russian' },
{ id: 'Hindi', label: t('settings.hindi') || 'Hindi' },
{ id: 'Chinese', label: t('settings.chinese') || 'Chinese' },
{ id: 'Japanese', label: t('settings.japanese') || 'Japanese' },
{ id: 'Korean', label: t('settings.korean') || 'Korean' },
] as const).map((option) => (
<TouchableOpacity
key={option.id}
onPress={() => updateSetting('autoplayPreferredLanguage', option.id)}
style={[
styles.optionButton,
styles.optionButtonLanguage,
settings.autoplayPreferredLanguage === option.id && { backgroundColor: currentTheme.colors.primary },
]}
>
<Text
style={[
styles.optionButtonText,
{ color: settings.autoplayPreferredLanguage === option.id ? '#fff' : currentTheme.colors.text },
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* Video Player Engine for Android */}
{Platform.OS === 'android' && !settings.useExternalPlayer && ( {Platform.OS === 'android' && !settings.useExternalPlayer && (
<> <>
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}> <View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>

View file

@ -62,6 +62,19 @@ const SUBTITLE_SOURCE_OPTIONS = [
{ value: 'any', label: 'Any Available', description: 'Use first available subtitle track' }, { value: 'any', label: 'Any Available', description: 'Use first available subtitle track' },
]; ];
const AUTOPLAY_QUALITY_OPTIONS = [
{ id: '4320p', label: '8K' },
{ id: '4K', label: '4K' },
{ id: '3660p', label: '3660p' },
{ id: '1440p', label: '1440p' },
{ id: '1080p', label: '1080p' },
{ id: '720p', label: '720p' },
{ id: '480p', label: '480p' },
{ id: '360p', label: '360p' },
{ id: '240p', label: '240p' },
{ id: '140p', label: '140p' },
];
// Props for the reusable content component // Props for the reusable content component
interface PlaybackSettingsContentProps { interface PlaybackSettingsContentProps {
isTablet?: boolean; isTablet?: boolean;
@ -151,30 +164,55 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
const audioLanguageSheetRef = useRef<BottomSheetModal>(null); const audioLanguageSheetRef = useRef<BottomSheetModal>(null);
const subtitleLanguageSheetRef = useRef<BottomSheetModal>(null); const subtitleLanguageSheetRef = useRef<BottomSheetModal>(null);
const subtitleSourceSheetRef = useRef<BottomSheetModal>(null); const subtitleSourceSheetRef = useRef<BottomSheetModal>(null);
const autoplayQualitySheetRef = useRef<BottomSheetModal>(null);
const autoplayLanguageSheetRef = useRef<BottomSheetModal>(null);
// Snap points // Snap points
const languageSnapPoints = useMemo(() => ['70%'], []); const languageSnapPoints = useMemo(() => ['70%'], []);
const sourceSnapPoints = useMemo(() => ['45%'], []); const sourceSnapPoints = useMemo(() => ['45%'], []);
const qualitySnapPoints = useMemo(() => ['45%'], []);
// Handlers to present sheets - ensure only one is open at a time // Handlers to present sheets - ensure only one is open at a time
const openAudioLanguageSheet = useCallback(() => { const openAudioLanguageSheet = useCallback(() => {
subtitleLanguageSheetRef.current?.dismiss(); subtitleLanguageSheetRef.current?.dismiss();
subtitleSourceSheetRef.current?.dismiss(); subtitleSourceSheetRef.current?.dismiss();
autoplayQualitySheetRef.current?.dismiss();
autoplayLanguageSheetRef.current?.dismiss();
setTimeout(() => audioLanguageSheetRef.current?.present(), 100); setTimeout(() => audioLanguageSheetRef.current?.present(), 100);
}, []); }, []);
const openSubtitleLanguageSheet = useCallback(() => { const openSubtitleLanguageSheet = useCallback(() => {
audioLanguageSheetRef.current?.dismiss(); audioLanguageSheetRef.current?.dismiss();
subtitleSourceSheetRef.current?.dismiss(); subtitleSourceSheetRef.current?.dismiss();
autoplayQualitySheetRef.current?.dismiss();
autoplayLanguageSheetRef.current?.dismiss();
setTimeout(() => subtitleLanguageSheetRef.current?.present(), 100); setTimeout(() => subtitleLanguageSheetRef.current?.present(), 100);
}, []); }, []);
const openSubtitleSourceSheet = useCallback(() => { const openSubtitleSourceSheet = useCallback(() => {
audioLanguageSheetRef.current?.dismiss(); audioLanguageSheetRef.current?.dismiss();
subtitleLanguageSheetRef.current?.dismiss(); subtitleLanguageSheetRef.current?.dismiss();
autoplayQualitySheetRef.current?.dismiss();
autoplayLanguageSheetRef.current?.dismiss();
setTimeout(() => subtitleSourceSheetRef.current?.present(), 100); setTimeout(() => subtitleSourceSheetRef.current?.present(), 100);
}, []); }, []);
const openAutoplayQualitySheet = useCallback(() => {
audioLanguageSheetRef.current?.dismiss();
subtitleLanguageSheetRef.current?.dismiss();
subtitleSourceSheetRef.current?.dismiss();
autoplayLanguageSheetRef.current?.dismiss();
setTimeout(() => autoplayQualitySheetRef.current?.present(), 100);
}, []);
const openAutoplayLanguageSheet = useCallback(() => {
audioLanguageSheetRef.current?.dismiss();
subtitleLanguageSheetRef.current?.dismiss();
subtitleSourceSheetRef.current?.dismiss();
autoplayQualitySheetRef.current?.dismiss();
setTimeout(() => autoplayLanguageSheetRef.current?.present(), 100);
}, []);
const isItemVisible = (itemId: string) => { const isItemVisible = (itemId: string) => {
if (!config?.items) return true; if (!config?.items) return true;
const item = config.items[itemId]; const item = config.items[itemId];
@ -226,6 +264,16 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
subtitleSourceSheetRef.current?.dismiss(); subtitleSourceSheetRef.current?.dismiss();
}; };
const handleSelectAutoplayQuality = (quality: string) => {
updateSetting('autoplayPreferredQuality', quality);
autoplayQualitySheetRef.current?.dismiss();
};
const handleSelectAutoplayLanguage = (language: string) => {
updateSetting('autoplayPreferredLanguage', language);
autoplayLanguageSheetRef.current?.dismiss();
};
return ( return (
<> <>
{hasVisibleItems(['video_player']) && ( {hasVisibleItems(['video_player']) && (
@ -248,6 +296,38 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
)} )}
<SettingsCard title={t('player.section_playback', { defaultValue: 'Playback' })} isTablet={isTablet}> <SettingsCard title={t('player.section_playback', { defaultValue: 'Playback' })} isTablet={isTablet}>
<SettingItem
title={t('player.autoplay_title', { defaultValue: 'Auto-play First Stream' })}
description={t('player.autoplay_desc', { defaultValue: 'Automatically start the first stream shown in the list.' })}
icon="play-arrow"
renderControl={() => (
<CustomSwitch
value={settings?.autoplayBestStream ?? false}
onValueChange={(value) => updateSetting('autoplayBestStream', value)}
/>
)}
isTablet={isTablet}
/>
{settings?.autoplayBestStream && (
<>
<SettingItem
title={t('player.preferred_quality_title', { defaultValue: 'Preferred Quality' })}
description={settings?.autoplayPreferredQuality || '1080p'}
icon="high-quality"
renderControl={() => <ChevronRight />}
onPress={openAutoplayQualitySheet}
isTablet={isTablet}
/>
<SettingItem
title={t('player.preferred_language_title', { defaultValue: 'Preferred Language' })}
description={settings?.autoplayPreferredLanguage === 'Any' ? t('common.any') : settings?.autoplayPreferredLanguage || t('common.any')}
icon="language"
renderControl={() => <ChevronRight />}
onPress={openAutoplayLanguageSheet}
isTablet={isTablet}
/>
</>
)}
<SettingItem <SettingItem
title={t('player.skip_intro_settings_title', { defaultValue: 'Skip Intro' })} title={t('player.skip_intro_settings_title', { defaultValue: 'Skip Intro' })}
description={t('player.powered_by_introdb', { defaultValue: 'Powered by IntroDB' })} description={t('player.powered_by_introdb', { defaultValue: 'Powered by IntroDB' })}
@ -528,6 +608,95 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
})} })}
</BottomSheetScrollView> </BottomSheetScrollView>
</BottomSheetModal> </BottomSheetModal>
{/* Autoplay Quality Bottom Sheet */}
<BottomSheetModal
ref={autoplayQualitySheetRef}
index={0}
snapPoints={qualitySnapPoints}
enableDynamicSizing={false}
enablePanDownToClose={true}
backdropComponent={renderBackdrop}
backgroundStyle={{ backgroundColor: '#1a1a1a' }}
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
>
<View style={styles.sheetHeader}>
<Text style={styles.sheetTitle}>{t('player.preferred_quality_title')}</Text>
</View>
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
{AUTOPLAY_QUALITY_OPTIONS.map((option) => {
const isSelected = option.id === (settings?.autoplayPreferredQuality || '1080p');
return (
<TouchableOpacity
key={option.id}
style={[
styles.languageItem,
isSelected && { backgroundColor: currentTheme.colors.primary + '20' }
]}
onPress={() => handleSelectAutoplayQuality(option.id)}
>
<Text style={[styles.languageName, { color: isSelected ? currentTheme.colors.primary : '#fff' }]}>
{option.label}
</Text>
{isSelected && (
<MaterialIcons name="check" size={20} color={currentTheme.colors.primary} />
)}
</TouchableOpacity>
);
})}
</BottomSheetScrollView>
</BottomSheetModal>
{/* Autoplay Language Bottom Sheet */}
<BottomSheetModal
ref={autoplayLanguageSheetRef}
index={0}
snapPoints={languageSnapPoints}
enableDynamicSizing={false}
enablePanDownToClose={true}
backdropComponent={renderBackdrop}
backgroundStyle={{ backgroundColor: '#1a1a1a' }}
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
>
<View style={styles.sheetHeader}>
<Text style={styles.sheetTitle}>{t('player.preferred_language_title')}</Text>
</View>
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
{[
{ id: 'Any', label: t('common.any') || 'Any' },
{ id: 'English', label: t('settings.english') || 'English' },
{ id: 'Spanish', label: t('settings.spanish') || 'Spanish' },
{ id: 'French', label: t('settings.french') || 'French' },
{ id: 'German', label: t('settings.german') || 'German' },
{ id: 'Italian', label: t('settings.italian') || 'Italian' },
{ id: 'Portuguese', label: t('settings.portuguese') || 'Portuguese' },
{ id: 'Russian', label: t('settings.russian') || 'Russian' },
{ id: 'Hindi', label: t('settings.hindi') || 'Hindi' },
{ id: 'Chinese', label: t('settings.chinese') || 'Chinese' },
{ id: 'Japanese', label: t('settings.japanese') || 'Japanese' },
{ id: 'Korean', label: t('settings.korean') || 'Korean' },
].map((option) => {
const isSelected = option.id === (settings?.autoplayPreferredLanguage || 'Any');
return (
<TouchableOpacity
key={option.id}
style={[
styles.languageItem,
isSelected && { backgroundColor: currentTheme.colors.primary + '20' }
]}
onPress={() => handleSelectAutoplayLanguage(option.id)}
>
<Text style={[styles.languageName, { color: isSelected ? currentTheme.colors.primary : '#fff' }]}>
{option.label}
</Text>
{isSelected && (
<MaterialIcons name="check" size={20} color={currentTheme.colors.primary} />
)}
</TouchableOpacity>
);
})}
</BottomSheetScrollView>
</BottomSheetModal>
</> </>
); );
}; };

View file

@ -22,6 +22,7 @@ import { TABLET_BREAKPOINT } from './constants';
import { import {
filterStreamsByQuality, filterStreamsByQuality,
filterStreamsByLanguage, filterStreamsByLanguage,
getLanguageVariations,
getQualityNumeric, getQualityNumeric,
inferVideoTypeFromUrl, inferVideoTypeFromUrl,
sortStreamsByQuality, sortStreamsByQuality,
@ -254,13 +255,20 @@ export const useStreamsScreen = () => {
let languageMatchedStreams = allStreams; let languageMatchedStreams = allStreams;
if (preferredLanguage && preferredLanguage !== 'Any') { if (preferredLanguage && preferredLanguage !== 'Any') {
languageMatchedStreams = allStreams.filter(item => { languageMatchedStreams = allStreams.filter(item => {
const streamName = (item.stream.name || '').toLowerCase();
const streamTitle = (item.stream.title || '').toLowerCase();
const streamDesc = (item.stream.description || '').toLowerCase();
const streamLang = (item.stream.lang || '').toLowerCase(); const streamLang = (item.stream.lang || '').toLowerCase();
const prefLang = preferredLanguage.toLowerCase();
// Match by name if lang is not set, or match by lang property const variations = getLanguageVariations(preferredLanguage);
return streamLang === prefLang ||
(item.stream.name || '').toLowerCase().includes(prefLang) || return variations.some(variant => {
(item.stream.title || '').toLowerCase().includes(prefLang) || const variantLower = variant.toLowerCase();
(item.stream.description || '').toLowerCase().includes(prefLang); return streamLang === variantLower ||
streamName.includes(variantLower) ||
streamTitle.includes(variantLower) ||
streamDesc.includes(variantLower);
});
}); });
} }