fix(web): robuster Mikrofon-Pfad fuer iOS-Safari + mp4-Format
- getUserMedia/MediaRecorder-Support pruefen, klare Fehlermeldung mit error.name (In-App-Browser/Privat-Modus/Permission) statt pauschalem "HTTPS noetig?" - Safari liefert audio/mp4 (nicht webm): tatsaechlichen mimeType verwenden und als format mitschicken; Server (faster-whisper) erkennt das Format am Inhalt (mp4/AAC verifiziert). Asset-Version v7 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7ebba4e755
commit
d87ebdf457
2 changed files with 26 additions and 8 deletions
|
|
@ -201,21 +201,39 @@ let recChunks = [];
|
||||||
|
|
||||||
async function startRecording() {
|
async function startRecording() {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
|
const md = navigator.mediaDevices;
|
||||||
|
if (!md || !md.getUserMedia) {
|
||||||
|
addMessage("system", "Mikrofon nicht freigegeben: Bitte die Seite direkt in Safari öffnen (nicht im In-App-Browser) und keinen Privat-Modus verwenden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof MediaRecorder === "undefined") {
|
||||||
|
addMessage("system", "Aufnahme nicht unterstützt (iOS < 14.3?). Bitte iOS aktualisieren.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let stream;
|
let stream;
|
||||||
try {
|
try {
|
||||||
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
stream = await md.getUserMedia({ audio: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addMessage("system", "Mikrofon nicht verfuegbar (HTTPS noetig?). " + e.message);
|
addMessage("system", "Mikrofon-Zugriff fehlgeschlagen: " + (e.name || "") +
|
||||||
|
". Tipp: iPhone → Einstellungen → Safari → Mikrofon = Erlauben/Fragen.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
recChunks = [];
|
recChunks = [];
|
||||||
mediaRecorder = new MediaRecorder(stream);
|
try {
|
||||||
|
mediaRecorder = new MediaRecorder(stream);
|
||||||
|
} catch (e) {
|
||||||
|
stream.getTracks().forEach((t) => t.stop());
|
||||||
|
addMessage("system", "Aufnahme nicht möglich: " + (e.name || e.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
mediaRecorder.ondataavailable = (e) => { if (e.data.size) recChunks.push(e.data); };
|
mediaRecorder.ondataavailable = (e) => { if (e.data.size) recChunks.push(e.data); };
|
||||||
mediaRecorder.onstop = async () => {
|
mediaRecorder.onstop = async () => {
|
||||||
stream.getTracks().forEach((t) => t.stop());
|
stream.getTracks().forEach((t) => t.stop());
|
||||||
const blob = new Blob(recChunks, { type: mediaRecorder.mimeType || "audio/webm" });
|
const mime = mediaRecorder.mimeType || "audio/mp4";
|
||||||
|
const blob = new Blob(recChunks, { type: mime });
|
||||||
const bytes = await blob.arrayBuffer();
|
const bytes = await blob.arrayBuffer();
|
||||||
sendVoice(bytes);
|
// Safari -> audio/mp4, andere -> webm; der Server erkennt das Format am Inhalt.
|
||||||
|
sendVoice(bytes, mime.includes("mp4") || mime.includes("mpeg") ? "mp4" : "webm");
|
||||||
};
|
};
|
||||||
mediaRecorder.start();
|
mediaRecorder.start();
|
||||||
micBtn.classList.remove("bg-emerald-600");
|
micBtn.classList.remove("bg-emerald-600");
|
||||||
|
|
@ -229,13 +247,13 @@ function stopRecording() {
|
||||||
micBtn.classList.add("bg-emerald-600");
|
micBtn.classList.add("bg-emerald-600");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendVoice(bytes) {
|
async function sendVoice(bytes, fmt = "webm") {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
busy = true;
|
busy = true;
|
||||||
await ensureSession();
|
await ensureSession();
|
||||||
statusEl.textContent = "verarbeite Sprache …";
|
statusEl.textContent = "verarbeite Sprache …";
|
||||||
await runTurn("/ws/voice", (ws) => {
|
await runTurn("/ws/voice", (ws) => {
|
||||||
ws.send(JSON.stringify({ type: "start", format: "webm", stream: true, audio_stream: true, ...overrides() }));
|
ws.send(JSON.stringify({ type: "start", format: fmt, stream: true, audio_stream: true, ...overrides() }));
|
||||||
ws.send(bytes);
|
ws.send(bytes);
|
||||||
ws.send(JSON.stringify({ type: "end" }));
|
ws.send(JSON.stringify({ type: "end" }));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,6 @@
|
||||||
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/app.js?v=6"></script>
|
<script src="/app.js?v=7"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue