UNPKG

murmuraba

Version:

Real-time audio noise reduction with advanced chunked processing for web applications

193 lines (192 loc) 6.63 kB
// Resource Preloader - Intelligent preloading with priority queue // 2025 best practices: Intersection Observer, requestIdleCallback, Network Information API export class ResourcePreloader { constructor() { this.preloadQueue = []; this.loadedResources = new Set(); this.isIdle = false; this.networkSpeed = 'medium'; this.detectNetworkSpeed(); this.setupIdleDetection(); } static getInstance() { if (!ResourcePreloader.instance) { ResourcePreloader.instance = new ResourcePreloader(); } return ResourcePreloader.instance; } // Detect network speed using Network Information API detectNetworkSpeed() { if ('connection' in navigator) { const connection = navigator.connection; const effectiveType = connection.effectiveType; if (effectiveType === 'slow-2g' || effectiveType === '2g') { this.networkSpeed = 'slow'; } else if (effectiveType === '3g') { this.networkSpeed = 'medium'; } else { this.networkSpeed = 'fast'; } // Listen for network changes connection.addEventListener('change', () => { this.detectNetworkSpeed(); this.adjustPreloadStrategy(); }); } } // Setup idle detection setupIdleDetection() { if ('requestIdleCallback' in window) { const checkIdle = () => { window.requestIdleCallback(() => { this.isIdle = true; this.processIdleQueue(); }, { timeout: 2000 }); }; // Check periodically setInterval(checkIdle, 5000); checkIdle(); } } // Add resource to preload queue preload(resource) { const resources = Array.isArray(resource) ? resource : [resource]; resources.forEach(res => { if (!this.loadedResources.has(res.url)) { this.preloadQueue.push(res); } }); // Sort by priority this.preloadQueue.sort((a, b) => { const priorityMap = { high: 0, medium: 1, low: 2 }; return priorityMap[a.priority] - priorityMap[b.priority]; }); // Process high priority immediately this.processHighPriority(); } // Process high priority resources immediately processHighPriority() { const highPriority = this.preloadQueue.filter(r => r.priority === 'high'); highPriority.forEach(resource => { this.loadResource(resource); this.removeFromQueue(resource); }); } // Process queue during idle time processIdleQueue() { if (!this.isIdle || this.preloadQueue.length === 0) return; // Load resources based on network speed const loadCount = this.networkSpeed === 'fast' ? 3 : this.networkSpeed === 'medium' ? 2 : 1; const toLoad = this.preloadQueue.slice(0, loadCount); toLoad.forEach(resource => { this.loadResource(resource); this.removeFromQueue(resource); }); // Continue if more resources if (this.preloadQueue.length > 0 && 'requestIdleCallback' in window) { window.requestIdleCallback(() => { this.processIdleQueue(); }); } } // Load individual resource loadResource(resource) { if (this.loadedResources.has(resource.url)) return; const link = document.createElement('link'); link.rel = 'preload'; link.href = resource.url; link.as = resource.type; if (resource.crossOrigin) { link.crossOrigin = 'anonymous'; } // Add specific attributes based on type if (resource.type === 'font') { link.type = 'font/woff2'; } link.onload = () => { this.loadedResources.add(resource.url); console.log(`[Preloader] Loaded: ${resource.url}`); }; link.onerror = () => { console.error(`[Preloader] Failed to load: ${resource.url}`); // Retry with lower priority if (resource.priority !== 'low') { this.preload({ ...resource, priority: 'low' }); } }; document.head.appendChild(link); } // Remove from queue removeFromQueue(resource) { this.preloadQueue = this.preloadQueue.filter(r => r.url !== resource.url); } // Adjust strategy based on network adjustPreloadStrategy() { if (this.networkSpeed === 'slow') { // Cancel low priority preloads this.preloadQueue = this.preloadQueue.filter(r => r.priority !== 'low'); } } // Preload critical resources for Murmuraba preloadCriticalResources() { this.preload([ { url: '/wasm/rnnoise.wasm', type: 'fetch', priority: 'high', crossOrigin: true } // Worker file doesn't exist yet // { // url: '/src/workers/rnnoise.worker.js', // type: 'script', // priority: 'high' // } ]); } // Preload based on user interaction patterns preloadForRoute(route) { switch (route) { case 'recording': this.preload([ { url: '/chunks/audio-processor.js', type: 'script', priority: 'medium' }, { url: '/chunks/waveform-visualizer.js', type: 'script', priority: 'low' } ]); break; case 'settings': this.preload({ url: '/chunks/settings-panel.js', type: 'script', priority: 'medium' }); break; } } // Get preload status getStatus() { return { queueLength: this.preloadQueue.length, loadedCount: this.loadedResources.size, networkSpeed: this.networkSpeed, isIdle: this.isIdle }; } } // Auto-initialize and export singleton export const preloader = ResourcePreloader.getInstance();