Last active
November 15, 2024 07:09
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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