fix: prefer preview-compatible audio recording formats
All checks were successful
Build & Deploy to K3s / build-and-deploy (push) Successful in 35s

This commit is contained in:
ordinarthur 2026-04-11 16:49:39 +02:00
parent 800214976f
commit 21cd789a62
2 changed files with 36 additions and 9 deletions

View File

@ -18,14 +18,31 @@ export function useAudioRecorder() {
/** Choisit le meilleur mimeType supporté par le navigateur. */ /** Choisit le meilleur mimeType supporté par le navigateur. */
const getMimeType = useCallback(() => { const getMimeType = useCallback(() => {
const types = [ const audio = document.createElement("audio");
"audio/webm;codecs=opus", const isAppleDevice = /iPhone|iPad|iPod|Mac/.test(navigator.userAgent);
"audio/webm",
"audio/mp4", const types = isAppleDevice
"audio/ogg;codecs=opus", ? [
]; "audio/mp4",
"audio/webm;codecs=opus",
"audio/webm",
"audio/ogg;codecs=opus",
]
: [
"audio/webm;codecs=opus",
"audio/webm",
"audio/ogg;codecs=opus",
"audio/mp4",
];
for (const type of types) { for (const type of types) {
if (MediaRecorder.isTypeSupported(type)) return type; const baseType = type.split(";")[0];
if (
MediaRecorder.isTypeSupported(type) &&
audio.canPlayType(baseType) !== ""
) {
return type;
}
} }
return ""; // fallback : le navigateur choisira return ""; // fallback : le navigateur choisira
}, []); }, []);
@ -45,7 +62,8 @@ export function useAudioRecorder() {
audio: { audio: {
echoCancellation: true, echoCancellation: true,
noiseSuppression: true, noiseSuppression: true,
sampleRate: 44100, autoGainControl: true,
channelCount: 1,
}, },
}); });
streamRef.current = stream; streamRef.current = stream;

View File

@ -83,6 +83,7 @@ export default function RecipeForm() {
const [audioFile, setAudioFile] = useState<File | null>(null) const [audioFile, setAudioFile] = useState<File | null>(null)
const [pageState, setPageState] = useState<PageState>("idle") const [pageState, setPageState] = useState<PageState>("idle")
const [error, setError] = useState("") const [error, setError] = useState("")
const [previewError, setPreviewError] = useState("")
const [recordingTime, setRecordingTime] = useState(0) const [recordingTime, setRecordingTime] = useState(0)
const [user, setUser] = useState<User | null>(null) const [user, setUser] = useState<User | null>(null)
const [tipIndex, setTipIndex] = useState(0) const [tipIndex, setTipIndex] = useState(0)
@ -119,6 +120,7 @@ export default function RecipeForm() {
setAudioFile(file) setAudioFile(file)
setPageState("review") setPageState("review")
setError("") setError("")
setPreviewError("")
}, [currentRecording]) }, [currentRecording])
// Timer // Timer
@ -572,7 +574,9 @@ export default function RecipeForm() {
if (isPlaying) { if (isPlaying) {
audio.pause() audio.pause()
} else { } else {
audio.play() audio.play().catch(() => {
setPreviewError("Impossible de lire cet aperçu audio sur ce navigateur.")
})
} }
}} }}
className="h-12 w-12 shrink-0 rounded-full bg-gradient-to-br from-orange-500 to-amber-500 text-white shadow-md flex items-center justify-center hover:shadow-lg hover:scale-105 transition-all" className="h-12 w-12 shrink-0 rounded-full bg-gradient-to-br from-orange-500 to-amber-500 text-white shadow-md flex items-center justify-center hover:shadow-lg hover:scale-105 transition-all"
@ -612,15 +616,20 @@ export default function RecipeForm() {
<audio <audio
id="review-audio" id="review-audio"
src={audioUrl} src={audioUrl}
preload="metadata"
onPlay={() => setIsPlaying(true)} onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)} onPause={() => setIsPlaying(false)}
onEnded={() => setIsPlaying(false)} onEnded={() => setIsPlaying(false)}
onError={() => setPreviewError("La lecture de l'aperçu audio a échoué.")}
className="hidden" className="hidden"
/> />
<div className="mt-3 text-[11px] text-muted-foreground text-right"> <div className="mt-3 text-[11px] text-muted-foreground text-right">
{(audioFile.size / 1024).toFixed(0)} KB {(audioFile.size / 1024).toFixed(0)} KB
</div> </div>
{previewError && (
<p className="mt-2 text-xs text-red-500 text-right">{previewError}</p>
)}
</div> </div>
{/* Actions */} {/* Actions */}