Skip to content

Instantly share code, notes, and snippets.

@cassidoo
Last active November 15, 2024 07:09
Show Gist options
  • Save cassidoo/dd1190c248d60c723de14fe9ee32f450 to your computer and use it in GitHub Desktop.
Save cassidoo/dd1190c248d60c723de14fe9ee32f450 to your computer and use it in GitHub Desktop.
A simple React microphone component, recording audio and showing the blob in the browser, styled with Tailwind.
"use client";
import { useState, useEffect, useRef } from "react";
function SimpleRecordButton() {
const [isRecording, setIsRecording] = useState(false);
const [audioStream, setAudioStream] = useState(null);
const [mediaRecorder, setMediaRecorder] = useState(null);
const [audioBlob, setAudioBlob] = useState(null);
const [recordingTime, setRecordingTime] = useState(0);
const timerRef = useRef(null);
const RECORDING_MAX_DURATION = 240; // 4 minutes in seconds
useEffect(() => {
if (!audioStream) {
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
setAudioStream(stream);
const mediaRecorder = new MediaRecorder(stream);
setMediaRecorder(mediaRecorder);
let audio;
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
audio = [event.data];
}
};
mediaRecorder.onstop = (event) => {
const b = new Blob(audio, { type: "audio/wav" });
setAudioBlob(b);
console.log("audioBlob", b);
};
})
.catch((error) => {
console.error("Error accessing microphone:", error);
});
}
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, [audioStream]);
const handleToggleRecording = (event) => {
event.preventDefault();
if (isRecording) {
stopRecording();
} else {
startRecording();
}
};
const startRecording = () => {
mediaRecorder.start();
setIsRecording(true);
setRecordingTime(0);
setAudioBlob(null);
timerRef.current = setInterval(() => {
setRecordingTime((prevTime) => {
if (prevTime >= RECORDING_MAX_DURATION - 1) {
stopRecording();
return RECORDING_MAX_DURATION;
}
return prevTime + 1;
});
}, 1000);
};
const stopRecording = () => {
mediaRecorder.stop();
setIsRecording(false);
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
const formatTime = (seconds) => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
};
return (
<div>
<button
onClick={handleToggleRecording}
className={`bg-red-400 hover:opacity-80 text-white font-bold py-2 px-4 rounded`}
>
{isRecording ? (
<>
<span className={`mr-3 ${isRecording && "animate-pulse"}`}>●</span>{" "}
Stop Recording
</>
) : audioBlob ? (
"Redo recording"
) : (
"Start Recording"
)}
</button>
<div>
{isRecording && (
<div>
<p>Recording...</p>
<p>Time: {formatTime(recordingTime)}</p>
</div>
)}
</div>
{audioBlob && (
<>
<div>Preview recording before submitting:</div>
<audio controls>
<source src={URL.createObjectURL(audioBlob)} type="audio/wav" />
</audio>
</>
)}
</div>
);
}
export default SimpleRecordButton;
@cassidoo
Copy link
Author

cassidoo commented Oct 2, 2024

As a future reference it might be useful to use 2 space indentation for the gist to be more readable, otherwise nice!

@TimotejKovacka

You can change how tabs are rendered in your personal GitHub settings!
https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-user-account-settings/managing-your-tab-size-rendering-preference

I prefer using tabs instead of spaces for accessibility reasons. With a screen reader, someone going through the code will hear every individual space, which gets to be a lot when you have some deeply nested things. A screen reader reading out the tab characters is much more manageable!

@TimotejKovacka
Copy link

@cassidoo I wasn't aware 😮! I feel a bit dumb about making the comment now... Anyway, I think I will start to do the same thing going forward to accommodate people with various disabilities 👍 Thanks for letting me on with this configuration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment