@@ -117,6 +117,28 @@ class AudioProcessor {
117117 processing . reject ( new Error ( data . error ) ) ;
118118 this . processingQueue . delete ( clipId ) ;
119119 break ;
120+
121+ case 'decode-failed' :
122+ // Worker fetched audio but couldn't decode (Firefox/Safari lack
123+ // OfflineAudioContext in workers). Decode on main thread using
124+ // the already-fetched data to avoid a second network request.
125+ debugLog ( 'AudioProcessor' , `🔄 Main-thread decode for ${ clipId } (worker transferred data)` ) ;
126+ processing . onProgress ?. ( 'decoding' , 70 ) ;
127+ {
128+ const audioCtx = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
129+ audioCtx . decodeAudioData ( data . arrayBuffer )
130+ . then ( audioBuffer => {
131+ const peaks = this . generateSimplePeaks ( audioBuffer ) ;
132+ processing . resolve ( { duration : audioBuffer . duration , peaks, method : 'worker-fetch-main-decode' } ) ;
133+ } )
134+ . catch ( err => {
135+ processing . reject ( new Error ( 'Main-thread decode failed: ' + err . message ) ) ;
136+ } )
137+ . finally ( ( ) => {
138+ this . processingQueue . delete ( clipId ) ;
139+ } ) ;
140+ }
141+ break ;
120142 }
121143 }
122144
@@ -451,32 +473,42 @@ class AudioProcessor {
451473 self.postMessage({ type: 'progress', clipId, stage: 'decoding', progress: 60 });
452474 console.log('🔧 Worker: Starting audio decode for', clipId);
453475
454- // Use OfflineAudioContext in worker
455- const decodeStart = performance.now();
456- const sampleRate = WORKER_CONSTANTS.DEFAULT_SAMPLE_RATE;
457- const offlineCtx = new OfflineAudioContext(2, sampleRate, sampleRate);
458- const audioBuffer = await offlineCtx.decodeAudioData(arrayBuffer);
459- const decodeTime = Math.round(performance.now() - decodeStart);
460- console.log('🔧 Worker: Decode completed in', decodeTime + 'ms for', clipId, '(duration:', audioBuffer.duration.toFixed(2) + 's)');
461-
462- self.postMessage({ type: 'progress', clipId, stage: 'generating-peaks', progress: 85 });
463- console.log('🌊 Worker: Generating peaks for', clipId);
464-
465- // Generate peaks
466- const peaksStart = performance.now();
467- const peaks = generatePeaks(audioBuffer, WORKER_CONSTANTS.DEFAULT_SAMPLES_PER_PIXEL);
468- const peaksTime = Math.round(performance.now() - peaksStart);
469- console.log('🌊 Worker: Peaks generated in', peaksTime + 'ms for', clipId, '(' + peaks.length + ' samples)');
470-
471- const totalTime = Math.round(performance.now() - startTime);
472- console.log('✅ Worker: Completed', clipId, 'in', totalTime + 'ms total');
473-
474- self.postMessage({
475- type: 'success',
476- clipId,
477- duration: audioBuffer.duration,
478- peaks
479- });
476+ // OfflineAudioContext is not available in Web Workers on Firefox/Safari.
477+ // If decode fails, transfer the fetched data back so the main thread
478+ // can decode without re-fetching.
479+ try {
480+ const decodeStart = performance.now();
481+ const sampleRate = WORKER_CONSTANTS.DEFAULT_SAMPLE_RATE;
482+ const offlineCtx = new OfflineAudioContext(2, sampleRate, sampleRate);
483+ const audioBuffer = await offlineCtx.decodeAudioData(arrayBuffer);
484+ const decodeTime = Math.round(performance.now() - decodeStart);
485+ console.log('🔧 Worker: Decode completed in', decodeTime + 'ms for', clipId, '(duration:', audioBuffer.duration.toFixed(2) + 's)');
486+
487+ self.postMessage({ type: 'progress', clipId, stage: 'generating-peaks', progress: 85 });
488+ console.log('🌊 Worker: Generating peaks for', clipId);
489+
490+ const peaksStart = performance.now();
491+ const peaks = generatePeaks(audioBuffer, WORKER_CONSTANTS.DEFAULT_SAMPLES_PER_PIXEL);
492+ const peaksTime = Math.round(performance.now() - peaksStart);
493+ console.log('🌊 Worker: Peaks generated in', peaksTime + 'ms for', clipId, '(' + peaks.length + ' samples)');
494+
495+ const totalTime = Math.round(performance.now() - startTime);
496+ console.log('✅ Worker: Completed', clipId, 'in', totalTime + 'ms total');
497+
498+ self.postMessage({
499+ type: 'success',
500+ clipId,
501+ duration: audioBuffer.duration,
502+ peaks
503+ });
504+ } catch (decodeError) {
505+ console.warn('⚠️ Worker: Decode failed for', clipId, '- transferring data to main thread:', decodeError.message);
506+ self.postMessage({
507+ type: 'decode-failed',
508+ clipId,
509+ arrayBuffer: arrayBuffer
510+ }, [arrayBuffer]);
511+ }
480512
481513 } catch (error) {
482514 const totalTime = Math.round(performance.now() - startTime);
0 commit comments