Logs zeigen jetzt KEINEN haengenden RNSound-Focus mehr (Library-Version
oder Sound-Lifecycle hat sich geaendert). Der Kick mit AUDIOFOCUS_GAIN
(permanent) sagte Spotify "user hat manuell etwas anderes gestartet" →
Spotify resumed nicht automatisch.
Ohne Kick: unser Focus war AUDIOFOCUS_GAIN_TRANSIENT (USAGE_ASSISTANT) —
beim release bekommt Spotify einen sauberen GAIN nach TRANSIENT-Loss
und resumed automatisch.
Native kickReleaseMedia bleibt fuer den Fall dass es nochmal gebraucht
wird, wird aber nicht mehr gerufen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisheriges Verhalten: Mute drueckt → stopPlayback. Mute zurueck → noch
eingehende chunks der gleichen Antwort starteten einen neuen Stream und
ARIA redete weiter wo sie war. Funktionierte nur 2x weil dann isFinal
schon kam und keine chunks mehr fluten.
Stefan: "Mund verbieten = Stop, fertig". Neue Antworten sollen normal
spielen.
Fix: _stoppedMessageId-Tracking. Bei Mute=true wird die aktuelle msgId
gemerkt — alle weiteren chunks dieser msgId bleiben silent, auch wenn
Mute zurueckgenommen wird. Reset bei neuer msgId.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: ältere Nachrichten (deren Cache-WAV weg ist) gehen ueber
tts_request neu rendern → kommen als PCM-Stream zurueck → werden ueber
PcmStreamPlayer abgespielt. Beim Mute lief stopPlayback aber ohne den
Spotify-resume-Kick weil hadRnSound=false war (kein currentSound).
Jetzt: kickReleaseMedia immer in stopPlayback rufen — kostet nichts,
deckt PCM- und RNSound-Pfad ab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-Renders / setInterval(loadSettings) triggern setMuted(true) oft
mehrfach hintereinander → jeder weitere stopPlayback rief erneut
kickReleaseMedia, Spotify pausierte+resumte mehrfach (Stefan: "spielt
kurz und pausiert dann wieder").
Fix: stopPlayback returnt sofort wenn nichts mehr aktiv ist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logs zeigten: react-native-sound requestet beim Sound.play() einen
EIGENEN AudioFocus mit USAGE_MEDIA, released den aber bei Sound.stop()/
release() NICHT (bekanntes RN-sound-Bug). Spotify sieht den haengenden
Media-Focus → bleibt pausiert.
Workaround: Native-Methode kickReleaseMedia() macht einen request+abandon-
Cycle mit USAGE_MEDIA, das System raeumt damit den Focus-Stack auf und
Spotify bekommt sauberen GAIN-Event. stopPlayback ruft das jetzt nach
Sound.release() wenn vorher ein RNSound aktiv war.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Button checkte nur ob audioPath gesetzt ist — auf eine geloeschte
Cache-Datei hat aber nichts geprueft. playFromPath warntete nur und
returnte stumm. Jetzt wird VOR playFromPath die Existenz geprueft, sonst
geht's ueber tts_request an die Bridge zum Neu-Rendern.
Plus: Logs in Sound.play-Callback und _releaseFocusDeferred fuer den
"Spotify resumed nicht nach Replay"-Bug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- App-Start raeumt orphane aria_tts_*.wav (>5min) aus dem Cache —
Wiedergaben die durch Anruf/Mute/Barge-In abgebrochen wurden
hinterliessen sonst Files, weil der completion-Callback nicht feuert.
- Neuer Settings-Button "TTS-Cache leeren" mit Live-Groessenanzeige —
parallel zum bestehenden "Update-Cache leeren".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pauseForCall stoppte zwar currentSound + setzte ihn auf null, hat aber
isPlaying=true gelassen. Folge: nach dem Anruf war jeder weitere Play-
Button-Klick wirkungslos, weil playAudio bei isPlaying=true den
_playNext-Pfad ueberspringt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stopPlayback stoppte bisher nur currentSound, nicht resumeSound — wenn
nach einem Anruf der Auto-Resume laeuft und der User Mute drueckt, bleibt
der Resume-Sound weiter spielen.
Plus Logs in setMuted/stopPlayback um zu sehen warum Stefans Mute beim
Replay nicht greift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logs zeigten: playFromPath setzt currentPlaybackMsgId='db710ff3-...', 9s
spaeter beim Anruf war captureInterruption msgId=(leer). Ursache:
_firePlaybackStarted setzt currentPlaybackMsgId blind aus pcmMessageId —
das ist beim Play-Button leer.
Jetzt nur noch setzen wenn ein PCM-Stream laeuft. Play-Button und Resume-
Sound setzen ihr Tracking selber im Caller.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Im Test 2 (zweiter Anruf in derselben Antwort) kam weder captureInterruption
noch resumeFromInterruption als Log — beide returnen frueh ohne Hinweis warum.
Jetzt loggen sie auch den Skip-Pfad damit man sieht ob's der idempotent-Guard
oder fehlende playbackStartTime ist.
Plus: _playFromPathAtPosition aktualisiert jetzt currentPlaybackMsgId und
playbackStartTime — sonst stehen die auf den Werten der ersten TTS-Wiedergabe
und ein zweiter Anruf-captureInterruption wuerde mit veraltetem Stand laufen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logs zeigten "WAV nicht binnen 30000ms verfuegbar" — pcmBuffer wurde von
haltAllPlayback geleert, isFinal schrieb daher eine leere WAV (oder gar
keine, weil pcmMessageId leer war).
Neue Methode pauseForCall (statt haltAllPlayback im Anruf-Pfad):
- AudioTrack stoppt + AudioFocus release (Spotify resumed)
- pcmBuffer + pcmMessageId BLEIBEN — Bridge-Chunks werden weiter gesammelt
- _pausedForCall macht weitere Chunks "silent" (kein writeChunk, nur Cache)
- isFinal schreibt WAV trotz Anruf → resumeFromInterruption findet sie
Plus captureInterruption idempotent gemacht: ringing→offhook ueberschreibt
die Position vom ersten Halt nicht mehr (Date.now-Tracking laeuft stumpf
weiter obwohl Audio gestoppt ist — der erste Halt ist die echte Position).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pcmStreamActive wurde beim isFinal-Chunk schon auf false gesetzt, der
AudioTrack spielte aber noch aus seinem Buffer (kann sekundenlang sein).
stopPlayback() uebersprang darum PcmStreamPlayer.stop() — ARIA redete
weiter obwohl Spotify schon resumed war.
Fix: stop() immer rufen, der Flag-Check faellt weg (ist eh idempotent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefans Edge-Case: waehrend des Telefonats stellt der User eine neue
Text-Frage. Die neue ARIA-Antwort startet sofort (offhook→offhook
loest keinen halt aus). Vorher haette resumeFromInterruption nach
Anruf-Ende noch die ALTE Antwort (die unterbrochen wurde) ab
Position spielen wollen — Konflikt mit der neuen Antwort.
Fix: in _handlePcmChunkImpl beim Wechsel zu einer neuen messageId:
- laufenden resumeSound stoppen
- pausedMessageId = '' wenn != neue messageId
Damit gewinnt immer die neueste Antwort.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefans Idee: Position beim Halt merken (Date.now() - playbackStart -
leadingSilence), nach dem Auflegen ab da weitermachen. Wenn der Cache
noch nicht komplett ist (final-Marker kam waehrend Anruf), warten wir
bis zu 30s auf das WAV — meistens ist's schon da weil das Telefonat
laenger als die Antwort dauerte.
audio.ts:
- captureInterruption(): merkt position + messageId, returnt Sekunden
- resumeFromInterruption(maxWaitMs): wartet auf WAV-Cache, lädt mit
Sound, setCurrentTime(position), play
- Tracking-Felder: playbackStartTime, currentPlaybackMsgId, pausedX
phoneCall.ts:
- _haltForCall ruft captureInterruption() VOR haltAllPlayback
- _resumeAfterCall triggert resumeFromInterruption(30s)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logcat-Befund:
12:22:54.860 — final-Chunk + Cache geschrieben
12:22:55.402 — abandonAudioFocus (~0.5s spaeter)
12:22:55 — Spotify resumed (Atlas: TotalTime 93s)
12:23:27.064 — Playback fertig (32s spaeter!)
→ ARIA spricht 32s parallel zu Spotify weil end() viel zu frueh
returnt. Stefans 'Spotify resumed obwohl ARIA noch redet'.
Fix:
- PcmStreamPlayerModule emittiert 'PcmPlaybackFinished' RN-Event nach
dem finally{}-Block im Writer-Thread (= AudioTrack hat alle Samples
wirklich durchgespielt)
- audioService subscribed im constructor → ruft erst dann
_releaseFocusDeferred()
- _handlePcmChunkImpl bei isFinal triggert NICHT mehr direkt das
Release — nur die playbackFinished-Listener (UI-Logic)
So bleibt Spotify pausiert bis ARIA tatsaechlich fertig ist, egal
wie viel Audio im AudioTrack-Buffer wartet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1 — dB-Range erweitert:
VAD_SILENCE_DB_MIN von -55 auf -85 dB. Damit hat Stefan einen weiten
Regler-Spielraum wenn die adaptive Auto-Erkennung in seiner Umgebung
nicht zuverlaessig greift.
Bug 5 — Mute-Button stoppt laufende TTS nicht:
audioService bekommt jetzt einen internen _muted-Flag. handlePcmChunk
setzt silent automatisch wenn _muted true ist, playAudio kehrt frueh
zurueck. Verhindert Race zwischen User-Klick auf Mute und einem
TTS-Chunk der im selben JS-Tick ankommt (vorher: Ref-Update via
useEffect erst nach dem Re-Render → Chunks "rutschten durch"). Plus
ttsCanPlayRef wird im toggleMute-Handler synchron aktualisiert.
Bug 4 — VoIP/Messenger-Anrufe erkennen:
AudioFocusModule emittiert jetzt "AudioFocusChanged" Events mit type
"loss"/"loss_transient"/"gain". WhatsApp/Signal/Discord/etc. requestn
AudioFocus_GAIN_TRANSIENT_EXCLUSIVE wenn ein Anruf reinkommt — wir
fangen das in phoneCall.ts ab und rufen halt + pauseForCall genau
wie beim klassischen Anruf. Plus getMode() Polling-Fallback (alle 3s)
weil GAIN nicht zuverlaessig kommt wenn wir den Focus selbst released
haben — sobald AudioMode wieder NORMAL ist, resumeFromCall.
Bug 6 — Bilder als "Strich":
attachmentImage hatte width: '100%' in einer Bubble mit maxWidth: '80%'
ohne explizite Parent-Breite → RN rendert auf 0px Breite. Neue ChatImage-
Komponente nutzt Image.getSize um die echte aspectRatio zu messen + setzt
sie dynamisch. Bubble passt sich dem Bild an.
Bugs 2 (lange Texte mid-cutoff) + 3 (Spotify resumed) — brauchen ADB-Logs.
ADB-WLAN ueber 192.168.177.22:5555 schlaegt fehl (refused) — bei Android
11+ braucht's Wireless-Debugging-Pairing-Code. Stefan kann den nennen
sobald er soweit ist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn die adaptive Baseline-Logik in einer Umgebung nicht zuverlaessig
greift (Stefan: "manchmal funktioniert die Stille-Erkennung nicht"),
kann der User die Schwelle jetzt manuell setzen.
Settings → Spracheingabe:
- "Stille-Pegel (dB)" mit −1/+1 Buttons + "Auf automatisch zuruecksetzen"
- Range −55 bis −15 dB, default "auto" (= adaptive Baseline)
- Info-Icon (i) oeffnet Modal mit Erklaerung:
• dB-Skala (negativ, naeher 0 = lauter)
• Faustregel-Pegel mit Farb-Code (−45 sensibel, −38 ausgewogen, −25 robust)
• Klarstellung "niedrigere Zahl = sensibler"
audio.ts:
- VAD_SILENCE_DB_OVERRIDE_KEY in AsyncStorage
- loadVadSilenceDbOverride() liefert null oder Zahl
- startRecording: wenn Override gesetzt, Adaptive-Baseline uebersteuert.
Speech-Schwelle wird auf Override + 10 dB gesetzt. Toast zeigt
"VAD: manuell stille>-XX dB"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 4 — Wake-Word laeuft bei Anruf weiter:
phoneCall ruft jetzt wakeWordService.pauseForCall bei RINGING/OFFHOOK
und resumeFromCall bei IDLE. Telefonie-App belegt das Mikro waehrend
des Anrufs, openWakeWord muss daher pausieren. Pre-Call-State wird
gemerkt — armed bleibt armed, conversing degraded zu armed (sonst
landet der User nach Auflegen in einem halben Dialog).
Bug 3 — App-Resume triggert faelschlich Wake-Word:
Beim Wechsel von Background nach Foreground gibt's Audio-Pegel-Spikes
(AudioFocus-Switch, AudioTrack re-route), die openWakeWord als Wake-
Word interpretiert. Neuer Cooldown-Mechanismus: AppState-Listener im
ChatScreen ruft wakeWordService.setResumeCooldown(1500) — Detections
in der Phase werden in onWakeDetected verworfen.
Bug 1 — Background-Aufnahme klappt nicht:
acquireBackgroundAudio('rec') wird jetzt VOR audioService.startRecorder
gerufen, acquireBackgroundAudio('wake') VOR OpenWakeWord.start. Sonst
greifen Androids Background-Mic-Restrictions (ab 11+) — der Service mit
foregroundServiceType=microphone muss zum Zeitpunkt des AudioRecord-
Starts schon aktiv sein, nicht erst per state-change-Listener
asynchron danach.
Bug 2 (VAD manchmal nicht): nicht in diesem Commit, vermutlich
umgebungsabhaengig. Toast zeigt die kalibrierten Schwellen — wenn
das nochmal auftritt, schick mir die Werte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIAs Antwort wird jetzt auch dann fertig vorgelesen wenn der User die
App im Hintergrund schickt. Vorher hat Android den Prozess kurz nach
dem Minimieren eingefroren — TTS verstummte mitten im Satz.
Native:
- AriaPlaybackService.kt: Service mit foregroundServiceType=mediaPlayback,
zeigt persistente Notification "ARIA spricht — antippen oeffnet die App"
(channel low-priority, ongoing, tap → MainActivity)
- BackgroundAudioModule.kt: RN-Bridge mit start()/stop()
- AndroidManifest: FOREGROUND_SERVICE + FOREGROUND_SERVICE_MEDIA_PLAYBACK
+ POST_NOTIFICATIONS Permissions, Service deklariert
JS:
- backgroundAudio.ts: idempotenter Wrapper (active-Flag verhindert
doppelte start/stop calls)
- ChatScreen onPlaybackStarted → startBackgroundAudio
- ChatScreen onPlaybackFinished → stopBackgroundAudio
- audio.ts stopPlayback ruft auch stopBackgroundAudio damit die
Notification bei Cancel/Barge-In/Anruf nicht haengen bleibt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Wenn beim Aufnahmestart sofort gesprochen wurde (z.B. Wake-Word-
Echo noch im Mikro) ODER der Hintergrund vorruebergehend laut war,
verschob die avg-basierte Baseline die Stille-Schwelle so weit nach
oben, dass normale Hintergrundgeraeusche dauerhaft als "Sprache"
zaehlten — VAD feuerte nie, Aufnahme lief unendlich.
Fix:
- Baseline = MINIMUM der 5 Samples statt Mittelwert (ruhigster Moment)
- Cap auf sinnvollen Bereich:
- Silence-Schwelle: -50dB bis -28dB (vorher unbegrenzt)
- Speech-Schwelle: -40dB bis -18dB
- Erweitertes Log: zeigt sowohl raw als auch geclamp-te Werte
Damit gibt's keine "tote" VAD-Konfiguration mehr — selbst wenn die
Baseline-Messung Schrott ist, bleiben die Schwellen praktikabel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Du kannst jetzt "Computer" sagen waehrend ARIA noch redet — TTS
verstummt, neue Aufnahme startet. Vorher musste man warten oder
manuell den Voice-Button tappen.
Native (OpenWakeWordModule.kt):
- AudioRecord-Source von MIC auf VOICE_COMMUNICATION (aktiviert auf
den meisten Geraeten Echo-Cancellation + Noise-Suppression)
- Zusaetzlich AcousticEchoCanceler/NoiseSuppressor/AutomaticGainControl
explizit aktiviert wenn vorhanden — robuster auf Geraeten wo die
VOICE_COMMUNICATION-Source die Effects nicht automatisch mitbringt
- releaseAudioEffects() im stop/dispose
JS (wakeword.ts):
- Neue API: startBargeListening / stopBargeListening — Wake-Word
parallel aktivieren, ohne den State 'conversing' zu verlassen
- onWakeDetected unterscheidet jetzt: in 'conversing' → barge-in-
Callback (nicht der normale wake-callback). Sonst Standard-Pfad.
- onBargeIn-Subscriber-API + isBargeListening-Getter
Lifecycle-Wiring (audio.ts + ChatScreen):
- audioService.onPlaybackStarted callback (neu)
- ChatScreen: Bei TTS-Start → wakeWord.startBargeListening
- ChatScreen: Bei TTS-Ende → wakeWord.stopBargeListening (sonst kein
AudioRecord fuer die naechste Aufnahme)
- ChatScreen: Bei BargeIn → haltAllPlayback + cancel_request +
150ms-Pause + neue Aufnahme starten
issue.md + README aktualisiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Max-Aufnahme:
Default rauf von 2 auf 5 Minuten, in den App-Settings konfigurierbar
zwischen 1 und 30 Minuten (loadMaxRecordingMs aus AsyncStorage,
Storage-Key aria_max_recording_sec). Notbremse-Verhalten bleibt:
nach Ablauf wird die Aufnahme automatisch beendet und gesendet.
Barge-In Kontext:
Wenn der User waehrend ARIA noch redet/arbeitet eine neue Sprach-
oder Text-Nachricht sendet, geht jetzt ein 'interrupted: true' Flag
mit. Bridge praefixed den Text fuer aria-core dann mit:
"[Hinweis: Stefan hat dich gerade unterbrochen waehrend du noch
gesprochen oder gearbeitet hast. Folgendes ist eine Korrektur,
Ergaenzung oder ein Themenwechsel zu deiner letzten Antwort.]"
So weiss ARIA dass die neue Message KEINE eigenstaendige Folgefrage
ist sondern auf den abgebrochenen Run bezogen. Der User sieht in
seinem Chat nur den reinen Text — der Hint geht nur an aria-core.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1 — Textauswahl in Bubbles ging nicht mehr:
MessageText hatte verschachtelte <Text onPress={...}> fuer Custom-Link-
Styling. Das fing die Long-Press-Geste ab, daher kein Markieren+Kopieren
mehr. Jetzt nur noch ein einzelnes <Text selectable dataDetectorType="all">,
Android macht URLs/Telefonnummern/Emails per System-Detection klickbar.
Bug 2 — VAD erkannte Stille nicht zuverlaessig (Aufnahme lief endlos):
Festwerte (-45dB Stille / -28dB Sprache) passten nicht zu jeder Umgebung.
In lauteren Raeumen lag der Hintergrundpegel ueber der Stille-Schwelle,
lastSpeechTime wurde dauerhaft aktualisiert → VAD feuerte nie, Aufnahme
lief bis 120s Max-Duration.
Jetzt adaptiv: erste 5 Mic-Samples (~500ms) bilden die Baseline; Stille-
Schwelle = baseline+6dB, Sprache-Schwelle = baseline+12dB. Toast zeigt
die kalibrierten Werte beim Aufnahmestart. Fallback auf -38dB/-22dB falls
das Mikro keine Metering-Updates liefert.
Bug 3 — Barge-In ("ach vergiss es"):
Wenn waehrend ARIAs Antwort eine neue Sprachnachricht aufgenommen wird,
wird ARIAs aktuelle Aktivitaet (TTS + thinking/tool) sofort abgebrochen
bevor die neue Message gesendet wird — wie in einem echten Gespraech wo
man den anderen unterbrechen darf.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1a — Anruf-Pause:
Neues PhoneCallModule.kt nutzt TelephonyCallback (API 31+) bzw.
PhoneStateListener (Pre-12) um auf RINGING/OFFHOOK/IDLE zu reagieren.
Bei Klingeln/Gespraech ruft phoneCall.ts → audioService.haltAllPlayback,
ARIA verstummt sofort. READ_PHONE_STATE Permission wird beim ersten
Start angefragt; ohne Permission failt der Listener leise.
Bug 1b — Spotify-Resume:
AudioFocus wird jetzt an den Conversation-Lifecycle gekoppelt statt an
einzelne Streams. Solange wakeWordState 'conversing' ist, blockt
acquireConversationFocus() jeden per-Stream-Release. Erst beim Wechsel
auf 'armed'/'off' darf der Focus tatsaechlich freigegeben werden.
Verhindert das "Spotify kommt nach 10s wieder hoch"-Phaenomen auch
ueber Render-Pausen + zwischen mehreren ARIA-Antworten hinweg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AudioFocus wird jetzt mit 800ms Verzoegerung freigegeben — wenn ARIA
direkt eine zweite Antwort hinterherschickt oder das Recording ins TTS
uebergeht, wird das Release abgebrochen. Spotify/YouTube haben damit
keine Mikro-Sekunden-Luecke mehr zum Hochkommen waehrend ARIA spricht.
README: neue Sektion zur Wake-Word-Einrichtung mit Picovoice
(7-Tage-Trial, Console-Link, Anleitung fuer eigene Keywords) und
veraltete Wake-Word-Limitation entfernt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 2: STT-Result ueberschrieb beide noch unaufgeloeste Audio-Bubbles
mit gleichem Text. Fix: nur die ERSTE matchende Bubble aktualisieren
(findIndex + index-Update statt map). Reihenfolge ist FIFO weil Whisper
sequenziell verarbeitet.
Bug 3: Speed-Param wird nun in jedem Hop geloggt:
- ChatScreen: "[Chat] sende mit voice=X speed=Y"
- aria-bridge: "XTTS-Request gesendet (voice=X, speed=Y.YYx)"
- f5tts-bridge: "F5-TTS: N Satz(e), voice=X, speed=Y.YYx"
Damit kann man im logcat/docker-logs eindeutig sehen wo speed evtl.
verloren geht oder ob die Stimme einfach von Natur aus schnell ist.
Bug 4: VAD-Trigger-Reason mit Schwelle: "VAD NNN ms Stille (Schwelle=NNN ms)".
Plus startRecording loggt jetzt VAD-Stille + MAX-Recording.
Bug 1 (Porcupine): mehr Debug + Toast-Meldungen.
- init failure: err.name/code/stack ins Log
- start() ohne Porcupine: Toast "Access Key in Settings setzen"
- start() Fehler: Toast mit Fehlermeldung
- configure(): Toast wenn init scheitert
- Erfolgreiches arming: Toast "Lausche auf X"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bei kurzen Saetzen (nur ein paar Chunks + sofort final) konnten die
async handlePcmChunk-Calls parallel laufen. Der final-Chunk konnte
native end() aufrufen BEVOR der vorherige Chunk seinen native start()
abgeschlossen hatte. Der Writer-Thread startete dann mit endRequested
bereits true, verarbeitete keine Chunks sauber → Audio ging verloren.
Fix: Wrapper chaint alle Chunk-Calls an eine Promise-Queue:
_pcmChunkQueue = Promise.resolve()
handlePcmChunk → _pcmChunkQueue.then(() => _handlePcmChunkImpl(p))
So werden start/writeChunk/end garantiert in der richtigen Reihenfolge
verarbeitet. Der API-Contract bleibt (gleiches return-Promise).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F5-TTS ist schnell genug dass der Puffer bei kurzen Saetzen eher
schadet als nuetzt — er verzoegert den play()-Start fuer Sekunden die
dann als Wartezeit auffallen.
Aenderungen:
- audio.ts: TTS_PREROLL_MIN_SEC 1.0 → 0 (Einstellbar in Settings)
- PcmStreamPlayerModule.kt: MIN_PREROLL_SECONDS auf 0.0, Fallback-
Logic respektiert jetzt 0 als gueltigen Wert (vorher hat der
.let { if (it > 0) it else DEFAULT } 0 zu 3.5s umgebogen).
Bei preroll=0 greift der Leading-Silence von 200ms immer noch, d.h.
AudioTrack-Startup bleibt sauber. play() wird dann beim allerersten
echten PCM-Chunk aufgerufen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TTS_SPEED_MIN 0.5 → 0.1, TTS_SPEED_MAX 2.0 → 5.0.
Bridge-seitige Validierungen (aria_bridge.py + f5tts/bridge.py) mit-
gezogen auf den gleichen Bereich.
Hinweis: Extremwerte (unter 0.5 oder ueber 2.0) koennen bei F5-TTS
verzerrte Ausgaben produzieren — Stefan bekommt die Freiheit zum
Experimentieren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei zusammenhaengende Bugs:
1. VAD-Timer feuerte im 200ms setInterval WEITER nachdem die Stille-
Schwelle erreicht war — listeners wurden pro Aufnahme bis zu 5x
getriggert. Parallel laufende stopRecording()-Calls lieferten
audio-recorder-player's nativen Layer OOM / Crash.
Fix: silenceFired-Latch + Timer-Clear SOFORT beim ersten Feuer
(fireSilenceOnce-Helper). Gleiche Logik fuer Max-Dauer + Conv-Window.
2. VoiceButton silence-listener re-registrierte bei jedem isRecording-
Flip (deps [isRecording, onRecordingComplete]). Closure-State war
stale, und bei schnellen flips gabs register/unregister-Races.
Fix: empty deps, state direkt vom audioService via getRecordingState()
lesen. onRecordingComplete via Ref (damit der Callback aktuell bleibt
ohne re-register).
3. handleTap las den Button-State aus React (isRecording), der bei
schnellen Taps stale sein konnte — "erst zweiter Tap geht" Symptom.
Fix: audioService.getRecordingState() als Source-of-Truth, plus
tapBusy-Ref als Anti-Doppel-Tap-Guard waehrend asyncer start/stop.
'processing'-State wird korrekt ignoriert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan berichtet dass Auto-Playback trotz Closure-Fix nicht greift.
Zwei neue Log-Zeilen die beim naechsten Test direkt zeigen was schief
laeuft:
- ChatScreen: "[Chat] audio-msg canPlay=X (enabled=Y muted=Z)"
- audio.ts: "[Audio] PCM-Stream start: silent=X messageId=Y ..."
Ausreichend um zu unterscheiden:
* canPlay=false trotz Mund-an → ttsMuted bleibt im State haengen
* canPlay=true aber silent=true in audio.ts → Ref-Bug oder race
* silent=false aber nichts hoerbar → native-module oder audio-routing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bugs:
- App Mute-/Auto-Playback: onMessage-Closure hielt stale ttsDeviceEnabled/
ttsMuted → Mute wurde ignoriert + AsyncStorage-Load kam nicht durch.
Fix via ttsCanPlayRef (live gespiegelt) statt Closure-Variablen.
- App Zombie-Recording: toggleWakeWord hat die laufende Aufnahme nicht
gestoppt → audioService.recordingState blieb 'recording' → normaler
Aufnahme-Button wirkungslos. Fix: await stopRecording() vor stop().
- Porcupine robuster: BuiltInKeywords-Enum Mapping mit String-Fallback,
errorCallback fuer Runtime-Crashes (state zurueck auf off statt
App-Crash), mehr Logging damit man beim naechsten Issue debuggen kann.
App-Features:
- MessageText Komponente: Text ist durchgehend selektierbar, erkennt
URLs (http/https), E-Mails, Telefonnummern und macht sie anklickbar
(oeffnet Browser / Mail-App / Android-Dialer via Linking).
- TTS-Wiedergabegeschwindigkeit pro Geraet einstellbar (Settings ->
"Sprechgeschwindigkeit", 0.5-2.0 in 0.1-Schritten, Default 1.0).
Wird als speed-Param an die F5-TTS-Bridge durchgereicht.
Bridge-Durchreichen:
- ChatScreen: speed aus AsyncStorage via ttsSpeedRef, an chat/audio/
tts_request mitgeschickt
- aria-bridge: _next_speed_override wie voice_override, an xtts_request
weitergereicht
- f5tts-bridge: speed-Param an F5TTS.infer() durchgereicht
Diagnostic-Feature:
- Voice-Preview-Button (Play-Icon) vor dem Delete-X in der Stimmen-Liste
- Modal mit Textfeld (Default-Beispieltext wird bei jedem Oeffnen neu
gesetzt) und Play-Button
- Server sammelt audio_pcm Frames der Preview-Anfrage, baut WAV,
schickt base64 zurueck, Browser spielt im <audio>-Tag ab
- 60s Timeout-Safety-Net
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Gespraechsmodus war bisher ein Endless-Loop: Mikro hat sich nach
jeder ARIA-Antwort wieder geoeffnet bis MAX_RECORDING_MS, danach Speech-
Gate verworfen und neu starten. Das Ohr blieb ewig an.
Neue Logik:
audio.ts: startRecording(autoStop, noSpeechTimeoutMs?) — wenn der User
innerhalb des Timeouts nicht anfaengt zu sprechen, wird Stille
gemeldet → stopRecording → Speech-Gate verwirft → result=null.
wakeword.ts: drei States off/armed/conversing. start() geht direkt in
'conversing' (kein Wake-Word verfuegbar; Stub fuer spaetere Porcupine-
Integration). endConversation() bei No-Speech.
ChatScreen: Aufnahme bekommt das Window aus AsyncStorage durchgereicht.
Bei null-Result → endConversation, UI-State synchron.
Settings: neuer +/- Block "Konversations-Fenster" 3-20s (Default 8).
Mit dem Stub ist die Architektur bereit fuer Porcupine: dann geht
endConversation auf 'armed' statt 'off' und der Wake-Word-Detector
laeuft passiv weiter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer +/- Block in SettingsScreen → Spracheingabe → "Stille-Toleranz",
1.0-8.0s, Default 2.8s. Wert in AsyncStorage (aria_vad_silence_sec).
audio.ts liest den Wert beim Aufnahme-Start und nutzt ihn fuer den
VAD-Auto-Stop-Schwellwert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AudioFocus.requestDuck nutzt jetzt AUDIOFOCUS_GAIN_TRANSIENT (statt
TRANSIENT_MAY_DUCK) — Spotify/YouTube pausieren komplett solange ARIA
spricht und kommen nicht mitten drin wieder hoch.
PcmStreamPlayer.end() resolved jetzt erst wenn der native Writer-Thread
wirklich fertig ist (alle Samples aus dem Pre-Roll-Puffer ausgespielt).
audio.ts wartet entsprechend, bevor AudioFocus.release() gerufen wird —
behebt das "Musik dreht hoch waehrend Antwort noch laeuft"-Problem.
Mic-Aufnahme: VAD_SILENCE_DURATION_MS 1800 → 2800ms (mehr Toleranz fuer
Sprechpausen), MAX_RECORDING_MS 30s → 120s (laengere Erklaerungen
moeglich, Notbremse bleibt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Kotlin start() nimmt jetzt prerollSeconds als dritten Parameter
(1.0-6.0s geclampt, Fallback 3.5s bei ungueltigem Wert)
- audio.ts liest Wert aus AsyncStorage vor jedem Stream-Start,
exportiert Default/Min/Max/Key als Konstanten
- SettingsScreen: +/- Buttons direkt unter dem TTS-Toggle,
Default auf 3.5s (von 2.5s) angehoben
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SettingsScreen:
- Piper-Reste entfernt (defaultVoice, highlightVoice, Speed-Slider,
Highlight-Trigger-Info)
- Nur noch EIN Toggle 'Sprachausgabe auf diesem Geraet' — geraetelokal,
persistent in aria_tts_enabled (AsyncStorage)
- Keine Config-Propagation mehr via RVS (das waere ja global gewesen)
- Hinweis dass Stimme + Voice-Cloning zentral in der Diagnose sind
ChatScreen: Mund-Button (👄 / 🤐)
- Neben Ohr-Button im Eingabebereich, NUR sichtbar wenn TTS im Setting
grundsaetzlich aktiv ist
- Tap toggelt Mute: 👄 an / 🤐 rot gemutet
- Persistent in aria_tts_muted (AsyncStorage)
- Stoppt bei Muten sofort laufende Wiedergabe (stopPlayback)
- Settings-Toggle wird alle 2s gepollt damit Aenderungen greifen
(einfache Loesung ohne globalen State-Context)
Audio-Handling respektiert lokalen Zustand
- Incoming audio/audio_pcm: nur abspielen wenn ttsDeviceEnabled && !ttsMuted
- Cache wird TROTZDEM immer geschrieben — Play-Button funktioniert
spaeter aus Cache, auch waehrend Mute
- audioService.handlePcmChunk akzeptiert silent-Flag: skipt AudioTrack
aber baut weiterhin den WAV-Cache pro messageId
Jedes Android-Geraet mit der App hat seinen eigenen Mute-Zustand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pipeline: XTTS-Server → xtts-bridge → aria-bridge → RVS → App AudioTrack
XTTS-Bridge (Gaming-PC):
- streamXTTSAsPCM(): liest /tts_to_audio/ Response inkrementell,
parst WAV-Header (samplerate/channels), teilt PCM in 8KB-Chunks
(~170ms bei 24kHz s16 mono) und sendet jeden als audio_pcm.
- Finaler Chunk mit final=true nach letztem Text-Chunk
aria-bridge:
- audio_pcm Handler leitet payload 1:1 weiter, filled messageId aus
requestId → messageId Map falls XTTS-Bridge messageId nicht hatte
- Alter xtts_response Pfad bleibt als Legacy-Fallback (WAV)
RVS: audio_pcm in ALLOWED_TYPES
Android Native:
- PcmStreamPlayerModule (Kotlin): AudioTrack MODE_STREAM mit
Writer-Thread und BlockingQueue. start(rate, ch) / writeChunk(b64)
/ end() / stop()
- 8x MinBufferSize grosszuegig dimensioniert, glatt auch bei
Netz-Aussetzern
- Registered im MainApplication via PcmStreamPlayerPackage
App JS:
- audioService.handlePcmChunk(): erkennt neue Session (messageId-Wechsel),
started nativen Stream, cached PCM-Bytes pro Message. Bei final=true
Stream sauber schliessen + _savePcmBufferAsWav → WAV-File im
tts_cache/<messageId>.wav
- _savePcmBufferAsWav: baut 44-byte WAV-Header (PCM s16le, korrekte
samplerate/channels), haengt alle gesammelten base64-PCM-Chunks an
- stopPlayback beendet auch aktiven PCM-Stream
- ChatScreen routet type=audio_pcm an handlePcmChunk, bei final
setzt audioPath in der Message
Play-Button: falls messageId einen audioPath hat → WAV aus Cache
(Sound-basiert), egal ob Original-TTS Piper oder XTTS war.
Audio-Focus:
- requestDuck() beim Stream-Start, release() bei Stream-Ende
- Andere Apps (Spotify etc.) werden leiser waehrend ARIA spricht
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QR-Code Onboarding
- Diagnostic: GET /api/onboarding gibt RVS-Credentials zurueck
- Einstellungen-UI: neue Sektion mit QR-Code (qrcode-generator via CDN)
- Format kompatibel mit bestehendem QRScanner.parseQRData (host/port/tls/token)
- App-SettingsScreen hatte QR-Scanner bereits — funktioniert out of the box
- Warnhinweis zu Token im Klartext
TTS-Audio-Cache
- Bridge: jede ARIA-Chat-Nachricht bekommt eine messageId (UUID)
Audio-Payload wird mit messageId verknuepft (Piper-Pfade)
- ChatScreen: messageId + audioPath in ChatMessage Interface
- audioService.cacheAudio(): speichert Base64 in DocumentDirectory/tts_cache/<id>.wav
- audioService.playFromPath(): spielt aus Cache ohne Regenerierung
- Play-Button: wenn audioPath gesetzt → aus Cache, sonst tts_request
- cleanupOldTTSCache(): alte unreferenzierte WAVs (>30 Tage) weg
- Persistiert via AsyncStorage — ueberlebt App-Restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) NO_REPLY Token wird in Bridge und Diagnostic erkannt und still
verworfen. Toleranz fuer Variationen (Whitespace, Punkt, Quotes).
Kein Chat-Eintrag, kein TTS.
2) AudioFocusModule (Kotlin) mit requestDuck / requestExclusive /
release. AudioService ruft:
- requestExclusive() bei Aufnahme-Start → andere Apps pausieren
- requestDuck() bei TTS-Playback-Start → andere Apps leiser
- release() bei Stop/Queue-Ende
MainApplication registriert AudioFocusPackage.
3) clean_text_for_tts() in Bridge — zentrale Aufbereitung:
- <voice>...</voice> Tag wird bevorzugt (falls ARIA es schreibt)
- Code-Bloecke (``` und `) komplett raus
- Markdown (Fett/Kursiv/Links/Headings/Listen) geschleift
- Einheiten ausgeschrieben: 22GB → 22 Gigabyte, 85% → 85 Prozent
- Abkuerzungen buchstabiert: CPU → C P U, API → A P I
- URLs durch "ein Link" ersetzt
Genutzt in VoiceEngine.synthesize und im XTTS-Request — Chat-Text
an die App bleibt unveraendert (original Markdown), nur TTS kriegt
die aufbereitete Version.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Probleme:
- Hintergrundgeraeusche wurden als Sprache erkannt und an Whisper geschickt
- App stuerzte nach laengerem Zuhoeren ab (OOM / Cache-Ueberlauf)
Aenderungen:
- VAD_SPEECH_THRESHOLD_DB -35 -> -28 (filtert Raum-Ambient)
- VAD_SPEECH_MIN_MS 300 -> 500 (keine Huestler/Klopfer mehr)
- Max-Aufnahmedauer 30s (Notbremse gegen Runaway-Loops)
- _cleanupStaleCacheFiles(): alte aria_recording_/aria_tts_ Files (>30s)
werden vor jeder neuen Aufnahme geloescht
- ChatScreen: capMessages() begrenzt Messages-Array auf 500 Eintraege
(OOM-Schutz in langen Gespraechen)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- App: AudioSamplingRateAndroid 16000 + AudioChannelsAndroid 1
→ Whisper bekommt direkt sein Ziel-Format, kein Resample mehr
- Bridge: STTEngine.reload() laedt Modell zur Laufzeit neu
(tiny/base/small/medium/large-v3)
- Bridge: Config-Message triggert Hot-Reload wenn whisperModel sich aendert
- Bridge: Default auf 'medium' (besser als 'small' bei aehnlicher Latenz)
- Diagnostic: Neue Sektion "Whisper (Spracherkennung)" mit Dropdown,
auto-save bei Auswahl, beim Laden wird der gespeicherte Wert gesetzt
- Diagnostic/Server: send_voice_config merged whisperModel in voice_config.json
- aria.env.example: WHISPER_MODEL + WHISPER_LANGUAGE dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- VAD_SPEECH_THRESHOLD_DB = -35 (louder than silence threshold)
- Needs 300ms of speech before counting as real speech
- Recording discarded if only background noise detected
- Prevents sending garbage to Whisper in conversation mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Preload next audio while current plays (eliminates gap between sentences)
- Remove trailing dots from sentences (XTTS reads them aloud)
- stopPlayback cleans up preloaded audio
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>