I wrote a simple wrapper around Howler 2.0 I am using for Shadows of Adam that seems to fix this issue:
ig.module(
'impact.audioManager'
)
.defines(function(){
AudioManager = function(){
// List of loaded music
this._audio = {};
this._musicID = null;
this._currentTrack = null;
this._musicIDFade = null;
this._s = ig.global.Config.Audio;
};
AudioManager.prototype.addMusic = function( name, id, data ){
if ( !data ) data = {};
this._audio[id] = new Howl({
src: [ this._s.MUSIC_ROOT_PATH + name + ".mp3", this._s.MUSIC_ROOT_PATH + name + ".ogg" ],
sprite: data.sprite,
loop: data.loop || false,
autoplay: data.autoPlay || false,
preload: data.preLoad || this._s.PRE_LOAD_AUDIO,
html5: data.html5 || true,
mute: data.mute || false,
rate: data.rate || this._s.MASTER_PLAYBACK_RATE,
pool: data.pool || this._s.MAX_MUSIC_CHANNEL_POOL,
volume: data.volume || this._s.MUSIC_VOLUME,
name: name,
onend: function( soundID ) {
this.playMusic( this.getCurrentTrackID(), 'loop');
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("The following sound ID has reached the end", soundID );
}.bind( this ),
onstop: function(){
}.bind( this )
})
};
AudioManager.prototype.addSound = function( name, id, data ){
if ( !data ) data = {};
this._audio[id] = new Howl({
src: [ this._s.SOUND_ROOT_PATH + name + ".mp3", this._s.MUSIC_ROOT_PATH + name + ".ogg" ],
sprite: data.sprite,
loop: data.loop || false,
autoplay: data.autoPlay || false,
preload: data.preLoad || this._s.PRE_LOAD_AUDIO,
html5: data.html5 || true,
mute: data.mute || false,
rate: data.rate || this._s.MASTER_PLAYBACK_RATE,
pool: data.pool || this._s.MAX_SOUND_CHANNEL_POOL,
volume: data.volume || this._s.MUSIC_VOLUME,
name: name
})
};
AudioManager.prototype.getID = function( id ) {
return this._audio[ id ];
};
AudioManager.prototype.playMusic = function( id, sprite ){
if ( ig.global.gamePaused ) {
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log( "Attempted to play music. Action blocked because game is paused");
return;
}
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log( "Attempting to play ", id, " Sprite is", sprite );
// Handle music pausing and resuming. If no id is present and we have a musicID value, play the paused track
if ( this.getMusicID() && !id ) {
id = this.getCurrentTrackID();
sprite = this.getMusicID();
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
console.log("Resume track: ", id, "At sprite:", sprite );
}
// Currently do not have a playing track nor do we have a sprite. Set sprite to intro
if ( !this.getMusicID() && !sprite ) {
sprite = "intro";
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("Playing new track:", id );
}
// We currently have a track playing. However the ID is not the same as the track playing. So play the new track
if ( this.getCurrentTrackID() !== id && this.getCurrentTrackID() != null ) {
sprite = ( !sprite ) ? "intro" : sprite;
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("Replacing track", this.getCurrentTrackID(), "with track", id );
this.stop();
}
// Track is playing the same ID but a different sprite.
if ( this.getCurrentTrackID() == id && sprite ) {
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("Playing new sprite", sprite );
this.stop();
}
// Track is playing the same ID but with an undefined sprite. Terminate
if ( this.getCurrentTrackID() == id && !sprite ) {
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("Tried playing the same song that was already playing. Action was blocked because music was already playing");
return;
}
// This track is a fadeOut track because the sprite is a loop and the current music ID is the same as the fade out music id
if ( sprite == 'loop' && this._musicIDFade == this.getMusicID() ){
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log("Attempted to play a loop sprite. Action was blocked because music was faded.");
return;
}
this._musicID = this.getID( id ).play( sprite );
this._currentTrack = id;
};
AudioManager.prototype.getCurrentTrackID = function(){
return this._currentTrack;
};
AudioManager.prototype.getMusicID = function(){
return this._musicID;
};
AudioManager.prototype.playSound = function( id ){
if ( !this.getID(id) ) return;
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
console.log("Playing sound", id );
this.getID(id).play();
};
AudioManager.prototype.stopSound = function( id ){
if ( !this.getID(id) ) return;
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
console.log("Stopping sound", id );
this.getID(id).stop();
};
AudioManager.prototype.pause = function( id ){
if ( !id ) id = this.getCurrentTrackID();
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
console.log("Track paused", id );
return this.getID( id ).pause( this.getMusicID() );
};
AudioManager.prototype.stop = function( id ){
if ( !id ) id = this.getCurrentTrackID();
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
console.log("Track stopped", this.getCurrentTrackID() );
return this.getID( id ).stop( this.getMusicID() );
};
AudioManager.prototype.mute = function( bool, id ){
if ( !id ) id = this.getCurrentTrackID();
return this.getID( id ).mute( bool );
};
AudioManager.prototype.volume = function( volume, id ){
if ( !id ) id = this.getCurrentTrackID();
return this.getID( id ).volume( volume );
};
AudioManager.prototype.fade = function( from, to, duration, id ){
if ( !id ) id = this.getCurrentTrackID();
if ( to == 0.0 ) {
this._musicIDFade = this.getMusicID();
} else {
this._musicIDFade = null;
}
if ( ig.global.Config.Settings.AUDIO_MANAGER_DEBUG_MODE )
ig.log(this.getMusicID() + " is starting to fade to volume " + to + " in " + duration + "ms");
return this.getID( id ).fade( from, to, duration );
};
AudioManager.prototype.rate = function( rate, id ){
if ( !id ) id = this.getCurrentTrackID();
return this.getID( id ).rate( rate, this.getMusicID() );
};
});
Example of addMusic:
// DEFINE MUSIC HERE
// Format: "fileName", "internalID", [ data ]
// Title music
ig.global.audioManager.addMusic('title', 'title',
{
sprite: {
intro: [0, 69999],
loop: [70000, 80000]}
}
);
Music is in two parts. Intro part plays from 0 milliseconds to 69,999 milliseconds. At the end of that part, it loops from 70,000 milliseconds to 80,000 milliseconds forever.