fix(audio): Wake-Word-Anruf-Pause + Resume-Cooldown + Background-Mic-Order

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>
This commit is contained in:
2026-05-07 07:49:02 +02:00
parent 2eb0b4df90
commit 52795530f9
5 changed files with 101 additions and 3 deletions
+12 -2
View File
@@ -19,6 +19,7 @@ import {
ToastAndroid,
} from 'react-native';
import audioService from './audio';
import wakeWordService from './wakeword';
interface PhoneCallNative {
start(): Promise<boolean>;
@@ -91,16 +92,25 @@ class PhoneCallService {
private _onStateChanged(state: PhoneState): void {
if (state === this.lastState) return;
console.log('[PhoneCall] State: %s → %s', this.lastState, state);
const prev = this.lastState;
console.log('[PhoneCall] State: %s → %s', prev, state);
this.lastState = state;
if (state === 'ringing' || state === 'offhook') {
audioService.haltAllPlayback(`Telefon-State: ${state}`);
// Wake-Word + Aufnahme pausieren: Telefonie-App belegt das Mikro
// waehrend des Anrufs, plus ARIA soll nicht im Telefonat zuhoeren.
wakeWordService.pauseForCall().catch(() => {});
ToastAndroid.show(
state === 'ringing' ? 'Anruf — ARIA pausiert' : 'Im Gespraech — ARIA pausiert',
ToastAndroid.SHORT,
);
} else if (state === 'idle' && prev !== 'idle') {
// Auflegen: Wake-Word reaktivieren wenn vor dem Anruf aktiv war.
// TTS kommt nicht automatisch zurueck (Stream weg) — User kann
// ARIAs letzte Antwort per Play-Button nochmal hoeren.
wakeWordService.resumeFromCall().catch(() => {});
ToastAndroid.show('Anruf beendet — ARIA wieder aktiv', ToastAndroid.SHORT);
}
// idle: nichts automatisch — User soll nichts unbeabsichtigt re-triggern
}
}