fix: Audio queue for sequential TTS playback (no overlap/skip)

- Audio packets queued instead of stopping previous
- _playNext() plays sequentially, each sentence after the previous
- stopPlayback() clears queue
- Fixes overlapping/skipping with XTTS sentence-by-sentence rendering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 02:09:35 +02:00
parent b3d3b8b6bc
commit e4e0e793a8
+27 -5
View File
@@ -55,6 +55,10 @@ class AudioService {
private recorder: AudioRecorderPlayer; private recorder: AudioRecorderPlayer;
private recordingPath: string = ''; private recordingPath: string = '';
// Audio-Queue fuer sequentielle TTS-Wiedergabe
private audioQueue: string[] = [];
private isPlaying: boolean = false;
// VAD State // VAD State
private vadEnabled: boolean = false; private vadEnabled: boolean = false;
private lastSpeechTime: number = 0; private lastSpeechTime: number = 0;
@@ -198,15 +202,27 @@ class AudioService {
// --- Wiedergabe --- // --- Wiedergabe ---
/** Base64-kodiertes Audio abspielen (z.B. TTS-Antwort von ARIA) */ /** Base64-kodiertes Audio in die Queue stellen und abspielen */
async playAudio(base64Data: string): Promise<void> { async playAudio(base64Data: string): Promise<void> {
if (!base64Data) return; if (!base64Data) return;
// Laufende Wiedergabe stoppen this.audioQueue.push(base64Data);
this.stopPlayback(); if (!this.isPlaying) {
this._playNext();
}
}
/** Naechstes Audio aus der Queue abspielen */
private async _playNext(): Promise<void> {
if (this.audioQueue.length === 0) {
this.isPlaying = false;
return;
}
this.isPlaying = true;
const base64Data = this.audioQueue.shift()!;
try { try {
// Base64 -> temporaere WAV-Datei -> Sound abspielen
const tmpPath = `${RNFS.CachesDirectoryPath}/aria_tts_${Date.now()}.wav`; const tmpPath = `${RNFS.CachesDirectoryPath}/aria_tts_${Date.now()}.wav`;
await RNFS.writeFile(tmpPath, base64Data, 'base64'); await RNFS.writeFile(tmpPath, base64Data, 'base64');
@@ -214,6 +230,7 @@ class AudioService {
if (error) { if (error) {
console.error('[Audio] Fehler beim Laden:', error); console.error('[Audio] Fehler beim Laden:', error);
RNFS.unlink(tmpPath).catch(() => {}); RNFS.unlink(tmpPath).catch(() => {});
this._playNext();
return; return;
} }
this.currentSound?.play((success) => { this.currentSound?.play((success) => {
@@ -225,15 +242,20 @@ class AudioService {
this.currentSound?.release(); this.currentSound?.release();
this.currentSound = null; this.currentSound = null;
RNFS.unlink(tmpPath).catch(() => {}); RNFS.unlink(tmpPath).catch(() => {});
// Naechstes Audio abspielen
this._playNext();
}); });
}); });
} catch (err) { } catch (err) {
console.error('[Audio] Wiedergabefehler:', err); console.error('[Audio] Wiedergabefehler:', err);
this._playNext();
} }
} }
/** Laufende Wiedergabe stoppen */ /** Laufende Wiedergabe stoppen + Queue leeren */
stopPlayback(): void { stopPlayback(): void {
this.audioQueue = [];
this.isPlaying = false;
if (this.currentSound) { if (this.currentSound) {
this.currentSound.stop(); this.currentSound.stop();
this.currentSound.release(); this.currentSound.release();