feat(audio): TTS pausiert bei Anruf + Conversation-Focus haelt Spotify durchgehend gepaust
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>
This commit is contained in:
@@ -198,6 +198,12 @@ class AudioService {
|
||||
private focusReleaseTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private readonly FOCUS_RELEASE_DELAY_MS = 800;
|
||||
|
||||
// Conversation-Mode: solange aktiv (Wake-Word Status 'conversing' ODER
|
||||
// wir wissen "ARIA spricht gerade in einem Multi-Turn-Dialog"), halten wir
|
||||
// den AudioFocus DAUERHAFT. Der per-Stream-Release wird unterdrueckt,
|
||||
// damit Spotify nicht in Render-Pausen oder zwischen Antworten zurueckkehrt.
|
||||
private _conversationFocusActive: boolean = false;
|
||||
|
||||
// VAD State
|
||||
private vadEnabled: boolean = false;
|
||||
private lastSpeechTime: number = 0;
|
||||
@@ -214,11 +220,18 @@ class AudioService {
|
||||
|
||||
/** AudioFocus mit kleiner Verzoegerung freigeben — Spotify/YouTube
|
||||
* springen sonst im Gap zwischen zwei TTS-Streams (oder wenn ARIA
|
||||
* eine zweite Antwort direkt hinterherschickt) kurz wieder an. */
|
||||
* eine zweite Antwort direkt hinterherschickt) kurz wieder an.
|
||||
* Im Conversation-Mode (Wake-Word conversing) wird das Release komplett
|
||||
* unterdrueckt — der Focus bleibt fuer die ganze Konversation gehalten. */
|
||||
private _releaseFocusDeferred(): void {
|
||||
if (this._conversationFocusActive) {
|
||||
this._cancelDeferredFocusRelease();
|
||||
return;
|
||||
}
|
||||
this._cancelDeferredFocusRelease();
|
||||
this.focusReleaseTimer = setTimeout(() => {
|
||||
this.focusReleaseTimer = null;
|
||||
if (this._conversationFocusActive) return;
|
||||
AudioFocus?.release().catch(() => {});
|
||||
}, this.FOCUS_RELEASE_DELAY_MS);
|
||||
}
|
||||
@@ -230,6 +243,33 @@ class AudioService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Conversation-Mode beginnt → AudioFocus dauerhaft halten (Spotify bleibt
|
||||
* pausiert). Idempotent: mehrfaches Aufrufen ist sicher. */
|
||||
acquireConversationFocus(): void {
|
||||
if (this._conversationFocusActive) return;
|
||||
this._conversationFocusActive = true;
|
||||
this._cancelDeferredFocusRelease();
|
||||
console.log('[Audio] Conversation-Focus aktiv (Spotify bleibt gepaust)');
|
||||
AudioFocus?.requestDuck().catch(() => {});
|
||||
}
|
||||
|
||||
/** Conversation-Mode endet → Focus darf wieder freigegeben werden
|
||||
* (verzoegert, damit eine direkt folgende Antwort nichts kaputtmacht). */
|
||||
releaseConversationFocus(): void {
|
||||
if (!this._conversationFocusActive) return;
|
||||
this._conversationFocusActive = false;
|
||||
console.log('[Audio] Conversation-Focus inaktiv');
|
||||
this._releaseFocusDeferred();
|
||||
}
|
||||
|
||||
/** TTS-Wiedergabe haart stoppen — z.B. wenn ein Anruf reinkommt.
|
||||
* Released auch sofort den AudioFocus damit der Anruf-Klingelton hoerbar ist. */
|
||||
haltAllPlayback(reason: string = ''): void {
|
||||
console.log('[Audio] haltAllPlayback: %s', reason || '(no reason)');
|
||||
this._conversationFocusActive = false;
|
||||
this.stopPlayback();
|
||||
}
|
||||
|
||||
// --- Berechtigungen ---
|
||||
|
||||
async requestMicrophonePermission(): Promise<boolean> {
|
||||
|
||||
Reference in New Issue
Block a user