feat: App TTS-Einstellungen vereinfacht + Mund-Button fuer lokales Muten
SettingsScreen: - Piper-Reste entfernt (defaultVoice, highlightVoice, Speed-Slider, Highlight-Trigger-Info) - Nur noch EIN Toggle 'Sprachausgabe auf diesem Geraet' — geraetelokal, persistent in aria_tts_enabled (AsyncStorage) - Keine Config-Propagation mehr via RVS (das waere ja global gewesen) - Hinweis dass Stimme + Voice-Cloning zentral in der Diagnose sind ChatScreen: Mund-Button (👄 / 🤐) - Neben Ohr-Button im Eingabebereich, NUR sichtbar wenn TTS im Setting grundsaetzlich aktiv ist - Tap toggelt Mute: 👄 an / 🤐 rot gemutet - Persistent in aria_tts_muted (AsyncStorage) - Stoppt bei Muten sofort laufende Wiedergabe (stopPlayback) - Settings-Toggle wird alle 2s gepollt damit Aenderungen greifen (einfache Loesung ohne globalen State-Context) Audio-Handling respektiert lokalen Zustand - Incoming audio/audio_pcm: nur abspielen wenn ttsDeviceEnabled && !ttsMuted - Cache wird TROTZDEM immer geschrieben — Play-Button funktioniert spaeter aus Cache, auch waehrend Mute - audioService.handlePcmChunk akzeptiert silent-Flag: skipt AudioTrack aber baut weiterhin den WAV-Cache pro messageId Jedes Android-Geraet mit der App hat seinen eigenen Mute-Zustand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -335,7 +335,8 @@ class AudioService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Einen PCM-Chunk aus einer audio_pcm Nachricht empfangen und spielen/cachen.
|
||||
/** Einen PCM-Chunk aus einer audio_pcm Nachricht empfangen.
|
||||
* silent=true → nur cachen, nicht abspielen (z.B. wenn TTS geraetelokal gemutet).
|
||||
* Gibt bei final=true den Cache-Pfad zurueck (file://) oder '' wenn nicht gecached. */
|
||||
async handlePcmChunk(payload: {
|
||||
base64: string;
|
||||
@@ -344,8 +345,10 @@ class AudioService {
|
||||
messageId?: string;
|
||||
chunk?: number;
|
||||
final?: boolean;
|
||||
silent?: boolean;
|
||||
}): Promise<string> {
|
||||
if (!PcmStreamPlayer) {
|
||||
const silent = !!payload.silent;
|
||||
if (!silent && !PcmStreamPlayer) {
|
||||
console.warn('[Audio] PcmStreamPlayer Native Module nicht verfuegbar');
|
||||
return '';
|
||||
}
|
||||
@@ -358,10 +361,8 @@ class AudioService {
|
||||
|
||||
// Neuer Stream? (messageId Wechsel oder nicht aktiv)
|
||||
if (!this.pcmStreamActive || this.pcmMessageId !== messageId) {
|
||||
// Vorherigen Stream clean beenden (falls da)
|
||||
if (this.pcmStreamActive) {
|
||||
try { await PcmStreamPlayer.stop(); } catch {}
|
||||
// Altes Buffer verwerfen (wurde nicht final — neue Message kam dazwischen)
|
||||
if (this.pcmStreamActive && !silent) {
|
||||
try { await PcmStreamPlayer!.stop(); } catch {}
|
||||
this.pcmBuffer = [];
|
||||
this.pcmBytesCollected = 0;
|
||||
}
|
||||
@@ -371,35 +372,36 @@ class AudioService {
|
||||
this.pcmChannels = channels;
|
||||
this.pcmBuffer = [];
|
||||
this.pcmBytesCollected = 0;
|
||||
try {
|
||||
await PcmStreamPlayer.start(sampleRate, channels);
|
||||
} catch (err) {
|
||||
console.error('[Audio] PcmStreamPlayer.start fehlgeschlagen:', err);
|
||||
this.pcmStreamActive = false;
|
||||
return '';
|
||||
if (!silent) {
|
||||
try {
|
||||
await PcmStreamPlayer!.start(sampleRate, channels);
|
||||
} catch (err) {
|
||||
console.error('[Audio] PcmStreamPlayer.start fehlgeschlagen:', err);
|
||||
this.pcmStreamActive = false;
|
||||
return '';
|
||||
}
|
||||
AudioFocus?.requestDuck().catch(() => {});
|
||||
}
|
||||
// Audio-Focus: andere Apps ducken
|
||||
AudioFocus?.requestDuck().catch(() => {});
|
||||
}
|
||||
|
||||
// Chunk abspielen + cachen
|
||||
// Chunk — immer cachen, nur bei !silent auch abspielen
|
||||
if (base64) {
|
||||
try { await PcmStreamPlayer.writeChunk(base64); } catch (err) { console.warn('[Audio] writeChunk', err); }
|
||||
// Buffer fuer Cache sammeln (wenn noch nicht zu gross)
|
||||
if (!silent) {
|
||||
try { await PcmStreamPlayer!.writeChunk(base64); } catch (err) { console.warn('[Audio] writeChunk', err); }
|
||||
}
|
||||
if (messageId && this.pcmBytesCollected < this.PCM_MAX_CACHE_BYTES) {
|
||||
this.pcmBuffer.push(base64);
|
||||
// 4 base64-chars ≈ 3 bytes — grobe Schaetzung
|
||||
this.pcmBytesCollected += Math.floor(base64.length * 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFinal) {
|
||||
// Stream sauber beenden (spielt noch bis Puffer leer ist)
|
||||
try { await PcmStreamPlayer.end(); } catch {}
|
||||
if (!silent) {
|
||||
try { await PcmStreamPlayer!.end(); } catch {}
|
||||
AudioFocus?.release().catch(() => {});
|
||||
}
|
||||
this.pcmStreamActive = false;
|
||||
AudioFocus?.release().catch(() => {});
|
||||
|
||||
// Aus gesammelten PCM-Chunks eine WAV-Datei fuer Replay bauen
|
||||
if (messageId && this.pcmBuffer.length > 0) {
|
||||
const audioPath = await this._savePcmBufferAsWav(messageId);
|
||||
this.pcmBuffer = [];
|
||||
|
||||
Reference in New Issue
Block a user