mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-19 08:22:00 +00:00
added prefered quality and language autoplaing
This commit is contained in:
parent
dee6bd3f52
commit
622315f944
3 changed files with 184 additions and 180 deletions
|
|
@ -261,180 +261,7 @@ const PlayerSettingsScreen: React.FC = () => {
|
|||
},
|
||||
]}
|
||||
>
|
||||
<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 },
|
||||
]}
|
||||
>
|
||||
{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 */}
|
||||
{/* Video Player Engine for Android */}
|
||||
{Platform.OS === 'android' && !settings.useExternalPlayer && (
|
||||
<>
|
||||
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,19 @@ const SUBTITLE_SOURCE_OPTIONS = [
|
|||
{ 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
|
||||
interface PlaybackSettingsContentProps {
|
||||
isTablet?: boolean;
|
||||
|
|
@ -151,30 +164,55 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
|||
const audioLanguageSheetRef = useRef<BottomSheetModal>(null);
|
||||
const subtitleLanguageSheetRef = useRef<BottomSheetModal>(null);
|
||||
const subtitleSourceSheetRef = useRef<BottomSheetModal>(null);
|
||||
const autoplayQualitySheetRef = useRef<BottomSheetModal>(null);
|
||||
const autoplayLanguageSheetRef = useRef<BottomSheetModal>(null);
|
||||
|
||||
// Snap points
|
||||
const languageSnapPoints = useMemo(() => ['70%'], []);
|
||||
const sourceSnapPoints = useMemo(() => ['45%'], []);
|
||||
const qualitySnapPoints = useMemo(() => ['45%'], []);
|
||||
|
||||
// Handlers to present sheets - ensure only one is open at a time
|
||||
const openAudioLanguageSheet = useCallback(() => {
|
||||
subtitleLanguageSheetRef.current?.dismiss();
|
||||
subtitleSourceSheetRef.current?.dismiss();
|
||||
autoplayQualitySheetRef.current?.dismiss();
|
||||
autoplayLanguageSheetRef.current?.dismiss();
|
||||
setTimeout(() => audioLanguageSheetRef.current?.present(), 100);
|
||||
}, []);
|
||||
|
||||
const openSubtitleLanguageSheet = useCallback(() => {
|
||||
audioLanguageSheetRef.current?.dismiss();
|
||||
subtitleSourceSheetRef.current?.dismiss();
|
||||
autoplayQualitySheetRef.current?.dismiss();
|
||||
autoplayLanguageSheetRef.current?.dismiss();
|
||||
setTimeout(() => subtitleLanguageSheetRef.current?.present(), 100);
|
||||
}, []);
|
||||
|
||||
const openSubtitleSourceSheet = useCallback(() => {
|
||||
audioLanguageSheetRef.current?.dismiss();
|
||||
subtitleLanguageSheetRef.current?.dismiss();
|
||||
autoplayQualitySheetRef.current?.dismiss();
|
||||
autoplayLanguageSheetRef.current?.dismiss();
|
||||
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) => {
|
||||
if (!config?.items) return true;
|
||||
const item = config.items[itemId];
|
||||
|
|
@ -226,6 +264,16 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
|||
subtitleSourceSheetRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const handleSelectAutoplayQuality = (quality: string) => {
|
||||
updateSetting('autoplayPreferredQuality', quality);
|
||||
autoplayQualitySheetRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const handleSelectAutoplayLanguage = (language: string) => {
|
||||
updateSetting('autoplayPreferredLanguage', language);
|
||||
autoplayLanguageSheetRef.current?.dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasVisibleItems(['video_player']) && (
|
||||
|
|
@ -248,6 +296,38 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
|||
)}
|
||||
|
||||
<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
|
||||
title={t('player.skip_intro_settings_title', { defaultValue: 'Skip Intro' })}
|
||||
description={t('player.powered_by_introdb', { defaultValue: 'Powered by IntroDB' })}
|
||||
|
|
@ -528,6 +608,95 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
|||
})}
|
||||
</BottomSheetScrollView>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { TABLET_BREAKPOINT } from './constants';
|
|||
import {
|
||||
filterStreamsByQuality,
|
||||
filterStreamsByLanguage,
|
||||
getLanguageVariations,
|
||||
getQualityNumeric,
|
||||
inferVideoTypeFromUrl,
|
||||
sortStreamsByQuality,
|
||||
|
|
@ -254,13 +255,20 @@ export const useStreamsScreen = () => {
|
|||
let languageMatchedStreams = allStreams;
|
||||
if (preferredLanguage && preferredLanguage !== 'Any') {
|
||||
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 prefLang = preferredLanguage.toLowerCase();
|
||||
// Match by name if lang is not set, or match by lang property
|
||||
return streamLang === prefLang ||
|
||||
(item.stream.name || '').toLowerCase().includes(prefLang) ||
|
||||
(item.stream.title || '').toLowerCase().includes(prefLang) ||
|
||||
(item.stream.description || '').toLowerCase().includes(prefLang);
|
||||
|
||||
const variations = getLanguageVariations(preferredLanguage);
|
||||
|
||||
return variations.some(variant => {
|
||||
const variantLower = variant.toLowerCase();
|
||||
return streamLang === variantLower ||
|
||||
streamName.includes(variantLower) ||
|
||||
streamTitle.includes(variantLower) ||
|
||||
streamDesc.includes(variantLower);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue