Search code examples
javascriptaudioweb-audio-api

How to make audio files play in the correct order using Web Audio API?


I have an array of imported audio files, and I'm trying to play them sequentially with the Web Audio API. I have some code which almost works, but it plays the sounds in the wrong order, seemingly randomly.

Can someone tell me what I'm doing wrong, or if there's a better way to achieve my goal?

import soundA from './assets/soundA.mp3'
import soundB from './assets/soundB.mp3'
import soundC from './assets/soundC.mp3'

function playSoundsSequentially() {

    let mySounds = [soundA, soundB, soundC]

    const ctx = new window.AudioContext();
    let time = 0;
    mySounds.forEach(sound => fetch(sound).then(response => response.arrayBuffer())
                                        .then(buffer => ctx.decodeAudioData(buffer))
                                        .then(buffer => {
        let track = ctx.createBufferSource();
        track.buffer = buffer;
        track.connect(ctx.destination);
        track.start(ctx.currentTime + time);
        time += track.buffer.duration;
    }));
}

Update: My solution based on JMP's insight on using promises. I also ditched the forEach for a simple for loop because I am more comfortable with that.

import soundA from './assets/soundA.mp3'
import soundB from './assets/soundB.mp3'
import soundC from './assets/soundC.mp3'

function playSoundsSequentially() {

    let mySounds = [soundA, soundB, soundC]

    let promises = []
    for(let i = 0; i < mySounds.length; ++i) {
        promises.push(fetch(mySounds[i]).then(response => response.arrayBuffer())
                                        .then(buffer => ctx.decodeAudioData(buffer)));
    }

    Promise.all(promises).then(buffer => {
        for(let i = 0; i < buffer.length; ++i) {
            let track = ctx.createBufferSource();
            track.buffer = buffer[i];
            track.connect(ctx.destination);
            track.start(ctx.currentTime + time);
            time += track.buffer.duration;
        }
    });
}

Solution

  • Your question is very similar to this question:

    Why is AJAX in for loop executing in wrong order

    Your code assembles the tracks in the order they are returned, not in the order they were sent,

    Promise.all() deals with an array of promises (MDN), and is also a Promise itself, which is only fulfilled when all of the array elements are, returning an array in the original order.

    Try

    let mySounds = [soundA, soundB, soundC]
    
    const ctx = new window.AudioContext();
    let time = 0;
    mySounds.forEach(sound => fetch(sound).then(response => response.arrayBuffer())
                                      .then(buffer => ctx.decodeAudioData(buffer)));
    Promise.all(mySounds).then(buffer => {
        let track = ctx.createBufferSource();
        track.buffer = buffer;
        track.connect(ctx.destination);
        track.start(ctx.currentTime + time);
        time += track.buffer.duration;
    });