fix: Mikrofon OverconstrainedError Fallback für WebKitGTK
All checks were successful
Build AppImage / build (push) Has been skipped

Bei OverconstrainedError wird erst Device-Enumeration versucht, dann
Fallback auf einfache Constraints. VoicePanel nutzt ebenfalls Fallback
von strikten auf einfache Audio-Constraints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-20 21:32:11 +02:00
parent 2e473cde00
commit 2ff3c8220f
2 changed files with 76 additions and 11 deletions

View file

@ -256,7 +256,35 @@
async function startRecording() { async function startRecording() {
try { try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); let stream: MediaStream;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (initialErr) {
// OverconstrainedError oder NotFoundError — auf WebKitGTK/Tauri kann
// selbst { audio: true } fehlschlagen. Versuche explizites Device.
if (initialErr instanceof DOMException &&
(initialErr.name === 'OverconstrainedError' || initialErr.name === 'NotFoundError')) {
console.warn('getUserMedia fehlgeschlagen, versuche Device-Enumeration:', initialErr);
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInput = devices.find(d => d.kind === 'audioinput');
if (audioInput) {
console.log('Audio-Device gefunden:', audioInput.label || audioInput.deviceId);
stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: audioInput.deviceId }
});
} else {
throw new Error(
'Kein Mikrofon gefunden. Unter WebKitGTK (Tauri/Linux) wird ' +
'PipeWire oder PulseAudio mit gst-plugin-pipewire benötigt.'
);
}
} else {
throw initialErr;
}
}
// Audio-Analyse für Pegel-Anzeige // Audio-Analyse für Pegel-Anzeige
audioContext = new AudioContext(); audioContext = new AudioContext();
@ -294,7 +322,10 @@
console.log('🎤 Aufnahme gestartet' + (vadEnabled ? ' (VAD aktiv)' : '')); console.log('🎤 Aufnahme gestartet' + (vadEnabled ? ' (VAD aktiv)' : ''));
} catch (err) { } catch (err) {
console.error('Mikrofon-Zugriff fehlgeschlagen:', err); console.error('Mikrofon-Zugriff fehlgeschlagen:', err);
addMessage('system', `⚠️ Mikrofon-Zugriff fehlgeschlagen: ${err}`); const hint = (err instanceof DOMException && err.name === 'OverconstrainedError')
? ' WebKitGTK (Tauri/Linux) unterstützt ggf. kein Audio-Capture — prüfe PipeWire/GStreamer-Plugins.'
: '';
addMessage('system', `⚠️ Mikrofon-Zugriff fehlgeschlagen: ${err instanceof Error ? err.message : err}${hint}`);
} }
} }

View file

@ -28,6 +28,9 @@
// Transkription (live) // Transkription (live)
let currentTranscript = ''; let currentTranscript = '';
// Fehler-Anzeige
let micError = '';
// TTS Audio-Element // TTS Audio-Element
let ttsAudio: HTMLAudioElement | null = null; let ttsAudio: HTMLAudioElement | null = null;
@ -58,9 +61,13 @@
async function startListening() { async function startListening() {
if (isListening) return; if (isListening) return;
micError = '';
try { try {
// Mikrofon-Zugriff // Mikrofon-Zugriff — zuerst mit optimalen Constraints versuchen,
// bei OverconstrainedError (z.B. WebKitGTK/Tauri) auf Fallback ausweichen
let usedFallback = false;
try {
mediaStream = await navigator.mediaDevices.getUserMedia({ mediaStream = await navigator.mediaDevices.getUserMedia({
audio: { audio: {
echoCancellation: true, echoCancellation: true,
@ -68,6 +75,13 @@
sampleRate: 16000, sampleRate: 16000,
}, },
}); });
console.log('🎤 Mikrofon mit optimalen Constraints geöffnet');
} catch (constraintErr) {
console.warn('Mikrofon-Constraints fehlgeschlagen, versuche Fallback:', constraintErr);
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
usedFallback = true;
console.log('🎤 Mikrofon mit Fallback { audio: true } geöffnet');
}
// Audio-Kontext für Visualisierung // Audio-Kontext für Visualisierung
audioContext = new AudioContext(); audioContext = new AudioContext();
@ -101,9 +115,12 @@
// Visualisierung starten // Visualisierung starten
visualize(); visualize();
console.log('🎤 Aufnahme gestartet'); console.log('🎤 Aufnahme gestartet' + (usedFallback ? ' (Fallback-Modus)' : ''));
} catch (err) { } catch (err) {
console.error('Mikrofon-Fehler:', err); console.error('Mikrofon-Fehler:', err);
micError = `Mikrofon-Zugriff fehlgeschlagen: ${err instanceof Error ? err.message : err}`;
// Fehler nach 8 Sekunden ausblenden
setTimeout(() => { micError = ''; }, 8000);
} }
} }
@ -454,6 +471,13 @@
</div> </div>
{/if} {/if}
<!-- Mikrofon-Fehler -->
{#if micError}
<div class="mic-error">
⚠️ {micError}
</div>
{/if}
<!-- Stimmen-Auswahl --> <!-- Stimmen-Auswahl -->
<div class="voice-select"> <div class="voice-select">
<label for="voice">Claudes Stimme:</label> <label for="voice">Claudes Stimme:</label>
@ -691,6 +715,16 @@
font-size: 0.8rem; font-size: 0.8rem;
} }
.mic-error {
padding: var(--spacing-sm);
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: var(--radius-md);
color: #ef4444;
font-size: 0.8rem;
margin-bottom: var(--spacing-md);
}
.setup-hint { .setup-hint {
text-align: center; text-align: center;
color: var(--text-secondary); color: var(--text-secondary);