React Web Audio API - Play, pause and export loaded audio file

My purpose is to upload and listen to an audio file using the WEB Audio API. I have been able to listen to the audio file when selecting it, but having trouble pausing and playing it afterwards. I need to export the file to WAV format aswell. I have created a simple example, any help will be much appreciated

Loading Audio and playing it from file input

const onFileChange = (e) => {
    let file = e.target.files[0];
    console.log(file);
    setFile(file);

    let fileReader = new FileReader();
    fileReader.onload = function (ev) {
      audioContext.decodeAudioData(ev.target.result).then(function (buffer) {
        playSound(buffer);
      });
    };
    fileReader.readAsArrayBuffer(file);
  };

Play sound using buffer source

  const playSound = (buffer, time) => {
    source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(audioContext.destination);
    source.start(time);
    setIsPlaying(true);
  };

I'm facing problem here with pausing and playing:

  const onPlayPause = (e) => {
    console.log("audioState", audioContext.state);
    console.log("duration", audioContext.currentTime);
    if (!isPlaying) {
      //source.start();
      setIsPlaying(true);
    } else if (audioContext.state === "running") {
      setPlayDuration(audioContext.currentTime);
      audioContext.suspend();
      setIsPlaying(false);
    } else if (audioContext.state === "suspended") {
      audioContext.resume();
    }
  };

Export Audio:

  const exportAudioFile = () => {

    offlineContext.render().then((buffer) => {
      setRenderState('encoding');
      const handleMessage = ({ data }) => {
        var blob = new window.Blob([new DataView(data)], {
          type: 'audio/wav',
        });

    //blob = new Blob([buffer], { type: "audio/wav" });
    const url = window.URL.createObjectURL(blob);
      }

    window.URL.revokeObjectURL(url);
  })};

Codesandboxlink: https://codesandbox.io/s/react-audiocontext-pause-play-fw20u?file=/src/App.js


Solution 1:

I've had my fair share of headaches with persisting things in react functional components. Fortunately, useRef is an awesome tool for just that:

https://reactjs.org/docs/hooks-reference.html#useref

As the documentation says, it essentially returns a container which .current property persists across re-renders.

I forked your code to useRef in action:

https://codesandbox.io/s/react-audiocontext-pause-play-forked-si59u?file=/src/App.js:120-159

Basically, when you load the file, store your shiny new AudioContext in the ref's .current field, and reference that throughout the rest of your component. You can clean it up a bit, IE store the .current in a constant scoped to the function you're using it in.

Two key spots:

export default function App() {
   const audioCtxContainer = useRef(null);
   ...

and

    audioCtxContainer.current = new AudioContext();
    audioCtxContainer.current
        .decodeAudioData(ev.target.result)
        .then(function (buffer) {
          playSound(buffer);
        });

useRef is useful for any mutable object that you want to persist for the lifetime of the component.

Let me know if that helps!