diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index 966d977..d550207 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -27,6 +27,10 @@ import { TTS_PREROLL_MIN_SEC, TTS_PREROLL_MAX_SEC, TTS_PREROLL_STORAGE_KEY, + VAD_SILENCE_DEFAULT_SEC, + VAD_SILENCE_MIN_SEC, + VAD_SILENCE_MAX_SEC, + VAD_SILENCE_STORAGE_KEY, } from '../services/audio'; import ModeSelector from '../components/ModeSelector'; import QRScanner from '../components/QRScanner'; @@ -82,6 +86,7 @@ const SettingsScreen: React.FC = () => { const [storageSize, setStorageSize] = useState('...'); const [ttsEnabled, setTtsEnabled] = useState(true); const [ttsPrerollSec, setTtsPrerollSec] = useState(TTS_PREROLL_DEFAULT_SEC); + const [vadSilenceSec, setVadSilenceSec] = useState(VAD_SILENCE_DEFAULT_SEC); const [editingPath, setEditingPath] = useState(false); const [xttsVoice, setXttsVoice] = useState(''); const [loadingVoice, setLoadingVoice] = useState(null); @@ -117,6 +122,14 @@ const SettingsScreen: React.FC = () => { } } }); + AsyncStorage.getItem(VAD_SILENCE_STORAGE_KEY).then(saved => { + if (saved != null) { + const n = parseFloat(saved); + if (isFinite(n) && n >= VAD_SILENCE_MIN_SEC && n <= VAD_SILENCE_MAX_SEC) { + setVadSilenceSec(n); + } + } + }); AsyncStorage.getItem('aria_xtts_voice').then(saved => { if (saved) setXttsVoice(saved); }); @@ -555,6 +568,43 @@ const SettingsScreen: React.FC = () => { + {/* === Spracheingabe (geraetelokal) === */} + Spracheingabe + + Stille-Toleranz + + Wie lange du eine Sprechpause machen darfst, bevor die Aufnahme + automatisch beendet und gesendet wird. Hoeher = mehr Zeit zum + Nachdenken; niedriger = schnelleres Senden. + Default: {VAD_SILENCE_DEFAULT_SEC.toFixed(1)}s. + + + { + const next = Math.max(VAD_SILENCE_MIN_SEC, Math.round((vadSilenceSec - 0.5) * 10) / 10); + setVadSilenceSec(next); + AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next)); + }} + disabled={vadSilenceSec <= VAD_SILENCE_MIN_SEC} + > + −0.5 + + {vadSilenceSec.toFixed(1)} s + { + const next = Math.min(VAD_SILENCE_MAX_SEC, Math.round((vadSilenceSec + 0.5) * 10) / 10); + setVadSilenceSec(next); + AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next)); + }} + disabled={vadSilenceSec >= VAD_SILENCE_MAX_SEC} + > + +0.5 + + + + {/* === Sprachausgabe (geraetelokal) === */} Sprachausgabe diff --git a/android/src/services/audio.ts b/android/src/services/audio.ts index 360dfa2..879dbd5 100644 --- a/android/src/services/audio.ts +++ b/android/src/services/audio.ts @@ -74,10 +74,29 @@ const AUDIO_ENCODING = 'audio/wav'; // VAD (Voice Activity Detection) — Stille-Erkennung const VAD_SILENCE_THRESHOLD_DB = -45; // dB unter dem als "Stille" gilt -const VAD_SILENCE_DURATION_MS = 2800; // ms Stille bevor Auto-Stop — laenger = mehr Toleranz fuer Sprechpausen const VAD_SPEECH_THRESHOLD_DB = -28; // dB ueber dem als "Sprache" gilt (Sprach-Gate) — hoeher = weniger Umgebungsgeraeusche const VAD_SPEECH_MIN_MS = 500; // ms Sprache bevor Aufnahme zaehlt — laenger = keine Huestler/Klopfer mehr +// VAD-Stille (in Sekunden) — wie lange Sprechpause toleriert wird, bevor +// die Aufnahme automatisch beendet wird. Einstellbar in den App-Settings. +export const VAD_SILENCE_DEFAULT_SEC = 2.8; +export const VAD_SILENCE_MIN_SEC = 1.0; +export const VAD_SILENCE_MAX_SEC = 8.0; +export const VAD_SILENCE_STORAGE_KEY = 'aria_vad_silence_sec'; + +async function loadVadSilenceMs(): Promise { + try { + const raw = await AsyncStorage.getItem(VAD_SILENCE_STORAGE_KEY); + if (raw != null) { + const n = parseFloat(raw); + if (isFinite(n) && n >= VAD_SILENCE_MIN_SEC && n <= VAD_SILENCE_MAX_SEC) { + return Math.round(n * 1000); + } + } + } catch {} + return Math.round(VAD_SILENCE_DEFAULT_SEC * 1000); +} + // Max-Dauer einer Aufnahme (Notbremse gegen Runaway-Loops). Auf 2 Minuten // hochgezogen damit auch laengere Erklaerungen durchgehen. const MAX_RECORDING_MS = 120000; @@ -238,12 +257,14 @@ class AudioService { // Andere Apps waehrend der Aufnahme pausieren (Musik, Videos etc.) AudioFocus?.requestExclusive().catch(() => {}); - // VAD aktivieren + // VAD aktivieren — Stille-Dauer aus AsyncStorage (Settings-konfigurierbar) this.vadEnabled = autoStop; if (autoStop) { + const vadSilenceMs = await loadVadSilenceMs(); + console.log('[Audio] VAD-Stille:', vadSilenceMs, 'ms'); this.vadTimer = setInterval(() => { const silenceDuration = Date.now() - this.lastSpeechTime; - if (silenceDuration >= VAD_SILENCE_DURATION_MS) { + if (silenceDuration >= vadSilenceMs) { console.log(`[Audio] VAD: ${silenceDuration}ms Stille — Auto-Stop`); this.silenceListeners.forEach(cb => cb()); }