Search code examples
web-audio-api

Do browsers limit the number of decodeAudioData calls per session?


I have a React Next web app that plays songs using web audio api. I noticed that after browsing a lot of songs, at some point my app won't load new songs.

In Chrome I get this error: Failed to execute 'decodeAudioData' on 'BaseAudioContext': Unable to decode audio data, and only after refreshing the page (sometimes need to refresh 2-3 times), I can load songs again. Looking at "Total JS heap size" in the Memory tab in Chrome devtools doesn't indicate a memory leak.

In Safari it just refreshes the page automatically.

To make sure it's not something in my code, I made a new web app with a minimal example, and I could reproduce the issue. I get the decoding error after ~250 times.

export const test = async () => {
  const audioContext = new AudioContext();
  const blob = (
    await axios.get("./url-to-some-song.wav", {
      responseType: "blob",
    })
  ).data;

  for (let i = 0; i < 300; i++) {
    const arrayBuffer = await blob.arrayBuffer();
    const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  }
};

I tried to add a delay after every iteration - it doesn't help. It looks like browsers may have some limits, but I couldn't find any documentation for that. What could it be?


Solution

  • I can reproduce the problem in Chrome v122. It seems to be fixed when using structuredClone(). But I don't have a good explanation why that works. I guess it's a bug in Chrome.

    export const test = async () => {
      const audioContext = new AudioContext();
      const blob = (
        await axios.get("./url-to-some-song.wav", {
          responseType: "blob",
        })
      ).data;
    
      for (let i = 0; i < 300; i++) {
        const arrayBuffer = await blob.arrayBuffer();
        const audioBuffer = await audioContext.decodeAudioData(
          structuredClone(arrayBuffer)
        );
      }
    };
    

    You could also avoid the additional step by getting the data as 'arrayBuffer' directly.

    export const test = async () => {
      const audioContext = new AudioContext();
      const arrayBuffer = (
        await axios.get("./url-to-some-song.wav", {
          responseType: "arrayBuffer",
        })
      ).data;
    
      for (let i = 0; i < 300; i++) {
        const audioBuffer = await audioContext.decodeAudioData(
          structuredClone(arrayBuffer)
        );
      }
    };