diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index ab2d274b..e372893c 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -140,10 +140,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat // Prevent re-initializing season selection repeatedly for the same series const initializedSeasonRef = useRef(false); - // Memory optimization: Track stream counts and implement cleanup + // Memory optimization: Track stream counts and implement cleanup (limits removed) const streamCountRef = useRef(0); - const maxStreamsPerAddon = 50; // Limit streams per addon to prevent memory bloat - const maxTotalStreams = 200; // Maximum total streams across all addons const cleanupTimeoutRef = useRef(null); // Add hook for persistent seasons @@ -182,42 +180,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat updateFn(); }, [cleanupStreams]); - // Memory optimization: Limit and optimize stream data + // Memory optimization: Lightly optimize stream data (no sorting or limiting) const optimizeStreams = useCallback((streams: Stream[]): Stream[] => { if (!streams || streams.length === 0) return streams; - - // Sort streams by quality/priority and limit count - const sortedStreams = streams - .sort((a, b) => { - // Prioritize free streams, then debrid, then by size - if (a.isFree && !b.isFree) return -1; - if (!a.isFree && b.isFree) return 1; - if (a.isDebrid && !b.isDebrid) return -1; - if (!a.isDebrid && b.isDebrid) return 1; - - // Sort by size (larger files often better quality) - const sizeA = a.size || 0; - const sizeB = b.size || 0; - return sizeB - sizeA; - }) - .slice(0, maxStreamsPerAddon); // Limit streams per addon - - // Optimize individual stream objects - return sortedStreams.map(stream => ({ + return streams.map(stream => ({ ...stream, - // Truncate long descriptions to prevent memory bloat description: stream.description && stream.description.length > 200 ? stream.description.substring(0, 200) + '...' : stream.description, - // Simplify behaviorHints to essential data only behaviorHints: stream.behaviorHints ? { cached: stream.behaviorHints.cached, notWebReady: stream.behaviorHints.notWebReady, bingeGroup: stream.behaviorHints.bingeGroup, - // Remove large objects like magnetUrl, sources, etc. } : undefined, })); - }, [maxStreamsPerAddon]); + }, []); const processStremioSource = async (type: string, id: string, isEpisode = false) => { const sourceStartTime = Date.now(); @@ -264,13 +241,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat if (__DEV__) logger.log(`โœ… [${logPrefix}:${sourceName}] Received ${streams.length} streams from ${addonName} (${addonId}) after ${processTime}ms`); if (streams.length > 0) { - // Memory optimization: Check total stream count and cleanup if needed - const currentTotalStreams = streamCountRef.current; - if (currentTotalStreams >= maxTotalStreams) { - if (__DEV__) logger.log(`๐Ÿงน [${logPrefix}:${sourceName}] Memory limit reached (${currentTotalStreams} streams), cleaning up`); - cleanupStreams(); - } - // Optimize streams before storing const optimizedStreams = optimizeStreams(streams); streamCountRef.current += optimizedStreams.length; diff --git a/src/screens/PlayerSettingsScreen.tsx b/src/screens/PlayerSettingsScreen.tsx index f41e749c..275b6c86 100644 --- a/src/screens/PlayerSettingsScreen.tsx +++ b/src/screens/PlayerSettingsScreen.tsx @@ -213,9 +213,9 @@ const PlayerSettingsScreen: React.FC = () => { } onPress={() => { if (Platform.OS === 'ios') { - updateSetting('preferredPlayer', option.id as AppSettings['preferredPlayer'], false); + updateSetting('preferredPlayer', option.id as AppSettings['preferredPlayer']); } else { - updateSetting('useExternalPlayer', option.id === 'external', false); + updateSetting('useExternalPlayer', option.id === 'external'); } }} isLast={index === playerOptions.length - 1} diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 2fd73037..db46675a 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -688,83 +688,7 @@ export const StreamsScreen = () => { }); }, [settings.excludedQualities]); - // Helper function to sort streams based on user preference - const sortStreams = useCallback((streams: Stream[]) => { - const installedAddons = stremioService.getInstalledAddons(); - - // Helper function to extract quality as number - const getQualityNumeric = (title: string | undefined): number => { - if (!title) return 0; - - // Check for 4K first (treat as 2160p) - if (/\b4k\b/i.test(title)) { - return 2160; - } - - 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 = (stream: Stream): number => { - const addonId = stream.addonId || stream.addonName || ''; - 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 - }; - - return [...streams].sort((a, b) => { - const qualityA = getQualityNumeric(a.name || a.title); - const qualityB = getQualityNumeric(b.name || b.title); - const providerPriorityA = getProviderPriority(a); - const providerPriorityB = getProviderPriority(b); - const isCachedA = a.behaviorHints?.cached || false; - const isCachedB = b.behaviorHints?.cached || false; - - // Always prioritize cached/debrid streams first - if (isCachedA !== isCachedB) { - return isCachedA ? -1 : 1; - } - - if (settings.streamSortMode === 'quality-then-scraper') { - // Sort by quality first, then by provider - if (qualityA !== qualityB) { - return qualityB - qualityA; // Higher quality first - } - if (providerPriorityA !== providerPriorityB) { - return providerPriorityB - providerPriorityA; // Better provider first - } - } else { - // Default: Sort by provider first, then by quality - if (providerPriorityA !== providerPriorityB) { - return providerPriorityB - providerPriorityA; // Better provider first - } - if (qualityA !== qualityB) { - return qualityB - qualityA; // Higher quality first - } - } - - return 0; - }); - }, [settings.excludedQualities, settings.streamSortMode]); + // Note: No additional sorting applied to stream cards; preserve provider order // Function to determine the best stream based on quality, provider priority, and other factors const getBestStream = useCallback((streamsData: typeof groupedStreams): Stream | null => { @@ -817,8 +741,6 @@ export const StreamsScreen = () => { stream: Stream; quality: number; providerPriority: number; - isDebrid: boolean; - isCached: boolean; }> = []; Object.entries(streamsData).forEach(([addonId, { streams }]) => { @@ -828,15 +750,10 @@ export const StreamsScreen = () => { filteredStreams.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, }); }); }); @@ -845,17 +762,12 @@ export const StreamsScreen = () => { // 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 + // 1. Prioritize higher quality if (a.quality !== b.quality) { return b.quality - a.quality; } - // 3. Prioritize better providers + // 2. Prioritize better providers if (a.providerPriority !== b.providerPriority) { return b.providerPriority - a.providerPriority; } @@ -863,7 +775,7 @@ export const StreamsScreen = () => { 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})`); + logger.log(`๐ŸŽฏ Best stream selected: ${allStreams[0].stream.name || allStreams[0].stream.title} (Quality: ${allStreams[0].quality}p, Provider Priority: ${allStreams[0].providerPriority})`); return allStreams[0].stream; }, [filterStreamsByQuality]); @@ -1032,7 +944,7 @@ export const StreamsScreen = () => { } // If stream is actually MKV format, force the in-app VLC-based player on iOS try { - if (Platform.OS === 'ios') { + if (Platform.OS === 'ios' && settings.preferredPlayer === 'internal') { // Check if the actual stream is an MKV file const lowerUri = (stream.url || '').toLowerCase(); const contentType = (stream.headers && ((stream.headers as any)['Content-Type'] || (stream.headers as any)['content-type'])) || ''; @@ -1041,7 +953,7 @@ export const StreamsScreen = () => { const isMkvFile = Boolean(isMkvByHeader || isMkvByPath); if (isMkvFile) { - logger.log(`[StreamsScreen] Stream is MKV format -> forcing VLC on iOS`); + logger.log(`[StreamsScreen] Stream is MKV format -> forcing VLC on iOS (internal preferred)`); navigateToPlayer(stream, { forceVlc: true }); return; } @@ -1051,7 +963,7 @@ export const StreamsScreen = () => { } // iOS: very short MKV detection race; never block longer than MKV_HEAD_TIMEOUT_MS - if (Platform.OS === 'ios') { + if (Platform.OS === 'ios' && settings.preferredPlayer === 'internal') { const lowerUrl = (stream.url || '').toLowerCase(); const isMkvByPath = lowerUrl.includes('.mkv') || /[?&]ext=mkv\b/i.test(lowerUrl) || /format=mkv\b/i.test(lowerUrl) || /container=mkv\b/i.test(lowerUrl); const isHttp = lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://'); @@ -1066,7 +978,7 @@ export const StreamsScreen = () => { ...(stream.headers || {}), 'Content-Type': 'video/x-matroska', } as Record; - logger.log('[StreamsScreen] HEAD detected MKV via Content-Type quickly, forcing in-app VLC on iOS'); + logger.log('[StreamsScreen] HEAD detected MKV via Content-Type quickly, forcing in-app VLC on iOS (internal preferred)'); navigateToPlayer(stream, { forceVlc: true, headers: mergedHeaders }); return; } @@ -1485,7 +1397,7 @@ export const StreamsScreen = () => { }; }); } - }, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, sortStreams, addonResponseOrder]); + }, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder]); const episodeImage = useMemo(() => { if (episodeThumbnail) {