From e0653ddcabdf4e2f2c86cd4fdd768a174e4d010a Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 20 Apr 2026 22:17:52 +0200 Subject: [PATCH] fix(voice): robust microphone fallback for WebKitGTK "Invalid constraint" WebKitGTK throws various error types (TypeError, OverconstrainedError, etc.) when getUserMedia is called. Instead of only catching specific error names, now ALL errors trigger device-enumeration fallback chain: 1. { audio: true } 2. Explicit deviceId from enumerateDevices() 3. { audio: {} } as last resort Co-Authored-By: Claude Opus 4.6 --- src/lib/components/ChatPanel.svelte | 25 +++++++++++++------------ src/lib/components/VoicePanel.svelte | 20 +++++++++++++++++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte index 485d9f5..f2ead0a 100644 --- a/src/lib/components/ChatPanel.svelte +++ b/src/lib/components/ChatPanel.svelte @@ -261,28 +261,29 @@ 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); + // WebKitGTK wirft verschiedene Fehler: OverconstrainedError, NotFoundError, + // TypeError("Invalid constraint"), etc. Bei JEDEM Fehler Fallback versuchen. + console.warn('getUserMedia({audio:true}) fehlgeschlagen, versuche Fallback:', initialErr); + try { 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 } + audio: { deviceId: { exact: audioInput.deviceId } } }); } else { - throw new Error( - 'Kein Mikrofon gefunden. Unter WebKitGTK (Tauri/Linux) wird ' + - 'PipeWire oder PulseAudio mit gst-plugin-pipewire benötigt.' - ); + // Letzter Versuch: komplett ohne Constraints + stream = await navigator.mediaDevices.getUserMedia({ audio: {} }); } - } else { - throw initialErr; + } catch (fallbackErr) { + throw new Error( + 'Kein Mikrofon verfügbar. Unter WebKitGTK (Tauri/Linux) wird ' + + 'PipeWire oder PulseAudio mit gst-plugin-pipewire benötigt. ' + + `Original: ${initialErr instanceof Error ? initialErr.message : initialErr}` + ); } } diff --git a/src/lib/components/VoicePanel.svelte b/src/lib/components/VoicePanel.svelte index 8f9c37d..4516ec1 100644 --- a/src/lib/components/VoicePanel.svelte +++ b/src/lib/components/VoicePanel.svelte @@ -77,10 +77,24 @@ }); 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 }); + // WebKitGTK wirft diverse Fehler (OverconstrainedError, TypeError "Invalid constraint", etc.) + console.warn('Mikrofon-Constraints fehlgeschlagen, versuche Fallbacks:', constraintErr); + try { + mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); + } catch (_) { + // Auch { audio: true } fehlgeschlagen — versuche explizites Device + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioInput = devices.find(d => d.kind === 'audioinput'); + if (audioInput) { + mediaStream = await navigator.mediaDevices.getUserMedia({ + audio: { deviceId: { exact: audioInput.deviceId } } + }); + } else { + mediaStream = await navigator.mediaDevices.getUserMedia({ audio: {} }); + } + } usedFallback = true; - console.log('🎤 Mikrofon mit Fallback { audio: true } geöffnet'); + console.log('🎤 Mikrofon mit Fallback geöffnet'); } // Audio-Kontext für Visualisierung