Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to start multiple audio tags simultaneously?

I have several tracks to a song that I want to play together and be able to mute some and play others. So I need to be able to start them all at the same time. Right now, they all start slightly out of sync:

// Start playing
for ( i = 0; i < 5; i++ ) {
    tracks[i].audio.play();
}

Even this is apparently not fast enough to start them all at the same time.

Is there any way in javascript to guarantee that HTML5 audio tags will start playing simultaneously?

like image 739
cstack Avatar asked Jul 11 '26 00:07

cstack


2 Answers

Not sure if you're already doing this, but Here's some sample code for preloading audio.

var audios = [];
var loading = 0;
AddNote("2C");
AddNote("2E");
AddNote("2G");
AddNote("3C");

function AddNote(name) {
    loading++;
    var audio = document.createElement("audio");
    audio.loop = true;
    audio.addEventListener("canplaythrough", function () {
        loading--;
        if (loading == 0) // All files are preloaded
            StartPlayingAll();
        }, false);
    audio.src = "piano/" + name + ".mp3";
    audios.push(audio);
}

function StartPlayingAll() {
    for (var i = 0; i < audios.length; i++)
        audios[i].play();
    }
}

The other thing you can try is setting audio.currentTime on each of the tracks to manually sync up the audio.

like image 159
Bill Avatar answered Jul 13 '26 15:07

Bill


I had the same issue and found a solution using the Audio API.

The problem is that the audio output has a delay of a few milliseconds, so it is impossible to start multiple audios at the same time. However, you can get around this by merging the audio sources into one using a ChannelMergerNode. By putting GainNodes in between, you can control the volume of each audio source separately.

I wrote a simple javascript class for this. This is how you can use it:

var audioMerger = new AudioMerger(["file1.ogg", "file2.mp3", "file3.mp3",
                                   "file4.ogg", "file5.mp3"]);
audioMerger.onBuffered(() => audioMerger.play());

// Make sure it's always in sync (delay should be less than 50 ms)
setInterval(() => {
  if (audioMerger.getDelay() >= 0.05) {
    audioMerger.setTime(audioMerger.getTime());
  }
}, 200);

// Set volume of 3rd audio to 50%
audioMerger.setVolume(0.5, 2);

// When you want to turn it off:
audioMerger.pause();

This code reduced the delay between the audios to less than 10 milliseconds in Firefox on my PC. This delay is so small you won't notice it. Unfortunately, it doesn't work in older browsers like Internet Explorer.

And here's the code for the class:

class AudioMerger {
  constructor(files) {
    this.files = files;
    this.audios = files.map(file => new Audio(file));

    var AudioContext = window.AudioContext || window.webkitAudioContext;
    var ctx = new AudioContext();
    this.merger = ctx.createChannelMerger(this.audios.length);
    this.merger.connect(ctx.destination);

    this.gains = this.audios.map(audio => {
      var gain = ctx.createGain();
      var source = ctx.createMediaElementSource(audio);
      source.connect(gain);
      gain.connect(this.merger);
      return gain;
    });

    this.buffered = false;

    var load = files.length;
    this.audios.forEach(audio => {
      audio.addEventListener("canplaythrough", () => {
        load--;
        if (load === 0) {
          this.buffered = true;
          if (this.bufferCallback != null) this.bufferCallback();
        }
      });
    });
  }

  onBuffered(callback) {
    if (this.buffered) callback();
    else this.bufferCallback = callback;
  }

  play() {
    this.audios.forEach(audio => audio.play());
  }

  pause() {
    this.audios.forEach(audio => audio.pause());
  }

  getTime() {
    return this.audios[0].currentTime;
  }

  setTime(time) {
    this.audios.forEach(audio => audio.currentTime = time);
  }

  getDelay() {
    var times = [];
    for (var i = 0; i < this.audios.length; i++) {
      times.push(this.audios[i].currentTime);
    }

    var minTime = Math.min.apply(Math, times);
    var maxTime = Math.max.apply(Math, times);
    return maxTime - minTime;
  }

  setVolume(volume, audioID) {
    this.gains[audioID].gain.value = volume;
  }
}
like image 27
Aloso Avatar answered Jul 13 '26 14:07

Aloso