fix: Mikrofon hängt — PipeWire-Fallback + getUserMedia Timeout [appimage]
All checks were successful
Build AppImage / build (push) Successful in 8m22s

Wenn PipeWire nicht läuft (z.B. PulseAudio/xRDP), wird pipewiresrc
auf Rank 0 gesetzt damit WebKitGTK auf pulsesrc zurückfällt.
getUserMedia bekommt 5s Timeout statt endlos zu hängen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-21 10:30:18 +02:00
parent a01959a55c
commit 6e1a3c41f2
2 changed files with 38 additions and 6 deletions

View file

@ -120,6 +120,12 @@ pkgs.stdenv.mkDerivation {
fi fi
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" "$@" exec "$BIN" "$@"
LAUNCHER LAUNCHER
chmod +x $out/bin/claude-desktop chmod +x $out/bin/claude-desktop

View file

@ -366,15 +366,39 @@
// ============ Voice Interface ============ // ============ Voice Interface ============
// getUserMedia mit Timeout — WebKitGTK hängt manchmal endlos statt zu fehlern
function getUserMediaWithTimeout(constraints: MediaStreamConstraints, timeoutMs = 5000): Promise<MediaStream> {
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() { async function startRecording() {
try { try {
let stream: MediaStream; 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 { try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream = await getUserMediaWithTimeout({ audio: true });
} catch (initialErr) { } catch (initialErr) {
// WebKitGTK wirft verschiedene Fehler: OverconstrainedError, NotFoundError, // 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); console.warn('getUserMedia({audio:true}) fehlgeschlagen, versuche Fallback:', initialErr);
try { try {
@ -383,17 +407,19 @@
if (audioInput) { if (audioInput) {
console.log('Audio-Device gefunden:', audioInput.label || audioInput.deviceId); console.log('Audio-Device gefunden:', audioInput.label || audioInput.deviceId);
stream = await navigator.mediaDevices.getUserMedia({ stream = await getUserMediaWithTimeout({
audio: { deviceId: { exact: audioInput.deviceId } } audio: { deviceId: { exact: audioInput.deviceId } }
}); });
} else { } else {
// Letzter Versuch: komplett ohne Constraints // Letzter Versuch: komplett ohne Constraints
stream = await navigator.mediaDevices.getUserMedia({ audio: {} }); stream = await getUserMediaWithTimeout({ audio: {} });
} }
} catch (fallbackErr) { } catch (fallbackErr) {
throw new Error( throw new Error(
'Kein Mikrofon verfügbar. Unter WebKitGTK (Tauri/Linux) wird ' + 'Mikrofon-Zugriff fehlgeschlagen. Mögliche Ursachen:\n' +
'PipeWire oder PulseAudio mit gst-plugin-pipewire benötigt. ' + '• 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}` `Original: ${initialErr instanceof Error ? initialErr.message : initialErr}`
); );
} }