Socket
Socket
Sign inDemoInstall

musquito

Package Overview
Dependencies
4
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.1.2 to 3.0.0

dist/musquito-3.0.0.js

2

dev.js
/* eslint-disable */
import $buzz from './src/Buzz';
import engine from './src/Engine';

@@ -15,2 +16,3 @@ class EngineTester {

window.$buzz = $buzz;
window.engine = engine;

@@ -17,0 +19,0 @@ this.run = this.run.bind(this);

2

index.js

@@ -1,1 +0,1 @@

module.exports = require('./dist/musquito-2.1.2');
module.exports = require('./dist/musquito-3.0.0');
{
"name": "musquito",
"version": "2.1.2",
"version": "3.0.0",
"description": "An audio engine for HTML5 games and interactive websites",

@@ -5,0 +5,0 @@ "keywords": [

@@ -14,2 +14,3 @@ <p align="center">

- Simple API to create and play sounds
- Supports sound groups
- Supports variety of codecs

@@ -42,3 +43,3 @@ - Supports audio sprites

```
<script src="musquito/dist/musquito-1.0.0.min.js"></script>
<script src="musquito/dist/musquito.min.js"></script>
```

@@ -51,41 +52,26 @@

### ES6 and above
```js
import $buzz from 'musquito';
const buzz = $buzz('gunfire.mp3');
buzz.play();
$buzz.play('gun.mp3');
```
### Classic JavaScript
```js
var buzz = $buzz('gunfire.mp3');
## Passing Additional Parameters
buzz.play();
```
The below example shows how you can pass additional parameters like volume, rate and callback.
## Advanced example
The below example shows how you can pass additional parameters like volume, rate and event handlers.
```js
const buzz = $buzz({
src: ['gunfire.mp3', 'gunfire.wav'],
$buzz.play({
src: ['greenade.mp3', 'greenade.wav'],
volume: 0.5,
rate: 2,
onplaystart: () => console.log('Playback started'),
onplayend: () => console.log('Playback ended')
playEndCallback: () => alert('Playback started')
});
buzz.play();
```
## Using sprites
## Using Sprites
Audio Sprites are like image sprites concatenates multiple small sounds in a single file. You can create audio sprite using this [tool](https://github.com/tonistiigi/audiosprite).
Audio Sprites are like image sprites concatenates multiple small sounds in a single file. You can create audio sprite using this tool.

@@ -95,16 +81,51 @@ Below is an example of how to use sprites.

```js
const buzz = $buzz({
src: 'gamesprite.mp3',
sprite:{
'gun': [0, 0.48],
'bomb': [2, 2.42],
'greenade': [4, 4.67]
}
$buzz.play({
src: 'sprite.mp3',
sprite: {
"beep": [
0,
0.48108843537414964
],
"button": [
2,
2.4290249433106577
],
"click": [
4,
4.672018140589569
]
},
sound: 'beep'
});
```
buzz.play('gun');
buzz.play('bomb');
## Pausing and Stopping Sounds
Calling the `play` method returns the sound id and you can use it to call other methods like pause, stop, change the volume and more properties of the sound.
```js
const soundid = $buzz.play('bg.mp3');
// Pausing sound
$buzz.pause(soundid);
// Stopping sound
$buzz.stop(soundid);
```
## Fading Sounds
You can fade the volume of a playing sound linearly or exponentially as shown below.
```js
const soundid = $buzz.play('bg.mp3');
setTimeout(() => {
$buzz.fade(soundid, 0, 3);
}, 2000);
```
## Playing Long Audio Files

@@ -115,24 +136,70 @@

```js
const buzz = $buzz({
$buzz.play({
src: 'bg.mp3',
stream: true
});
```
buzz.play();
## Advanced Example
The below example shows how we can setup audio engine by passing audio resources with shorthand identifiers initially before playing sounds. The `setup` method also takes lot of other arguments to configure the engine, please refer the API docs.
```js
$buzz.setup({
src: {
bg: 'bg.mp3',
sprite: {
url: 'sprite.mp3',
sprite: {
"beep": [
0,
0.48108843537414964
],
"button": [
2,
2.4290249433106577
],
"click": [
4,
4.672018140589569
]
}
}
},
oninit: () => {
// Playing sounds with identifiers
$buzz.play('#bg');
$buzz.play('#sprite.button');
}
});
```
## Fading Sounds
## Creating Audio Groups
You can fade the volume of a playing sound linearly or exponentially as shown below.
Sometimes it's convenient to create a sound group which is called as "Buzz" that helps to create and manage multiple sounds for a single audio resource. Buzzes also supports events. The below example shows how we can create a sound group for a sprite and play multiple sounds easier.
```js
const buzz = $buzz({
src: 'bg.mp3'
src: 'sprite.mp3',
sprite:{
"beep": [
0,
0.48108843537414964
],
"button": [
2,
2.4290249433106577
],
"click": [
4,
4.672018140589569
]
}
});
buzz.play();
...
buzz.fade(0, 3);
buzz.play('beep');
buzz.play('button');
buzz.play('click');
```

@@ -146,2 +213,48 @@

### `$buzz` static methods
Sets-up the audio engine. When you call the `$buzz` function the setup method will be automatically called. Often you call this method manually during the application startup to pass the parameters to the audio engine and also when you like to pre-load all the audio resources upfront. Before calling any method in engine you should call this method first.
The parameters you can pass to the `setup` method are below.
| Method | Returns | Description |
|--------|:-------:|-------------|
| setup(args?: object) | $buzz | Sets-up the audio engine. The different parameters you can pass in arguments object are `volume`, `muted`, `maxNodesPerSource`, `cleanUpInterval`, `autoEnable`, `src`, `preload`, `progress` and event handler functions like `oninit`, `onstop`, `onmute`, `onvolume`, `onsuspend`, `onresume`, `onerror` and `ondone`. |
| play(idOrSoundArgs: number|string|Array<string>|object) | $buzz,number | Creates and plays a new sound or the existing sound for the passed id. Returns sound id if new one is created. |
| pause(id: number) | $buzz | Pauses the sound for the passed id. |
| stop(id?: number) | $buzz | Stops the sound for the passed id or all the playing sounds. Stopping engine fires the "stop" event. |
| mute(id?: number) | $buzz | Mutes the sound if id is passed or the engine. Fires the "mute" event if engine is muted. |
| unmute(id?: number) | $buzz | Un-mutes the sound if id is passed or the engine. Fires the "mute" event if engine is un-muted. |
| volume(vol?: number, id?: number) | $buzz,number | Gets/sets the volume for the audio engine that controls global volume for all sounds or the sound of the passed id. Fires the "volume" event in case of engine. The value of the passed volume should be from 0 to 1. |
| fade(id: number, to: number, duration: number, type = 'linear','exponential') | $buzz | Fades the sound belongs to the passed id. |
| fadeStop(id: number) | $buzz | Stops the running fade. |
| rate(id: number, rate?: number) | $buzz,number | Gets/sets the rate of the passed sound. The value of the passed rate should be from 1 to 5. |
| seek(id: number, seek?: number) | $buzz,number | Gets/sets the current position of the passed sound. |
| loop(id: number, loop?: boolean) | $buzz,boolean | Gets/sets the loop parameter of the sound. |
| destroy(id: number) | $buzz | Destroys the passed sound. |
| load(urls: string, Array<string>, progressCallback: function) | Promise | Loads single or multiple audio resources into audio buffers and returns them. |
| loadMedia(urls: string, Array<string>) | Promise | Pre-loads single or multiple HTML5 audio nodes with the passed resources and returns them. |
| unload(urls: string, Array<string>) | $buzz | Unloads single or multiple loaded audio buffers from cache. |
| unloadMedia(urls: string, Array<string>) | $buzz | Releases audio nodes allocated for the passed urls. |
| register(src: string|Array<string>|object, key: string) | $buzz | Assigns a short-hand key for the audio source. In case of object the properties are `url`, `format` and `sprite`. |
| unregister(src: string|Array<string>|object, key: string) | $buzz | Removes the assigned key for the audio source. |
| getSource(key: string) | string,Array<string>,object | Returns the assigned audio source for the passed key. |
| suspend() | $buzz | Stops all the playing sounds and suspends the engine immediately. |
| resume() | $buzz | Resumes the engine from the suspended mode. |
| terminate() | $buzz | Shuts down the engine. |
| muted() | boolean | Returns whether the engine is currently muted or not. |
| state() | EngineState | Returns the state of the engine. The different values are "notready", "ready", "suspending", "suspended", "resuming", "destroying", "done" and "no-audio". |
| buzz(id: number) | Buzz | Returns the buzz for the passed id. |
| buzzes() | Array<Buzz> | Returns all the buzzes. |
| sound(id: number) | Sound | Returns the sound for the passed id. `Sound` is an internal object and you don't have to deal with it usually. |
| sounds() | Array<Sound> | Returns all the sounds created directly by engine. Sounds created by sound groups are not included. |
| context() | AudioContext | Returns the created audio context. |
| isAudioAvailable() | boolean | Returns true if Web Audio API is available. |
| on(eventName: string, handler: function, once = false) | $buzz | Subscribes to an event. |
| off(eventName: string, handler: function) | $buzz | Un-subscribes from an event. |
| masterGain() | GainNode | Returns the master gain node. |
| bufferLoader() | BufferLoader | Returns buffer loader. |
| mediaLoader() | MediaLoader | Returns media loader. |
### `$buzz` function

@@ -186,2 +299,3 @@

### `Buzz` object methods

@@ -219,32 +333,3 @@

### `$buzz` static / global methods
These are wrapper methods of engine that helps to control the audio globally. You can invoke this method by `$buzz.[methodname](args)`.
| Method | Returns | Description |
|--------|:-------:|-------------|
| setup(args?: object) | $buzz | Sets-up the audio engine. The different parameters you can pass in arguments object are `volume`, `muted`, `maxNodesPerSource`, `cleanUpInterval`, `autoEnable` and event handler functions like `onstop`, `onmute`, `onvolume`, `onsuspend`, `onresume`, `onerror` and `ondone`. |
| load(urls: string, Array<string>, progressCallback: function) | Promise | Loads single or multiple audio resources into audio buffers and returns them. |
| loadMedia(urls: string, Array<string>) | Promise | Pre-loads single or multiple HTML5 audio nodes with the passed resources and returns them. |
| unload(urls: string, Array<string>) | $buzz | Unloads single or multiple loaded audio buffers from cache. |
| unloadMedia(urls: string, Array<string>) | $buzz | Releases audio nodes allocated for the passed urls. |
| mute() | $buzz | Mutes the engine. |
| unmute() | $buzz | Un-mutes the engine. |
| volume(vol?: number) | $buzz, number | Gets/sets the volume for the audio engine that controls global volume for all sounds. |
| stop() | $buzz | Stops all the currently playing sounds. |
| suspend() | $buzz | Stops all the playing sounds and suspends the engine immediately. |
| resume() | $buzz | Resumes the engine from the suspended mode. |
| terminate() | $buzz | Shuts down the engine. |
| muted() | boolean | Returns whether the engine is currently muted or not. |
| state() | EngineState | Returns the state of the engine. The different values are "notready", "ready", "suspending", "suspended", "resuming", "destroying", "done" and "no-audio". |
| buzz(id: number) | Buzz | Returns the buzz for the passed id. |
| buzzes() | Array<Buzz> | Returns all the buzzes. |
| context() | AudioContext | Returns the created audio context. |
| isAudioAvailable() | boolean | Returns true if Web Audio API is available. |
| on(eventName: string, handler: function, once = false) | $buzz | Subscribes to an event. |
| off(eventName: string, handler: function) | $buzz | Un-subscribes from an event. |
| masterGain() | GainNode | Returns the master gain node. |
| bufferLoader() | BufferLoader | Returns buffer loader. |
| mediaLoader() | MediaLoader | Returns media loader. |
## License

@@ -251,0 +336,0 @@

@@ -7,2 +7,3 @@ import engine, { EngineEvents, EngineState, ErrorType } from './Engine';

import DownloadStatus from './DownloadStatus';
import LoadState from './LoadState';

@@ -42,14 +43,4 @@ /**

/**
* Enum that represents the different states occurs while loading a sound.
* @enum {string}
* A wrapper class that simplifies managing multiple sounds created for a single source.
*/
const LoadState = {
NotLoaded: 'notloaded',
Loading: 'loading',
Loaded: 'loaded'
};
/**
* A wrapper class that simplifies dealing with group of sounds.
*/
class Buzz {

@@ -86,2 +77,9 @@

/**
* True to use HTML5 audio node.
* @type {boolean}
* @private
*/
_stream = false;
/**
* The current volume of the sound. Should be from 0.0 to 1.0.

@@ -129,9 +127,2 @@ * @type {number}

/**
* True to use HTML5 audio node.
* @type {boolean}
* @private
*/
_stream = false;
/**
* Duration of the playback in seconds.

@@ -165,58 +156,65 @@ * @type {number}

/**
* The action queue.
* @type {Queue}
* True if the group is currently fading.
* @type {boolean}
* @private
*/
_queue = null;
_fading = false;
/**
* The audio engine.
* @type {Engine}
* The timer that runs function after the fading is complete.
* @type {number|null}
* @private
*/
_engine = null;
_fadeTimer = null;
/**
* Web API's audio context.
* @type {AudioContext}
* Number of audio resource loading calls in progress.
* @type {number}
* @private
*/
_context = null;
_noOfLoadCalls = 0;
/**
* The group's gain node.
* @type {GainNode}
* Array of sounds belongs to this group.
* @type {Array<Sound>}
* @private
*/
_gainNode = null;
_soundsArray = [];
/**
* True if the group is currently fading.
* @type {boolean}
* Array of pre-loaded HTML5 audio elements.
* @type {Array<Audio>}
* @private
*/
_fading = false;
_audioNodes = [];
/**
* The timer that runs function after the fading is complete.
* @type {number|null}
* The action queue.
* @type {Queue}
* @private
*/
_fadeTimer = null;
_queue = null;
/**
* Number of audio resource loading calls in progress.
* @type {number}
* The audio engine.
* @type {Engine}
* @private
*/
_noOfLoadCalls = 0;
_engine = null;
/**
* Array of sounds belongs to this group.
* @type {Array}
* Web API's audio context.
* @type {AudioContext}
* @private
*/
_soundsArray = [];
_context = null;
/**
* The group's gain node.
* @type {GainNode}
* @private
*/
_gainNode = null;
/**
* Initializes the internal properties.

@@ -226,11 +224,11 @@ * @param {string|Array<string>|object} args The input parameters of this sound group.

* @param {string|string[]} args.src Single or array of audio urls/base64 strings.
* @param {string|string[]} [args.format] The file format(s) of the passed audio source(s).
* @param {object} [args.sprite] The audio sprite definition.
* @param {boolean} [args.stream = false] True to use HTML5 audio node.
* @param {number} [args.volume = 1.0] The initial volume of the sound. Should be from 0.0 to 1.0.
* @param {number} [args.rate = 1] The initial playback rate of the sound. Should be from 0.5 to 5.0.
* @param {boolean} [args.muted = false] True to be muted initially.
* @param {boolean} [args.loop = false] True to play the sound repeatedly.
* @param {boolean} [args.muted = false] True to be muted initially.
* @param {boolean} [args.preload = false] True to pre-load the sound after construction.
* @param {boolean} [args.autoplay = false] True to play automatically after construction.
* @param {boolean} [args.stream = false] True to use HTML5 audio node.
* @param {string|string[]} [args.format] The file format(s) of the passed audio source(s).
* @param {object} [args.sprite] The sprite definition.
* @param {function} [args.onload] Event-handler for the "load" event.

@@ -241,4 +239,4 @@ * @param {function} [args.onloadprogress] Event-handler for the "loadprogress" event (only for non-stream types).

* @param {function} [args.onplayend] Event-handler for the "playend" event.
* @param {function} [args.onpause] Event-handler for the "pause" event.
* @param {function} [args.onstop] Event-handler for the "stop" event.
* @param {function} [args.onpause] Event-handler for the "pause" event.
* @param {function} [args.onmute] Event-handler for the "mute" event.

@@ -253,28 +251,11 @@ * @param {function} [args.onvolume] Event-handler for the "volume" event.

constructor(args) {
this._engine = engine;
this._onLoadProgress = this._onLoadProgress.bind(this);
// Setup the audio engine.
this._engine = engine;
this._engine.setup();
// If no audio is available throw error.
if (!this._engine.isAudioAvailable()) {
this._fire(BuzzEvents.Error, null, { type: ErrorType.NoAudio, error: 'Web Audio is un-available' });
return this;
}
// Store the audio context.
this._context = this._engine.context();
// Add the created buzz to the engine and connect the gain nodes.
this._engine.add(this);
this._gainNode = this._engine.context().createGain();
this._gainNode.gain.setValueAtTime(this._muted ? 0 : this._volume, this._context.currentTime);
this._gainNode.connect(this._engine.masterGain());
// Subscribe to engine's resume event.
this._engine.on(EngineEvents.Resume, this._onEngineResume = this._onEngineResume.bind(this));
if (typeof args === 'string') {
this._src = [args];
if (args.startsWith('#')) {
this._setSource(args);
} else {
this._src = [args];
}
} else if (Array.isArray(args) && args.length) {

@@ -288,2 +269,3 @@ this._src = args;

sprite,
stream,
volume,

@@ -293,5 +275,4 @@ rate,

loop,
preload,
autoplay,
stream,
preload,
onload,

@@ -302,4 +283,4 @@ onloadprogress,

onplayend,
onpause,
onstop,
onpause,
onmute,

@@ -313,3 +294,3 @@ onvolume,

// Set the passed id or the random one.
// Set the passed id or the auto-generated one.
this._id = typeof id === 'number' ? id : utility.id();

@@ -319,4 +300,8 @@

if (typeof src === 'string') {
this._src = [src];
} else if (Array.isArray(src) && src.length) {
if (src.startsWith('#')) {
this._setSource(src);
} else {
this._src = [src];
}
} else if (Array.isArray(src)) {
this._src = src;

@@ -334,2 +319,3 @@ }

typeof sprite === 'object' && (this._sprite = sprite);
typeof stream === 'boolean' && (this._stream = stream);
typeof volume === 'number' && volume >= 0 && volume <= 1.0 && (this._volume = volume);

@@ -339,5 +325,4 @@ typeof rate === 'number' && rate >= 0.5 && rate <= 5 && (this._rate = rate);

typeof loop === 'boolean' && (this._loop = loop);
typeof preload === 'boolean' && (this._preload = preload);
typeof autoplay === 'boolean' && (this._autoplay = autoplay);
typeof stream === 'boolean' && (this._stream = stream);
typeof preload === 'boolean' && (this._preload = preload);

@@ -350,4 +335,4 @@ // Bind the passed event handlers to events.

typeof onplayend === 'function' && this.on(BuzzEvents.PlayEnd, onplayend);
typeof onpause === 'function' && this.on(BuzzEvents.Pause, onpause);
typeof onstop === 'function' && this.on(BuzzEvents.Stop, onstop);
typeof onpause === 'function' && this.on(BuzzEvents.Pause, onpause);
typeof onmute === 'function' && this.on(BuzzEvents.Mute, onmute);

@@ -362,9 +347,30 @@ typeof onvolume === 'function' && this.on(BuzzEvents.Volume, onvolume);

// Throw error if source is not passed.
if (!this._src) {
if (!this._src.length) {
throw new Error('You should pass the source for the audio.');
}
// Instantiate the dependencies.
// Instantiate the queue.
this._queue = new Queue();
// Setup the audio engine.
this._engine.setup();
// If audio is not supported throw error.
if (!this._engine.isAudioAvailable()) {
this._fire(BuzzEvents.Error, null, { type: ErrorType.NoAudio, error: 'Web Audio is un-available' });
return this;
}
// Store the audio context.
this._context = this._engine.context();
// Add the created buzz to the engine and connect the gain nodes.
this._engine.add(this);
this._gainNode = this._engine.context().createGain();
this._gainNode.gain.setValueAtTime(this._muted ? 0 : this._volume, this._context.currentTime);
this._gainNode.connect(this._engine.masterGain());
// Subscribe to engine's resume event.
this._engine.on(EngineEvents.Resume, this._onEngineResume = this._onEngineResume.bind(this));
if (this._autoplay) {

@@ -390,3 +396,3 @@ this.play();

// If the sound is not of stream and the source is loaded or currently loading then return.
if (!this._stream && (this.isLoaded() || this._loadState === LoadState.Loading)) {
if (!this._stream && this._loadState !== LoadState.NotLoaded) {
return this;

@@ -401,2 +407,3 @@ }

// Get the first compatible source.
const src = this._compatibleSrc || (this._compatibleSrc = this.getCompatibleSource());

@@ -409,8 +416,5 @@

if (this._stream && this._state === BuzzState.Destroyed) {
this._engine.releaseForGroup(this._compatibleSrc, this._id);
return;
}
// During the loading, if buzz is destroyed or un-loaded then return.
if (this._state === BuzzState.Destroyed || this._loadState === LoadState.NotLoaded) {
this._stream && this._engine.releaseForGroup(this._compatibleSrc, this._id);
return;

@@ -477,41 +481,42 @@ }

const newSoundId = utility.id(),
playSound = () => {
const soundArgs = {
id: newSoundId,
buffer: this._buffer,
stream: this._stream,
audio: this._stream ? this._engine.allocateForSound(this._compatibleSrc, this._id, newSoundId) : null,
volume: this._volume,
rate: this._rate,
muted: this._muted,
loop: this._loop,
playEndCallback: () => this._fire(BuzzEvents.PlayEnd, newSoundId),
destroyCallback: () => {
this._removeSound(newSoundId);
this._fire(BuzzEvents.Destroy, newSoundId);
emitter.clear(newSoundId);
},
fadeEndCallback: () => this._fire(BuzzEvents.FadeEnd, newSoundId),
audioErrorCallback: (sound, err) => {
this._fire(BuzzEvents.Error, newSoundId, { type: ErrorType.LoadError, error: err });
sound.destroy();
},
loadCallback: () => {
this._fire(BuzzEvents.Load, newSoundId);
}
};
// Create a unique id for sound.
const newSoundId = utility.id();
if (typeof soundOrId === 'string' && this._sprite && this._sprite.hasOwnProperty(soundOrId)) {
const positions = this._sprite[soundOrId];
soundArgs.startPos = positions[0];
soundArgs.endPos = positions[1];
}
// Create the arguments of the sound.
const soundArgs = {
id: newSoundId,
stream: this._stream,
volume: this._volume,
rate: this._rate,
muted: this._muted,
loop: this._loop,
playEndCallback: () => this._fire(BuzzEvents.PlayEnd, newSoundId),
fadeEndCallback: () => this._fire(BuzzEvents.FadeEnd, newSoundId),
audioErrorCallback: (sound, err) => this._fire(BuzzEvents.Error, newSoundId, { type: ErrorType.LoadError, error: err }),
loadCallback: () => this._fire(BuzzEvents.Load, newSoundId),
destroyCallback: () => {
this._removeSound(newSoundId);
this._engine.releaseForSound(this._compatibleSrc, this._id, newSoundId);
this._fire(BuzzEvents.Destroy, newSoundId);
emitter.clear(newSoundId);
}
};
const newSound = new Sound(soundArgs);
newSound._gain().connect(this._gainNode);
this._soundsArray.push(newSound);
this._play(newSound);
};
// In case of sprite, set the positions.
if (typeof soundOrId === 'string' && this._sprite && this._sprite.hasOwnProperty(soundOrId)) {
const positions = this._sprite[soundOrId];
soundArgs.startPos = positions[0];
soundArgs.endPos = positions[1];
}
// Create the sound and connect the gains.
const newSound = new Sound(soundArgs);
newSound._gain().connect(this._gainNode);
this._soundsArray.push(newSound);
const playSound = () => {
newSound.source(this._stream ? this._engine.allocateForSound(this._compatibleSrc, this._id, newSoundId) : this._buffer);
this._play(newSound);
};
// If the sound is not yet loaded push an action to the queue to play the sound once it's loaded.

@@ -738,6 +743,2 @@ if (!this.isLoaded()) {

seek(id, seek) {
if (!id) {
return this;
}
const sound = this.sound(id);

@@ -849,3 +850,3 @@

this._buffer = null;
this._stream && (this._duration = 0);
this._duration = 0;
this._loadState = LoadState.NotLoaded;

@@ -1016,2 +1017,37 @@ this._noOfLoadCalls = 0;

/**
* Sets source, format and sprite from the assigned key.
* @param {string} key The source key.
* @private
*/
_setSource(key) {
const src = this._engine.getSource(key.substring(1));
if (typeof src === 'string') {
this._src = [src];
} else if (Array.isArray(src)) {
this._src = src;
} else if (typeof src === 'object') {
const {
url,
format,
sprite
} = src;
if (typeof url === 'string') {
this._src = [url];
} else if (Array.isArray(url)) {
this._src = url;
}
if (Array.isArray(format)) {
this._format = format;
} else if (typeof format === 'string' && format) {
this._format = [format];
}
typeof sprite === 'object' && (this._sprite = sprite);
}
}
/**
* Called on failure of loading audio source.

@@ -1141,2 +1177,7 @@ * @param {*} error The audio source load error.

'setup',
'play',
'pause',
'register',
'unregister',
'getSource',
'load',

@@ -1149,2 +1190,8 @@ 'loadMedia',

'volume',
'fade',
'fadeStop',
'rate',
'seek',
'loop',
'destroy',
'stop',

@@ -1162,3 +1209,7 @@ 'suspend',

'on',
'off'
'off',
'buzz',
'buzzes',
'sound',
'sounds'
].forEach(method => {

@@ -1165,0 +1216,0 @@ $buzz[method] = function () {

@@ -1,6 +0,8 @@

import BufferLoader from './BufferLoader';
import MediaLoader from './MediaLoader';
import emitter from './Emitter';
import Queue from './Queue';
import utility from './Utility';
import BufferLoader from './BufferLoader';
import MediaLoader from './MediaLoader';
import emitter from './Emitter';
import Queue from './Queue';
import utility from './Utility';
import Sound from './Sound';
import DownloadStatus from './DownloadStatus';

@@ -38,2 +40,3 @@ /**

const EngineEvents = {
Init: 'init',
Volume: 'volume',

@@ -107,3 +110,3 @@ Mute: 'mute',

/**
* Inactive time of sound.
* Inactive time of sound/HTML5 audio.
* @type {number}

@@ -171,2 +174,9 @@ * @private

/**
* Array of sounds created directly by engine.
* @type {Array<Sound>}
* @private
*/
_soundsArray = [];
/**
* Loader - the component that loads audio buffers with audio data.

@@ -186,2 +196,9 @@ * @type {BufferLoader}

/**
* Registry of audio sources, sprites with short-hand notations.
* @type {object}
* @private
*/
_sourceRegistry = {};
/**
* Instantiates the action queue.

@@ -203,3 +220,8 @@ * @constructor

* @param {number} [args.cleanUpInterval = 5] The sounds garbage collection interval period in minutes.
* @param {number} [args.inactiveTime = 2] The period after which sound/HTML5 audio node is marked as inactive.
* @param {boolean} [args.autoEnable = true] Auto-enables audio in first user interaction.
* @param {object} [args.src] The audio sources.
* @param {boolean} [args.preload = true] True to preload audio sources.
* @param {function} [args.progress] The function that's called to notify the progress of resources loaded.
* @param {function} [args.oninit] Event-handler for the "init" event.
* @param {function} [args.onstop] Event-handler for the "stop" event.

@@ -238,3 +260,8 @@ * @param {function} [args.onmute] Event-handler for the "mute" event.

cleanUpInterval,
inactiveTime,
autoEnable,
src,
preload = true,
progress,
oninit,
onstop,

@@ -254,3 +281,5 @@ onmute,

typeof cleanUpInterval === 'number' && (this._cleanUpInterval = cleanUpInterval);
typeof inactiveTime === 'number' && (this._inactiveTime = inactiveTime);
typeof autoEnable === 'boolean' && (this._autoEnable = autoEnable);
typeof oninit === 'function' && this.on(EngineEvents.Init, oninit);
typeof onstop === 'function' && this.on(EngineEvents.Stop, onstop);

@@ -268,4 +297,5 @@ typeof onmute === 'function' && this.on(EngineEvents.Mute, onmute);

// Create the media loader.
this._mediaLoader = new MediaLoader(this._maxNodesPerSource, (src) => {
this._buzzesArray.forEach(buzz => buzz.getCompatibleSource() === src && buzz.free());
this._mediaLoader = new MediaLoader(this._maxNodesPerSource, this._inactiveTime, (x) => {
this._freeSounds();
this._buzzesArray.forEach(buzz => buzz.getCompatibleSource() === x && buzz.free());
});

@@ -288,110 +318,201 @@

return this;
}
if (typeof src === 'object') {
Object.keys(src).forEach(x => this.register(src[x], x));
/**
* Loads single or multiple audio resources into audio buffers and returns them.
* @param {string|string[]} urls Single or array of audio urls.
* @param {function} [progressCallback] The callback that is called to intimate the percentage downloaded.
* @return {Promise}
*/
load(urls, progressCallback) {
return this._bufferLoader.load(urls, progressCallback);
}
if (preload) {
const getCompatibleSrc = (sources) => {
return sources.map(x => {
let s = [], f = [];
/**
* Loads HTML5 audio nodes for the passed urls.
* @param {string|string[]} urls Single or array of audio urls.
* @return {Promise<DownloadResult|Array<DownloadResult>>}
*/
loadMedia(urls) {
return this._mediaLoader.load(urls);
if (typeof x === 'string') {
s = [x];
} else if (Array.isArray(x)) {
s = x;
} else if (typeof x === 'object') {
const { url, format = [] } = x;
if (typeof url === 'string') {
s = [url];
} else if (Array.isArray(url)) {
s = url;
}
f = format;
}
return this._getCompatibleSource(s, f);
});
};
const bufferUrls = getCompatibleSrc(Object.values(src).filter(x => !x.stream));
const mediaUrls = getCompatibleSrc(Object.values(src).filter(x => x.stream));
Promise.all([this.load(bufferUrls, progress), this.loadMedia(mediaUrls)])
.then(result => this._fire(EngineEvents.Init, result));
} else {
this._fire(EngineEvents.Init);
}
}
return this;
}
/**
* Stores the buzz in the internal collection.
* @param {Buzz} buzz The newly created buzz.
* @return {Engine}
* Plays the sound belongs to the passed id or creates a new sound and play it.
* @param {number|string|Array<string>|object} idOrSoundArgs The sound id or new sound arguments.
* @param {number} [idOrSoundArgs.id] The unique id of the sound.
* @param {string|Array<string>} [idOrSoundArgs.src] Single or array of audio urls/base64 strings.
* @param {string|string[]} [idOrSoundArgs.format] The file format(s) of the passed audio source(s).
* @param {object} [idOrSoundArgs.sprite] The sprite definition.
* @param {string} [idOrSoundArgs.sound] The sound defined in sprite to play.
* @param {boolean} [idOrSoundArgs.stream = false] True to use HTML5 audio node for playing sound.
* @param {number} [idOrSoundArgs.volume = 1.0] The initial volume of the sound. Should be from 0.0 to 1.0.
* @param {number} [idOrSoundArgs.rate = 1] The initial playback rate of the sound. Should be from 0.5 to 5.0.
* @param {boolean} [idOrSoundArgs.loop = false] True to play the sound repeatedly.
* @param {boolean} [idOrSoundArgs.muted = false] True to be muted initially.
* @param {function} [idOrSoundArgs.loadCallback] The callback that will be called when the underlying HTML5 audio node is loaded.
* @param {function} [idOrSoundArgs.playEndCallback] The callback that will be invoked after the play ends.
* @param {function} [idOrSoundArgs.destroyCallback] The callback that will be invoked after destroyed.
* @param {function} [idOrSoundArgs.fadeEndCallback] The callback that will be invoked the fade is completed.
* @param {function} [idOrSoundArgs.audioErrorCallback] The callback that will be invoked when there is error in HTML5 audio node.
* @return {Engine|number}
*/
add(buzz) {
if (this._buzzesArray.indexOf(buzz) > -1) {
play(idOrSoundArgs) {
// If id is passed then get the sound from the engine and play it.
if (typeof idOrSoundArgs === 'number') {
const sound = this.sound(idOrSoundArgs);
sound && sound.play();
return this;
}
this._buzzesArray.push(buzz);
let audioSrc = [],
audioFormat = [],
audioSprite = null,
spriteSound = null,
soundArgs = {};
return this;
}
if (typeof idOrSoundArgs === 'string') {
if (idOrSoundArgs.startsWith('#')) {
const parts = idOrSoundArgs.split('.'),
key = parts[0].substring(1);
/**
* Removes the stored buzz from the internal collection.
* @param {Buzz} buzz The buzz.
* @return {Engine}
*/
remove(buzz) {
this._buzzesArray.splice(this._buzzesArray.indexOf(buzz), 1);
return this;
}
spriteSound = parts[1];
/**
* Loads audio node for group.
* @param {string} url The audio file url.
* @param {number} groupId The group id.
* @return {Promise<DownloadResult>}
*/
allocateForGroup(url, groupId) {
return this._mediaLoader.allocateForGroup(url, groupId);
}
({ audioSrc, audioFormat, audioSprite } = this._getSourceInfo(key));
} else {
audioSrc = [idOrSoundArgs];
}
} else if (Array.isArray(idOrSoundArgs)) {
audioSrc = idOrSoundArgs;
} else if (typeof idOrSoundArgs === 'object') {
const {
id,
src,
format,
sprite,
sound,
stream,
volume,
rate,
loop,
muted,
loadCallback,
playEndCallback,
destroyCallback,
fadeEndCallback,
audioErrorCallback
} = idOrSoundArgs;
/**
* Allocates an audio node for sound and returns it.
* @param {string} src The audio file url.
* @param {number} groupId The buzz id.
* @param {number} soundId The sound id.
* @return {Audio}
*/
allocateForSound(src, groupId, soundId) {
return this._mediaLoader.allocateForSound(src, groupId, soundId);
}
if (typeof src === 'string') {
if (src.startsWith('#')) {
const parts = src.split('.'),
key = parts[0].substring(1);
/**
* Unloads single or multiple loaded audio buffers from cache.
* @param {string|string[]} [urls] Single or array of audio urls.
* @return {Engine}
*/
unload(urls) {
if (urls) {
this._bufferLoader.unload(urls);
return this;
({ audioSrc, audioFormat, audioSprite } = this._getSourceInfo(key));
} else {
audioSrc = [src];
}
} else if (Array.isArray(src)) {
audioSrc = src;
}
if (Array.isArray(format)) {
audioFormat = format;
} else if (typeof format === 'string' && format) {
audioFormat = [format];
}
typeof id === 'number' && (soundArgs.id = id);
typeof sprite === 'object' && (audioSprite = sprite);
typeof sound === 'string' && (spriteSound = sound);
typeof stream === 'boolean' && (soundArgs.stream = stream);
typeof volume === 'number' && volume >= 0 && volume <= 1.0 && (soundArgs.volume = volume);
typeof rate === 'number' && rate >= 0.5 && rate <= 5 && (soundArgs.rate = rate);
typeof muted === 'boolean' && (soundArgs.muted = muted);
typeof loop === 'boolean' && (soundArgs.loop = loop);
typeof loadCallback === 'function' && (soundArgs.loadCallback = loadCallback);
typeof playEndCallback === 'function' && (soundArgs.playEndCallback = playEndCallback);
typeof destroyCallback === 'function' && (soundArgs.destroyCallback = destroyCallback);
typeof fadeEndCallback === 'function' && (soundArgs.fadeEndCallback = fadeEndCallback);
typeof audioErrorCallback === 'function' && (soundArgs.audioErrorCallback = audioErrorCallback);
}
this._bufferLoader.unload();
if (!audioSrc.length) {
throw new Error('You should pass the source for the audio.');
}
return this;
}
this.setup();
/**
* Releases audio nodes allocated for the passed urls.
* @param {string|string[]} [urls] Single or array of audio urls.
* @return {Engine}
*/
unloadMedia(urls) {
if (urls) {
this._mediaLoader.unload(urls);
const {
stream,
loadCallback,
audioErrorCallback,
destroyCallback
} = soundArgs;
if (!this.isAudioAvailable()) {
audioErrorCallback && audioErrorCallback({ type: ErrorType.NoAudio, error: 'Web Audio is un-available' });
return this;
}
this._mediaLoader.unload();
const compatibleSrc = this._getCompatibleSource(audioSrc, audioFormat);
return this;
soundArgs.destroyCallback = (sound) => {
this._removeSound(sound);
this.releaseForSound(compatibleSrc, this._id, sound.id());
destroyCallback && destroyCallback(sound);
};
if (typeof spriteSound === 'string' && audioSprite && audioSprite.hasOwnProperty(spriteSound)) {
const positions = audioSprite[spriteSound];
soundArgs.startPos = positions[0];
soundArgs.endPos = positions[1];
}
const sound = new Sound(soundArgs);
sound._gain().connect(this._gainNode);
this._soundsArray.push(sound);
const load$ = stream ? this.allocateForGroup(compatibleSrc, this._id) : this.load(compatibleSrc);
load$.then(downloadResult => {
if (downloadResult.status === DownloadStatus.Success) {
sound.source(stream ? this.allocateForSound(compatibleSrc, this._id, sound.id()) : downloadResult.value);
this._play(sound);
}
loadCallback && loadCallback(downloadResult, sound);
});
return sound.id();
}
/**
* Releases the allocated audio nodes for the group.
* @param {string} url The audio file url.
* @param {number} groupId The group id.
* Pauses the sound.
* @param {number} id The sound id.
* @return {Engine}
*/
releaseForGroup(url, groupId) {
this._mediaLoader.releaseForGroup(url, groupId);
pause(id) {
const sound = this.sound(id);
sound && sound.pause();
return this;

@@ -401,16 +522,35 @@ }

/**
* Returns if there are free audio nodes available for a group.
* @param {string} src The audio file url.
* @param {number} groupId The group id.
* @return {boolean}
* Stops all the currently playing sounds.
* @param {number} [id] The sound id.
* @return {Engine}
*/
hasFreeNodes(src, groupId) {
return this._mediaLoader.hasFreeNodes(src, groupId);
stop(id) {
if (typeof id !== 'undefined') {
const sound = this.sound(id);
sound && sound.stop();
return this;
}
// Stop all the sounds.
this.buzzes().forEach(buzz => buzz.stop());
this.sounds().forEach(sound => sound.stop());
// Fire the "stop" event.
this._fire(EngineEvents.Stop);
return this;
}
/**
* Mutes the engine.
* Mutes the engine or the passed sound.
* @param {number} [id] The sound id.
* @return {Engine}
*/
mute() {
mute(id) {
if (typeof id !== 'undefined') {
const sound = this.sound(id);
sound && sound.mute();
return this;
}
// If the engine is already muted return.

@@ -434,6 +574,13 @@ if (this._muted) {

/**
* Un-mutes the engine.
* Un-mutes the engine or the passed sound.
* @param {number} [id] The sound id.
* @return {Engine}
*/
unmute() {
unmute(id) {
if (typeof id !== 'undefined') {
const sound = this.sound(id);
sound && sound.unmute();
return this;
}
// If the engine is not muted return.

@@ -457,7 +604,21 @@ if (!this._muted) {

/**
* Gets/sets the volume for the audio engine that controls global volume for all sounds.
* Gets/sets the volume for the audio engine or the passed sound.
* @param {number} [vol] Should be within 0.0 to 1.0.
* @param {number} [id] The sound id.
* @return {Engine|number}
*/
volume(vol) {
volume(vol, id) {
if (typeof id !== 'undefined') {
const sound = this.sound(id);
if (sound) {
if (vol === undefined) {
return sound.volume();
}
sound.volume(vol);
return this;
}
}
// If no parameter is passed then return the current volume.

@@ -486,12 +647,43 @@ if (vol === undefined) {

/**
* Stops all the currently playing sounds.
* Fades the sound volume to the passed value in the passed duration.
* @param {number} id The sound id.
* @param {number} to The destination volume.
* @param {number} duration The period of fade.
* @param {string} [type = linear] The fade type (linear or exponential).
* @return {Engine}
*/
stop() {
// Stop all the sounds.
this.buzzes().forEach(buzz => buzz.stop());
fade(id, to, duration, type = 'linear') {
const sound = this.sound(id);
sound && sound.fade(to, duration, type);
return this;
}
// Fire the "stop" event.
this._fire(EngineEvents.Stop);
/**
* Stops the current running fade.
* @param {number} id The sound id.
* @return {Engine}
*/
fadeStop(id) {
const sound = this.sound(id);
sound && sound.fadeStop();
return this;
}
/**
* Gets/sets the playback rate.
* @param {number} id The sound id.
* @param {number} [rate] The playback rate. Should be from 0.5 to 5.
* @return {Engine|number}
*/
rate(id, rate) {
const sound = this.sound(id);
if (sound) {
if (typeof rate === 'undefined') {
return sound.rate();
}
sound.rate(rate);
}
return this;

@@ -501,2 +693,53 @@ }

/**
* Gets/sets the seek position.
* @param {number} id The sound id.
* @param {number} [seek] The seek position.
* @return {Engine|number}
*/
seek(id, seek) {
const sound = this.sound(id);
if (sound) {
if (typeof seek === 'undefined') {
return sound.seek();
}
sound.seek(seek);
}
return this;
}
/**
* Gets/sets the loop parameter of the sound.
* @param {number} id The sound id.
* @param {boolean} [loop] True to loop the sound.
* @return {Engine/boolean}
*/
loop(id, loop) {
const sound = this.sound(id);
if (sound) {
if (typeof loop === 'undefined') {
return sound.loop();
}
sound.loop(loop);
}
return this;
}
/**
* Destroys the passed sound.
* @param {number} id The sound id.
* @return {Engine}
*/
destroy(id) {
const sound = this.sound(id);
sound && sound.destroy();
return this;
}
/**
* Stops all the playing sounds and suspends the audio context immediately.

@@ -579,2 +822,5 @@ * @return {Engine}

// Destroy all sounds.
this._soundsArray.forEach(sound => sound.destroy());
// Clear the cache and remove the loader.

@@ -593,2 +839,3 @@ if (this._bufferLoader) {

this._buzzesArray = [];
this._soundsArray = [];
this._context = null;

@@ -628,2 +875,169 @@ this._queue.clear();

/**
* Assigns a short-hand key for the audio source.
* @param {string|Array<string>|object} src The audio source.
* @param {string|Array<string>} src.url The audio source.
* @param {string|Array<string>} src.format The audio formats.
* @param {object} src.sprite The sprite definition.
* @param {string} key The shorthand key to access it.
* @return {Engine}
*/
register(src, key) {
if (this._sourceRegistry.hasOwnProperty(key)) {
return this;
}
this._sourceRegistry[key] = src;
return this;
}
/**
* Removes the assigned key for the audio source.
* @param {string} src The audio source.
* @param {string} key The shorthand key to access it.
* @return {Engine}
*/
unregister(src, key) {
delete this._sourceRegistry[key];
return this;
}
/**
* Returns the source assigned for the key.
* @param {string} key The shorthand key.
* @return {*}
*/
getSource(key) {
return this._sourceRegistry[key];
}
/**
* Loads single or multiple audio resources into audio buffers and returns them.
* @param {string|string[]} urls Single or array of audio urls.
* @param {function} [progressCallback] The callback that is called to intimate the percentage downloaded.
* @return {Promise}
*/
load(urls, progressCallback) {
return this._bufferLoader.load(urls, progressCallback);
}
/**
* Loads HTML5 audio nodes for the passed urls.
* @param {string|string[]} urls Single or array of audio urls.
* @return {Promise<DownloadResult|Array<DownloadResult>>}
*/
loadMedia(urls) {
return this._mediaLoader.load(urls);
}
/**
* Stores the buzz in the internal collection.
* @param {Buzz} buzz The newly created buzz.
* @return {Engine}
*/
add(buzz) {
if (this._buzzesArray.indexOf(buzz) > -1) {
return this;
}
this._buzzesArray.push(buzz);
return this;
}
/**
* Removes the stored buzz from the internal collection.
* @param {Buzz} buzz The buzz.
* @return {Engine}
*/
remove(buzz) {
this._buzzesArray.splice(this._buzzesArray.indexOf(buzz), 1);
return this;
}
/**
* Loads audio node for group.
* @param {string} url The audio file url.
* @param {number} groupId The group id.
* @return {Promise<DownloadResult>}
*/
allocateForGroup(url, groupId) {
return this._mediaLoader.allocateForGroup(url, groupId);
}
/**
* Allocates an audio node for sound and returns it.
* @param {string} src The audio file url.
* @param {number} groupId The buzz id.
* @param {number} soundId The sound id.
* @return {Audio}
*/
allocateForSound(src, groupId, soundId) {
return this._mediaLoader.allocateForSound(src, groupId, soundId);
}
/**
* Unloads single or multiple loaded audio buffers from cache.
* @param {string|string[]} [urls] Single or array of audio urls.
* @return {Engine}
*/
unload(urls) {
if (urls) {
this._bufferLoader.unload(urls);
return this;
}
this._bufferLoader.unload();
return this;
}
/**
* Releases audio nodes allocated for the passed urls.
* @param {string|string[]} [urls] Single or array of audio urls.
* @return {Engine}
*/
unloadMedia(urls) {
if (urls) {
this._mediaLoader.unload(urls);
return this;
}
this._mediaLoader.unload();
return this;
}
/**
* Releases the allocated audio nodes for the group.
* @param {string} url The audio file url.
* @param {number} groupId The group id.
* @return {Engine}
*/
releaseForGroup(url, groupId) {
this._mediaLoader.releaseForGroup(url, groupId);
return this;
}
/**
* Destroys the audio node reserved for sound.
* @param {string} src The audio file url.
* @param {number} groupId The buzz id.
* @param {number} soundId The sound id.
*/
releaseForSound(src, groupId, soundId) {
this._mediaLoader.releaseForSound(src, groupId, soundId);
}
/**
* Returns if there are free audio nodes available for a group.
* @param {string} src The audio file url.
* @param {number} groupId The group id.
* @return {boolean}
*/
hasFreeNodes(src, groupId) {
return this._mediaLoader.hasFreeNodes(src, groupId);
}
/**
* Subscribes to an event.

@@ -656,2 +1070,3 @@ * @param {string} eventName Name of the event.

free() {
this._freeSounds();
this._buzzesArray.forEach(buzz => buzz.free());

@@ -720,3 +1135,3 @@ this._mediaLoader.cleanUp();

* Returns the buzz for the passed id.
* @param {number} [id] The buzz id.
* @param {number} id The buzz id.
* @return {Buzz}

@@ -737,2 +1152,19 @@ */

/**
* Returns the sound for the passed id.
* @param {number} id The sound id.
* @return {Sound}
*/
sound(id) {
return this._soundsArray.find(x => x.id() === id);
}
/**
* Returns all the sounds.
* @return {Array<Sound>}
*/
sounds() {
return this._soundsArray;
}
/**
* Returns in active time.

@@ -746,2 +1178,45 @@ * @return {number}

/**
* Returns source, format and sprite from the assigned key.
* @param {string} key The source key.
* @return {object}
* @private
*/
_getSourceInfo(key) {
const src = this.getSource(key);
const sourceInfo = {
audioSrc: [],
audioFormat: [],
audioSprite: null
};
if (typeof src === 'string') {
sourceInfo.audioSrc = [src];
} else if (Array.isArray(src)) {
sourceInfo.audioSrc = src;
} else if (typeof src === 'object') {
const {
url,
format,
sprite
} = src;
if (typeof url === 'string') {
sourceInfo.audioSrc = [url];
} else if (Array.isArray(url)) {
sourceInfo.audioSrc = url;
}
if (Array.isArray(format)) {
sourceInfo.format = format;
} else if (typeof format === 'string' && format) {
sourceInfo.format = [format];
}
typeof sprite === 'object' && (sourceInfo.audioSprite = sprite);
}
return sourceInfo;
}
/**
* Fires an event of engine.

@@ -766,2 +1241,79 @@ * @param {string} eventName The event name.

}
/**
* Removes the passed sound from the array.
* @param {number|Sound} sound The sound.
* @private
*/
_removeSound(sound) {
if (typeof sound === 'number') {
this._soundsArray = this._soundsArray.filter(x => x.id() !== sound);
return;
}
this._soundsArray.splice(this._soundsArray.indexOf(sound), 1);
}
/**
* Returns the first compatible source based on the passed sources and the format.
* @param {Array<string>} src The array of audio sources.
* @param {Array<string>} format The array of audio formats.
* @private
* @return {string}
*/
_getCompatibleSource(src, format) {
// If the user has passed "format", check if it is supported or else retrieve the first supported source from the array.
return format.length ?
src[format.indexOf(utility.getSupportedFormat(format))] :
utility.getSupportedSource(src);
}
/**
* Checks the engine state and plays the passed sound.
* @param {Sound} sound The sound.
* @private
*/
_play(sound) {
const { audioErrorCallback } = sound;
if (this._state === EngineState.Destroying || this._state === EngineState.Done) {
audioErrorCallback && audioErrorCallback({ type: ErrorType.PlayError, error: 'The engine is stopping/stopped' }, sound);
return;
}
if (this._state === EngineState.NoAudio) {
audioErrorCallback && audioErrorCallback({ type: ErrorType.NoAudio, error: 'Web Audio is un-available' }, sound);
return;
}
if ([EngineState.Suspending, EngineState.Suspended, EngineState.Resuming].indexOf(this._state) > -1) {
this._queue.add('after-resume', `sound-${sound.id()}`, () => sound.play());
this._state !== EngineState.Resuming && this.resume();
return;
}
sound.play();
}
/**
* Destroys inactive sounds.
* @private
*/
_freeSounds() {
const now = new Date();
this._soundsArray = this._soundsArray.filter(sound => {
const inactiveDurationInSeconds = (now - sound.lastPlayed()) / 1000;
if (sound.isPersistent() ||
sound.isPlaying() ||
sound.isPaused() ||
inactiveDurationInSeconds < this.inactiveTime() * 60) {
return true;
}
sound.destroy();
return false;
});
}
}

@@ -768,0 +1320,0 @@

@@ -17,2 +17,9 @@ import utility from './Utility';

/**
* Inactive time of sound/HTML5 audio.
* @type {number}
* @private
*/
_inactiveTime = 2;
/**
* The sounds store.

@@ -41,6 +48,8 @@ * @type {function}

* @param {number} maxNodesPerSource Maximum number of audio nodes allowed for a resource.
* @param {number} inactiveTime The period after which HTML5 audio node is marked as inactive.
* @param {function} soundCleanUpCallback The inactive sounds cleanup callback.
*/
constructor(maxNodesPerSource, soundCleanUpCallback) {
constructor(maxNodesPerSource, inactiveTime, soundCleanUpCallback) {
this._maxNodesPerSource = maxNodesPerSource;
this._inactiveTime = inactiveTime;
this._soundCleanUpCallback = soundCleanUpCallback;

@@ -62,3 +71,3 @@ }

const audio = new Audio();
unallocated.push(audio);
unallocated.push({ audio: audio, time: new Date() });

@@ -80,5 +89,5 @@ return audio;

{ unallocated, allocated } = nodes,
audio = unallocated.length ? unallocated.shift() : new Audio();
audio = unallocated.length ? unallocated.shift().audio : new Audio();
allocated[groupId].push(audio);
allocated[groupId].push({ soundId: null, audio: audio, time: new Date() });

@@ -92,17 +101,19 @@ return audio;

* @param {number} groupId The group id.
* @param {number} soundId The sound id.
* @return {Audio}
*/
allocateForSound(src, groupId) {
allocateForSound(src, groupId, soundId) {
this._createGroup(src, groupId);
const nodes = this._resourceNodesMap[src],
{ allocated } = nodes;
{ allocated } = nodes,
notAllocatedAudioObj = allocated[groupId].find(x => x.soundId === null);
const groupSounds = allocated[groupId];
if (!groupSounds.length) {
if (!notAllocatedAudioObj) {
throw new Error(`No free audio nodes available in the group ${groupId}`);
}
return groupSounds.shift();
notAllocatedAudioObj.soundId = soundId;
return notAllocatedAudioObj.audio;
}

@@ -125,3 +136,3 @@

unallocated.forEach(x => this._destroyNode(x));
unallocated.forEach(x => this._destroyNode(x.audio));

@@ -142,3 +153,3 @@ Object.keys(allocated).forEach(groupId => this.releaseForGroup(src, groupId));

allocated[groupId].forEach(audio => this._destroyNode(audio));
allocated[groupId].map(x => x.audio).forEach(node => this._destroyNode(node));
delete allocated[groupId];

@@ -150,15 +161,21 @@ }

* @param {string} src The audio file url.
* @param {Audio} audio The sound id.
* @param {number} groupId The buzz id.
* @param {number|Audio} soundIdOrAudio The sound id or audio.
*/
releaseAudio(src, audio, groupId) {
releaseAudio(src, groupId, soundIdOrAudio) {
const nodes = this._resourceNodesMap[src],
{ allocated, unallocated } = nodes;
this._destroyNode(audio);
if (soundIdOrAudio instanceof Audio) {
this._destroyNode(soundIdOrAudio);
if (groupId) {
allocated[groupId] = allocated[groupId].filter(x => x !== audio);
if (groupId) {
allocated[groupId] = allocated[groupId].filter(x => x.audio !== soundIdOrAudio);
} else {
nodes.unallocated = unallocated.filter(x => x.audio !== soundIdOrAudio);
}
} else {
nodes.unallocated = unallocated.filter(x => x !== audio);
const allocatedAudioObj = allocated[groupId].find(x => x.soundId === soundIdOrAudio);
this._destroyNode(allocatedAudioObj.audio);
allocated[groupId] = allocated[groupId].filter(x => x.soundId !== soundIdOrAudio);
}

@@ -171,5 +188,7 @@

/**
* Acquires the unallocated audio nodes and removes the excess ones.
* Removes inactive HTML5 audio nodes.
*/
cleanUp() {
const now = new Date();
Object.keys(this._resourceNodesMap).forEach(src => {

@@ -179,10 +198,15 @@ const nodes = this._resourceNodesMap[src],

let audioNodes = [];
Object.keys(allocated).forEach(groupId => {
const inactiveNodes = allocated[groupId]
.filter(x => x.soundId === null && ((now - x.time) / 1000 > this._inactiveTime * 60));
Object.keys(allocated).forEach(groupId => {
audioNodes = [...audioNodes, ...allocated[groupId]];
delete allocated[groupId];
allocated[groupId] = allocated[groupId].filter(x => inactiveNodes.indexOf(x) === -1);
inactiveNodes.forEach(x => this._destroyNode(x.audio));
});
nodes.unallocated = [...unallocated, ...audioNodes].slice(0, this._maxNodesPerSource);
const inactiveNodes = unallocated.filter(x => ((now - x.time) / 1000 > this._inactiveTime * 60));
nodes.unallocated = unallocated.filter(x => inactiveNodes.indexOf(x) === -1);
inactiveNodes.forEach(x => this._destroyNode(x.audio));
});

@@ -199,5 +223,5 @@ }

/**
* Returns true if there are free audio nodes available for a group.
* Returns true if there are free audio nodes available for a source or group.
* @param {string} src The audio file url.
* @param {number} groupId The group id.
* @param {number} [groupId] The group id.
* @return {boolean}

@@ -211,5 +235,5 @@ */

const nodes = this._resourceNodesMap[src],
{ allocated } = nodes;
{ unallocated, allocated } = nodes;
return allocated[groupId].length > 0;
return !groupId ? unallocated.length > 0 : allocated[groupId].filter(x => x.soundId === null).length > 0;
}

@@ -253,3 +277,3 @@

/**
* Chekcks and throws error if max audio nodes reached for the passed resource.
* Checks and throws error if max audio nodes reached for the passed resource.
* @param {string} src The source url.

@@ -277,2 +301,3 @@ * @private

if (!this._cleanUpCalled) {
this.cleanUp();
this._soundCleanUpCallback(src);

@@ -279,0 +304,0 @@ this._cleanUpCalled = true;

@@ -34,6 +34,7 @@ import Html5AudioPool from './Html5AudioPool';

* @param {number} maxNodesPerSource Maximum number of audio nodes allowed for a url.
* @param {number} inactiveTime The period after which HTML5 audio node is marked as inactive.
* @param {function} soundCleanUpCallback The inactive sounds cleanup callback.
*/
constructor(maxNodesPerSource, soundCleanUpCallback) {
this._audioPool = new Html5AudioPool(maxNodesPerSource, soundCleanUpCallback);
constructor(maxNodesPerSource, inactiveTime, soundCleanUpCallback) {
this._audioPool = new Html5AudioPool(maxNodesPerSource, inactiveTime, soundCleanUpCallback);
}

@@ -47,3 +48,2 @@

load(urls) {
console.log(typeof urls);
if (typeof urls === 'string') {

@@ -115,2 +115,12 @@ return this._load(urls);

/**
* Destroys the audio node reserved for sound.
* @param {string} src The audio file url.
* @param {number} groupId The buzz id.
* @param {number} soundId The sound id.
*/
releaseForSound(src, groupId, soundId) {
this._audioPool.releaseAudio(src, groupId, soundId);
}
/**
* Returns if there are free audio nodes available for a group.

@@ -175,3 +185,3 @@ * @param {string} src The audio file url.

audioObj && this._cleanUp(audioObj);
this._audioPool.releaseAudio(url, audio, groupId);
this._audioPool.releaseAudio(url, groupId, audio);
resolve(new DownloadResult(url, null, err));

@@ -178,0 +188,0 @@ };

@@ -1,4 +0,4 @@

import engine from './Engine';
import utility from './Utility';
import workerTimer from './WorkerTimer';
import engine from './Engine';
import utility from './Utility';
import workerTimer from './WorkerTimer';

@@ -234,2 +234,16 @@ /**

/**
* True if the audio source (buffer or html5 audio) exists.
* @type {boolean}
* @private
*/
_sourceExists = false;
/**
* True if the HTML5 audio node is pre-loaded.
* @type {boolean}
* @private
*/
_loaded = false;
/**
* Initializes the internal properties of the sound.

@@ -281,9 +295,10 @@ * @param {object} args The input parameters of the sound.

// Set the passed audio buffer and duration.
// Set the passed audio buffer or HTML5 audio node.
this._buffer = buffer;
this._audio = audio;
this._sourceExists = Boolean(this._buffer) || Boolean(this._audio);
// Set other properties.
this._stream = Boolean(stream);
this._endPos = this._stream ? this._audio.duration : this._buffer.duration;
this._stream = stream;
this._sourceExists && (this._endPos = this._stream ? this._audio.duration : this._buffer.duration);
volume && (this._volume = volume);

@@ -302,6 +317,6 @@ rate && (this._rate = rate);

this._duration = this._endPos - this._startPos;
this._isSprite = this._duration < this._endPos;
this._isSprite = typeof endPos !== 'undefined';
// If stream is `true` then set the playback rate, looping and listen to `error` event.
if (this._stream) {
if (this._stream && this._audio) {
this._audio.playbackRate = this._rate;

@@ -319,3 +334,3 @@ this._setLoop(this._loop);

// Create media element audio source node.
if (this._stream) {
if (this._stream && this._audio) {
this._mediaElementAudioSourceNode = this._context.createMediaElementSource(this._audio);

@@ -328,6 +343,38 @@ this._mediaElementAudioSourceNode.connect(this._gainNode);

/**
* Sets the audio source for the sound.
* @param {AudioBuffer | Audio} source The audio source.
*/
source(source) {
if (this._sourceExists) {
return;
}
if (this._stream) {
this._audio = source;
!this._isSprite && (this._endPos = this._audio.duration);
this._audio.playbackRate = this._rate;
this._setLoop(this._loop);
this._audio.addEventListener('error', this._onAudioError);
this._mediaElementAudioSourceNode = this._context.createMediaElementSource(this._audio);
this._mediaElementAudioSourceNode.connect(this._gainNode);
} else {
this._buffer = source;
!this._isSprite && (this._endPos = this._buffer.duration);
}
this._sourceExists = true;
this._duration = this._endPos - this._startPos;
this._loaded = true;
}
/**
* Pre-loads the underlying HTML audio node (only in case of stream).
*/
load() {
if (!this._stream || this.isPlaying() || this.state() === SoundState.Destroyed) {
if (!this._stream ||
!this._sourceExists ||
this.isPlaying() ||
this.state() === SoundState.Destroyed) {
return;

@@ -338,6 +385,3 @@ }

this._audio.currentTime = 0;
if (this._audio.readyState >= 3) {
this._onCanPlayThrough();
}
this.canPlay() && this._onCanPlayThrough();
}

@@ -350,4 +394,4 @@

play() {
// If the sound is already playing then return.
if (this.isPlaying()) {
// If the source not exists or sound is already playing then return.
if (!this._sourceExists || this.isPlaying()) {
return this;

@@ -370,4 +414,4 @@ }

pause() {
// If the sound is already playing return.
if (!this.isPlaying()) {
// If the source not exists or the sound is already playing return.
if (!this._sourceExists || !this.isPlaying()) {
return this;

@@ -399,4 +443,4 @@ }

stop() {
// If the sound is not playing or paused return.
if (!this.isPlaying() && !this.isPaused()) {
// If the source not exists or the sound is not playing or paused return.
if (!this._sourceExists || (!this.isPlaying() && !this.isPaused())) {
return this;

@@ -581,3 +625,3 @@ }

if (this._stream) {
return this._audio.currentTime;
return this._audio ? this._audio.currentTime : null;
}

@@ -746,2 +790,10 @@

/**
* Returns true if the audio can play without delay.
* @return {boolean}
*/
canPlay() {
return this._audio ? this._audio.readyState >= 3 : false;
}
/**
* HTML5 Audio error handler.

@@ -940,2 +992,6 @@ * @param {object} err Error object.

_setLoop(loop) {
if (!this._sourceExists) {
return;
}
if (this._stream) {

@@ -942,0 +998,0 @@ this._audio.loop = loop;

@@ -189,2 +189,14 @@ /**

}
/**
* Destroys the passed audio node.
* @param {Audio} audio The HTML5 audio element.
*/
destroyHtml5Audio(audio) {
audio.pause();
this.isIE() && (audio.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA');
audio.onerror = null;
audio.onend = null;
audio.canplaythrough = null;
}
}

@@ -191,0 +203,0 @@

@@ -29,3 +29,3 @@ import webpack from 'webpack';

`/*!
* musquito v2.1.2
* musquito v3.0.0
* http://musquitojs.com

@@ -32,0 +32,0 @@ *

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc