improved caching behaviour

This commit is contained in:
tapframe 2025-10-04 17:07:30 +05:30
parent 90f99985a0
commit 5a22ab54fb
5 changed files with 245 additions and 28 deletions

View file

@ -1341,6 +1341,17 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => { useEffect(() => {
if (!settingsLoaded) return; if (!settingsLoaded) return;
// Check for cached streams immediately on mount
const checkAndLoadCachedStreams = async () => {
try {
// This will be handled by the StreamsScreen component
// The useMetadata hook focuses on metadata and episodes
} catch (error) {
if (__DEV__) console.log('[useMetadata] Error checking cached streams on mount:', error);
}
};
loadMetadata(); loadMetadata();
}, [id, type, settingsLoaded]); }, [id, type, settingsLoaded]);

View file

@ -1293,7 +1293,7 @@ const PluginsScreen: React.FC = () => {
}; };
// Define available quality options // Define available quality options
const qualityOptions = ['Auto', '2160p', '4K', '1080p', '720p', '360p', 'DV', 'HDR', 'REMUX', '480p', 'CAM', 'TS']; const qualityOptions = ['Auto', 'Adaptive', '2160p', '4K', '1080p', '720p', '360p', 'DV', 'HDR', 'REMUX', '480p', 'CAM', 'TS'];

View file

