@@ -281,18 +281,40 @@ export class AudioRecorder {
281281export class AudioPlayer {
282282 private isPlaying : boolean = false ;
283283 private isSetup : boolean = false ;
284+ private isSettingUp : boolean = false ;
285+ private setupPromise : Promise < void > | null = null ;
284286 private tempFiles : string [ ] = [ ] ;
285287 private chunkCounter : number = 0 ;
288+ private pendingChunks : number = 0 ; // Incrementato SINCRONAMENTE prima dell'async
286289 private onCompleteCallback : ( ( ) => void ) | null = null ;
287290 private queueEndedListener : any = null ;
288291 private allChunksReceived : boolean = false ;
289292
290293 /**
291- * Inizializza TrackPlayer se non ancora configurato
294+ * Inizializza TrackPlayer se non ancora configurato.
295+ * Usa un pattern singleton per evitare setup multipli concorrenti.
292296 */
293297 async setup ( ) : Promise < void > {
294298 if ( this . isSetup ) return ;
295299
300+ // Se è già in corso un setup, attendi quello
301+ if ( this . isSettingUp && this . setupPromise ) {
302+ await this . setupPromise ;
303+ return ;
304+ }
305+
306+ this . isSettingUp = true ;
307+ this . setupPromise = this . _doSetup ( ) ;
308+
309+ try {
310+ await this . setupPromise ;
311+ } finally {
312+ this . isSettingUp = false ;
313+ this . setupPromise = null ;
314+ }
315+ }
316+
317+ private async _doSetup ( ) : Promise < void > {
296318 try {
297319 await TrackPlayer . setupPlayer ( {
298320 autoHandleInterruptions : true ,
@@ -311,17 +333,38 @@ export class AudioPlayer {
311333 }
312334 }
313335
336+ /**
337+ * Pre-inizializza TrackPlayer anticipatamente (da chiamare all'avvio sessione).
338+ * Evita il ritardo del primo addChunk().
339+ */
340+ async preSetup ( ) : Promise < void > {
341+ try {
342+ await this . setup ( ) ;
343+ } catch ( error ) {
344+ console . error ( 'TrackPlayer: Errore pre-setup:' , error ) ;
345+ }
346+ }
347+
314348 /**
315349 * Aggiunge un chunk PCM16 base64 direttamente alla queue di TrackPlayer.
316350 * Il chunk viene wrappato in WAV, scritto su file e aggiunto alla queue.
317351 * Se è il primo chunk, avvia la riproduzione immediatamente.
318352 */
319353 async addChunk ( base64Data : string , chunkIndex ?: number ) : Promise < boolean > {
354+ // Incrementa il contatore SINCRONAMENTE prima di qualsiasi operazione async.
355+ // Questo garantisce che getChunksCount() e hasPendingChunks() riflettano
356+ // immediatamente la presenza di chunk in arrivo, evitando race conditions
357+ // con agent_end/audio_end che controllano se c'è audio da riprodurre.
358+ this . pendingChunks ++ ;
359+
320360 try {
321361 await this . setup ( ) ;
322362
323363 const binary = decodeBase64 ( base64Data ) ;
324- if ( binary . length === 0 ) return false ;
364+ if ( binary . length === 0 ) {
365+ this . pendingChunks -- ;
366+ return false ;
367+ }
325368
326369 // Wrappa in WAV
327370 const wavData = wrapPcm16InWav ( binary , AUDIO_CONFIG . SAMPLE_RATE ) ;
@@ -343,6 +386,7 @@ export class AudioPlayer {
343386 } ) ;
344387
345388 this . chunkCounter ++ ;
389+ this . pendingChunks -- ;
346390
347391 // Avvia la riproduzione al primo chunk
348392 if ( ! this . isPlaying ) {
@@ -351,34 +395,88 @@ export class AudioPlayer {
351395 console . log ( 'TrackPlayer: Riproduzione streaming avviata' ) ;
352396 }
353397
398+ // Se tutti i chunk sono stati segnalati (audio_end ricevuto) e non ci sono
399+ // altri chunk in attesa, ora possiamo configurare il listener di completamento.
400+ // Questo gestisce il caso in cui audio_end arriva mentre i chunk sono ancora
401+ // in fase di scrittura su file.
402+ if ( this . allChunksReceived && this . pendingChunks === 0 && ! this . queueEndedListener ) {
403+ console . log ( `TrackPlayer: Ultimo chunk pending processato, configuro listener completamento (${ this . chunkCounter } chunk totali)` ) ;
404+ this . setupQueueEndedListener ( ) ;
405+ }
406+
354407 return true ;
355408 } catch ( error ) {
409+ this . pendingChunks -- ;
410+
411+ // Anche in caso di errore, se era l'ultimo pending e allChunksReceived,
412+ // dobbiamo configurare il completamento
413+ if ( this . allChunksReceived && this . pendingChunks === 0 && ! this . queueEndedListener ) {
414+ this . setupQueueEndedListener ( ) ;
415+ }
416+
356417 console . error ( 'TrackPlayer: Errore aggiunta chunk:' , error ) ;
357418 return false ;
358419 }
359420 }
360421
361422 /**
362423 * Segnala che tutti i chunk sono stati ricevuti (audio_end).
363- * Registra il listener per la fine della queue per invocare onComplete.
424+ * Se ci sono chunk ancora in fase di processamento (pendingChunks > 0),
425+ * il listener di completamento verrà configurato da addChunk() quando
426+ * l'ultimo chunk pending viene processato.
364427 */
365428 async signalAllChunksReceived ( onComplete ?: ( ) => void ) : Promise < void > {
366429 this . allChunksReceived = true ;
367430 this . onCompleteCallback = onComplete || null ;
368431
369- // Se non sta riproducendo (nessun chunk ricevuto), completa subito
370- if ( ! this . isPlaying ) {
432+ // Se ci sono chunk ancora in fase di processamento async (scrittura file WAV),
433+ // NON configurare il listener ora. addChunk() lo farà dopo aver processato
434+ // l'ultimo chunk pending.
435+ if ( this . pendingChunks > 0 ) {
436+ console . log ( `TrackPlayer: ${ this . pendingChunks } chunk ancora in elaborazione, listener differito` ) ;
437+ return ;
438+ }
439+
440+ // Nessun chunk pending. Se non sta nemmeno riproducendo (nessun chunk mai ricevuto),
441+ // completa subito.
442+ if ( ! this . isPlaying && this . chunkCounter === 0 ) {
443+ console . log ( 'TrackPlayer: Nessun chunk ricevuto, completamento immediato' ) ;
371444 this . handlePlaybackComplete ( ) ;
372445 return ;
373446 }
374447
375- // Controlla se la riproduzione è già terminata
376- const state = await TrackPlayer . getPlaybackState ( ) ;
377- if ( state . state === State . Ended || state . state === State . Stopped ) {
448+ // Tutti i chunk sono stati aggiunti alla queue, configura il listener
449+ this . setupQueueEndedListener ( ) ;
450+ }
451+
452+ /**
453+ * Configura il listener per la fine della riproduzione della queue.
454+ * Controlla prima se la riproduzione è già terminata.
455+ */
456+ private async setupQueueEndedListener ( ) : Promise < void > {
457+ // Safety: rimuovi listener precedente se presente
458+ if ( this . queueEndedListener ) {
459+ this . queueEndedListener . remove ( ) ;
460+ this . queueEndedListener = null ;
461+ }
462+
463+ // Se non sta riproducendo (tutti i chunk sono falliti?), completa subito
464+ if ( ! this . isPlaying ) {
378465 this . handlePlaybackComplete ( ) ;
379466 return ;
380467 }
381468
469+ // Controlla se la riproduzione è già terminata
470+ try {
471+ const state = await TrackPlayer . getPlaybackState ( ) ;
472+ if ( state . state === State . Ended || state . state === State . Stopped ) {
473+ this . handlePlaybackComplete ( ) ;
474+ return ;
475+ }
476+ } catch ( error ) {
477+ console . error ( 'TrackPlayer: Errore verifica stato:' , error ) ;
478+ }
479+
382480 // Registra listener per la fine della queue
383481 this . queueEndedListener = TrackPlayer . addEventListener (
384482 Event . PlaybackQueueEnded ,
@@ -440,6 +538,7 @@ export class AudioPlayer {
440538 clearChunks ( ) : void {
441539 this . allChunksReceived = false ;
442540 this . chunkCounter = 0 ;
541+ this . pendingChunks = 0 ;
443542 }
444543
445544 async stopPlayback ( ) : Promise < void > {
@@ -458,6 +557,7 @@ export class AudioPlayer {
458557 this . onCompleteCallback = null ;
459558 this . allChunksReceived = false ;
460559 this . chunkCounter = 0 ;
560+ this . pendingChunks = 0 ;
461561 await this . cleanupTempFiles ( ) ;
462562 }
463563
@@ -469,6 +569,14 @@ export class AudioPlayer {
469569 return this . chunkCounter ;
470570 }
471571
572+ /**
573+ * Restituisce true se ci sono chunk in fase di processamento (non ancora aggiunti alla queue)
574+ * o già aggiunti alla queue. Utile per evitare premature auto-unmute.
575+ */
576+ hasPendingOrQueuedChunks ( ) : boolean {
577+ return this . pendingChunks > 0 || this . chunkCounter > 0 ;
578+ }
579+
472580 async destroy ( ) : Promise < void > {
473581 await this . stopPlayback ( ) ;
474582 }
0 commit comments