fix(audio): playbackFinished-Listener feuern erst wenn AudioTrack wirklich durch ist
Race-Condition entdeckt im Log: nach jeder ARIA-Antwort lief endConversation 5s nach TTS-Start (= "letzter Chunk eingetroffen"), nicht wenn der AudioTrack-Hardware-Buffer wirklich am Ende war. ARIA sprach also noch hoerbar, waehrend OpenWakeWord schon re-armte. Folge: ARIAs eigene Stimme ging direkt nach AudioRecord.startRecording ins Mikro. Die OpenWakeWord-Sessions von AudioRecord und AudioTrack sind verschieden → AcousticEchoCanceler kann den Output nicht subtrahieren (kein gemeinsamer Reference-Stream). Threshold + Patience-State der Wake-Word-Inferenz wird durch ARIAs konstante Audio-Eingabe verwirrt, der naechste echte "Computer"-Trigger geht unter. Fix: Listener-Fire aus handlePcmChunk(isFinal=true) raus, dafuer in den schon existierenden PcmPlaybackFinished-Native-Event-Handler rein. Die Kotlin-Seite emittiert das Event aus dem Writer-Thread- finally-Block — also genau dann wenn AudioTrack alle Samples durchgeschrieben hat. Side-Effect: UI-Konsumenten von onPlaybackFinished sehen den "finished"-State jetzt 1-2s spaeter (= ehrlicher zur Realitaet, ist eigentlich eine UX-Verbesserung).
This commit is contained in:
@@ -341,8 +341,21 @@ class AudioService {
|
||||
try {
|
||||
const emitter = new NativeEventEmitter(NativeModules.PcmStreamPlayer as any);
|
||||
emitter.addListener('PcmPlaybackFinished', () => {
|
||||
console.log('[Audio] PcmPlaybackFinished — Focus jetzt freigeben');
|
||||
console.log('[Audio] PcmPlaybackFinished — AudioTrack drained');
|
||||
this._releaseFocusDeferred();
|
||||
// Erst HIER playbackFinished-Listener feuern — nicht schon beim
|
||||
// Empfang des letzten PCM-Chunks (siehe handlePcmChunk). AudioTrack
|
||||
// braucht nach end() noch 1-2s zum Drainen seines Hardware-Buffers.
|
||||
// Wenn wir die Listener zu frueh feuern, re-armt OpenWakeWord
|
||||
// waehrend ARIA noch hoerbar spricht → ARIAs Stimme verwirrt die
|
||||
// Wake-Word-Detection (kein gemeinsames AEC zwischen AudioTrack-
|
||||
// und AudioRecord-Session). Stefan-Reproduktion: nach jeder ARIA-
|
||||
// Antwort schluckte das Wake-Word den naechsten Trigger.
|
||||
import('./logger').then(m => m.reportAppDebug('audio.playback',
|
||||
'PcmPlaybackFinished native event → fire listeners')).catch(()=>{});
|
||||
this.playbackFinishedListeners.forEach(cb => {
|
||||
try { cb(); } catch (e) { console.warn('[Audio] playbackFinished cb err:', e); }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err);
|
||||
@@ -1368,12 +1381,13 @@ class AudioService {
|
||||
// releasen den AudioFocus NICHT hier — der writer braucht u.U. noch
|
||||
// 30+ Sekunden bis der Buffer wirklich abgespielt ist. Den release
|
||||
// triggert das native Event "PcmPlaybackFinished" wenn AudioTrack
|
||||
// wirklich am Ende ist (siehe ensurePlaybackFinishedListener).
|
||||
// wirklich am Ende ist (siehe Constructor-PcmPlaybackFinished-Handler).
|
||||
//
|
||||
// playbackFinishedListeners feuern AUCH erst dort — frueher feuerten
|
||||
// sie hier (beim Eintreffen des letzten Chunks), das fuehrte zu
|
||||
// einem Race: OpenWakeWord re-armte waehrend AudioTrack noch hoerbar
|
||||
// ARIAs Stimme abspielte → naechstes Wake-Word ging unter.
|
||||
try { await PcmStreamPlayer!.end(); } catch {}
|
||||
// playbackFinished-Listener informieren (UI-Logik)
|
||||
this.playbackFinishedListeners.forEach(cb => {
|
||||
try { cb(); } catch (e) { console.warn('[Audio] playbackFinished cb err:', e); }
|
||||
});
|
||||
}
|
||||
this.pcmStreamActive = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user