covid19-demo/games.gdevelop-app.com/vincenzo/game-adbfc30b-3d8b-43b1-87a.../howler-sound-manager/howler-sound-manager.js

420 lines
11 KiB
JavaScript

/*
* GDevelop JS Platform
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
/**
* A thin wrapper around a Howl object with:
* * Extra methods `paused`, `stopped`, `getRate`/`setRate` and `canBeDestroyed` methods.
* * Automatic clamping when calling `setRate` to ensure a valid value is passed to Howler.js.
*
* See https://github.com/goldfire/howler.js#methods for the full documentation.
*
* @memberof gdjs
* @class HowlerSound
*/
gdjs.HowlerSound = function(o) {
Howl.call(this, o);
this._paused = false;
this._stopped = true;
this._canBeDestroyed = false;
this._rate = o.rate || 1;
//Add custom events listener to keep
//track of the sound status.
var that = this;
this.on('end', function() {
if (!that.loop()) {
that._canBeDestroyed = true;
that._paused = false;
that._stopped = true;
}
});
this.on('playerror', function(id, error) {
console.error(
"Can't play a sound, considering it as stopped. Error is:",
error
);
that._paused = false;
that._stopped = true;
});
// Track play/pause event to be sure the status is
// sync'ed with the sound - though this should be redundant
// with `play`/`pause` methods already doing that. Keeping
// that to be sure that the status is always correct.
this.on('play', function() {
that._paused = false;
that._stopped = false;
});
this.on('pause', function() {
that._paused = true;
that._stopped = false;
});
};
gdjs.HowlerSound.prototype = Object.create(Howl.prototype);
// Redefine `stop`/`play`/`pause` to ensure the status of the sound
// is immediately updated (so that calling `stopped` just after
// `play` will return false).
gdjs.HowlerSound.prototype.stop = function() {
this._paused = false;
this._stopped = true;
return Howl.prototype.stop.call(this);
};
gdjs.HowlerSound.prototype.play = function() {
this._paused = false;
this._stopped = false;
return Howl.prototype.play.call(this);
};
gdjs.HowlerSound.prototype.pause = function() {
this._paused = true;
this._stopped = false;
return Howl.prototype.pause.call(this);
};
// Add methods to query the status of the sound:
gdjs.HowlerSound.prototype.paused = function() {
return this._paused;
};
gdjs.HowlerSound.prototype.stopped = function() {
return this._stopped;
};
gdjs.HowlerSound.prototype.canBeDestroyed = function() {
return this._canBeDestroyed;
};
// Methods to safely update the rate of the sound:
gdjs.HowlerSound.prototype.getRate = function() {
return this._rate;
};
gdjs.HowlerSound.prototype.setRate = function(rate) {
this._rate = gdjs.HowlerSoundManager.clampRate(rate);
this.rate(this._rate);
};
/**
* HowlerSoundManager is used to manage the sounds and musics of a RuntimeScene.
*
* It is basically a container to associate channels to sounds and keep a list
* of all sounds being played.
*
* @memberof gdjs
* @class HowlerSoundManager
*/
gdjs.HowlerSoundManager = function(resources) {
this._resources = resources;
this._availableResources = {}; //Map storing "audio" resources for faster access.
this._globalVolume = 100;
this._sounds = {};
this._musics = {};
this._freeSounds = []; //Sounds without an assigned channel.
this._freeMusics = []; //Musics without an assigned channel.
this._pausedSounds = [];
this._paused = false;
var that = this;
this._checkForPause = function() {
if (that._paused) {
this.pause();
that._pausedSounds.push(this);
}
};
document.addEventListener('deviceready', function() {
// pause/resume sounds in Cordova when the app is being paused/resumed
document.addEventListener(
'pause',
function() {
var soundList = that._freeSounds.concat(that._freeMusics);
for (var key in that._sounds) {
if (that._sounds.hasOwnProperty(key)) {
soundList.push(that._sounds[key]);
}
}
for (var key in that._musics) {
if (that._musics.hasOwnProperty(key)) {
soundList.push(that._musics[key]);
}
}
for (var i = 0; i < soundList.length; i++) {
var sound = soundList[i];
if (!sound.paused() && !sound.stopped()) {
sound.pause();
that._pausedSounds.push(sound);
}
}
that._paused = true;
},
false
);
document.addEventListener(
'resume',
function() {
for (var i = 0; i < that._pausedSounds.length; i++) {
var sound = that._pausedSounds[i];
if (!sound.stopped()) {
sound.play();
}
}
that._pausedSounds.length = 0;
that._paused = false;
},
false
);
});
};
gdjs.SoundManager = gdjs.HowlerSoundManager; //Register the class to let the engine use it.
/**
* Ensure rate is in a range valid for Howler.js
* @return The clamped rate
* @private
*/
gdjs.HowlerSoundManager.clampRate = function(rate) {
if (rate > 4.0) return 4.0;
if (rate < 0.5) return 0.5;
return rate;
};
/**
* Return the file associated to the given sound name.
*
* Names and files are loaded from resources when preloadAudio is called. If no
* file is associated to the given name, then the name will be considered as a
* filename and will be returned.
*
* @private
* @return The associated filename
*/
gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function(soundName) {
if (
this._availableResources.hasOwnProperty(soundName) &&
this._availableResources[soundName].file
) {
return this._availableResources[soundName].file;
}
return soundName;
};
/**
* Store the sound in the specified array, put it at the first index that
* is free, or add it at the end if no element is free
* ("free" means that the gdjs.HowlerSound can be destroyed).
*
* @param {Array} arr The array containing the sounds.
* @param {gdjs.HowlerSound} arr The gdjs.HowlerSound to add.
* @return The gdjs.HowlerSound that have been added (i.e: the second parameter).
* @private
*/
gdjs.HowlerSoundManager.prototype._storeSoundInArray = function(arr, sound) {
//Try to recycle an old sound.
var index = null;
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] !== null && arr[i].canBeDestroyed()) {
arr[index] = sound;
return sound;
}
}
arr.push(sound);
return sound;
};
gdjs.HowlerSoundManager.prototype.playSound = function(
soundName,
loop,
volume,
pitch
) {
var soundFile = this._getFileFromSoundName(soundName);
var sound = new gdjs.HowlerSound({
src: [soundFile], //TODO: ogg, mp3...
loop: loop,
volume: volume / 100,
rate: gdjs.HowlerSoundManager.clampRate(pitch),
});
this._storeSoundInArray(this._freeSounds, sound).play();
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(
soundName,
channel,
loop,
volume,
pitch
) {
var oldSound = this._sounds[channel];
if (oldSound) {
oldSound.unload();
}
var soundFile = this._getFileFromSoundName(soundName);
var sound = new gdjs.HowlerSound({
src: [soundFile], //TODO: ogg, mp3...
loop: loop,
volume: volume / 100,
rate: gdjs.HowlerSoundManager.clampRate(pitch),
});
sound.play();
this._sounds[channel] = sound;
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.getSoundOnChannel = function(channel) {
return this._sounds[channel];
};
gdjs.HowlerSoundManager.prototype.playMusic = function(
soundName,
loop,
volume,
pitch
) {
var soundFile = this._getFileFromSoundName(soundName);
var sound = new gdjs.HowlerSound({
src: [soundFile], //TODO: ogg, mp3...
loop: loop,
html5: true, //Force HTML5 audio so we don't wait for the full file to be loaded on Android.
volume: volume / 100,
rate: gdjs.HowlerSoundManager.clampRate(pitch),
});
this._storeSoundInArray(this._freeMusics, sound).play();
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(
soundName,
channel,
loop,
volume,
pitch
) {
var oldMusic = this._musics[channel];
if (oldMusic) {
oldMusic.unload();
}
var soundFile = this._getFileFromSoundName(soundName);
var music = new gdjs.HowlerSound({
src: [soundFile], //TODO: ogg, mp3...
loop: loop,
html5: true, //Force HTML5 audio so we don't wait for the full file to be loaded on Android.
volume: volume / 100,
rate: gdjs.HowlerSoundManager.clampRate(pitch),
});
music.play();
this._musics[channel] = music;
music.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function(channel) {
return this._musics[channel];
};
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function(volume) {
this._globalVolume = volume;
if (this._globalVolume > 100) this._globalVolume = 100;
if (this._globalVolume < 0) this._globalVolume = 0;
Howler.volume(this._globalVolume / 100);
};
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function() {
return this._globalVolume;
};
gdjs.HowlerSoundManager.prototype.clearAll = function() {
for (var i = 0; i < this._freeSounds.length; ++i) {
if (this._freeSounds[i]) this._freeSounds[i].unload();
}
for (var i = 0; i < this._freeMusics.length; ++i) {
if (this._freeMusics[i]) this._freeMusics[i].unload();
}
this._freeSounds.length = 0;
this._freeMusics.length = 0;
for (var p in this._sounds) {
if (this._sounds.hasOwnProperty(p) && this._sounds[p]) {
this._sounds[p].unload();
delete this._sounds[p];
}
}
for (var p in this._musics) {
if (this._musics.hasOwnProperty(p) && this._musics[p]) {
this._musics[p].unload();
delete this._musics[p];
}
}
this._pausedSounds.length = 0;
};
gdjs.HowlerSoundManager.prototype.preloadAudio = function(
onProgress,
onComplete,
resources
) {
resources = resources || this._resources;
//Construct the list of files to be loaded.
//For one loaded file, it can have one or more resources
//that use it.
var files = [];
for (var i = 0, len = resources.length; i < len; ++i) {
var res = resources[i];
if (res.file && res.kind === 'audio') {
this._availableResources[res.name] = res;
if (files.indexOf(res.file) === -1) {
files.push(res.file);
}
}
}
if (files.length === 0) return onComplete(files.length);
var loaded = 0;
function onLoad(audioFile) {
loaded++;
if (loaded === files.length) {
return onComplete(files.length);
}
onProgress(loaded, files.length);
}
var that = this;
for (var i = 0; i < files.length; ++i) {
(function(audioFile) {
var sound = new XMLHttpRequest();
sound.addEventListener('load', onLoad.bind(that, audioFile));
sound.addEventListener('error', onLoad.bind(that, audioFile));
sound.addEventListener('abort', onLoad.bind(that, audioFile));
sound.open('GET', audioFile);
sound.send();
})(files[i]);
}
};