diff --git a/android/src/services/audio.ts b/android/src/services/audio.ts index a82670c..a0cd062 100644 --- a/android/src/services/audio.ts +++ b/android/src/services/audio.ts @@ -459,7 +459,13 @@ class AudioService { /** Einen PCM-Chunk aus einer audio_pcm Nachricht empfangen. * silent=true → nur cachen, nicht abspielen (z.B. wenn TTS geraetelokal gemutet). - * Gibt bei final=true den Cache-Pfad zurueck (file://) oder '' wenn nicht gecached. */ + * Gibt bei final=true den Cache-Pfad zurueck (file://) oder '' wenn nicht gecached. + * + * Wrapper serialisiert aufeinanderfolgende Chunk-Calls via Promise-Queue — + * sonst gabs bei kurzen Streams einen Race: final-Chunk konnte `end()` rufen + * BEVOR der vorherige `start()` im Native-Modul fertig war. Der Writer- + * Thread sah dann endRequested=true ohne jemals Chunks zu verarbeiten. */ + private _pcmChunkQueue: Promise = Promise.resolve(); async handlePcmChunk(payload: { base64: string; sampleRate?: number; @@ -468,6 +474,24 @@ class AudioService { chunk?: number; final?: boolean; silent?: boolean; + }): Promise { + const p = this._pcmChunkQueue.then(() => this._handlePcmChunkImpl(payload)).catch(err => { + console.warn('[Audio] handlePcmChunk queued err:', err); + return ''; + }); + // Chain only on the side effect — callers still get the per-call result + this._pcmChunkQueue = p; + return p; + } + + private async _handlePcmChunkImpl(payload: { + base64: string; + sampleRate?: number; + channels?: number; + messageId?: string; + chunk?: number; + final?: boolean; + silent?: boolean; }): Promise { const silent = !!payload.silent; if (!silent && !PcmStreamPlayer) {