Skip to content

Instantly share code, notes, and snippets.

@Armster15
Last active June 10, 2024 05:30
Show Gist options
  • Save Armster15/a1fd4dbb1206985ffc810e23ab4e017d to your computer and use it in GitHub Desktop.
Save Armster15/a1fd4dbb1206985ffc810e23ab4e017d to your computer and use it in GitHub Desktop.
A lightweight React hook for the `MediaRecorder` API that stays out of your way and makes it easy to record audio
import { useRef } from "react";
const ALL_MEDIA_RECORDER_EVENTS = [
"dataavailable",
"error",
"pause",
"resume",
"start",
"stop",
];
export interface UseMediaRecorderOptions {
onChunks?: (chunks: Blob[]) => void;
onRecordingFinished?: (chunks: Blob[]) => void;
onState?: (status: RecordingState) => void;
onDuration?: (duration: number) => void;
}
export const useMediaRecorder = (options?: UseMediaRecorderOptions) => {
const {
onChunks = () => {},
onRecordingFinished = () => {},
onState = () => {},
onDuration = () => {},
} = options ?? {};
const mediaRecorderRef = useRef<MediaRecorder | undefined>(undefined);
const mediaChunksRef = useRef<Blob[]>([]);
const lastBroadcastedStateRef = useRef<RecordingState | undefined>(undefined);
const durationRef = useRef(0);
function handleDataAvailable(ev: BlobEvent) {
mediaChunksRef.current.push(ev.data);
durationRef.current += 1000;
onChunks(mediaChunksRef.current);
onDuration(durationRef.current);
}
function handleAllEvents() {
if (!mediaRecorderRef.current) return;
const state = mediaRecorderRef.current.state;
if (lastBroadcastedStateRef.current !== state) {
onState(state);
lastBroadcastedStateRef.current = state;
}
}
async function initRecording() {
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (err) {
throw new Error("Permission must be granted to record audio");
}
const recorder = new MediaRecorder(stream);
// add event listeners
recorder.addEventListener("dataavailable", handleDataAvailable);
for (const event of ALL_MEDIA_RECORDER_EVENTS) {
recorder.addEventListener(event, handleAllEvents);
}
mediaRecorderRef.current = recorder;
}
function startRecording() {
// chunk on every 1 second of recording
mediaRecorderRef.current?.start(1000);
}
function stopRecording() {
if (!mediaRecorderRef.current) return;
// request data
mediaRecorderRef.current.requestData();
// stop media recorder
mediaRecorderRef.current.stop();
// stop media stream's tracks
mediaRecorderRef.current.stream.getTracks().forEach((track) => {
track.stop();
track.enabled = false;
});
// remove event listeners
mediaRecorderRef.current.removeEventListener(
"dataavailable",
handleDataAvailable,
);
for (const event of ALL_MEDIA_RECORDER_EVENTS) {
mediaRecorderRef.current.removeEventListener(event, handleAllEvents);
}
// fulfill recording
onRecordingFinished(mediaChunksRef.current);
// reset refs + state
mediaRecorderRef.current = undefined;
mediaChunksRef.current = [];
lastBroadcastedStateRef.current = undefined;
durationRef.current = 0;
}
function pauseRecording() {
mediaRecorderRef.current?.pause();
}
function resumeRecording() {
mediaRecorderRef.current?.resume();
}
return {
initRecording,
startRecording,
stopRecording,
pauseRecording,
resumeRecording,
mimeType: mediaRecorderRef.current?.mimeType,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment