feat(app): Hintergrund-Wake als Schalter (default aus)
Punkt 2: das Wake-Wort triggerte nur im Vordergrund — eine bewusste JS-Zeile verwarf jede Hintergrund-Erkennung (native Erkennung + Foreground-Service liefen längst durch). Jetzt hinter Einstellung 'Auch bei gesperrtem Bildschirm zuhören' (default aus, mehr Fehltrigger möglich). Greift live via setBgWakeEnabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,8 @@ import wakeWordService, {
|
|||||||
WAKE_THRESHOLD_MAX,
|
WAKE_THRESHOLD_MAX,
|
||||||
loadWakeThreshold,
|
loadWakeThreshold,
|
||||||
saveWakeThreshold,
|
saveWakeThreshold,
|
||||||
|
loadBgWakeEnabled,
|
||||||
|
saveBgWakeEnabled,
|
||||||
} from '../services/wakeword';
|
} from '../services/wakeword';
|
||||||
import ModeSelector from '../components/ModeSelector';
|
import ModeSelector from '../components/ModeSelector';
|
||||||
import QRScanner from '../components/QRScanner';
|
import QRScanner from '../components/QRScanner';
|
||||||
@@ -212,6 +214,8 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const [wakeStatus, setWakeStatus] = useState<string>('');
|
const [wakeStatus, setWakeStatus] = useState<string>('');
|
||||||
const [wakeReadySound, setWakeReadySound] = useState<boolean>(true);
|
const [wakeReadySound, setWakeReadySound] = useState<boolean>(true);
|
||||||
const [wakeThreshold, setWakeThreshold] = useState<number>(WAKE_THRESHOLD_DEFAULT);
|
const [wakeThreshold, setWakeThreshold] = useState<number>(WAKE_THRESHOLD_DEFAULT);
|
||||||
|
// Hintergrund-Wake: auch bei gesperrtem Bildschirm auf das Wake-Wort hoeren. Default aus.
|
||||||
|
const [bgWake, setBgWake] = useState<boolean>(false);
|
||||||
const [editingPath, setEditingPath] = useState(false);
|
const [editingPath, setEditingPath] = useState(false);
|
||||||
const [xttsVoice, setXttsVoice] = useState('');
|
const [xttsVoice, setXttsVoice] = useState('');
|
||||||
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
||||||
@@ -336,6 +340,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
});
|
});
|
||||||
isWakeReadySoundEnabled().then(setWakeReadySound);
|
isWakeReadySoundEnabled().then(setWakeReadySound);
|
||||||
loadWakeThreshold().then(setWakeThreshold).catch(() => {});
|
loadWakeThreshold().then(setWakeThreshold).catch(() => {});
|
||||||
|
loadBgWakeEnabled().then(setBgWake).catch(() => {});
|
||||||
updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
|
updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
|
||||||
audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {});
|
audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {});
|
||||||
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
||||||
@@ -1894,6 +1899,28 @@ const SettingsScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={[styles.toggleRow, {marginTop: 20, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 16}]}>
|
||||||
|
<View style={styles.toggleInfo}>
|
||||||
|
<Text style={styles.toggleLabel}>Auch bei gesperrtem Bildschirm zuhören</Text>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
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.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={bgWake}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
setBgWake(val);
|
||||||
|
saveBgWakeEnabled(val).catch(() => {});
|
||||||
|
wakeWordService.setBgWakeEnabled(val);
|
||||||
|
}}
|
||||||
|
trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
|
||||||
|
thumbColor={bgWake ? '#FFFFFF' : '#666680'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Text style={[styles.toggleLabel, {marginTop: 20}]}>Weiterreden nach der Antwort</Text>
|
<Text style={[styles.toggleLabel, {marginTop: 20}]}>Weiterreden nach der Antwort</Text>
|
||||||
<Text style={styles.toggleHint}>
|
<Text style={styles.toggleHint}>
|
||||||
Nach einer gesprochenen ARIA-Antwort geht das Mikro auf — du kannst ohne
|
Nach einer gesprochenen ARIA-Antwort geht das Mikro auf — du kannst ohne
|
||||||
|
|||||||
@@ -65,6 +65,28 @@ export async function saveWakeThreshold(v: number): Promise<void> {
|
|||||||
await AsyncStorage.setItem(WAKE_THRESHOLD_STORAGE_KEY, String(v));
|
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<boolean> {
|
||||||
|
try {
|
||||||
|
return (await AsyncStorage.getItem(BG_WAKE_STORAGE_KEY)) === 'true';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveBgWakeEnabled(enabled: boolean): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(BG_WAKE_STORAGE_KEY, String(enabled));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
/** Verfuegbare Wake-Words — entsprechen den .onnx Dateien in
|
/** Verfuegbare Wake-Words — entsprechen den .onnx Dateien in
|
||||||
* android/app/src/main/assets/openwakeword/. Custom-Keywords (eigenes
|
* android/app/src/main/assets/openwakeword/. Custom-Keywords (eigenes
|
||||||
* Training via openwakeword Notebook) muessen aktuell als Asset eingebaut
|
* Training via openwakeword Notebook) muessen aktuell als Asset eingebaut
|
||||||
@@ -133,6 +155,10 @@ class WakeWordService {
|
|||||||
* Hintergrund-Detections sind quasi immer false-positives (TV, Husten,
|
* Hintergrund-Detections sind quasi immer false-positives (TV, Husten,
|
||||||
* AudioFocus-Switch beim Wechsel zu Musik etc.). */
|
* AudioFocus-Switch beim Wechsel zu Musik etc.). */
|
||||||
private inBackground: boolean = false;
|
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
|
/** Re-Entry-Guard fuer onWakeDetected: native kann mehrere
|
||||||
* WakeWordDetected-Events emitten BEVOR OpenWakeWord.stop() in JS
|
* WakeWordDetected-Events emitten BEVOR OpenWakeWord.stop() in JS
|
||||||
* resolved (Bridge-Queue + Doze-Backlog). Mit dem Flag wird das zweite
|
* resolved (Bridge-Queue + Doze-Backlog). Mit dem Flag wird das zweite
|
||||||
@@ -214,7 +240,8 @@ class WakeWordService {
|
|||||||
this.initInProgress = (async () => {
|
this.initInProgress = (async () => {
|
||||||
try {
|
try {
|
||||||
const threshold = await loadWakeThreshold();
|
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);
|
await OpenWakeWord.init(this.keyword, threshold, DEFAULT_PATIENCE, DEFAULT_DEBOUNCE_MS);
|
||||||
// Subscribe nur einmal
|
// Subscribe nur einmal
|
||||||
if (!this.eventSub) {
|
if (!this.eventSub) {
|
||||||
@@ -311,7 +338,14 @@ class WakeWordService {
|
|||||||
* was als „Wake-Word" reinkommt ist Husten/TV/AudioFocus-Switch. */
|
* was als „Wake-Word" reinkommt ist Husten/TV/AudioFocus-Switch. */
|
||||||
setBackground(): void {
|
setBackground(): void {
|
||||||
this.inBackground = true;
|
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
|
/** App im Vordergrund: Detections wieder freigeben, plus kurzer Cooldown
|
||||||
@@ -326,9 +360,9 @@ class WakeWordService {
|
|||||||
|
|
||||||
/** Wake-Word getriggert: Native-Modul pausieren, Konversation starten. */
|
/** Wake-Word getriggert: Native-Modul pausieren, Konversation starten. */
|
||||||
private async onWakeDetected(): Promise<void> {
|
private async onWakeDetected(): Promise<void> {
|
||||||
if (this.inBackground) {
|
if (this.inBackground && !this.bgWakeEnabled) {
|
||||||
console.log('[WakeWord] Trigger ignoriert (App im Hintergrund)');
|
console.log('[WakeWord] Trigger ignoriert (App im Hintergrund, Hintergrund-Wake aus)');
|
||||||
import('./logger').then(m => m.reportAppDebug('wake.detect', 'ignored: app in background')).catch(()=>{});
|
import('./logger').then(m => m.reportAppDebug('wake.detect', 'ignored: app in background (bg-wake off)')).catch(()=>{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Re-Entry-Guard: blocken wenn ein Detection-Zyklus schon laeuft.
|
// Re-Entry-Guard: blocken wenn ein Detection-Zyklus schon laeuft.
|
||||||
|
|||||||
Reference in New Issue
Block a user