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() {
|
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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,16 +61,27 @@
|
||||||
|
|
||||||
async function startListening() {
|
async function startListening() {
|
||||||
if (isListening) return;
|
if (isListening) return;
|
||||||
|
micError = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Mikrofon-Zugriff
|
// Mikrofon-Zugriff — zuerst mit optimalen Constraints versuchen,
|
||||||
mediaStream = await navigator.mediaDevices.getUserMedia({
|
// bei OverconstrainedError (z.B. WebKitGTK/Tauri) auf Fallback ausweichen
|
||||||
audio: {
|
let usedFallback = false;
|
||||||
echoCancellation: true,
|
try {
|
||||||
noiseSuppression: true,
|
mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
sampleRate: 16000,
|
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
|
// 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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue