mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
143 lines
5.0 KiB
TypeScript
143 lines
5.0 KiB
TypeScript
// 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);
|
|
});
|
|
} |