masus04
masus04

Reputation: 983

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?

Upvotes: 1

Views: 4325

Answers (2)

masus04
masus04

Reputation: 983

Thank you @jsejcksn for the elaborate answer!

The short version would be as follows:

  1. Make sure you retrieve the Blob correctly, e.g. by specifying {responseType: 'blob'} using axios

  2. Wrap the Blob into an ObjectURL

    url = URL.createObjectURL(blob)
    
  3. Pass the url to the audio tag as src

    <audio src={url} />
    
  4. If the url is no longer required, release the ressources as follows:

    URL.revokeObjectURL(url)
    

Edit: Added @jsejcksn's comment

Upvotes: 1

jsejcksn
jsejcksn

Reputation: 33929

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/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/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>

Upvotes: 3

Related Questions