const core = require('engine/core');
const loader = require('engine/loader');
const { Resource } = loader;
const { Howl, Howler } = require('engine/audio/howler.core');
const EventEmitter = require('engine/EventEmitter');
const config = require('game/config');
/**
* Audio manager.
* @private
*/
const audio = new EventEmitter();
Object.assign(audio, {
Howl: Howl,
Howler: Howler,
/**
* Map of loaded audio files.
* @memberof module:engine/audio
* @type {Object<String, Howl>}
*/
sounds: {},
/**
* Whether audio is muted.
* @memberof module:engine/audio
* @type {boolean}
*/
muted: false,
/**
* Mute.
* @memberof module:engine/audio
* @method mute
*/
mute: function() { Howler.mute(true); audio.muted = true; audio.emit('mute', true); },
/**
* Unmute.
* @memberof module:engine/audio
* @method unmute
*/
unmute: function() { Howler.mute(false); audio.muted = false; audio.emit('mute', false); },
/**
* Get/set global audio volume.
* @memberof module:engine/audio
* @method volume
* @param {number} v Volume to set.
*/
volume: function(v) { Howler.volume(v); },
});
let mutedBeforePause = false;
core.on('pause', function() {
if (audio.muted) {
mutedBeforePause = true;
}
audio.mute();
});
core.on('resume', function() {
if (!mutedBeforePause) {
audio.unmute();
}
});
// Utils
const AudioUse = (config.audio && Array.isArray(config.audio.use)) ? config.audio.use : ['webm', 'mp3'];
/**
* Get file extension from a path
* @private
* @param {String} path Full path.
* @return {String} Extension
*/
function getFileExt(path) {
return (/[.]/.exec(path)) ? /[^.]+$/.exec(path) : undefined;
}
/**
* Split "|" separated extensions.
* @private
* @param {String} ext Full extension string.
* @return {Array} List of extensions.
*/
function splitExts(ext) {
return (/[|]/.exec(ext)) ? ext.split('|') : ext;
}
/**
* Overrided `load` function.
* @private
* @param {Function} cb Callback when loading completed.
*/
function load(cb) {
if (this.isLoading) {return;}
if (this.isComplete) {
if (cb) {setTimeout(() => cb(this), 1);}
return;
}
else if (cb) {
this.onComplete.once(cb);
}
this.data = new Howl({ src: this.url });
this.loadType = Resource.LOAD_TYPE.AUDIO;
this.type = Resource.TYPE.AUDIO;
this._setFlag(Resource.STATUS_FLAGS.LOADING, true);
this.onStart.dispatch(this);
this.data.on('loaderror', this._boundOnError, false);
this.data.on('load', this._boundComplete, false);
// Save to sound hash
audio.sounds[this.name] = this.data;
}
/**
* Overrided `complete` function.
* @private
*/
function complete() {
if (this.data) {
this.data.off('loaderror', this._boundOnError, false);
this.data.off('load', this._boundComplete, false);
}
if (this.isComplete) {
throw new Error('Complete called again for an already completed resource.');
}
this._setFlag(Resource.STATUS_FLAGS.COMPLETE, true);
this._setFlag(Resource.STATUS_FLAGS.LOADING, false);
this.onComplete.dispatch(this);
}
// Add middleware to support Howler.js
loader.pre((res, next) => {
let i, ext = getFileExt(res.url);
let urlWithoutExt = res.url.slice(0, ext.index);
// Check whether this resource is a supported audio file
for (i = 0; i < AudioUse.length; i++) {
if (ext[0].indexOf(AudioUse[i]) >= 0) {
res.url = splitExts(ext[0]);
// Has a list of extensions
if (Array.isArray(res.url)) {
for (i = 0; i < res.url.length; i++) {
res.url[i] = `${urlWithoutExt}${res.url[i]}`;
}
}
// Has a single extension
else {
res.url = [`${urlWithoutExt}${res.url}`];
}
// Use specific load and complete functions
res.load = load;
res.complete = complete;
res._boundComplete = res.complete.bind(res);
next();
return;
}
}
next();
});
/**
* Audio module is a simple wrapper of Howler.js.
* For more details, see the [Howler.js official site](http://goldfirestudios.com/blog/104/howler.js-Modern-Web-Audio-Javascript-Library).
*
* @example <caption>Load audio files</caption>
* import loader from 'engine/loader';
*
* // Add audio file with extensions, and give it an `id` for later use.
* loader.add('bgm', 'bgm.ogg');
* // You can
* loader.add('bgm2', 'bgm2.webm|mp3');
*
* // Note that ONLY files with extensions in `config.audio.use` will be
* // properly loaded.
*
* @example <caption>Play loaded sound file</caption>
* import audio from 'engine/audio';
*
* // Sound objects are just `Howl` instances
* audio.sounds['bgm'].loop(true).play();
*
* @emits mute
* @emits unmute
*
* @exports engine/audio
*
* @requires engine/EventEmitter
* @requires engine/core
* @requires engine/loader
* @requires engine/audio/howler.core
* @requires game/config
*/
module.exports = audio;