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>
94 lines
3.1 KiB
Kotlin
94 lines
3.1 KiB
Kotlin
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
|
|
}
|
|
}
|