Click here to Skip to main content
15,887,135 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
This is one problem I am facing while developing an small web app with Next.JS.

The app allows the user to record his own voice and then play it back. Although it works up to a point, it has a serious issue. At this stage, I can do the audio recording as well as play the audio back once. But it starts getting into trouble if I want to play back multiple times. I am likely making some basic mistake in my code, but have no idea where. If somebody sees something missing, please let me know.

This is the kind of error message I get, when trying to play more than once:

Unhandled Runtime Error (Uncaught (in promise))
AbortError(DOMException):
The fetching process for the media resource was aborted by the user agent at the user's request.


Now, below is the complete code for the Next.JS component.

And since this is the unique component I am using, it is easy to make a bare-bone next.js app and use this component to test what happens.

If more detailed information is needed, let me know in the comments.

What I have tried:

TypeScript
'use client'

import {useState,useRef} from "react";

const AudioRecorderPlayer = () => {
  const [permission, setPermission] = useState(false);
  const [stream, setStream] = useState<MediaStream>();
  const [recordingStatus, setRecordingStatus] = useState("inactive");
  const mrRef = useRef<MediaRecorder | null>(null);
  const audioChunksRef = useRef<Blob[]>([]);
  const [audio, setAudio] = useState('');
  const dataType = "video/webm";

  const getMicrophonePermission = async () => {
    if ("MediaRecorder" in window) {
      try {
        const streamData = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false,
        });
        setPermission(true);
        setStream(streamData);
      } catch (err) {
        alert("err.message");
      }
    } else {
      alert("The MediaRecorder API is not supported in your browser.");
    }
  }; /* End of getMicrophonePermission */

  const startRecording = async () => {
        mrRef.current = null
        audioChunksRef.current = []

        setRecordingStatus("recording");
        const mediaRecorder = new MediaRecorder(stream!, {
            mimeType: dataType,
            audioBitsPerSecond: 16*44100
        });
        mrRef.current = mediaRecorder;

        let localAudioChunks = [];//[Blob];
        mediaRecorder.start()
        mediaRecorder.ondataavailable = (event) => {
            if (typeof event.data === "undefined") return;
            if (event.data.size === 0) return;
            localAudioChunks.push(event.data);
            audioChunksRef.current.push(event.data);
        };
    }; /* End of startRecording */

    const stopRecording = () => {
        setRecordingStatus("inactive");
        if (!mrRef.current) return
        mrRef.current?.stop();
        mrRef.current.onstop = async () => {
            console.log("Here dataType = ",dataType)
            const audioBlob = new Blob(audioChunksRef.current,
                 {
                    type: dataType});
            const audioUrl = URL.createObjectURL(audioBlob);
            setAudio(audioUrl);
        };
    }; /* End of stopRecording */

  return (
    <div>
      <main>
                {!permission ? (
                    <button onClick=
                      {getMicrophonePermission} type="button">
                        Get Microphone
                    </button>
                ): null}
                {permission &&
                 recordingStatus === "inactive" ? (
          <button onClick={startRecording} type="button">
            Start Recording
                    </button>
                ) : null}
                {recordingStatus === "recording" ? (
            <button onClick={stopRecording} type="button">
                        Stop Recording
                    </button>
                ) : null}
        <audio src={audio} id="audio-player" controls />
      </main>
    </div>
  );

}; /* End of AudioRecorderPlayer */

export default AudioRecorderPlayer;
Posted
Updated 17-Aug-23 7:44am
v6

1 solution

It's difficult to pinpoint exactly why you cannot do a 2nd playback, I think it lies in your URL you are using to create the audio object as it might be getting invalidated or not properly handled after the first playback. One possible reason for this is that the URL created using 'URL.createObjectURL(audioBlob)' might not be released properly after each playback, causing conflicts when you try to create a new URL for the same audio data.

To fix this (if it is the culprit), you should make sure to revoke the object URL using 'URL.revokeObjectURL()' when you're done using it - HTML DOM revokeObjectURL() method[^]. You can do this in your 'stopRecording' function after setting the audio state -
JavaScript
const stopRecording = () => {
    setRecordingStatus("inactive");
    if (!mrRef.current) return;
    mrRef.current?.stop();
    mrRef.current.onstop = async () => {
        const audioBlob = new Blob(audioChunksRef.current, {
            type: dataType
        });
        const audioUrl = URL.createObjectURL(audioBlob);
        setAudio(audioUrl);
        //Revoke the previous object URL...
        if (audio) {
            URL.revokeObjectURL(audio);
        }
    };
};
 
Share this answer
 
Comments
Michel BF 17-Aug-23 3:26am    
Thanks for the tip, I tried your suggestion; but unfortunately it doesn't not work.
Nothing changes.
Andre Oosthuizen 17-Aug-23 4:27am    
I will research a bit more, maybe there is another solution.
Michel BF 23-Aug-23 21:27pm    
I think you can easily reproduce it as mentioned in my post.
Andre Oosthuizen 24-Aug-23 5:07am    
That will be your task to complete, I just gave you some pointers. When you run the code, does it error? If so where?, what error etc.
Michel BF 25-Aug-23 1:48am    
OK. Thank you. Just one question: How do you release "properly after each playback" as you mention. I assume the question is Where to use 'URL.revokeObjectURL()' ?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900