fix: preserve recorder blob without object-url refetch
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:45:23 +02:00
parent 6dd7d2c4e5
commit 800214976f
2 changed files with 23 additions and 42 deletions

View File

@ -9,12 +9,12 @@ import { useState, useCallback, useEffect, useRef } from "react";
export function useAudioRecorder() {
const [isLoading, setIsLoading] = useState(false);
const [isRecording, setIsRecording] = useState(false);
const [recordings, setRecordings] = useState<string[]>([]);
const [currentRecording, setCurrentRecording] = useState<string | null>(null);
const [currentRecording, setCurrentRecording] = useState<Blob | null>(null);
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const chunksRef = useRef<Blob[]>([]);
const streamRef = useRef<MediaStream | null>(null);
const stopResolverRef = useRef<((value: { blob: Blob }) => void) | null>(null);
/** Choisit le meilleur mimeType supporté par le navigateur. */
const getMimeType = useCallback(() => {
@ -54,7 +54,6 @@ export function useAudioRecorder() {
const mimeType = getMimeType();
const recorder = new MediaRecorder(stream, {
mimeType: mimeType || undefined,
audioBitsPerSecond: 128000,
});
recorder.ondataavailable = (e) => {
@ -66,10 +65,11 @@ export function useAudioRecorder() {
recorder.onstop = () => {
const actualMime = recorder.mimeType || mimeType || "audio/webm";
const blob = new Blob(chunksRef.current, { type: actualMime });
const url = URL.createObjectURL(blob);
setRecordings((prev) => [...prev, url]);
setCurrentRecording(url);
setCurrentRecording(blob);
stopResolverRef.current?.({ blob });
stopResolverRef.current = null;
mediaRecorderRef.current = null;
chunksRef.current = [];
// Arrête les pistes micro
stream.getTracks().forEach((t) => t.stop());
@ -92,26 +92,15 @@ export function useAudioRecorder() {
if (!isRecording || !mediaRecorderRef.current) return;
setIsLoading(true);
return new Promise<{ blob: Blob; url: string }>((resolve) => {
return new Promise<{ blob: Blob }>((resolve) => {
const recorder = mediaRecorderRef.current!;
const mimeType = recorder.mimeType || getMimeType() || "audio/webm";
const ext = getExtension(mimeType);
const originalOnStop = recorder.onstop;
recorder.onstop = (ev) => {
// Appelle le handler de base (qui crée le blob + currentRecording)
if (originalOnStop) originalOnStop.call(recorder, ev);
const blob = new Blob(chunksRef.current, { type: mimeType });
const url = URL.createObjectURL(blob);
setIsRecording(false);
setIsLoading(false);
resolve({ blob, url });
};
stopResolverRef.current = resolve;
recorder.requestData();
recorder.stop();
setIsRecording(false);
setIsLoading(false);
});
}, [isRecording, getMimeType, getExtension]);
}, [isRecording]);
const toggleRecording = useCallback(async () => {
if (isRecording) {
@ -122,25 +111,21 @@ export function useAudioRecorder() {
}, [isRecording, startRecording, stopRecording]);
const clearRecordings = useCallback(() => {
recordings.forEach((url) => URL.revokeObjectURL(url));
setRecordings([]);
setCurrentRecording(null);
}, [recordings]);
}, []);
// Cleanup au démontage
useEffect(() => {
return () => {
recordings.forEach((url) => URL.revokeObjectURL(url));
if (streamRef.current) {
streamRef.current.getTracks().forEach((t) => t.stop());
}
};
}, [recordings]);
}, []);
return {
isLoading,
isRecording,
recordings,
currentRecording,
startRecording,
stopRecording,

View File

@ -109,20 +109,16 @@ export default function RecipeForm() {
return () => clearInterval(id)
}, [pageState])
// Convertit l'enregistrement en File
// Convertit l'enregistrement en File sans repasser par une blob URL,
// pour éviter toute altération ou perte lors du fetch() local.
useEffect(() => {
if (!currentRecording) return
fetch(currentRecording)
.then((res) => res.blob())
.then((blob) => {
// Détecte le format réel du blob (webm, mp4, ogg…)
const mime = blob.type || "audio/webm"
const ext = mime.includes("mp4") ? "m4a" : mime.includes("ogg") ? "ogg" : "webm"
const file = new File([blob], `recording.${ext}`, { type: mime })
setAudioFile(file)
setPageState("review")
setError("")
})
const mime = currentRecording.type || "audio/webm"
const ext = mime.includes("mp4") ? "m4a" : mime.includes("ogg") ? "ogg" : "webm"
const file = new File([currentRecording], `recording.${ext}`, { type: mime })
setAudioFile(file)
setPageState("review")
setError("")
}, [currentRecording])
// Timer