Do browsers limit the number of decodeAudioData calls per session?

91 Views Asked by At

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?

1

There are 1 best solutions below

4
chrisguttandin On BEST ANSWER

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)
    );
  }
};