mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
seriescontent optimziation
This commit is contained in:
parent
4667357a25
commit
a5d2756854
5 changed files with 208 additions and 59 deletions
BIN
.App.tsx.swp
Normal file
BIN
.App.tsx.swp
Normal file
Binary file not shown.
|
|
@ -274,6 +274,19 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
}, [episodes, metadata?.id])
|
||||
);
|
||||
|
||||
// Memory optimization: Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Clear any pending timeouts
|
||||
if (__DEV__) console.log('[SeriesContent] Component unmounted, cleaning up memory');
|
||||
|
||||
// Force garbage collection if available (development only)
|
||||
if (__DEV__ && global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Add effect to scroll to selected season
|
||||
useEffect(() => {
|
||||
if (selectedSeason && seasonScrollViewRef.current && Object.keys(groupedEpisodes).length > 0) {
|
||||
|
|
|
|||
|
|
@ -136,10 +136,86 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const [activeFetchingScrapers, setActiveFetchingScrapers] = useState<string[]>([]);
|
||||
// Prevent re-initializing season selection repeatedly for the same series
|
||||
const initializedSeasonRef = useRef(false);
|
||||
|
||||
// Memory optimization: Track stream counts and implement cleanup
|
||||
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
|
||||
const { getSeason, saveSeason } = usePersistentSeasons();
|
||||
|
||||
// Memory optimization: Stream cleanup and garbage collection
|
||||
const cleanupStreams = useCallback(() => {
|
||||
if (__DEV__) console.log('[useMetadata] Running stream cleanup to free memory');
|
||||
|
||||
// Clear preloaded streams cache
|
||||
setPreloadedStreams({});
|
||||
setPreloadedEpisodeStreams({});
|
||||
|
||||
// Reset stream count
|
||||
streamCountRef.current = 0;
|
||||
|
||||
// Force garbage collection if available (development only)
|
||||
if (__DEV__ && global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Memory optimization: Debounced stream state updates
|
||||
const debouncedStreamUpdate = useCallback((updateFn: () => void) => {
|
||||
// Clear existing timeout
|
||||
if (cleanupTimeoutRef.current) {
|
||||
clearTimeout(cleanupTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Set new timeout for cleanup
|
||||
cleanupTimeoutRef.current = setTimeout(() => {
|
||||
cleanupStreams();
|
||||
}, 30000); // Cleanup after 30 seconds of inactivity
|
||||
|
||||
// Execute the update
|
||||
updateFn();
|
||||
}, [cleanupStreams]);
|
||||
|
||||
// Memory optimization: Limit and optimize stream data
|
||||
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 => ({
|
||||
...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();
|
||||
const logPrefix = isEpisode ? 'loadEpisodeStreams' : 'loadStreams';
|
||||
|
|
@ -185,27 +261,40 @@ 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) {
|
||||
// Use the streams directly as they are already processed by stremioService
|
||||
const updateState = (prevState: GroupedStreams): GroupedStreams => {
|
||||
if (__DEV__) logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
|
||||
return {
|
||||
...prevState,
|
||||
[addonId]: {
|
||||
addonName: addonName,
|
||||
streams: streams // Use the received streams directly
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (isEpisode) {
|
||||
setEpisodeStreams(updateState);
|
||||
// Turn off loading when we get streams
|
||||
setLoadingEpisodeStreams(false);
|
||||
} else {
|
||||
setGroupedStreams(updateState);
|
||||
// Turn off loading when we get streams
|
||||
setLoadingStreams(false);
|
||||
// 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;
|
||||
|
||||
if (__DEV__) logger.log(`📊 [${logPrefix}:${sourceName}] Optimized ${streams.length} → ${optimizedStreams.length} streams, total: ${streamCountRef.current}`);
|
||||
|
||||
// Use debounced update to prevent rapid state changes
|
||||
debouncedStreamUpdate(() => {
|
||||
const updateState = (prevState: GroupedStreams): GroupedStreams => {
|
||||
if (__DEV__) logger.log(`🔄 [${logPrefix}:${sourceName}] Updating state for addon ${addonName} (${addonId})`);
|
||||
return {
|
||||
...prevState,
|
||||
[addonId]: {
|
||||
addonName: addonName,
|
||||
streams: optimizedStreams // Use optimized streams
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (isEpisode) {
|
||||
setEpisodeStreams(updateState);
|
||||
setLoadingEpisodeStreams(false);
|
||||
} else {
|
||||
setGroupedStreams(updateState);
|
||||
setLoadingStreams(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (__DEV__) logger.log(`🤷 [${logPrefix}:${sourceName}] No streams found for addon ${addonName} (${addonId})`);
|
||||
}
|
||||
|
|
@ -1088,7 +1177,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
useEffect(() => {
|
||||
setLoadAttempts(0);
|
||||
initializedSeasonRef.current = false;
|
||||
}, [id, type]);
|
||||
|
||||
// Memory optimization: Clean up streams when content changes
|
||||
cleanupStreams();
|
||||
|
||||
// Clear any pending cleanup timeouts
|
||||
if (cleanupTimeoutRef.current) {
|
||||
clearTimeout(cleanupTimeoutRef.current);
|
||||
cleanupTimeoutRef.current = null;
|
||||
}
|
||||
}, [id, type, cleanupStreams]);
|
||||
|
||||
// Auto-retry on error with delay
|
||||
useEffect(() => {
|
||||
|
|
@ -1230,6 +1328,21 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
return () => unsubscribe();
|
||||
}, [id]);
|
||||
|
||||
// Memory optimization: Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Clear cleanup timeout
|
||||
if (cleanupTimeoutRef.current) {
|
||||
clearTimeout(cleanupTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Force cleanup
|
||||
cleanupStreams();
|
||||
|
||||
if (__DEV__) console.log('[useMetadata] Component unmounted, memory cleaned up');
|
||||
};
|
||||
}, [cleanupStreams]);
|
||||
|
||||
return {
|
||||
metadata,
|
||||
loading,
|
||||
|
|
|
|||
|
|
@ -322,6 +322,37 @@ const MetadataScreen: React.FC = () => {
|
|||
}
|
||||
}, [metadata]);
|
||||
|
||||
// Memory monitoring and cleanup
|
||||
useEffect(() => {
|
||||
if (__DEV__) {
|
||||
const memoryMonitor = () => {
|
||||
// Check if we have access to memory info
|
||||
if (performance && (performance as any).memory) {
|
||||
const memory = (performance as any).memory;
|
||||
const usedMB = Math.round(memory.usedJSHeapSize / 1048576);
|
||||
const totalMB = Math.round(memory.totalJSHeapSize / 1048576);
|
||||
const limitMB = Math.round(memory.jsHeapSizeLimit / 1048576);
|
||||
|
||||
if (__DEV__) console.log(`[MetadataScreen] Memory usage: ${usedMB}MB / ${totalMB}MB (limit: ${limitMB}MB)`);
|
||||
|
||||
// Trigger cleanup if memory usage is high
|
||||
if (usedMB > limitMB * 0.8) {
|
||||
if (__DEV__) console.warn(`[MetadataScreen] High memory usage detected (${usedMB}MB), triggering cleanup`);
|
||||
// Force garbage collection if available
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Monitor memory every 10 seconds
|
||||
const interval = setInterval(memoryMonitor, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Memoized derived values for performance
|
||||
const isReady = useMemo(() => !loading && metadata && !metadataError, [loading, metadata, metadataError]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1120,67 +1120,59 @@ class StremioService {
|
|||
const isDirectStreamingUrl = this.isDirectStreamingUrl(streamUrl);
|
||||
const isMagnetStream = streamUrl?.startsWith('magnet:');
|
||||
|
||||
// Determine the best title: Prioritize description if it seems detailed,
|
||||
// otherwise fall back to title or name.
|
||||
// Memory optimization: Limit title length to prevent memory bloat
|
||||
let displayTitle = stream.title || stream.name || 'Unnamed Stream';
|
||||
if (stream.description && stream.description.includes('\n') && stream.description.length > (stream.title?.length || 0)) {
|
||||
// If description exists, contains newlines (likely formatted metadata),
|
||||
// and is longer than the title, prefer it.
|
||||
displayTitle = stream.description;
|
||||
// and is longer than the title, prefer it but truncate if too long
|
||||
displayTitle = stream.description.length > 150
|
||||
? stream.description.substring(0, 150) + '...'
|
||||
: stream.description;
|
||||
}
|
||||
|
||||
// Truncate display title if still too long
|
||||
if (displayTitle.length > 100) {
|
||||
displayTitle = displayTitle.substring(0, 100) + '...';
|
||||
}
|
||||
|
||||
// Use the original name field for the primary identifier if available
|
||||
const name = stream.name || stream.title || 'Unnamed Stream';
|
||||
let name = stream.name || stream.title || 'Unnamed Stream';
|
||||
if (name.length > 80) {
|
||||
name = name.substring(0, 80) + '...';
|
||||
}
|
||||
|
||||
// Extract size: Prefer behaviorHints.videoSize, fallback to top-level size
|
||||
const sizeInBytes = stream.behaviorHints?.videoSize || stream.size || undefined;
|
||||
|
||||
// Consolidate behavior hints, prioritizing specific data extraction
|
||||
let behaviorHints: Stream['behaviorHints'] = {
|
||||
...(stream.behaviorHints || {}), // Start with existing hints
|
||||
// Memory optimization: Minimize behaviorHints to essential data only
|
||||
const behaviorHints: Stream['behaviorHints'] = {
|
||||
notWebReady: !isDirectStreamingUrl,
|
||||
isMagnetStream,
|
||||
// Addon Info
|
||||
addonName: addon.name,
|
||||
addonId: addon.id,
|
||||
// Extracted data (provide defaults or undefined)
|
||||
cached: stream.behaviorHints?.cached || undefined, // For RD/AD detection
|
||||
filename: stream.behaviorHints?.filename || undefined, // Filename if available
|
||||
cached: stream.behaviorHints?.cached || undefined,
|
||||
bingeGroup: stream.behaviorHints?.bingeGroup || undefined,
|
||||
// Add size here if extracted
|
||||
size: sizeInBytes,
|
||||
};
|
||||
|
||||
// Specific handling for magnet/torrent streams to extract more details
|
||||
if (isMagnetStream) {
|
||||
behaviorHints = {
|
||||
...behaviorHints,
|
||||
// Only include essential torrent data for magnet streams
|
||||
...(isMagnetStream ? {
|
||||
infoHash: stream.infoHash || streamUrl?.match(/btih:([a-zA-Z0-9]+)/)?.[1],
|
||||
fileIdx: stream.fileIdx,
|
||||
magnetUrl: streamUrl,
|
||||
type: 'torrent',
|
||||
sources: stream.sources || [],
|
||||
seeders: stream.seeders, // Explicitly map seeders if present
|
||||
size: sizeInBytes || stream.seeders, // Use extracted size, fallback for torrents
|
||||
title: stream.title, // Torrent title might be different
|
||||
};
|
||||
}
|
||||
} : {}),
|
||||
};
|
||||
|
||||
// Explicitly construct the final Stream object
|
||||
// Explicitly construct the final Stream object with minimal data
|
||||
const processedStream: Stream = {
|
||||
url: streamUrl,
|
||||
name: name, // Use the original name/title for primary ID
|
||||
title: displayTitle, // Use the potentially more detailed title from description
|
||||
name: name,
|
||||
title: displayTitle,
|
||||
addonName: addon.name,
|
||||
addonId: addon.id,
|
||||
// Map other potential top-level fields if they exist
|
||||
description: stream.description || undefined, // Keep original description too
|
||||
// Memory optimization: Only include essential fields
|
||||
description: stream.description && stream.description.length <= 100
|
||||
? stream.description
|
||||
: undefined, // Skip long descriptions
|
||||
infoHash: stream.infoHash || undefined,
|
||||
fileIdx: stream.fileIdx,
|
||||
size: sizeInBytes, // Assign the extracted size
|
||||
size: sizeInBytes,
|
||||
isFree: stream.isFree,
|
||||
isDebrid: !!(stream.behaviorHints?.cached), // Map debrid status more reliably
|
||||
// Assign the consolidated behaviorHints
|
||||
isDebrid: !!(stream.behaviorHints?.cached),
|
||||
behaviorHints: behaviorHints,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue