mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
added option to exclude qualities
This commit is contained in:
parent
ebcbced142
commit
84689a9ba7
3 changed files with 126 additions and 6 deletions
|
|
@ -42,6 +42,8 @@ export interface AppSettings {
|
|||
scraperTimeout: number; // Timeout for scraper execution in seconds
|
||||
enableScraperUrlValidation: boolean; // Enable/disable URL validation for scrapers
|
||||
streamDisplayMode: 'separate' | 'grouped'; // How to display streaming links - separately by provider or grouped under one name
|
||||
// Quality filtering settings
|
||||
excludedQualities: string[]; // Array of quality strings to exclude (e.g., ['2160p', '4K', '1080p', '720p'])
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: AppSettings = {
|
||||
|
|
@ -66,6 +68,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
scraperTimeout: 60, // 60 seconds timeout
|
||||
enableScraperUrlValidation: true, // Enable URL validation by default
|
||||
streamDisplayMode: 'separate', // Default to separate display by provider
|
||||
// Quality filtering defaults
|
||||
excludedQualities: [], // No qualities excluded by default
|
||||
};
|
||||
|
||||
const SETTINGS_STORAGE_KEY = 'app_settings';
|
||||
|
|
|
|||
|
|
@ -328,6 +328,33 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
qualityChipsContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
marginTop: 8,
|
||||
},
|
||||
qualityChip: {
|
||||
backgroundColor: colors.elevation2,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
},
|
||||
qualityChipSelected: {
|
||||
backgroundColor: '#ff3b30',
|
||||
borderColor: '#ff3b30',
|
||||
},
|
||||
qualityChipText: {
|
||||
color: colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
},
|
||||
qualityChipTextSelected: {
|
||||
color: colors.white,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
const PluginsScreen: React.FC = () => {
|
||||
|
|
@ -510,6 +537,25 @@ const PluginsScreen: React.FC = () => {
|
|||
await updateSetting('enableScraperUrlValidation', enabled);
|
||||
};
|
||||
|
||||
const handleToggleQualityExclusion = async (quality: string) => {
|
||||
const currentExcluded = settings.excludedQualities || [];
|
||||
const isExcluded = currentExcluded.includes(quality);
|
||||
|
||||
let newExcluded: string[];
|
||||
if (isExcluded) {
|
||||
// Remove from excluded list
|
||||
newExcluded = currentExcluded.filter(q => q !== quality);
|
||||
} else {
|
||||
// Add to excluded list
|
||||
newExcluded = [...currentExcluded, quality];
|
||||
}
|
||||
|
||||
await updateSetting('excludedQualities', newExcluded);
|
||||
};
|
||||
|
||||
// Define available quality options
|
||||
const qualityOptions = ['2160p', '4K', '1080p', '720p', '360p', 'DV', 'HDR', 'REMUX', '480p', 'CAM', 'TS'];
|
||||
|
||||
|
||||
|
||||
return (
|
||||
|
|
@ -757,6 +803,45 @@ const PluginsScreen: React.FC = () => {
|
|||
</View>
|
||||
</View>
|
||||
|
||||
{/* Quality Filtering */}
|
||||
<View style={[styles.section, !settings.enableLocalScrapers && styles.disabledSection]}>
|
||||
<Text style={[styles.sectionTitle, !settings.enableLocalScrapers && styles.disabledText]}>Quality Filtering</Text>
|
||||
<Text style={[styles.sectionDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
Exclude specific video qualities from search results. Tap on a quality to exclude it from plugin results.
|
||||
</Text>
|
||||
|
||||
<View style={styles.qualityChipsContainer}>
|
||||
{qualityOptions.map((quality) => {
|
||||
const isExcluded = (settings.excludedQualities || []).includes(quality);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={quality}
|
||||
style={[
|
||||
styles.qualityChip,
|
||||
isExcluded && styles.qualityChipSelected,
|
||||
!settings.enableLocalScrapers && styles.disabledButton
|
||||
]}
|
||||
onPress={() => handleToggleQualityExclusion(quality)}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
>
|
||||
<Text style={[
|
||||
styles.qualityChipText,
|
||||
isExcluded && styles.qualityChipTextSelected,
|
||||
!settings.enableLocalScrapers && styles.disabledText
|
||||
]}>
|
||||
{isExcluded ? '✕ ' : ''}{quality}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{(settings.excludedQualities || []).length > 0 && (
|
||||
<Text style={[styles.infoText, { marginTop: 12 }, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
💡 Excluded qualities: {(settings.excludedQualities || []).join(', ')}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* About */}
|
||||
<View style={[styles.section, styles.lastSection]}>
|
||||
|
|
|
|||
|
|
@ -516,6 +516,27 @@ export const StreamsScreen = () => {
|
|||
setSelectedProvider(provider);
|
||||
}, []);
|
||||
|
||||
// Helper function to filter streams by quality exclusions
|
||||
const filterStreamsByQuality = useCallback((streams: Stream[]) => {
|
||||
if (!settings.excludedQualities || settings.excludedQualities.length === 0) {
|
||||
return streams;
|
||||
}
|
||||
|
||||
return streams.filter(stream => {
|
||||
const streamTitle = stream.title || stream.name || '';
|
||||
|
||||
// Check if any excluded quality is found in the stream title
|
||||
const hasExcludedQuality = settings.excludedQualities.some(excludedQuality => {
|
||||
// Create a case-insensitive regex pattern for the quality
|
||||
const pattern = new RegExp(excludedQuality.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
||||
return pattern.test(streamTitle);
|
||||
});
|
||||
|
||||
// Return true to keep the stream (if it doesn't have excluded quality)
|
||||
return !hasExcludedQuality;
|
||||
});
|
||||
}, [settings.excludedQualities]);
|
||||
|
||||
// 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) {
|
||||
|
|
@ -566,7 +587,10 @@ export const StreamsScreen = () => {
|
|||
}> = [];
|
||||
|
||||
Object.entries(streamsData).forEach(([addonId, { streams }]) => {
|
||||
streams.forEach(stream => {
|
||||
// Apply quality filtering to streams before processing
|
||||
const filteredStreams = filterStreamsByQuality(streams);
|
||||
|
||||
filteredStreams.forEach(stream => {
|
||||
const quality = getQualityNumeric(stream.name || stream.title);
|
||||
const providerPriority = getProviderPriority(addonId);
|
||||
const isDebrid = stream.behaviorHints?.cached || false;
|
||||
|
|
@ -607,7 +631,7 @@ export const StreamsScreen = () => {
|
|||
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;
|
||||
}, []);
|
||||
}, [filterStreamsByQuality]);
|
||||
|
||||
const currentEpisode = useMemo(() => {
|
||||
if (!selectedEpisode) return null;
|
||||
|
|
@ -977,13 +1001,17 @@ export const StreamsScreen = () => {
|
|||
|
||||
filteredEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => {
|
||||
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
|
||||
|
||||
// Apply quality filtering to streams
|
||||
const filteredStreams = filterStreamsByQuality(providerStreams);
|
||||
|
||||
if (isInstalledAddon) {
|
||||
addonStreams.push(...providerStreams);
|
||||
addonStreams.push(...filteredStreams);
|
||||
if (!addonNames.includes(addonName)) {
|
||||
addonNames.push(addonName);
|
||||
}
|
||||
} else {
|
||||
pluginStreams.push(...providerStreams);
|
||||
pluginStreams.push(...filteredStreams);
|
||||
if (!pluginNames.includes(addonName)) {
|
||||
pluginNames.push(addonName);
|
||||
}
|
||||
|
|
@ -1010,14 +1038,17 @@ export const StreamsScreen = () => {
|
|||
} else {
|
||||
// Use separate sections for each provider (current behavior)
|
||||
return filteredEntries.map(([addonId, { addonName, streams: providerStreams }]) => {
|
||||
// Apply quality filtering to streams
|
||||
const filteredStreams = filterStreamsByQuality(providerStreams);
|
||||
|
||||
return {
|
||||
title: addonName,
|
||||
addonId,
|
||||
data: providerStreams
|
||||
data: filteredStreams
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode]);
|
||||
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality]);
|
||||
|
||||
const episodeImage = useMemo(() => {
|
||||
if (episodeThumbnail) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue