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);
    });
}