diff --git a/nix/default.nix b/nix/default.nix index 49478be..8647c6a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -120,6 +120,12 @@ pkgs.stdenv.mkDerivation { fi fi + # GStreamer/PipeWire: Wenn PipeWire NICHT laeuft, pipewiresrc runterranken + # damit WebKitGTK auf pulsesrc zurueckfaellt (sonst haengt getUserMedia endlos) + if ! pgrep -x pipewire >/dev/null 2>&1; then + export GST_PLUGIN_FEATURE_RANK="pipewiresrc:0,pipewiresink:0" + fi + exec "$BIN" "$@" LAUNCHER chmod +x $out/bin/claude-desktop diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte index 49291db..d45a16a 100644 --- a/src/lib/components/ChatPanel.svelte +++ b/src/lib/components/ChatPanel.svelte @@ -366,15 +366,39 @@ // ============ Voice Interface ============ + // getUserMedia mit Timeout — WebKitGTK hängt manchmal endlos statt zu fehlern + function getUserMediaWithTimeout(constraints: MediaStreamConstraints, timeoutMs = 5000): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`getUserMedia Timeout nach ${timeoutMs}ms — GStreamer-Pipeline hängt`)); + }, timeoutMs); + + navigator.mediaDevices.getUserMedia(constraints) + .then((stream) => { + clearTimeout(timer); + resolve(stream); + }) + .catch((err) => { + clearTimeout(timer); + reject(err); + }); + }); + } + async function startRecording() { try { let stream: MediaStream; + // Prüfe zuerst ob mediaDevices überhaupt verfügbar ist + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + throw new Error('MediaDevices API nicht verfügbar — WebKitGTK braucht GStreamer + PipeWire'); + } + try { - stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + stream = await getUserMediaWithTimeout({ audio: true }); } catch (initialErr) { // WebKitGTK wirft verschiedene Fehler: OverconstrainedError, NotFoundError, - // TypeError("Invalid constraint"), etc. Bei JEDEM Fehler Fallback versuchen. + // TypeError("Invalid constraint"), Timeout. Bei JEDEM Fehler Fallback versuchen. console.warn('getUserMedia({audio:true}) fehlgeschlagen, versuche Fallback:', initialErr); try { @@ -383,17 +407,19 @@ if (audioInput) { console.log('Audio-Device gefunden:', audioInput.label || audioInput.deviceId); - stream = await navigator.mediaDevices.getUserMedia({ + stream = await getUserMediaWithTimeout({ audio: { deviceId: { exact: audioInput.deviceId } } }); } else { // Letzter Versuch: komplett ohne Constraints - stream = await navigator.mediaDevices.getUserMedia({ audio: {} }); + stream = await getUserMediaWithTimeout({ audio: {} }); } } catch (fallbackErr) { throw new Error( - 'Kein Mikrofon verfügbar. Unter WebKitGTK (Tauri/Linux) wird ' + - 'PipeWire oder PulseAudio mit gst-plugin-pipewire benötigt. ' + + 'Mikrofon-Zugriff fehlgeschlagen. Mögliche Ursachen:\n' + + '• GStreamer-Plugins fehlen (gst-plugin-pipewire)\n' + + '• PipeWire läuft nicht\n' + + '• App wurde nicht über den Nix-Wrapper gestartet\n' + `Original: ${initialErr instanceof Error ? initialErr.message : initialErr}` ); }