diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index b3a702f..c563ce5 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -112,6 +112,8 @@ import wakeWordService, { WAKE_THRESHOLD_MAX, loadWakeThreshold, saveWakeThreshold, + loadBgWakeEnabled, + saveBgWakeEnabled, } from '../services/wakeword'; import ModeSelector from '../components/ModeSelector'; import QRScanner from '../components/QRScanner'; @@ -212,6 +214,8 @@ const SettingsScreen: React.FC = () => { const [wakeStatus, setWakeStatus] = useState(''); const [wakeReadySound, setWakeReadySound] = useState(true); const [wakeThreshold, setWakeThreshold] = useState(WAKE_THRESHOLD_DEFAULT); + // Hintergrund-Wake: auch bei gesperrtem Bildschirm auf das Wake-Wort hoeren. Default aus. + const [bgWake, setBgWake] = useState(false); const [editingPath, setEditingPath] = useState(false); const [xttsVoice, setXttsVoice] = useState(''); const [loadingVoice, setLoadingVoice] = useState(null); @@ -336,6 +340,7 @@ const SettingsScreen: React.FC = () => { }); isWakeReadySoundEnabled().then(setWakeReadySound); loadWakeThreshold().then(setWakeThreshold).catch(() => {}); + loadBgWakeEnabled().then(setBgWake).catch(() => {}); updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {}); audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {}); AsyncStorage.getItem('aria_xtts_voice').then(saved => { @@ -1894,6 +1899,28 @@ const SettingsScreen: React.FC = () => { /> + + + Auch bei gesperrtem Bildschirm zuhören + + AUS (empfohlen): das Wake-Wort greift nur, wenn die App offen ist — + im Hintergrund sind die meisten „Trigger" Fehlalarme (TV, Husten). + AN: ARIA hört auch bei gesperrtem Bildschirm / im Hintergrund auf + „{KEYWORD_LABELS[wakeKeyword as keyof typeof KEYWORD_LABELS] || wakeKeyword}" — mehr Fehlauslöser möglich. + + + { + setBgWake(val); + saveBgWakeEnabled(val).catch(() => {}); + wakeWordService.setBgWakeEnabled(val); + }} + trackColor={{ false: '#2A2A3E', true: '#0096FF' }} + thumbColor={bgWake ? '#FFFFFF' : '#666680'} + /> + + Weiterreden nach der Antwort Nach einer gesprochenen ARIA-Antwort geht das Mikro auf — du kannst ohne diff --git a/android/src/services/wakeword.ts b/android/src/services/wakeword.ts index 2b0771d..6e924c4 100644 --- a/android/src/services/wakeword.ts +++ b/android/src/services/wakeword.ts @@ -65,6 +65,28 @@ export async function saveWakeThreshold(v: number): Promise { await AsyncStorage.setItem(WAKE_THRESHOLD_STORAGE_KEY, String(v)); } +// Hintergrund-Wake: darf das Wake-Wort auch triggern, wenn die App im +// Hintergrund / der Bildschirm gesperrt ist? Default AUS — im Hintergrund +// sind die meisten „Trigger" Fehlalarme (TV, Husten, AudioFocus-Spikes). +// AN = auch bei gesperrtem Bildschirm zuhoeren. Die native Erkennung laeuft +// ohnehin durch (Foreground-Service + Wake-Locks) — dieser Schalter oeffnet +// nur das JS-Gate in onWakeDetected. +export const BG_WAKE_STORAGE_KEY = 'aria_bg_wake_enabled'; + +export async function loadBgWakeEnabled(): Promise { + try { + return (await AsyncStorage.getItem(BG_WAKE_STORAGE_KEY)) === 'true'; + } catch { + return false; + } +} + +export async function saveBgWakeEnabled(enabled: boolean): Promise { + try { + await AsyncStorage.setItem(BG_WAKE_STORAGE_KEY, String(enabled)); + } catch {} +} + /** Verfuegbare Wake-Words — entsprechen den .onnx Dateien in * android/app/src/main/assets/openwakeword/. Custom-Keywords (eigenes * Training via openwakeword Notebook) muessen aktuell als Asset eingebaut @@ -133,6 +155,10 @@ class WakeWordService { * Hintergrund-Detections sind quasi immer false-positives (TV, Husten, * AudioFocus-Switch beim Wechsel zu Musik etc.). */ private inBackground: boolean = false; + /** Wenn true: Wake-Wort triggert auch im Hintergrund / bei gesperrtem + * Bildschirm. Default false. Wird beim Arm aus AsyncStorage geladen und + * bei Aenderung in den Einstellungen via setBgWakeEnabled() aktualisiert. */ + private bgWakeEnabled: boolean = false; /** Re-Entry-Guard fuer onWakeDetected: native kann mehrere * WakeWordDetected-Events emitten BEVOR OpenWakeWord.stop() in JS * resolved (Bridge-Queue + Doze-Backlog). Mit dem Flag wird das zweite @@ -214,7 +240,8 @@ class WakeWordService { this.initInProgress = (async () => { try { const threshold = await loadWakeThreshold(); - console.log('[WakeWord] init mit threshold=%s', threshold); + this.bgWakeEnabled = await loadBgWakeEnabled(); + console.log('[WakeWord] init mit threshold=%s, bgWake=%s', threshold, this.bgWakeEnabled); await OpenWakeWord.init(this.keyword, threshold, DEFAULT_PATIENCE, DEFAULT_DEBOUNCE_MS); // Subscribe nur einmal if (!this.eventSub) { @@ -311,7 +338,14 @@ class WakeWordService { * was als „Wake-Word" reinkommt ist Husten/TV/AudioFocus-Switch. */ setBackground(): void { this.inBackground = true; - console.log('[WakeWord] App im Hintergrund — Detections gesperrt'); + console.log('[WakeWord] App im Hintergrund — Detections %s', + this.bgWakeEnabled ? 'AKTIV (Hintergrund-Wake an)' : 'gesperrt'); + } + + /** Hintergrund-Wake ein/aus schalten (aus den Einstellungen). */ + setBgWakeEnabled(enabled: boolean): void { + this.bgWakeEnabled = enabled; + console.log('[WakeWord] Hintergrund-Wake = %s', enabled); } /** App im Vordergrund: Detections wieder freigeben, plus kurzer Cooldown @@ -326,9 +360,9 @@ class WakeWordService { /** Wake-Word getriggert: Native-Modul pausieren, Konversation starten. */ private async onWakeDetected(): Promise { - if (this.inBackground) { - console.log('[WakeWord] Trigger ignoriert (App im Hintergrund)'); - import('./logger').then(m => m.reportAppDebug('wake.detect', 'ignored: app in background')).catch(()=>{}); + if (this.inBackground && !this.bgWakeEnabled) { + console.log('[WakeWord] Trigger ignoriert (App im Hintergrund, Hintergrund-Wake aus)'); + import('./logger').then(m => m.reportAppDebug('wake.detect', 'ignored: app in background (bg-wake off)')).catch(()=>{}); return; } // Re-Entry-Guard: blocken wenn ein Detection-Zyklus schon laeuft.