murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
193 lines (192 loc) • 6.63 kB
JavaScript
// 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();