/** * Background-Audio + Hintergrund-Persistenz: ARIAs TTS, Mic-Aufnahme, * Wake-Word-Lauschen UND der allgemeine Hintergrund-Modus laufen * weiter wenn die App minimiert ist. Wir starten dafuer einen Foreground- * Service mit foregroundServiceType=mediaPlayback|microphone, der eine * persistente Notification zeigt solange irgendein Slot aktiv ist. * * Mehrere Komponenten koennen den Service unabhaengig "halten": * - 'tts' : ARIA spricht * - 'rec' : Aufnahme laeuft * - 'wake' : Wake-Word lauscht passiv (Ohr aktiv) * - 'background' : Persistenter Hintergrund-Modus (Settings-Toggle). * Haelt JS-Engine + WebSocket auch ohne Audio am Leben * → Trigger-Replies, Reconnects, Push-Reaktionen. * * Solange mindestens ein Slot aktiv ist, laeuft der Service. Wenn alle * Slots leer sind, wird er gestoppt. Der Notification-Text passt sich an * den hoechstprioren Slot an (tts > rec > wake > background). */ import { NativeModules } from 'react-native'; interface BackgroundAudioNative { start(reason: string): Promise; stop(): Promise; } const { BackgroundAudio } = NativeModules as { BackgroundAudio?: BackgroundAudioNative }; type Slot = 'tts' | 'rec' | 'wake' | 'background'; const slots = new Set(); // Prioritaet fuer den Notification-Text — hoechste zuerst. 'background' // ist die fallback-Anzeige wenn nichts anderes laeuft. const PRIORITY: Slot[] = ['tts', 'rec', 'wake', 'background']; function topReason(): string { for (const s of PRIORITY) { if (slots.has(s)) return s; } return ''; } async function applyState(): Promise { if (!BackgroundAudio) return; if (slots.size === 0) { try { await BackgroundAudio.stop(); } catch {} console.log('[BackgroundAudio] Service gestoppt (keine Slots)'); return; } const reason = topReason(); try { await BackgroundAudio.start(reason); console.log('[BackgroundAudio] Service aktiv (slot=%s, slots=%s)', reason, [...slots].join('+')); } catch (err: any) { console.warn('[BackgroundAudio] start fehlgeschlagen:', err?.message || err); } } export async function acquireBackgroundAudio(slot: Slot): Promise { if (slots.has(slot)) return; slots.add(slot); await applyState(); } export async function releaseBackgroundAudio(slot: Slot): Promise { if (!slots.has(slot)) return; slots.delete(slot); await applyState(); } export function backgroundAudioActive(): boolean { return slots.size > 0; } // --- Legacy API (nur tts-Slot) — fuer Aufruf-Sites die noch nichts vom Slot- // system wissen. Mappt auf den 'tts'-Slot. --- export const startBackgroundAudio = () => acquireBackgroundAudio('tts'); export const stopBackgroundAudio = () => releaseBackgroundAudio('tts');