fix: Mikrofon OverconstrainedError Fallback für WebKitGTK
All checks were successful
Build AppImage / build (push) Has been skipped
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:
parent
2e473cde00
commit
2ff3c8220f
2 changed files with 76 additions and 11 deletions
|
|
@ -256,7 +256,35 @@
|
|||
|
||||
async function startRecording() {
|
||||
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
|
||||
audioContext = new AudioContext();
|
||||
|
|
@ -294,7 +322,10 @@
|
|||
console.log('🎤 Aufnahme gestartet' + (vadEnabled ? ' (VAD aktiv)' : ''));
|
||||
} catch (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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
// Transkription (live)
|
||||
let currentTranscript = '';
|
||||
|
||||
// Fehler-Anzeige
|
||||
let micError = '';
|
||||
|
||||
// TTS Audio-Element
|
||||
let ttsAudio: HTMLAudioElement | null = null;
|
||||
|
||||
|
|
@ -58,16 +61,27 @@
|
|||
|
||||
async function startListening() {
|
||||
if (isListening) return;
|
||||
micError = '';
|
||||
|
||||
try {
|
||||
// Mikrofon-Zugriff
|
||||
mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
sampleRate: 16000,
|
||||
},
|
||||
});
|
||||
// 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({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
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
|
||||
audioContext = new AudioContext();
|
||||
|
|
@ -101,9 +115,12 @@
|
|||
// Visualisierung starten
|
||||
visualize();
|
||||
|
||||
console.log('🎤 Aufnahme gestartet');
|
||||
console.log('🎤 Aufnahme gestartet' + (usedFallback ? ' (Fallback-Modus)' : ''));
|
||||
} catch (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>
|
||||
{/if}
|
||||
|
||||
<!-- Mikrofon-Fehler -->
|
||||
{#if micError}
|
||||
<div class="mic-error">
|
||||
⚠️ {micError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Stimmen-Auswahl -->
|
||||
<div class="voice-select">
|
||||
<label for="voice">Claudes Stimme:</label>
|
||||
|
|
@ -691,6 +715,16 @@
|
|||
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 {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
|
|
|
|||
Loading…
Reference in a new issue