skip to Main Content

First, I tried MediaRecorder API to record audio clips but the output files have some issues. Then I tried different libraries such as react-audio-voice-recorder, react-mic, …etc. But they also gave me the same output.

Issues I face:

  1. The output file doesn’t have a length value (therefore audio players don’t show the timeline correctly).
  2. Output files are played on Android phones but not on iPhones.

This example of React-Mic-Gold gives the output I want but, it has announced that React-Mic-Gold no longer supporting

I would appreciate anyone with experience implementing an audio recorder in React here.

2

Answers


  1. Chosen as BEST ANSWER

    I was able to fix the issues I mentioned above. I published a npm package so if any are facing the same problems I encountered, You can try this.

    react-use-audio-recorder


  2. You can refer to this raw code from one of my portfolio used with React, typescript and Tailwind UI

    import React, { useEffect, useState, useRef } from "react";
    
    const formatTime = (seconds: number): string =>
      [Math.floor(seconds / 60), seconds % 60]
        .map((v) => `0${Math.floor(v)}`.slice(-2))
        .join(":");
    
    type IProps = {
      setAnswer: any;
      error: boolean;
      setError: any;
      isRecording?: boolean;
      handleRecording?: any;
    };
    
    const AudioRecorder: React.FC<IProps> = ({
      setAnswer,
      error,
      handleRecording = () => {},
      setError,
    }) => {
      const [recordedUrl, setRecordedUrl] = useState<string>("");
      const [recordingTime, setRecordingTime] = useState<number>(0);
      const [isRecording, setIsRecording] = useState<boolean>(false);
      const [isPlaying, setIsPlaying] = useState<boolean>(false);
      const mediaStream = useRef<MediaStream | null>(null);
      const mediaRecorder = useRef<MediaRecorder | null>(null);
      const chunks = useRef<Blob[]>([]);
      const recordingInterval = useRef<NodeJS.Timeout | null>(null);
      const audioRef = useRef<HTMLAudioElement | null>(null);
    
      useEffect(() => {
        navigator?.mediaDevices?.enumerateDevices().then((devices) => {
          devices.forEach((device) => {
            console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
          });
        });
      }, []);
    
      const startRecording = async () => {
        try {
          const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
          mediaStream.current = stream;
          mediaRecorder.current = new MediaRecorder(stream);
          setIsRecording(true);
          handleRecording(true);
          setRecordingTime(0);
    
          mediaRecorder.current.ondataavailable = (e: BlobEvent) => {
            if (e.data.size > 0) {
              chunks.current.push(e.data);
            }
          };
    
          mediaRecorder.current.onstop = () => {
            const recordedBlob = new Blob(chunks.current, { type: "audio/webm" });
            const url = URL.createObjectURL(recordedBlob);
            setRecordedUrl(url);
            setAnswer(recordedBlob);
            chunks.current = [];
            setIsRecording(false);
            handleRecording(false);
    
            if (recordingInterval.current) {
              clearInterval(recordingInterval.current);
            }
          };
    
          mediaRecorder.current.start();
          recordingInterval.current = setInterval(() => {
            setRecordingTime((prev) => {
              const newTime = prev + 1;
              if (newTime >= 120) {
                stopRecording(newTime);
              }
              return newTime;
            });
          }, 1000);
        } catch (error) {
          alert("Error accessing microphone");
          console.error("Error accessing microphone:", error);
        }
      };
    
      const stopRecording = (time: number) => {
        if (time < 60) {
          alert("Minimum recording time is 1 minute.");
          return;
        } else if (time >= 120) {
          alert("Maximum recording time is 2 minutes.");
        }
    
        if (mediaRecorder.current && mediaRecorder.current.state === "recording") {
          mediaRecorder.current.stop();
        }
    
        if (mediaStream.current) {
          mediaStream.current.getTracks().forEach((track) => {
            track.stop();
          });
        }
        setIsRecording(false);
        handleRecording(false);
    
        if (recordingInterval.current) {
          clearInterval(recordingInterval.current);
        }
      };
    
      const handlePlayPause = () => {
        if (!audioRef.current) return;
    
        if (isPlaying) {
          audioRef.current.pause();
        } else {
          audioRef.current.play();
        }
    
        setIsPlaying(!isPlaying);
      };
    
      const handleError = async () => {
        console.log("Please record your answer");
        setError(false);
      };
    
      useEffect(() => {
        if (error) {
          handleError();
        }
      }, [error]);
    
      return (
        <div className="w-full">
          {recordedUrl ? (
            <>
              <div className="flex items-center w-full justify-between space-x-3">
                <button
                  onClick={handlePlayPause}
                  className="p-2 rounded border border-secondary-200 w-fit cursor-pointer"
                >
                  {isPlaying ? "Pause" : "Play"}
                </button>
                <audio ref={audioRef} src={recordedUrl} onEnded={() => setIsPlaying(false)} />
                <div className="px-2 py-1 font-medium text-xs rounded-full bg-secondary-100 text-secondary-700">
                  {formatTime(recordingTime)}
                </div>
              </div>
            </>
          ) : (
            <>
              <div className="flex items-center w-full justify-between">
                {/* Start Recording */}
                {!isRecording && (
                  <button
                    onClick={startRecording}
                    className="bg-primary-600 hover:bg-primary-700 text-white font-bold py-2 px-4 rounded flex items-center space-x-2"
                  >
                    Start Recording
                  </button>
                )}
    
                {/* Finish Recording */}
                {isRecording && (
                  <button
                    onClick={() => stopRecording(recordingTime)}
                    className="bg-secondary-600 hover:bg-secondary-700 text-white font-bold py-2 px-4 rounded flex items-center space-x-2"
                  >
                    Finish Recording
                  </button>
                )}
    
                {/* Recording Time */}
                {isRecording && (
                  <div className="px-2 py-1 font-medium text-xs rounded-full bg-secondary-100 text-secondary-700">
                    {formatTime(recordingTime)}
                  </div>
                )}
              </div>
            </>
          )}
        </div>
      );
    };
    
    export default AudioRecorder;
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search