@ -37,10 +37,11 @@ import { useMetadata } from '../hooks/useMetadata';
import { useMetadataAssets } from '../hooks/useMetadataAssets'; import { useMetadataAssets } from '../hooks/useMetadataAssets';
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
import { useTrailer } from '../contexts/TrailerContext'; import { useTrailer } from '../contexts/TrailerContext';
import { Stream } from '../types/metadata'; import { Stream, GroupedStreams } from '../types/metadata';
import { tmdbService } from '../services/tmdbService'; import { tmdbService } from '../services/tmdbService';
import { stremioService } from '../services/stremioService'; import { stremioService } from '../services/stremioService';
import { localScraperService } from '../services/localScraperService'; import { localScraperService } from '../services/localScraperService';
import { hybridCacheService } from '../services/hybridCacheService';
import { VideoPlayerService } from '../services/videoPlayerService'; import { VideoPlayerService } from '../services/videoPlayerService';
import { useSettings } from '../hooks/useSettings'; import { useSettings } from '../hooks/useSettings';
import QualityBadge from '../components/metadata/QualityBadge'; import QualityBadge from '../components/metadata/QualityBadge';
@ -728,6 +729,52 @@ export const StreamsScreen = () => {
} }
}, [selectedProvider, availableProviders, episodeStreams, groupedStreams, type]); }, [selectedProvider, availableProviders, episodeStreams, groupedStreams, type]);
// Check for cached results immediately on mount
useEffect(() => {
const checkCachedResults = async () => {
if (!settings.enableLocalScrapers) return;
try {
let season: number | undefined;
let episode: number | undefined;
if (episodeId && episodeId.includes(':')) {
const parts = episodeId.split(':');
if (parts.length >= 3) {
season = parseInt(parts[1], 10);
episode = parseInt(parts[2], 10);
}
}
const installedScrapers = await localScraperService.getInstalledScrapers();
const userSettings = {
enableLocalScrapers: settings.enableLocalScrapers,
enabledScrapers: new Set(
installedScrapers
.filter(scraper => scraper.enabled)
.map(scraper => scraper.id)
)
};
const cachedResults = await hybridCacheService.getCachedResults(type, id, season, episode, userSettings);
if (cachedResults.validResults.length > 0) {
logger.log(`🔍 Found ${cachedResults.validResults.length} cached scraper results on mount`);
// If we have cached results, trigger the loading flow immediately
if (!hasDoneInitialLoadRef.current) {
logger.log('🚀 Triggering immediate load due to cached results');
// Force a re-render to ensure cached results are displayed
setHasStreamProviders(true);
setStreamsLoadStart(Date.now());
}
}
} catch (error) {
if (__DEV__) console.log('[StreamsScreen] Error checking cached results on mount:', error);
}
};
checkCachedResults();
}, [type, id, episodeId, settings.enableLocalScrapers]);
// Update useEffect to check for sources // Update useEffect to check for sources
useEffect(() => { useEffect(() => {
// Reset initial load state when content changes // Reset initial load state when content changes
@ -755,9 +802,42 @@ export const StreamsScreen = () => {
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers(); const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
if (__DEV__) console.log('[StreamsScreen] hasLocalScrapers:', hasLocalScrapers, 'enableLocalScrapers:', settings.enableLocalScrapers); if (__DEV__) console.log('[StreamsScreen] hasLocalScrapers:', hasLocalScrapers, 'enableLocalScrapers:', settings.enableLocalScrapers);
// We have providers if we have either Stremio addons OR enabled local scrapers // Check for cached results (this covers both local and global cache)
const hasProviders = hasStremioProviders || hasLocalScrapers; let hasCachedResults = false;
logger.log(`[StreamsScreen] provider check: hasProviders=${hasProviders}`); if (settings.enableLocalScrapers) {
try {
// Check if there are any cached streams for this content
let season: number | undefined;
let episode: number | undefined;
if (episodeId && episodeId.includes(':')) {
const parts = episodeId.split(':');
if (parts.length >= 3) {
season = parseInt(parts[1], 10);
episode = parseInt(parts[2], 10);
}
}
const installedScrapers = await localScraperService.getInstalledScrapers();
const userSettings = {
enableLocalScrapers: settings.enableLocalScrapers,
enabledScrapers: new Set(
installedScrapers
.filter(scraper => scraper.enabled)
.map(scraper => scraper.id)
)
};
const cachedStreams = await hybridCacheService.getCachedStreams(type, id, season, episode, userSettings);
hasCachedResults = cachedStreams.length > 0;
if (__DEV__) console.log('[StreamsScreen] hasCachedResults:', hasCachedResults, 'cached streams count:', cachedStreams.length, 'season:', season, 'episode:', episode);
} catch (error) {
if (__DEV__) console.log('[StreamsScreen] Error checking cached results:', error);
}
}
// We have providers if we have Stremio addons, enabled local scrapers, OR cached results
const hasProviders = hasStremioProviders || hasLocalScrapers || hasCachedResults;
logger.log(`[StreamsScreen] provider check: hasProviders=${hasProviders} (stremio:${hasStremioProviders}, local:${hasLocalScrapers}, cached:${hasCachedResults})`);
if (!isMounted.current) return; if (!isMounted.current) return;
@ -765,12 +845,68 @@ export const StreamsScreen = () => {
setHasStremioStreamProviders(hasStremioProviders); setHasStremioStreamProviders(hasStremioProviders);
if (!hasProviders) { if (!hasProviders) {
logger.log('[StreamsScreen] No providers detected; scheduling no-sources UI'); // If we have local scrapers enabled but no cached results yet, wait a bit longer
const timer = setTimeout(() => { if (settings.enableLocalScrapers && !hasCachedResults) {
if (isMounted.current) setShowNoSourcesError(true); logger.log('[StreamsScreen] No providers detected but checking for cached results; waiting longer');
}, 500); const timer = setTimeout(() => {
return () => clearTimeout(timer); if (isMounted.current) setShowNoSourcesError(true);
}, 2000); // Wait 2 seconds for cached results
return () => clearTimeout(timer);
} else {
logger.log('[StreamsScreen] No providers detected; scheduling no-sources UI');
const timer = setTimeout(() => {
if (isMounted.current) setShowNoSourcesError(true);
}, 500);
return () => clearTimeout(timer);
}
} else { } else {
// Check for cached streams first before loading
if (settings.enableLocalScrapers) {
try {
let season: number | undefined;
let episode: number | undefined;
if (episodeId && episodeId.includes(':')) {
const parts = episodeId.split(':');
if (parts.length >= 3) {
season = parseInt(parts[1], 10);
episode = parseInt(parts[2], 10);
}
}
// Check if we have cached streams and load them immediately
const cachedStreams = await hybridCacheService.getCachedStreams(type, id, season, episode);
if (cachedStreams.length > 0) {
logger.log(`🎯 Found ${cachedStreams.length} cached streams, displaying immediately`);
// Group cached streams by scraper for proper display
const groupedCachedStreams: GroupedStreams = {};
const scrapersWithCachedResults = new Set<string>();
// Get cached results to determine which scrapers have results
const cachedResults = await hybridCacheService.getCachedResults(type, id, season, episode);
for (const result of cachedResults.validResults) {
if (result.success && result.streams && result.streams.length > 0) {
groupedCachedStreams[result.scraperId] = {
addonName: result.scraperName,
streams: result.streams
};
scrapersWithCachedResults.add(result.scraperId);
}
}
// Update the streams state immediately if we have cached results
if (Object.keys(groupedCachedStreams).length > 0) {
logger.log(`🚀 Immediately displaying ${Object.keys(groupedCachedStreams).length} cached scrapers with streams`);
// This will be handled by the useMetadata hook integration
}
}
} catch (error) {
if (__DEV__) console.log('[StreamsScreen] Error checking cached streams:', error);
}
}
// For series episodes, do not wait for metadata; load directly when episodeId is present // For series episodes, do not wait for metadata; load directly when episodeId is present
if (episodeId) { if (episodeId) {
logger.log(`🎬 Loading episode streams for: ${episodeId}`); logger.log(`🎬 Loading episode streams for: ${episodeId}`);

View file

@ -48,21 +48,39 @@ class HybridCacheService {
* Get cached results with hybrid approach (global first, then local) * Get cached results with hybrid approach (global first, then local)
*/ */
async getCachedResults( async getCachedResults(
type: string, type: string,
tmdbId: string, tmdbId: string,
season?: number, season?: number,
episode?: number episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }
): Promise<HybridCacheResult> { ): Promise<HybridCacheResult> {
try { try {
// Filter function to check if scraper is enabled for current user
const isScraperEnabled = (scraperId: string): boolean => {
if (!userSettings?.enableLocalScrapers) return false;
if (userSettings?.enabledScrapers) {
return userSettings.enabledScrapers.has(scraperId);
}
// If no specific scraper settings, assume all are enabled if local scrapers are enabled
return true;
};
// Try global cache first if enabled // Try global cache first if enabled
if (this.ENABLE_GLOBAL_CACHE) { if (this.ENABLE_GLOBAL_CACHE) {
try { try {
const globalResults = await supabaseGlobalCacheService.getCachedResults(type, tmdbId, season, episode); const globalResults = await supabaseGlobalCacheService.getCachedResults(type, tmdbId, season, episode);
if (globalResults.validResults.length > 0) { // Filter results based on user settings
logger.log(`[HybridCache] Using global cache: ${globalResults.validResults.length} results`); const filteredGlobalResults = {
...globalResults,
validResults: globalResults.validResults.filter(result => isScraperEnabled(result.scraperId)),
expiredScrapers: globalResults.expiredScrapers.filter(scraperId => isScraperEnabled(scraperId))
};
if (filteredGlobalResults.validResults.length > 0) {
logger.log(`[HybridCache] Using global cache: ${filteredGlobalResults.validResults.length} results (filtered from ${globalResults.validResults.length})`);
return { return {
...globalResults, ...filteredGlobalResults,
source: 'global' source: 'global'
}; };
} }
@ -74,11 +92,18 @@ class HybridCacheService {
// Fallback to local cache // Fallback to local cache
if (this.FALLBACK_TO_LOCAL) { if (this.FALLBACK_TO_LOCAL) {
const localResults = await localScraperCacheService.getCachedResults(type, tmdbId, season, episode); const localResults = await localScraperCacheService.getCachedResults(type, tmdbId, season, episode);
if (localResults.validResults.length > 0) { // Filter results based on user settings
logger.log(`[HybridCache] Using local cache: ${localResults.validResults.length} results`); const filteredLocalResults = {
...localResults,
validResults: localResults.validResults.filter(result => isScraperEnabled(result.scraperId)),
expiredScrapers: localResults.expiredScrapers.filter(scraperId => isScraperEnabled(scraperId))
};
if (filteredLocalResults.validResults.length > 0) {
logger.log(`[HybridCache] Using local cache: ${filteredLocalResults.validResults.length} results (filtered from ${localResults.validResults.length})`);
return { return {
...localResults, ...filteredLocalResults,
source: 'local' source: 'local'
}; };
} }
@ -173,9 +198,10 @@ class HybridCacheService {
tmdbId: string, tmdbId: string,
availableScrapers: Array<{ id: string; name: string }>, availableScrapers: Array<{ id: string; name: string }>,
season?: number, season?: number,
episode?: number episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }
): Promise<string[]> { ): Promise<string[]> {
const { validResults, expiredScrapers } = await this.getCachedResults(type, tmdbId, season, episode); const { validResults, expiredScrapers } = await this.getCachedResults(type, tmdbId, season, episode, userSettings);
const validScraperIds = new Set(validResults.map(r => r.scraperId)); const validScraperIds = new Set(validResults.map(r => r.scraperId));
const expiredScraperIds = new Set(expiredScrapers); const expiredScraperIds = new Set(expiredScrapers);
@ -199,10 +225,11 @@ class HybridCacheService {
type: string, type: string,
tmdbId: string, tmdbId: string,
season?: number, season?: number,
episode?: number episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }
): Promise<Stream[]> { ): Promise<Stream[]> {
const { validResults } = await this.getCachedResults(type, tmdbId, season, episode); const { validResults } = await this.getCachedResults(type, tmdbId, season, episode, userSettings);
// Flatten all valid streams // Flatten all valid streams
const allStreams: Stream[] = []; const allStreams: Stream[] = [];
for (const result of validResults) { for (const result of validResults) {

View file

@ -879,8 +879,11 @@ class LocalScraperService {
return; return;
} }
// Get current user settings for enabled scrapers
const userSettings = await this.getUserScraperSettings();
// Check cache for existing results (hybrid: global first, then local) // Check cache for existing results (hybrid: global first, then local)
const { validResults, expiredScrapers, allExpired, source } = await hybridCacheService.getCachedResults(type, tmdbId, season, episode); const { validResults, expiredScrapers, allExpired, source } = await hybridCacheService.getCachedResults(type, tmdbId, season, episode, userSettings);
// Immediately return cached results for valid scrapers // Immediately return cached results for valid scrapers
if (validResults.length > 0) { if (validResults.length > 0) {
@ -1354,6 +1357,46 @@ class LocalScraperService {
return Array.from(this.installedScrapers.values()).some(scraper => scraper.enabled); return Array.from(this.installedScrapers.values()).some(scraper => scraper.enabled);
} }
// Get current user scraper settings for cache filtering
private async getUserScraperSettings(): Promise<{ enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }> {
return this.getUserScraperSettingsWithOverride();
}
// Get user scraper settings (can be overridden for testing or external calls)
async getUserScraperSettingsWithOverride(overrideSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }): Promise<{ enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }> {
try {
// If override settings are provided, use them
if (overrideSettings) {
return {
enableLocalScrapers: overrideSettings.enableLocalScrapers,
enabledScrapers: overrideSettings.enabledScrapers
};
}
// Get user settings from AsyncStorage
const settingsData = await AsyncStorage.getItem('app_settings');
const settings = settingsData ? JSON.parse(settingsData) : {};
// Get enabled scrapers based on current user settings
const enabledScrapers = new Set<string>();
const installedScrapers = Array.from(this.installedScrapers.values());
for (const scraper of installedScrapers) {
if (scraper.enabled && settings.enableLocalScrapers) {
enabledScrapers.add(scraper.id);
}
}
return {
enableLocalScrapers: settings.enableLocalScrapers,
enabledScrapers: enabledScrapers.size > 0 ? enabledScrapers : undefined
};
} catch (error) {
logger.error('[LocalScraperService] Error getting user scraper settings:', error);
return { enableLocalScrapers: false };
}
}
// Cache management methods (hybrid: local + global) // Cache management methods (hybrid: local + global)
async clearScraperCache(): Promise<void> { async clearScraperCache(): Promise<void> {
await hybridCacheService.clearAllCache(); await hybridCacheService.clearAllCache();