// Intelligent Cache Manager - Auto-manages video segment caching const CACHE_NAME = 'video-cache-v1'; const METADATA_STORE = 'cache-metadata'; const CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days const MAX_CACHE_SIZE_MB = 1000; // 1GB interface CacheMetadata { url: string; videoUrl: string; cachedAt: number; size: number; lastAccessed: number; } interface CacheStats { totalEntries: number; totalSizeMB: number; oldestEntry: number; newestEntry: number; } class CacheManager { private metadata = new Map(); private initialized = false; async initialize(): Promise { if (this.initialized) return; try { const stored = localStorage.getItem(METADATA_STORE); if (stored) this.metadata = new Map(Object.entries(JSON.parse(stored))); } catch (error) { console.error('[CacheManager] Init failed:', error); } this.initialized = true; } private save(): void { try { localStorage.setItem(METADATA_STORE, JSON.stringify(Object.fromEntries(this.metadata))); } catch (e) { console.error('[CacheManager] Save failed:', e); } } async addCacheEntry(url: string, videoUrl: string, size: number): Promise { await this.initialize(); this.metadata.set(url, { url, videoUrl, cachedAt: Date.now(), size, lastAccessed: Date.now() }); this.save(); } async isCacheValid(url: string): Promise { await this.initialize(); const meta = this.metadata.get(url); if (!meta) return false; if (Date.now() - meta.cachedAt > CACHE_TTL) { return false; } meta.lastAccessed = Date.now(); this.save(); return true; } async getCacheStats(): Promise { await this.initialize(); const entries = Array.from(this.metadata.values()); const totalSize = entries.reduce((sum, e) => sum + e.size, 0); const times = entries.map(e => e.cachedAt); return { totalEntries: entries.length, totalSizeMB: totalSize / (1024 * 1024), oldestEntry: times.length ? Math.min(...times) : 0, newestEntry: times.length ? Math.max(...times) : 0 }; } async checkAndCleanup(): Promise { await this.initialize(); const stats = await this.getCacheStats(); if (stats.totalSizeMB > MAX_CACHE_SIZE_MB) { await this.cleanupOldEntries(); } await this.cleanupExpiredEntries(); } async cleanupExpiredEntries(): Promise { await this.initialize(); if (!('caches' in window)) return 0; const cache = await caches.open(CACHE_NAME); const now = Date.now(); let cleaned = 0; for (const [url, meta] of this.metadata.entries()) { if (now - meta.cachedAt > CACHE_TTL) { await cache.delete(url); this.metadata.delete(url); cleaned++; } } if (cleaned > 0) { this.save(); } return cleaned; } async cleanupOldEntries(): Promise { await this.initialize(); if (!('caches' in window)) return 0; const cache = await caches.open(CACHE_NAME); const sorted = Array.from(this.metadata.entries()).sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed); const toRemove = Math.ceil(sorted.length * 0.3); let removed = 0; for (let i = 0; i < toRemove && i < sorted.length; i++) { await cache.delete(sorted[i][0]); this.metadata.delete(sorted[i][0]); removed++; } if (removed > 0) { this.save(); } return removed; } async clearVideoCache(videoUrl: string): Promise { await this.initialize(); if (!('caches' in window)) return 0; const cache = await caches.open(CACHE_NAME); let cleared = 0; for (const [url, meta] of this.metadata.entries()) { if (meta.videoUrl === videoUrl) { await cache.delete(url); this.metadata.delete(url); cleared++; } } if (cleared > 0) { this.save(); } return cleared; } async clearAllCache(): Promise { await this.initialize(); if (!('caches' in window)) return 0; const cache = await caches.open(CACHE_NAME); await Promise.all((await cache.keys()).map(k => cache.delete(k))); const count = this.metadata.size; this.metadata.clear(); this.save(); return count; } } export const cacheManager = new CacheManager(); export const clearSegmentsForUrl = (url: string) => cacheManager.clearVideoCache(url); export const clearAllCache = () => cacheManager.clearAllCache(); if (typeof window !== 'undefined') { cacheManager.initialize().then(() => { setInterval(() => cacheManager.checkAndCleanup(), 5 * 60 * 1000); setTimeout(() => cacheManager.checkAndCleanup(), 10000); }); }