From 3405325cbbfb39e9f998535558a08270dd8bac95 Mon Sep 17 00:00:00 2001 From: Mistral Vibe Date: Wed, 8 Apr 2026 21:47:29 +0200 Subject: [PATCH] chore: remove accidental backup file --- web/src/services/audioService.ts.backup2 | 415 ----------------------- 1 file changed, 415 deletions(-) delete mode 100755 web/src/services/audioService.ts.backup2 diff --git a/web/src/services/audioService.ts.backup2 b/web/src/services/audioService.ts.backup2 deleted file mode 100755 index 2595f0c..0000000 --- a/web/src/services/audioService.ts.backup2 +++ /dev/null @@ -1,415 +0,0 @@ -import WaveSurfer from "wavesurfer.js"; -import { usePlayerStore } from "../stores/playerStore"; - -// Log level enum -enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3 -} - -// Type extension for WaveSurfer backend access -interface WaveSurferWithBackend extends WaveSurfer { - backend?: { - getAudioContext?: () => AudioContext; - ac?: AudioContext; - audioContext?: AudioContext; - }; - getAudioContext?: () => AudioContext; - getContainer?: () => HTMLElement; - setContainer?: (container: HTMLElement) => void; -} - -class AudioService { - private static instance: AudioService; - private wavesurfer: WaveSurfer | null = null; - private audioContext: AudioContext | null = null; - private currentUrl: string | null = null; - private lastPlayTime: number = 0; - private lastTimeUpdate: number = 0; - private readonly TIME_UPDATE_THROTTLE: number = 100; -private readonly PLAY_DEBOUNCE_MS: number = 100; - private lastSeekTime: number = 0; - private readonly SEEK_DEBOUNCE_MS: number = 200; - private logLevel: LogLevel = LogLevel.INFO; - private playbackAttempts: number = 0; - private readonly MAX_PLAYBACK_ATTEMPTS: number = 3; - - private constructor() { - this.log(LogLevel.INFO, 'AudioService initialized'); - } - - private log(level: LogLevel, message: string, ...args: unknown[]) { - if (level < this.logLevel) return; - - const prefix = `[AudioService:${LogLevel[level]}]`; - - switch(level) { - case LogLevel.DEBUG: - if (console.debug) { - console.debug(prefix, message, ...args); - } - break; - case LogLevel.INFO: - console.info(prefix, message, ...args); - break; - case LogLevel.WARN: - console.warn(prefix, message, ...args); - break; - case LogLevel.ERROR: - console.error(prefix, message, ...args); - break; - } - } - - // Add method to set log level from outside - public setLogLevel(level: LogLevel) { - this.log(LogLevel.INFO, `Log level set to: ${LogLevel[level]}`); - this.logLevel = level; - } - - public static getInstance() { - if (!this.instance) { - this.instance = new AudioService(); - } - return this.instance; - } - - public async initialize(container: HTMLElement, url: string) { - this.log(LogLevel.DEBUG, 'AudioService.initialize called', { url, containerExists: !!container }); - - // Validate inputs - if (!container) { - this.log(LogLevel.ERROR, 'AudioService: container element is null'); - throw new Error('Container element is required'); - } - - if (!url || url === 'null' || url === 'undefined') { - this.log(LogLevel.ERROR, 'AudioService: invalid URL', { url }); - throw new Error('Valid audio URL is required'); - } - - // If same URL and we already have an instance, just update container reference - if (this.currentUrl === url && this.wavesurfer) { - this.log(LogLevel.INFO, 'Reusing existing WaveSurfer instance for URL:', url); - try { - // Check if container is different and needs updating - const ws = this.wavesurfer as WaveSurferWithBackend; - const currentContainer = ws.getContainer?.(); - if (currentContainer !== container) { - this.log(LogLevel.DEBUG, 'Updating container reference for existing instance'); - // Update container reference without recreating instance - ws.setContainer?.(container); - } else { - this.log(LogLevel.DEBUG, 'Using existing instance - no changes needed'); - } - return this.wavesurfer; - } catch (error) { - this.log(LogLevel.ERROR, 'Failed to reuse existing instance:', error); - this.cleanup(); - } - } - - // Clean up existing instance if different URL - if (this.wavesurfer && this.currentUrl !== url) { - this.log(LogLevel.INFO, 'Cleaning up existing instance for new URL:', url); - this.cleanup(); - } - - // Create new WaveSurfer instance - this.log(LogLevel.DEBUG, 'Creating new WaveSurfer instance for URL:', url); - let ws; - try { - ws = WaveSurfer.create({ - container: container, - waveColor: "rgba(255,255,255,0.09)", - progressColor: "#c8861a", - cursorColor: "#e8a22a", - barWidth: 2, - barRadius: 2, - height: 104, - normalize: true, - // Ensure we can control playback manually - autoplay: false, - }); - - if (!ws) { - throw new Error('WaveSurfer.create returned null or undefined'); - } - - // @ts-expect-error - WaveSurfer typing doesn't expose backend - if (!ws.backend) { - console.warn('WaveSurfer instance has no backend property yet - this might be normal in v7+'); - // Don't throw error - we'll try to access backend later when needed - } - } catch (error) { - console.error('Failed to create WaveSurfer instance:', error); - throw error; - } - - // Store references - this.wavesurfer = ws; - this.currentUrl = url; - - // Get audio context from wavesurfer - // Note: In WaveSurfer v7+, backend might not be available immediately - // We'll try to access it now, but also set up a handler to get it when ready - this.setupAudioContext(ws); - - // Set up event handlers before loading - this.setupEventHandlers(); - - // Load the audio with error handling - this.log(LogLevel.DEBUG, 'Loading audio URL:', url); - try { - const loadPromise = new Promise((resolve, reject) => { - ws.on('ready', () => { - this.log(LogLevel.DEBUG, 'WaveSurfer ready event fired'); - // Now that WaveSurfer is ready, set up audio context and finalize initialization - this.setupAudioContext(ws); - - // Update player store with duration - const playerStore = usePlayerStore.getState(); - playerStore.setDuration(ws.getDuration()); - - resolve(); - }); - - ws.on('error', (error) => { - this.log(LogLevel.ERROR, 'WaveSurfer error event:', error); - reject(error); - }); - - // Start loading - ws.load(url); - }); - - await loadPromise; - this.log(LogLevel.INFO, 'Audio loaded successfully'); - - } catch (error) { - this.log(LogLevel.ERROR, 'Failed to load audio:', error); - this.cleanup(); - throw error; - } - - return ws; - } - - private setupEventHandlers() { - if (!this.wavesurfer) return; - - const ws = this.wavesurfer; - const playerStore = usePlayerStore.getState(); - - ws.on("play", () => { - this.log(LogLevel.DEBUG, 'AudioService: play event'); - playerStore.setPlaying(true); - }); - - ws.on("pause", () => { - this.log(LogLevel.DEBUG, 'AudioService: pause event'); - playerStore.setPlaying(false); - }); - - ws.on("finish", () => { - this.log(LogLevel.DEBUG, 'AudioService: finish event'); - playerStore.setPlaying(false); - }); - - ws.on("audioprocess", (time) => { - const now = Date.now(); - if (now - this.lastTimeUpdate >= this.TIME_UPDATE_THROTTLE) { - playerStore.setCurrentTime(time); - this.lastTimeUpdate = now; - } - }); - - // Note: Ready event is handled in the load promise, so we don't set it up here - // to avoid duplicate event handlers - } - - public async play(): Promise { - if (!this.wavesurfer) { - this.log(LogLevel.WARN, 'AudioService: no wavesurfer instance'); - return; - } - - // Debounce rapid play calls - const now = Date.now(); - if (now - this.lastPlayTime < this.PLAY_DEBOUNCE_MS) { - this.log(LogLevel.DEBUG, 'Playback debounced - too frequent calls'); - return; - } - this.lastPlayTime = now; - - this.log(LogLevel.INFO, 'AudioService.play called'); - - try { - // Ensure we have a valid audio context - await this.ensureAudioContext(); - - await this.wavesurfer.play(); - this.log(LogLevel.INFO, 'Playback started successfully'); - this.playbackAttempts = 0; // Reset on success - } catch (error) { - this.playbackAttempts++; - this.log(LogLevel.ERROR, `Playback failed (attempt ${this.playbackAttempts}):`, error); - - if (this.playbackAttempts >= this.MAX_PLAYBACK_ATTEMPTS) { - this.log(LogLevel.ERROR, 'Max playback attempts reached, resetting player'); - this.cleanup(); - // Could trigger re-initialization here if needed - } else { - // Exponential backoff for retry - const delay = 100 * this.playbackAttempts; - this.log(LogLevel.WARN, `Retrying playback in ${delay}ms...`); - await new Promise(resolve => setTimeout(resolve, delay)); - return this.play(); // Retry - } - } - } - - public pause() { - if (!this.wavesurfer) { - this.log(LogLevel.WARN, 'AudioService: no wavesurfer instance'); - return; - } - - this.log(LogLevel.INFO, 'AudioService.pause called'); - this.wavesurfer.pause(); - } - - public seekTo(time: number) { - if (!this.wavesurfer) { - this.log(LogLevel.WARN, 'AudioService: no wavesurfer instance'); - return; - } - - this.log(LogLevel.INFO, 'AudioService.seekTo called', { time }); - this.wavesurfer.setTime(time); - } - - public getCurrentTime(): number { - if (!this.wavesurfer) return 0; - return this.wavesurfer.getCurrentTime(); - } - - public getDuration(): number { - if (!this.wavesurfer) return 0; - return this.wavesurfer.getDuration(); - } - - public isPlaying(): boolean { - if (!this.wavesurfer) return false; - return this.wavesurfer.isPlaying(); - } - - public cleanup() { - this.log(LogLevel.INFO, 'AudioService.cleanup called'); - - if (this.wavesurfer) { - try { - // Disconnect audio nodes but keep audio context alive - this.wavesurfer.unAll(); - this.wavesurfer.destroy(); - this.log(LogLevel.DEBUG, 'WaveSurfer instance cleaned up'); - } catch (error) { - this.log(LogLevel.ERROR, 'Error cleaning up WaveSurfer:', error); - } - this.wavesurfer = null; - } - - this.currentUrl = null; - // Note: We intentionally don't nullify audioContext to keep it alive - } - - private async ensureAudioContext(): Promise { - // If we already have a valid audio context, return it - if (this.audioContext) { - // Resume if suspended (common in mobile browsers) - if (this.audioContext.state === 'suspended') { - try { - await this.audioContext.resume(); - console.log('Audio context resumed successfully'); - } catch (error) { - console.error('Failed to resume audio context:', error); - } - } - return this.audioContext; - } - - // Create new audio context - try { - this.audioContext = new (window.AudioContext || (window as { webkitAudioContext?: new () => AudioContext }).webkitAudioContext)(); - console.log('Audio context created:', this.audioContext.state); - - // Handle context state changes - this.audioContext.onstatechange = () => { - console.log('Audio context state changed:', this.audioContext?.state); - }; - - return this.audioContext; - } catch (error) { - console.error('Failed to create audio context:', error); - throw error; - } - } - - private setupAudioContext(ws: WaveSurferWithBackend) { - // Try multiple methods to get audio context from WaveSurfer v7+ - try { - // Method 1: Try standard backend.getAudioContext() - this.audioContext = ws.backend?.getAudioContext?.() ?? null; - - // Method 2: Try accessing audio context directly from backend - if (!this.audioContext) { - this.audioContext = ws.backend?.ac ?? null; - } - - // Method 3: Try accessing through backend.getAudioContext() without optional chaining - if (!this.audioContext) { - this.audioContext = ws.backend?.getAudioContext?.() ?? null; - } - - // Method 4: Try accessing through wavesurfer.getAudioContext() if it exists - if (!this.audioContext && typeof ws.getAudioContext === 'function') { - this.audioContext = ws.getAudioContext() ?? null; - } - - // Method 5: Try accessing through backend.ac directly - if (!this.audioContext) { - this.audioContext = ws.backend?.ac ?? null; - } - - // Method 6: Try accessing through backend.audioContext - if (!this.audioContext) { - this.audioContext = ws.backend?.audioContext ?? null; - } - - if (this.audioContext) { - console.log('Audio context accessed successfully:', this.audioContext.state); - } else { - console.warn('Could not access audio context from WaveSurfer - playback may have issues'); - // Log the wavesurfer structure for debugging - console.debug('WaveSurfer structure:', { - hasBackend: !!ws.backend, - backendType: typeof ws.backend, - backendKeys: ws.backend ? Object.keys(ws.backend) : 'no backend', - wavesurferKeys: Object.keys(ws) - }); - } - } catch (error) { - console.error('Error accessing audio context:', error); - } - } - - - - public getAudioContextState(): string | undefined { - return this.audioContext?.state; - } -} - -export const audioService = AudioService.getInstance();