Search code examples
javascripthtmlweb-audio-api

Web Audio event "ended" not being fired when playing part of a source buffer


We have a web audio helper function that plays sounds from a sound sheet, and lets us know when they are finished. In the past, we've used playbackState on an update loop to check for a node having finished its playback, but since Chrome 36 now uses the current spec, which does not support playbackState we've updated the code to use the 'ended' event.

This does not seem to be getting fired, however.

The following is our playAudio function: -

playAudio: function(trackName){
    var node = this._atx.createBufferSource();
    node.buffer = this._buffer;

    var gain = this._atx.createGain();

    node.connect(gain);
    gain.connect(this._atx.destination);

    var start = this._tracks[trackName][0];
    var length = this._tracks[trackName][1] - start;

    node.start(0, start, length);

    var sound = {name: trackName, srcNode: node, gainNode: gain, startTime: this._atx.currentTime, duration: length, loop: false, playHeadStart: start};

    if (this._features.hasEvents) {
        //node.addEventListener("ended", function () {
        //   this._soundEnded(sound)
        //}.bind(this));
        node.onended = function(){
            this._soundEnded(sound);
        }.bind(this);
    }

    this._playingSounds[++this._soundId] = sound;
    return this._soundId;
},
_soundEnded: function (sound) {
    this._playingSounds[sound] = null;
    delete this._playingSounds[sound];
}

The this._features.hasEvents variable gets set by checking for node.onended !== null on an audio node created during load. The buffer is created by XHR'ing a file. This can be either an mp3 or an m4a, depending on what we're playing on.

What is the reason that the _soundedEnded function is not being called? I've tried both node.onended = function(){...} and node.addEventListener("ended", function(){..}) syntaxes, with no luck from either. We seem to have the same behaviour in both iOS 7 Safari, and desktop/Android Chrome.


Solution

  • I've just solved this, after realising I was doing something stupid. The bug isn't with WebAudio, but instead with the callback function that was supposed to remove the sound object from the _playingSounds array.

    This bit of code:

    var sound = {name: trackName, srcNode: node, gainNode: gain, startTime:     this._atx.currentTime, duration: length, loop: false, playHeadStart: start};
    
    if (this._features.hasEvents) {
        //node.addEventListener("ended", function () {
        //   this._soundEnded(sound)
        //}.bind(this));
        node.onended = function(){
            this._soundEnded(sound);
        }.bind(this);
    }
    
    this._playingSounds[++this._soundId] = sound;
    return this._soundId;
    

    should have been more like this:

    var sound = {name: trackName, srcNode: node, gainNode: gain, startTime: this._atx.currentTime, duration: length, loop: false, playHeadStart: start};
    var soundId = ++this._soundId;
    if (this._features.hasEvents) {
    
        node.onended = function(){
            this._soundEnded(soundId);
        }.bind(this);
    }
    
    this._playingSounds[soundId] = sound;
    return soundId;
    

    I was attempting to remove the object by the value, rather than the key, which obviously doesn't work. The code now passes the key through to the function, and everything works again.