This commit is contained in:
tapframe 2025-09-17 00:25:05 +05:30
parent 3c839c5ea1
commit 1ee1a0c3f7
3 changed files with 15 additions and 133 deletions

View file

@ -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<NodeJS.Timeout | null>(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;

View file

@ -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}

View file

@ -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<string, string>;
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) {