feat: NO_REPLY-Filter + Audio-Ducking + TTS-Cleanup

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>
This commit is contained in:
2026-04-19 16:10:54 +02:00
parent 23add7a107
commit 8b0a72dc9b
6 changed files with 255 additions and 18 deletions
@@ -0,0 +1,93 @@
package com.ariacockpit
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
/**
* Steuert Audio-Focus fuer Ducking/Muten anderer Apps.
*
* - requestDuck() → andere Apps werden leiser (ARIA spricht TTS)
* - requestExclusive() → andere Apps werden pausiert (Mikrofon-Aufnahme)
* - release() → Focus abgeben, andere Apps duerfen wieder
*/
class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "AudioFocus"
private var currentRequest: AudioFocusRequest? = null
private fun audioManager(): AudioManager? =
reactApplicationContext.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
private fun requestFocus(durationHint: Int, usage: Int, promise: Promise) {
val am = audioManager()
if (am == null) {
promise.reject("NO_AUDIO_MANAGER", "AudioManager nicht verfuegbar")
return
}
release()
val result: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val attrs = AudioAttributes.Builder()
.setUsage(usage)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
val req = AudioFocusRequest.Builder(durationHint)
.setAudioAttributes(attrs)
.setOnAudioFocusChangeListener { /* kein Callback noetig */ }
.build()
currentRequest = req
am.requestAudioFocus(req)
} else {
@Suppress("DEPRECATION")
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, durationHint)
}
promise.resolve(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
}
/** Andere Apps werden leiser (TTS spricht). */
@ReactMethod
fun requestDuck(promise: Promise) {
requestFocus(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
AudioAttributes.USAGE_ASSISTANT,
promise,
)
}
/** Andere Apps werden pausiert (Mikrofon-Aufnahme / Gespraech). */
@ReactMethod
fun requestExclusive(promise: Promise) {
requestFocus(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
AudioAttributes.USAGE_VOICE_COMMUNICATION,
promise,
)
}
/** Focus abgeben — andere Apps duerfen wieder volle Lautstaerke. */
@ReactMethod
fun release(promise: Promise) {
release()
promise.resolve(true)
}
private fun release() {
val am = audioManager() ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
currentRequest?.let { am.abandonAudioFocusRequest(it) }
} else {
@Suppress("DEPRECATION")
am.abandonAudioFocus(null)
}
currentRequest = null
}
}
@@ -0,0 +1,16 @@
package com.ariacockpit
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class AudioFocusPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(AudioFocusModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
@@ -19,6 +19,7 @@ class MainApplication : Application(), ReactApplication {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(ApkInstallerPackage())
add(AudioFocusPackage())
}
override fun getJSMainModuleName(): String = "index"