/** * Audio cleanup utilities to prevent memory leaks */ /** * Safely disconnect and cleanup an AudioNode */ export function cleanupAudioNode(node: AudioNode | null | undefined): void { if (!node) return; try { node.disconnect(); } catch (error) { // Node may already be disconnected, ignore error console.debug('AudioNode cleanup error (expected):', error); } } /** * Cleanup multiple audio nodes */ export function cleanupAudioNodes(nodes: Array): void { nodes.forEach(cleanupAudioNode); } /** * Safely stop and cleanup an AudioBufferSourceNode */ export function cleanupAudioSource(source: AudioBufferSourceNode | null | undefined): void { if (!source) return; try { source.stop(); } catch (error) { // Source may already be stopped, ignore error console.debug('AudioSource stop error (expected):', error); } cleanupAudioNode(source); } /** * Cleanup canvas and release resources */ export function cleanupCanvas(canvas: HTMLCanvasElement | null | undefined): void { if (!canvas) return; const ctx = canvas.getContext('2d'); if (ctx) { // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Reset transform ctx.setTransform(1, 0, 0, 1, 0, 0); } // Release context (helps with memory) canvas.width = 0; canvas.height = 0; } /** * Cancel animation frame safely */ export function cleanupAnimationFrame(frameId: number | null | undefined): void { if (frameId !== null && frameId !== undefined) { cancelAnimationFrame(frameId); } } /** * Cleanup media stream tracks */ export function cleanupMediaStream(stream: MediaStream | null | undefined): void { if (!stream) return; stream.getTracks().forEach(track => { track.stop(); }); } /** * Create a cleanup registry for managing multiple cleanup tasks */ export class CleanupRegistry { private cleanupTasks: Array<() => void> = []; /** * Register a cleanup task */ register(cleanup: () => void): void { this.cleanupTasks.push(cleanup); } /** * Register an audio node for cleanup */ registerAudioNode(node: AudioNode): void { this.register(() => cleanupAudioNode(node)); } /** * Register an audio source for cleanup */ registerAudioSource(source: AudioBufferSourceNode): void { this.register(() => cleanupAudioSource(source)); } /** * Register a canvas for cleanup */ registerCanvas(canvas: HTMLCanvasElement): void { this.register(() => cleanupCanvas(canvas)); } /** * Register an animation frame for cleanup */ registerAnimationFrame(frameId: number): void { this.register(() => cleanupAnimationFrame(frameId)); } /** * Register a media stream for cleanup */ registerMediaStream(stream: MediaStream): void { this.register(() => cleanupMediaStream(stream)); } /** * Execute all cleanup tasks and clear the registry */ cleanup(): void { this.cleanupTasks.forEach(task => { try { task(); } catch (error) { console.error('Cleanup task failed:', error); } }); this.cleanupTasks = []; } /** * Get the number of registered cleanup tasks */ get size(): number { return this.cleanupTasks.length; } }