当我 运行 AudioBufferSourceNode.start() 当我有多个音轨时,有时会出现延迟

When I run AudioBufferSourceNode.start() when I have multiple tracks, I sometimes get a delay

我正在制作一个读取和播放两个音频文件的应用程序。
CodeSnadBox
上面的CodeSandBox有如下规格。

问题

播放音频时,有时会有延迟。
但是,音频延迟并不总是存在的,有时会出现两个曲目可以完全同时播放的情况。

虽然没有在上面的 CodeSandBox 中实现,但我目前正在开发的应用程序实现了一个搜索栏来指示当前播放位置。 通过移动搜索栏以指示当前播放位置,音频将被重新加载,由此产生的延迟可能会得到解决。 另一方面,移动搜索栏可能会导致延迟,即使音频播放的时间完全相同。

请问,有没有办法同时稳定一致地播放多个音轨?

代码

let ctx,
  tr1,
  tr2,
  tr1gain = 0,
  tr2gain = 0,
  start = false;

const trackList = ["track1", "track2"];

const App = () => {
  useEffect(() => {
    ctx = new AudioContext();
    tr1 = ctx.createBufferSource();
    tr2 = ctx.createBufferSource();
    tr1gain = ctx.createGain();
    tr2gain = ctx.createGain();
    trackList.forEach(async (item) => {
      const res = await fetch("/" + item + ".mp3");
      const arrayBuffer = await res.arrayBuffer();
      const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
      item === "track1"
        ? (tr1.buffer = audioBuffer)
        : (tr2.buffer = audioBuffer);
    });
    tr1.connect(tr1gain);
    tr1gain.connect(ctx.destination);
    tr2.connect(tr2gain);
    tr2gain.connect(ctx.destination);
    return () => ctx.close();
  }, []);

  const [playing, setPlaying] = useState(false);
  const playAudio = () => {
    if (!start) {
      tr1.start();
      tr2.start();
      start = true;
    }
    ctx.resume();
    setPlaying(true);
  };
  const pauseAudio = () => {
    ctx.suspend();
    setPlaying(false);
  };

  const changeVolume = (e) => {
    const target = e.target.ariaLabel;
    target === "track1"
      ? (tr1gain.gain.value = e.target.value)
      : (tr2gain.gain.value = e.target.value);
  };
  const Inputs = trackList.map((item, index) => (
    <div key={index}>
      <span>{item}</span>
      <input
        type="range"
        onChange={changeVolume}
        step="any"
        max="1"
        aria-label={item}
      />
    </div>
  ));

  return (
    <>
      <button
        onClick={playing ? pauseAudio : playAudio}
        style={{ display: "block" }}
      >
        {playing ? "pause" : "play"}
      </button>
      {Inputs}
    </>
  );
};

当调用不带参数的 start() 时,它与调用 AudioContextcurrentTime 作为第一个参数的开始相同。在您的示例中,它看起来像这样:

tr1.start(tr1.context.currentTime);
tr2.start(tr2.context.currentTime);

根据定义,AudioContextcurrentTime 会随着时间的推移而增加。这完全有可能发生在两次通话之间。因此,解决该问题的第一个尝试可能是确保两个函数调用使用相同的值。

const currentTime = tr1.context.currentTime;

tr1.start(currentTime);
tr2.start(currentTime);

由于 currentTime 通常会随着渲染量程的时间增加,您可以通过添加一点延迟来添加额外的安全网。

const currentTime = tr1.context.currentTime + 128 / tr1.context.sampleRate;

tr1.start(currentTime);
tr2.start(currentTime);

如果这没有帮助,您还可以使用 OfflineAudioContext 将您的混音预先渲染成单个 AudioBuffer