Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove the pause between audio tracks in Javascript

I am building a javascript game, and i want to create a background music based on sound file snippets. Short mp3 files to play them as one continuous track. I have tried binding an "ended" event handler on the audio file, though this causes a delay between audio fragments.

To solve this I made a hacky solution that still does not work, changing the audio 1 second before it finishes.

Ebuc.manageAudio = function(){
    var listener = function (event) {
         if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
             Ebuc.manageAudio();
             console.log("aduio");
             Ebuc.bgnext = false;
         }
         if(this.currentTime < 2){
            Ebuc.bgnext = true;
             console.log("reset");
         }
        console.log(event);
        console.log("listener active")

    };
    var color = Level.current.color;
    if(Ebuc.bgsong == null) {
        Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
        Ebuc.bgsong.addEventListener('timeupdate', listener, true);

    }
    else{
        Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
    }

    Ebuc.bgsong.play();
    Resources.audioSetList[color].next();

};

This sample works once, when it is time to switch fragment 2 to fragment 3 the loop stops. Console logging the event listener gives 4 times a log before stopping.

Q1: Why is this eventlistener suddenly disappearing? Q2: Is there a non hack solution for chaining these audio fragments.

I thank you in advance.

like image 314
Martijn de Langh Avatar asked Mar 22 '16 12:03

Martijn de Langh


3 Answers

You're going to have more than just pausing issues trying to rapidly switch between two short audio clips, you're going to probably want to crossfade between the two audio tracks quickly as well to prevent any popping, artifacts, etc.

Here's an example of crossfading using howler from howler's github issues. You could probably use this example, and keep a queue of loaded instances to transition to. I hope that helps.

//you'll probably want this crossfade duration to be shorter.
var crossfadeDuration = 5000,
    volume            = 0.7;

var instance1, instance2, soundDuration;

// Singleton helper to build similar instances
var createHowlerInstance = function (urls, onload) {
  return new Howl({
    urls: urls,
    loop: false,
    volume: 0,
    onload: onload
  });
};

// Create "slave" instance. This instance is meant
// to be played after the first one is done.
instance2 = createHowlerInstance(['file2.mp3']);

// Create "master" instance. The onload function passed to
// the singleton creator will coordinate the crossfaded loop
instance1 = createHowlerInstance(['file1.mp3'], function(){

  // Get the sound duration in ms from the Howler engine
  soundDuration = Math.floor(instance1._duration * 1000);

  (function crossfadedLoop(enteringInstance, leavingInstance){

    // Fade in entering instance
    enteringInstance.pos(0).play().fade(0, volume, crossfadeDuration);

    // Wait for the audio end to fade out entering instance
    // white fading in leaving instance
    setTimeout(function(){

      enteringInstance.fade(volume, 0, crossfadeDuration);
      crossfadedLoop(leavingInstance, enteringInstance);

    }, soundDuration - crossfadeDuration);

  })(instance1, instance2);

});
like image 175
pantalohnes Avatar answered Nov 10 '22 14:11

pantalohnes


By using the idea of setting a timeOut in the answer of pantalohnes I have created the following code to solve the gap:

Ebuc.manageAudio = function(){
        var color = Level.current.color;
        Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
        Ebuc.bgsong.addEventListener("loadedmetadata",function(){
            setTimeout(Ebuc.manageAudio, (Ebuc.bgsong.duration * 1000) - 50);
            Ebuc.bgsong.play();
            console.log(Ebuc.bgsong.duration);
            Resources.audioSetList[color].next();
        });
    };

The 50 milliseconds timeout bridges the gap between the sequenced files exactly.

like image 24
Martijn de Langh Avatar answered Nov 10 '22 12:11

Martijn de Langh


Answering your question (although I see you found another solution), I think I found your bug:

The second time your enter Ebuc.manageAudio(), Ebuc.bgsong is already set and you just create a new Audio Ebuc.bgsong = new Audio(...) without attaching the listener to it, so you're not being notified for any 'timeupdate' events emitted when playing the second audio file.

You should also remove the listener from the previous playing audio.

So, if all else is ok, I think this should fix it:

Ebuc.manageAudio = function(){
    var listener = function (event) {
        if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
            Ebuc.manageAudio();
            console.log("aduio");
            Ebuc.bgnext = false;
        }
        if(this.currentTime < 2){
            Ebuc.bgnext = true;
            console.log("reset");
        }
        console.log(event);
        console.log("listener active")

    };
    var color = Level.current.color;
    if(Ebuc.bgsong != null) {
        Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
    }
    Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
    Ebuc.bgsong.addEventListener('timeupdate', listener, true);

    Ebuc.bgsong.play();
    Resources.audioSetList[color].next();

};

More than that, I think that if you properly remove the listener from the previous playing audio, you won't need that bgnext hack at all:

var listener = function (event) {
    if (this.currentTime > (this.duration - 1)) {
        Ebuc.manageAudio();
        console.log("aduio");
    }
    console.log(event);
    console.log("listener active")
};

Ebuc.manageAudio = function () {
    var color = Level.current.color;
    if (Ebuc.bgsong != null) {
        Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
    }
    Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
    Ebuc.bgsong.addEventListener('timeupdate', listener, true);

    Ebuc.bgsong.play();
    Resources.audioSetList[color].next();

};

Let me know if that worked :)

like image 1
Yoav Aharoni Avatar answered Nov 10 '22 12:11

Yoav Aharoni