Search code examples
reactjsaudiohtml5-audio

Play audio from Blob in React


I receive some audio from the backend (in .wav format) and would like to play it in the react frontend.

An old implementation used files in a public folder and a tag like this:

<audio ref={audioPlayer} src={new Blob(output.data)} preload="metadata" onEnded={onEnded} onLoadedMetadata={onLoadedMetadata}/>

How can I use the binary data from my request instead of a source here, or is there any other simple way of playing an audio file from memory?


Solution

  • You can create an object URL from binary data in a blob-like format for your audio element source.

    Here's a commented example, including a convenience hook:

    <div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.4/babel.min.js"></script>
    <script type="text/babel" data-type="module" data-presets="react">
    
    const {useEffect, useMemo, useState} = React;
    
    /**
     * This is just for the demo.
     * You seem to already have the binary data for the blob.
     */
    function useBlob () {
      const [blob, setBlob] = useState();
      const [error, setError] = useState();
    
      useEffect(() => {
        (async () => {
          try {
            // A random doorbell audio sample I found on GitHub
            const url = 'https://raw.githubusercontent.com/prof3ssorSt3v3/media-sample-files/65dbf140bdf0e66e8373fccff580ac0ba043f9c4/doorbell.mp3';
            const response = await fetch(url);
            if (!response.ok) throw new Error(`Response not OK (${response.status})`);
            setBlob(await response.blob());
          }
          catch (ex) {
            setError(ex instanceof Error ? ex : new Error(String(ex)));
          }
        })();
      }, []);
    
      return {blob, error};
    }
    
    /**
     * Get an object URL for the current blob. Will revoke old URL if blob changes.
     * https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
     */
    function useObjectUrl (blob) {
      const url = useMemo(() => URL.createObjectURL(blob), [blob]);
      useEffect(() => () => URL.revokeObjectURL(url), [blob]);
      return url;
    }
    
    // Use the hook and render the audio element
    function AudioPlayer ({blob}) {
      const src = useObjectUrl(blob);
      return <audio controls {...{src}} />;
    }
    
    function Example () {
      const {blob, error} = useBlob();
      return (
        <div>
          <h2>Audio player using binary data</h2>
          {
            blob ? <AudioPlayer {...{blob}} />
              : error ? <div>There was an error fetching the audio file: {String(error)}</div>
              : <div>Loading audio...</div>
          }
        </div>
      );
    }
    
    ReactDOM.render(<Example />, document.getElementById('root'));
    
    </script>