From dde15813be57fdf7c1dc009019df7bc07cac1864 Mon Sep 17 00:00:00 2001 From: Ender Date: Fri, 24 Oct 2025 03:10:52 +0200 Subject: [PATCH] fix(recorder): choose supported MediaRecorder mime (webm/mp4) and use it for blob/upload to improve playback compatibility --- apps/admin/src/features/recorder/Recorder.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/admin/src/features/recorder/Recorder.tsx b/apps/admin/src/features/recorder/Recorder.tsx index 1aee400..4c0de9f 100644 --- a/apps/admin/src/features/recorder/Recorder.tsx +++ b/apps/admin/src/features/recorder/Recorder.tsx @@ -4,6 +4,7 @@ import { Box, Button, Stack, Typography } from '@mui/material'; export default function Recorder({ onTranscript }: { onTranscript?: (t: string) => void }) { const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); + const mimeRef = useRef('audio/webm'); const [recording, setRecording] = useState(false); const [audioUrl, setAudioUrl] = useState(null); const [audioBlob, setAudioBlob] = useState(null); @@ -29,7 +30,24 @@ export default function Recorder({ onTranscript }: { onTranscript?: (t: string) const stream = await requestStream(); if (!stream) return; - const mr = new MediaRecorder(stream); + // Pick a supported mimeType (Safari prefers audio/mp4, Chrome supports audio/webm) + const candidates = [ + 'audio/webm;codecs=opus', + 'audio/webm', + 'audio/mp4;codecs=opus', + 'audio/mp4' + ]; + let selected: string | undefined; + for (const c of candidates) { + // @ts-ignore + if ((window as any).MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(c)) { + selected = c; + break; + } + } + mimeRef.current = selected || 'audio/webm'; + + const mr = selected ? new MediaRecorder(stream, { mimeType: selected }) : new MediaRecorder(stream); mediaRecorderRef.current = mr; chunksRef.current = []; @@ -39,7 +57,7 @@ export default function Recorder({ onTranscript }: { onTranscript?: (t: string) } }; mr.onstop = () => { - const blob = new Blob(chunksRef.current, { type: 'audio/webm' }); + const blob = new Blob(chunksRef.current, { type: mimeRef.current }); const url = URL.createObjectURL(blob); setAudioUrl((prev) => { if (prev) URL.revokeObjectURL(prev); @@ -71,7 +89,8 @@ export default function Recorder({ onTranscript }: { onTranscript?: (t: string) return; } const form = new FormData(); - form.append('audio', audioBlob, 'recording.webm'); + const ext = mimeRef.current.includes('mp4') ? 'm4a' : 'webm'; + form.append('audio', audioBlob, `recording.${ext}`); const res = await fetch('/api/media/audio', { method: 'POST', body: form,