feat(audio): Foreground-Service haelt TTS am Leben bei minimierter App
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>
This commit is contained in:
@@ -27,6 +27,7 @@ import audioService from '../services/audio';
|
||||
import wakeWordService from '../services/wakeword';
|
||||
import phoneCallService from '../services/phoneCall';
|
||||
import { playWakeReadySound } from '../services/wakeReadySound';
|
||||
import { startBackgroundAudio, stopBackgroundAudio } from '../services/backgroundAudio';
|
||||
import updateService from '../services/updater';
|
||||
import VoiceButton from '../components/VoiceButton';
|
||||
import FileUpload, { FileData } from '../components/FileUpload';
|
||||
@@ -568,12 +569,16 @@ const ChatScreen: React.FC = () => {
|
||||
|
||||
// TTS-Lifecycle: solange ARIA spricht und Wake-Word verfuegbar ist,
|
||||
// parallel mitlauschen — User kann "Computer" sagen statt manuell tappen.
|
||||
// PLUS: Foreground-Service starten damit Android den App-Prozess nicht
|
||||
// killt wenn die App im Hintergrund ist (TTS waere sonst mitten im Satz weg).
|
||||
const unsubTtsStart = audioService.onPlaybackStarted(() => {
|
||||
startBackgroundAudio().catch(() => {});
|
||||
if (wakeWordService.isConversing() && wakeWordService.hasWakeWord()) {
|
||||
wakeWordService.startBargeListening().catch(() => {});
|
||||
}
|
||||
});
|
||||
const unsubTtsEnd = audioService.onPlaybackFinished(() => {
|
||||
stopBackgroundAudio().catch(() => {});
|
||||
// Vor naechster Aufnahme: barge-listening aus damit der AudioRecorder
|
||||
// das Mikro greifen kann.
|
||||
wakeWordService.stopBargeListening().catch(() => {});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Platform, PermissionsAndroid, NativeModules, ToastAndroid } from 'react
|
||||
import Sound from 'react-native-sound';
|
||||
import RNFS from 'react-native-fs';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { stopBackgroundAudio } from './backgroundAudio';
|
||||
import AudioRecorderPlayer, {
|
||||
AudioEncoderAndroidType,
|
||||
AudioSourceAndroidType,
|
||||
@@ -898,6 +899,9 @@ class AudioService {
|
||||
|
||||
/** Laufende Wiedergabe stoppen + Queue leeren */
|
||||
stopPlayback(): void {
|
||||
// Foreground-Service auch stoppen — sonst bleibt die Notification haengen
|
||||
// wenn Wiedergabe abgebrochen wird (Anruf, Cancel, Barge-In).
|
||||
stopBackgroundAudio().catch(() => {});
|
||||
this.audioQueue = [];
|
||||
this.isPlaying = false;
|
||||
if (this.currentSound) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Background-Audio: ARIAs TTS soll auch bei minimierter App weiterlaufen.
|
||||
* Wir starten dafuer einen Foreground-Service mit foregroundServiceType=
|
||||
* mediaPlayback, der eine persistente Notification zeigt waehrend ARIA spricht.
|
||||
*
|
||||
* API ist intentional simpel — start() vor TTS-Wiedergabe, stop() danach.
|
||||
* Idempotent: mehrfaches start/stop ist sicher.
|
||||
*/
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
interface BackgroundAudioNative {
|
||||
start(): Promise<boolean>;
|
||||
stop(): Promise<boolean>;
|
||||
}
|
||||
|
||||
const { BackgroundAudio } = NativeModules as { BackgroundAudio?: BackgroundAudioNative };
|
||||
|
||||
let active = false;
|
||||
|
||||
export async function startBackgroundAudio(): Promise<void> {
|
||||
if (active || !BackgroundAudio) return;
|
||||
try {
|
||||
await BackgroundAudio.start();
|
||||
active = true;
|
||||
console.log('[BackgroundAudio] Foreground-Service gestartet');
|
||||
} catch (err: any) {
|
||||
console.warn('[BackgroundAudio] start fehlgeschlagen:', err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopBackgroundAudio(): Promise<void> {
|
||||
if (!active || !BackgroundAudio) return;
|
||||
try {
|
||||
await BackgroundAudio.stop();
|
||||
active = false;
|
||||
console.log('[BackgroundAudio] Foreground-Service gestoppt');
|
||||
} catch (err: any) {
|
||||
console.warn('[BackgroundAudio] stop fehlgeschlagen:', err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
export function isBackgroundAudioActive(): boolean {
|
||||
return active;
|
||||
}
|
||||
Reference in New Issue
Block a user