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(() => {
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();
}, [id, type, settingsLoaded]);

View file

@ -1293,7 +1293,7 @@ const PluginsScreen: React.FC = () => {
};
// 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 { useTheme } from '../contexts/ThemeContext';
import { useTrailer } from '../contexts/TrailerContext';
import { Stream } from '../types/metadata';
import { Stream, GroupedStreams } from '../types/metadata';
import { tmdbService } from '../services/tmdbService';
import { stremioService } from '../services/stremioService';
import { localScraperService } from '../services/localScraperService';
import { hybridCacheService } from '../services/hybridCacheService';
import { VideoPlayerService } from '../services/videoPlayerService';
import { useSettings } from '../hooks/useSettings';
import QualityBadge from '../components/metadata/QualityBadge';
@ -728,6 +729,52 @@ export const StreamsScreen = () => {
}
}, [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
useEffect(() => {
// Reset initial load state when content changes
@ -755,9 +802,42 @@ export const StreamsScreen = () => {
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
if (__DEV__) console.log('[StreamsScreen] hasLocalScrapers:', hasLocalScrapers, 'enableLocalScrapers:', settings.enableLocalScrapers);
// We have providers if we have either Stremio addons OR enabled local scrapers
const hasProviders = hasStremioProviders || hasLocalScrapers;
logger.log(`[StreamsScreen] provider check: hasProviders=${hasProviders}`);
// Check for cached results (this covers both local and global cache)
let hasCachedResults = false;
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;
@ -765,12 +845,68 @@ export const StreamsScreen = () => {
setHasStremioStreamProviders(hasStremioProviders);
if (!hasProviders) {
logger.log('[StreamsScreen] No providers detected; scheduling no-sources UI');
const timer = setTimeout(() => {
if (isMounted.current) setShowNoSourcesError(true);
}, 500);
return () => clearTimeout(timer);
// If we have local scrapers enabled but no cached results yet, wait a bit longer
if (settings.enableLocalScrapers && !hasCachedResults) {
logger.log('[StreamsScreen] No providers detected but checking for cached results; waiting longer');
const timer = setTimeout(() => {
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 {
// 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
if (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)
*/
async getCachedResults(
type: string,
tmdbId: string,
season?: number,
episode?: number
type: string,
tmdbId: string,
season?: number,
episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }
): Promise<HybridCacheResult> {
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
if (this.ENABLE_GLOBAL_CACHE) {
try {
const globalResults = await supabaseGlobalCacheService.getCachedResults(type, tmdbId, season, episode);
if (globalResults.validResults.length > 0) {
logger.log(`[HybridCache] Using global cache: ${globalResults.validResults.length} results`);
// Filter results based on user settings
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 {
...globalResults,
...filteredGlobalResults,
source: 'global'
};
}
@ -74,11 +92,18 @@ class HybridCacheService {
// Fallback to local cache
if (this.FALLBACK_TO_LOCAL) {
const localResults = await localScraperCacheService.getCachedResults(type, tmdbId, season, episode);
if (localResults.validResults.length > 0) {
logger.log(`[HybridCache] Using local cache: ${localResults.validResults.length} results`);
// Filter results based on user settings
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 {
...localResults,
...filteredLocalResults,
source: 'local'
};
}
@ -173,9 +198,10 @@ class HybridCacheService {
tmdbId: string,
availableScrapers: Array<{ id: string; name: string }>,
season?: number,
episode?: number
episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<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 expiredScraperIds = new Set(expiredScrapers);
@ -199,10 +225,11 @@ class HybridCacheService {
type: string,
tmdbId: string,
season?: number,
episode?: number
episode?: number,
userSettings?: { enableLocalScrapers?: boolean; enabledScrapers?: Set<string> }
): 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
const allStreams: Stream[] = [];
for (const result of validResults) {

View file

@ -879,8 +879,11 @@ class LocalScraperService {
return;
}
// Get current user settings for enabled scrapers
const userSettings = await this.getUserScraperSettings();
// 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
if (validResults.length > 0) {
@ -1354,6 +1357,46 @@ class LocalScraperService {
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)
async clearScraperCache(): Promise<void> {
await hybridCacheService.clearAllCache();