I have this react app that records audio using the MediaRecorder API, it then takes the recorded audio data and converts it to base64. The code looks like this:
import React, { useState } from "react";
const App: React.FC = () => {
const [recording, setRecording] = useState(false);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder>();
const [audioChunks, setAudioChunks] = useState<Blob[]>([]);
const handleStartRecording = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
const mediaRecorder = new MediaRecorder(stream);
setMediaRecorder(mediaRecorder);
mediaRecorder.start();
mediaRecorder.addEventListener("dataavailable", handleDataAvailable);
setRecording(true);
};
const handleStopRecording = () => {
if (mediaRecorder) {
mediaRecorder.stop();
setRecording(false);
}
};
const handleDataAvailable = (e: BlobEvent) => {
if (e.data.size > 0) {
setAudioChunks((prev) => [...prev, e.data]);
}
};
const handleDownload = async () => {
if (audioChunks) {
const audioBlob = new Blob(audioChunks);
convertToBase64AndSend(audioBlob);
setMediaRecorder(undefined);
setAudioChunks([]);
}
};
const convertToBase64AndSend = (audioBlob: Blob) => {
const reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = () => {
const base64data = reader.result as string;
console.log(base64data);
}
}
return (
<div>
<button onClick={recording ? handleStopRecording : handleStartRecording}>
{recording ? "Stop Recording" : "Start Recording"}
</button>
{audioChunks.length > 0 && (
<button onClick={handleDownload}>Send Audio</button>
)}
</div>
);
};
export default App;
`
Now the code in this state works exactly as expected. You click start recording and then the audio starts recording, you click stop recording and then the audio stops recording and when you click download, it successfully converts the audio data to base64 and logs it:
I want to modify this code to automatically initiate the download function when I click Stop Recording. So I removed the Download button and made the handleStopRecording() function call the handleDownload() function like so:
const handleStopRecording = () => {
if (mediaRecorder) {
mediaRecorder.stop();
setRecording(false);
}
handleDownload();
};
return (
<div>
<button onClick={recording ? handleStopRecording : handleStartRecording}>
{recording ? "Stop Recording" : "Start Recording"}
</button>
</div>
);
However, now this causes an issue where the base64AudioData that I log is empty. Presumably because the audioChunks hook has not been set properly. It appears that the audioChunks hook only gets set properly when there is an additional button press, but I don't understand react well enough to know why. How can I correct this issue?
You to need to wait for the
audioChunks
state to be set, and since setting a state is async, and in this case it only happens after thedataavailable
event is called, the chunks are not ready when you callhandleDownload()
right aftermediaRecorder.stop()
.A simple solution would be to move the download code to a
useEffect
block, so it would be executed after the chunks are set:However, since you only need the chunks now for the download, and you reset them afterwards, you don't need the
audioChunks
state, and you can call the download directly from thedataavailable
event handler and pass it the current chunk: