Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I solve "HTMLMediaElement already connected previously to a different MediaElementSourceNode" problem using React functional component

I am using React Functional Components in my app created with create-react-app, so React.StrictMode is automatically enabled. I don't want to disable it as it is helping me spot potential issues as am developing. Trouble is that it makes the component render twice which creates an error:
Uncaught DOMException: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode as the HTMLMediaElement gets connected in the first render. I am wondering, is there a way to disconnect the HTMLMediaElement from the previous MediaElementSourceNode... maybe set it to null or something, so that it can get a fresh connection?
Here below, is my code that throws the error:

import React, { useEffect, useRef } from "react";

const Canvas = () => {
  const audioRef = useRef(null);
  const filUploadRef = useRef(null);
  const canvasRef = useRef(null);

  let audioSource;
  let analyser;
  let dataArray;

  useEffect(() => {
    const audioElement = audioRef.current;
    const canvas = canvasRef.current;
    const fileUploadField = filUploadRef.current;

    fileUploadField.addEventListener("change", () => {
      const audioContext = new AudioContext();

      const files = fileUploadField.files;
      audioElement.src = URL.createObjectURL(files[0]);
      audioElement.load();
      audioElement.play();

      audioSource = audioContext.createMediaElementSource(audioElement);
      analyser = audioContext.createAnalyser();
      audioSource.connect(analyser);
      analyser.connect(audioContext.destination);
      dataArray = new Uint8Array(analyser.frequencyBinCount);
    });
  }, []);

  const handleTogglePlayPause = () => {
    //TODO: Implement toggle Play/Pause when the button is clicked.
  };

  return (
    <>
      <canvas ref={canvasRef} />
      <audio controls ref={audioRef} />
      <input type="file" id="file-upload" accept="audio/*" ref={filUploadRef} />
      <button onClick={handleTogglePlayPause} type="button">
        {/* ^ This button element is related to handleTogglePlayPause function for a different stage*/}
        Play/Pause
      </button>
    </>
  );
};

export default Canvas;
like image 797
Klem Lloyd Mwenya Avatar asked Oct 21 '25 23:10

Klem Lloyd Mwenya


1 Answers

You can check if audioSource already has a source, and if so you can try to avoid creating a new media element source. Also, you do not need to create a new analyser and connect it.

Add this if check to your code, and it should solve the problem.

// ..
audioElement.play();

if (!audioSource) {
    audioSource = audioContext.createMediaElementSource(audioElement);
    analyser = audioContext.createAnalyser();
    audioSource.connect(analyser);
    analyser.connect(audioContext.destination);
}
//..

Well, react strict mode helps identifying problems, but at the same time it creates new ones. Hence, you might consider closing the strict mode at all.

like image 114
ilketorun Avatar answered Oct 24 '25 12:10

ilketorun