Search code examples
javascripthtml5-audio

How to get total audio duration from multiple audio files


I am trying to get the total duration of audio from an array of audio paths.

Here is what the array would look like:

var sound_paths = ["1.mp3","2.mp3",...]

I have looked at this post, which helped: how to get audio.duration value by a function

However, I do not know how to implement this over an array. The idea is that I want to loop over each audio file, get its duration, and add it to a "sum_duration" variable.

I cannot seem to figure out a way to do this over a for loop. I have tried Promises (which I am admittedly new at): (Note the functions come from a class)

getDuration(src,cb){
    // takes a source audio file and a callback function
    var audio = new Audio();
    audio.addEventListener("loadedmetadata",()=>{
        cb(audio.duration);
    });
    audio.src = src;
}

getAudioArrayDuration(aud_path_arr){
    // takes in an array of audio paths, finds the total audio duration
    // in seconds

    return new Promise((resolve)=>{
        var duration = 0;

        for(const aud_path in aud_path_arr){
            var audio = new Audio();
            audio.src = aud_path;
            audio.onloadedmetadata = ()=>{
                console.log(audio.duration);
                duration += audio.duration;
            }
            resolve(duration);
        }

    });

}

However, this obviously does not work, and will just return the duration value of 0.

How can I loop over audio files and return the total audio duration of each file summed?


Solution

  • I think, in this case, working with promises is the right approach, time to get used to them ;) Try to remember, a promise will fullfil your wish in the near future. What makes your problem harder is that you have an array of files to check, each will need to separately be covered by it's own Promise, just so that your program can know when all of them have been fullfilled.

    I always call my 'promised' getters 'fetch...', that way I know it'll return a promise instead of a direct value.

    function fetchDuration(path) {
      return new Promise((resolve) => {
        const audio = new Audio();
        audio.src = path;
        audio.addEventListener(
          'loadedmetadata',
          () => {
            // To keep a promise maintainable, only do 1
            // asynchronous activity for each promise you make
            resolve(audio.duration)
          },
        );
      })
    }
    
    function fetchTotalDuration(paths) {
      // Create an array of promises and wait until all have completed
      return Promise.all(paths.map((path) => fetchDuration(path)))
        // Reduce the results back to a single value
        .then((durations) => durations.reduce(
          (acc, duration) => acc + duration,
          0,
        ))
      ;
    }
    

    At some point, your code is going to have to deal with this asynchronous stuff, I honestly believe that Promises are the easiest way to do this. It takes a little getting used to, but it'll be worth it in the end. The above could be used in your code something along the lines of:

    window.addEventListener('DOMContentLoaded', () => {
      fetchTotalDuration(["1.mp3","2.mp3",...])
        .then((totalDuration) => {
          document.querySelector('.player__total-duration').innerHTML = totalDuration;
        })
      ;
    });