File size: 5,132 Bytes
3c76719 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | // 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<string, CacheMetadata>();
private initialized = false;
async initialize(): Promise<void> {
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<void> {
await this.initialize();
this.metadata.set(url, { url, videoUrl, cachedAt: Date.now(), size, lastAccessed: Date.now() });
this.save();
}
async isCacheValid(url: string): Promise<boolean> {
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<CacheStats> {
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<void> {
await this.initialize();
const stats = await this.getCacheStats();
if (stats.totalSizeMB > MAX_CACHE_SIZE_MB) {
await this.cleanupOldEntries();
}
await this.cleanupExpiredEntries();
}
async cleanupExpiredEntries(): Promise<number> {
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<number> {
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<number> {
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<number> {
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);
});
} |