Restored the 3.55.2 sound system + fixes for iOS

This commit is contained in:
Richard Davey 2022-02-04 16:59:13 +00:00
parent f733f8429b
commit 4f0232e8c8
12 changed files with 503 additions and 1393 deletions

View file

@ -30,7 +30,7 @@ var IsPlainObject = require('../../utils/object/IsPlainObject');
* @param {(string|Phaser.Types.Loader.FileTypes.AudioFileConfig)} key - The key to use for this file, or a file configuration object.
* @param {any} [urlConfig] - The absolute or relative URL to load this file from in a config object.
* @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
* @param {Phaser.Sound.WebAudioSoundManager} [soundManager] - The Web Audio Sound Manager.
* @param {AudioContext} [audioContext] - The AudioContext this file will use to process itself.
*/
var AudioFile = new Class({
@ -39,7 +39,7 @@ var AudioFile = new Class({
initialize:
// URL is an object created by AudioFile.findAudioURL
function AudioFile (loader, key, urlConfig, xhrSettings, soundManager)
function AudioFile (loader, key, urlConfig, xhrSettings, audioContext)
{
if (IsPlainObject(key))
{
@ -47,6 +47,7 @@ var AudioFile = new Class({
key = GetFastValue(config, 'key');
xhrSettings = GetFastValue(config, 'xhrSettings');
audioContext = GetFastValue(config, 'context', audioContext);
}
var fileConfig = {
@ -57,7 +58,7 @@ var AudioFile = new Class({
key: key,
url: urlConfig.url,
xhrSettings: xhrSettings,
config: { soundManager: soundManager }
config: { context: audioContext }
};
File.call(this, loader, fileConfig);
@ -74,9 +75,26 @@ var AudioFile = new Class({
{
this.state = CONST.FILE_PROCESSING;
this.config.soundManager.decodeAudio(this.key, this.xhrLoader.response);
var _this = this;
this.onProcessComplete();
// interesting read https://github.com/WebAudio/web-audio-api/issues/1305
this.config.context.decodeAudioData(this.xhrLoader.response,
function (audioBuffer)
{
_this.data = audioBuffer;
_this.onProcessComplete();
},
function (e)
{
// eslint-disable-next-line no-console
console.error('Error decoding audio: ' + _this.key + ' - ', e ? e.message : null);
_this.onProcessError();
}
);
this.config.context = null;
}
});
@ -106,7 +124,7 @@ AudioFile.create = function (loader, key, urls, config, xhrSettings)
if (deviceAudio.webAudio && !audioConfig.disableWebAudio)
{
return new AudioFile(loader, key, urlConfig, xhrSettings, game.sound);
return new AudioFile(loader, key, urlConfig, xhrSettings, game.sound.context);
}
else
{

View file

@ -5,7 +5,6 @@
*/
var Class = require('../../utils/Class');
var CONST = require('../const');
var Events = require('../events');
var File = require('../File');
var GetFastValue = require('../../utils/object/GetFastValue');
@ -17,7 +16,7 @@ var IsPlainObject = require('../../utils/object/IsPlainObject');
* A single Audio File suitable for loading by the Loader.
*
* These are created when you use the Phaser.Loader.LoaderPlugin#audio method and are not typically created directly.
*
*
* For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#audio.
*
* @class HTML5AudioFile
@ -59,8 +58,7 @@ var HTML5AudioFile = new Class({
File.call(this, loader, fileConfig);
// New properties specific to this class
this.soundManager = loader.systems.sound;
this.locked = this.soundManager.locked;
this.locked = 'ontouchstart' in window;
this.loaded = false;
this.filesLoaded = 0;
this.filesTotal = 0;
@ -109,8 +107,6 @@ var HTML5AudioFile = new Class({
* @method Phaser.Loader.FileTypes.HTML5AudioFile#onProgress
* @fires Phaser.Loader.Events#FILE_PROGRESS
* @since 3.0.0
*
* @param {ProgressEvent} event - The DOM ProgressEvent.
*/
onProgress: function (event)
{
@ -127,11 +123,7 @@ var HTML5AudioFile = new Class({
if (this.filesLoaded === this.filesTotal)
{
var success = !(event.target && event.target.status !== 200);
this.state = CONST.FILE_LOADED;
this.loader.nextFile(this, success);
this.onLoad();
}
},
@ -156,33 +148,26 @@ var HTML5AudioFile = new Class({
for (var i = 0; i < instances; i++)
{
var audio = new Audio();
var dataset = audio.dataset;
if (dataset === undefined)
if (!audio.dataset)
{
audio.dataset = dataset = {
name: '',
used: ''
};
audio.dataset = {};
}
dataset.name = this.key + '-' + i.toString();
dataset.used = 'false';
audio.dataset.name = this.key + ('0' + i).slice(-2);
audio.dataset.used = 'false';
if (this.locked)
{
dataset.locked = 'true';
// console.log('HTML5AudioFile:', dataset.name, 'locked');
audio.dataset.locked = 'true';
}
else
{
dataset.locked = 'false';
audio.dataset.locked = 'false';
audio.preload = 'auto';
audio.oncanplaythrough = this.onProgress.bind(this);
audio.onerror = this.onError.bind(this);
// console.log('HTML5AudioFile:', dataset.name, 'unlocked');
}
this.data.push(audio);
@ -191,19 +176,19 @@ var HTML5AudioFile = new Class({
for (i = 0; i < this.data.length; i++)
{
audio = this.data[i];
audio.src = GetURL(this, this.loader.baseURL);
if (!this.locked)
{
audio.load();
// console.log('HTML5AudioFile:', dataset.name, 'load called');
}
}
if (this.locked)
{
this.loader.nextFile(this, true);
// This is super-dangerous but works. Race condition potential high.
// Is there another way?
setTimeout(this.onLoad.bind(this));
}
}

View file

@ -512,6 +512,13 @@ var SceneManager = new Class({
*/
loadComplete: function (loader)
{
// TODO - Remove. This should *not* be handled here
// Try to unlock HTML5 sounds every time any loader completes
if (this.game.sound && this.game.sound.onBlurPausedSounds)
{
this.game.sound.unlock();
}
this.create(loader.scene);
},

View file

@ -125,11 +125,8 @@ var BaseSoundManager = new Class({
this._detune = 0;
/**
* All browsers require sounds to be triggered from an explicit user action,
* such as a tap, before any sound can be loaded / played on a web page.
*
* https://developer.chrome.com/blog/autoplay/
*
* Mobile devices require sounds to be triggered from an explicit user action,
* such as a tap, before any sound can be loaded/played on a web page.
* Set to true if the audio system is currently locked awaiting user interaction.
*
* @name Phaser.Sound.BaseSoundManager#locked
@ -137,11 +134,11 @@ var BaseSoundManager = new Class({
* @readonly
* @since 3.0.0
*/
this.locked = true;
this.locked = this.locked || false;
/**
* Flag used internally for handling when the audio system
* has been unlocked.
* has been unlocked, if there ever was a need for it.
*
* @name Phaser.Sound.BaseSoundManager#unlocked
* @type {boolean}
@ -151,74 +148,10 @@ var BaseSoundManager = new Class({
*/
this.unlocked = false;
/**
*
*
* @name Phaser.Sound.BaseSoundManager#pendingUnlock
* @type {boolean}
* @since 3.60.0
*/
this.pendingUnlock = false;
game.events.on(GameEvents.BLUR, this.onGameBlur, this);
game.events.on(GameEvents.FOCUS, this.onGameFocus, this);
game.events.on(GameEvents.PRE_STEP, this.update, this);
game.events.once(GameEvents.DESTROY, this.destroy, this);
if (this.locked && game.isBooted)
{
this.unlock();
}
else
{
game.events.once(GameEvents.BOOT, this.unlock, this);
}
},
/**
* Unlocks the Audio API on the initial input event.
*
* @method Phaser.Sound.BaseSoundManager#unlock
* @since 3.60.0
*/
unlock: function ()
{
if (this.pendingUnlock)
{
return;
}
console.log('BaseSoundManager.unlock');
var _this = this;
var body = document.body;
if (body)
{
var unlockHandler = function unlockHandler ()
{
if (!_this.pendingUnlock)
{
return;
}
_this.unlockHandler();
body.removeEventListener('touchstart', unlockHandler);
body.removeEventListener('touchend', unlockHandler);
body.removeEventListener('click', unlockHandler);
body.removeEventListener('keydown', unlockHandler);
};
body.addEventListener('touchstart', unlockHandler, false);
body.addEventListener('touchend', unlockHandler, false);
body.addEventListener('click', unlockHandler, false);
body.addEventListener('keydown', unlockHandler, false);
this.pendingUnlock = true;
}
},
/**
@ -231,7 +164,7 @@ var BaseSoundManager = new Class({
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {(Phaser.Sound.BaseSound|Phaser.Sound.NoAudioSound|Phaser.Sound.HTML5AudioSound|Phaser.Sound.WebAudioSound)} The new sound instance.
* @return {Phaser.Sound.BaseSound} The new sound instance.
*/
add: NOOP,
@ -246,7 +179,7 @@ var BaseSoundManager = new Class({
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {(Phaser.Sound.BaseSound|Phaser.Sound.NoAudioSound|Phaser.Sound.HTML5AudioSound|Phaser.Sound.WebAudioSound)} The new audio sprite sound instance.
* @return {(Phaser.Sound.HTML5AudioSound|Phaser.Sound.WebAudioSound)} The new audio sprite sound instance.
*/
addAudioSprite: function (key, config)
{
@ -288,7 +221,7 @@ var BaseSoundManager = new Class({
*
* @param {string} key - Sound asset key.
*
* @return {?(Phaser.Sound.BaseSound|Phaser.Sound.NoAudioSound|Phaser.Sound.HTML5AudioSound|Phaser.Sound.WebAudioSound)} - The sound, or null.
* @return {?Phaser.Sound.BaseSound} - The sound, or null.
*/
get: function (key)
{
@ -303,7 +236,7 @@ var BaseSoundManager = new Class({
*
* @param {string} key - Sound asset key.
*
* @return {(Phaser.Sound.BaseSound[]|Phaser.Sound.NoAudioSound[]|Phaser.Sound.HTML5AudioSound[]|Phaser.Sound.WebAudioSound[])} - The sounds, or an empty array.
* @return {Phaser.Sound.BaseSound[]} - The sounds, or an empty array.
*/
getAll: function (key)
{
@ -500,6 +433,7 @@ var BaseSoundManager = new Class({
this.emit(Events.STOP_ALL, this);
},
/**
* Stops any sounds matching the given key.
*
@ -522,6 +456,19 @@ var BaseSoundManager = new Class({
return stopped;
},
/**
* Method used internally for unlocking audio playback on devices that
* require user interaction before any sound can be played on a web page.
*
* Read more about how this issue is handled here in [this article](https://medium.com/@pgoloskokovic/unlocking-web-audio-the-smarter-way-8858218c0e09).
*
* @method Phaser.Sound.BaseSoundManager#unlock
* @override
* @protected
* @since 3.0.0
*/
unlock: NOOP,
/**
* Method used internally for pausing sound manager if
* Phaser.Sound.BaseSoundManager#pauseOnBlur is set to true.
@ -588,21 +535,48 @@ var BaseSoundManager = new Class({
*/
update: function (time, delta)
{
var i;
var sounds = this.sounds;
for (i = sounds.length - 1; i >= 0; i--)
if (this.unlocked)
{
if (sounds[i].pendingRemove)
this.unlocked = false;
this.locked = false;
this.emit(Events.UNLOCKED, this);
}
for (var i = this.sounds.length - 1; i >= 0; i--)
{
if (this.sounds[i].pendingRemove)
{
sounds.splice(i, 1);
this.sounds.splice(i, 1);
}
}
for (i = 0; i < sounds.length; i++)
this.sounds.forEach(function (sound)
{
sounds[i].update(time, delta);
}
sound.update(time, delta);
});
},
/**
* Destroys all the sounds in the game and all associated events.
*
* @method Phaser.Sound.BaseSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
this.game.events.off(GameEvents.BLUR, this.onGameBlur, this);
this.game.events.off(GameEvents.FOCUS, this.onGameFocus, this);
this.game.events.off(GameEvents.PRE_STEP, this.update, this);
this.removeAllListeners();
this.removeAll();
this.sounds.length = 0;
this.sounds = null;
this.game = null;
},
/**
@ -617,20 +591,15 @@ var BaseSoundManager = new Class({
*/
forEachActiveSound: function (callback, scope)
{
// eslint-disable-next-line consistent-this
if (scope === undefined) { scope = this; }
var _this = this;
var sounds = this.sounds;
for (var i = 0; i < sounds.length; i++)
this.sounds.forEach(function (sound, index)
{
var sound = sounds[i];
if (sound && !sound.pendingRemove)
{
callback.call(scope, sound, i, sounds);
callback.call(scope || _this, sound, index, _this.sounds);
}
}
});
},
/**
@ -732,31 +701,6 @@ var BaseSoundManager = new Class({
this.emit(Events.GLOBAL_DETUNE, this, value);
}
},
/**
* Destroys all the sounds in the game and all associated events.
*
* @method Phaser.Sound.BaseSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
var events = this.game.events;
events.off(GameEvents.BLUR, this.onGameBlur, this);
events.off(GameEvents.FOCUS, this.onGameFocus, this);
events.off(GameEvents.PRE_STEP, this.update, this);
events.off(GameEvents.BOOT, this.unlock, this);
this.removeAllListeners();
this.removeAll();
this.sounds.length = 0;
this.sounds = null;
this.game = null;
}
});

View file

@ -1,22 +0,0 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* The Audio Data Decoded Event.
*
* This event is dispatched by the Web Audio Sound Manager as a result of calling the `decodeAudio` method.
*
* Listen to it from the Sound Manager in a Scene using `this.sound.on('decoded', listener)`, i.e.:
*
* ```javascript
* this.sound.on('decoded', handler);
* this.sound.decodeAudio(key, audioData);
* ```
*
* @event Phaser.Sound.Events#DECODED_KEY
* @since 3.60.0
*/
module.exports = 'decoded-';

View file

@ -11,9 +11,8 @@
module.exports = {
COMPLETE: require('./COMPLETE_EVENT'),
DECODED_ALL: require('./DECODED_ALL_EVENT'),
DECODED_KEY: require('./DECODED_KEY_EVENT'),
DECODED: require('./DECODED_EVENT'),
DECODED_ALL: require('./DECODED_ALL_EVENT'),
DESTROY: require('./DESTROY_EVENT'),
DETUNE: require('./DETUNE_EVENT'),
GLOBAL_DETUNE: require('./GLOBAL_DETUNE_EVENT'),

View file

@ -44,11 +44,11 @@ var HTML5AudioSound = new Class({
* @private
* @since 3.0.0
*/
this.tags = manager.cache.get(key);
this.tags = manager.game.cache.audio.get(key);
if (!this.tags)
{
throw new Error('Audio cache missing "' + key + '"');
throw new Error('There is no audio asset with key "' + key + '" in the audio cache');
}
/**
@ -92,8 +92,6 @@ var HTML5AudioSound = new Class({
this.totalDuration = this.tags[0].duration;
BaseSound.call(this, manager, key, config);
console.log('HTML5AudioSound created', this.tags);
},
/**
@ -114,27 +112,22 @@ var HTML5AudioSound = new Class({
{
if (this.manager.isLocked(this, 'play', [ markerName, config ]))
{
console.log('HAS.play 1');
return false;
}
if (!BaseSound.prototype.play.call(this, markerName, config))
{
console.log('HAS.play 2');
return false;
}
// \/\/\/ isPlaying = true, isPaused = false \/\/\/
if (!this.pickAndPlayAudioTag())
{
console.log('HAS.play 3');
return false;
}
this.emit(Events.PLAY, this);
console.log('HAS.play 4');
return true;
},

View file

@ -8,7 +8,6 @@
var BaseSoundManager = require('../BaseSoundManager');
var Class = require('../../utils/Class');
var Events = require('../events');
var GameEvents = require('../../core/events');
var HTML5AudioSound = require('./HTML5AudioSound');
/**
@ -27,8 +26,6 @@ var HTML5AudioSound = require('./HTML5AudioSound');
*
* There is a good guide to what's supported: [Cross-browser audio basics: Audio codec support](https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics#Audio_Codec_Support).
*
* Audio cannot be played without a user-gesture in the browser: https://developer.chrome.com/blog/autoplay/
*
* @class HTML5AudioSoundManager
* @extends Phaser.Sound.BaseSoundManager
* @memberof Phaser.Sound
@ -100,6 +97,8 @@ var HTML5AudioSoundManager = new Class({
*/
this.onBlurPausedSounds = [];
this.locked = 'ontouchstart' in window;
/**
* A queue of all actions performed on sound objects while audio was locked.
* Once the audio gets unlocked, after an explicit user interaction,
@ -111,7 +110,7 @@ var HTML5AudioSoundManager = new Class({
* @private
* @since 3.0.0
*/
this.lockedActionsQueue = [];
this.lockedActionsQueue = this.locked ? [] : null;
/**
* Property that actually holds the value of global mute
@ -137,127 +136,40 @@ var HTML5AudioSoundManager = new Class({
*/
this._volume = 1;
/**
* The Audio cache, where audio data is stored.
*
* @name Phaser.Sound.HTML5AudioSoundManager#cache
* @type {Phaser.Cache.BaseCache}
* @since 3.60.0
*/
this.cache = game.cache.audio;
BaseSoundManager.call(this, game);
},
/**
* Handles additional processing when this Audio Manager is unlocked.
* Adds a new sound into the sound manager.
*
* @method Phaser.Sound.HTML5AudioSoundManager#unlockHandler
* @since 3.60.0
* @method Phaser.Sound.HTML5AudioSoundManager#add
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {Phaser.Sound.HTML5AudioSound} The new sound instance.
*/
unlockHandler: function ()
add: function (key, config)
{
this.createAudioTags();
var sound = new HTML5AudioSound(this, key, config);
this.sounds.push(sound);
return sound;
},
/**
* Unlocks HTML5 Audio loading and playback on mobile
* devices on the initial explicit user interaction.
*
*
* @method Phaser.Sound.HTML5AudioSoundManager#createAudioTags
* @since 3.60.0
* @method Phaser.Sound.HTML5AudioSoundManager#unlock
* @since 3.0.0
*/
createAudioTags: function ()
unlock: function ()
{
console.log('HTML5AudioSoundManager.createAudioTags');
if (!this.pendingUnlock)
{
console.log('unlock bail 1 - pending unlock false');
return;
}
this.locked = false;
this.emit(Events.UNLOCKED, this);
var lockedTags = [];
this.cache.entries.each(function (key, tags)
{
for (var i = 0; i < tags.length; i++)
{
var tag = tags[i];
console.log('tag unlock', tag.dataset.name, tag.dataset.locked);
if (tag.dataset.locked === 'true')
{
lockedTags.push(tag);
}
}
// return true;
});
if (lockedTags.length === 0)
{
return;
}
var lastTag = lockedTags[lockedTags.length - 1];
lastTag.oncanplaythrough = function ()
{
lastTag.oncanplaythrough = null;
lockedTags.forEach(function (tag)
{
tag.dataset.locked = 'false';
console.log('lastTag unlock', tag.dataset.name);
});
};
lockedTags.forEach(function (tag)
{
tag.load();
console.log('tag.load', tag.dataset.name);
});
/*
console.log('unlocked event handler - actions?', this.lockedActionsQueue.length);
this.forEachActiveSound(function (sound)
{
if (sound.currentMarker === null && sound.duration === 0)
{
sound.duration = sound.tags[0].duration;
}
sound.totalDuration = sound.tags[0].duration;
});
var len = this.lockedActionsQueue.length;
for (var i = 0; i < len; i++)
{
var lockedAction = this.lockedActionsQueue.shift();
console.log(lockedAction);
if (lockedAction.sound[lockedAction.prop].apply)
{
lockedAction.sound[lockedAction.prop].apply(lockedAction.sound, lockedAction.value || []);
}
else
{
lockedAction.sound[lockedAction.prop] = lockedAction.value;
}
}
*/
/*
var _this = this;
this.game.cache.audio.entries.each(function (key, tags)
@ -266,8 +178,6 @@ var HTML5AudioSoundManager = new Class({
{
if (tags[i].dataset.locked === 'true')
{
console.log('dataset locked', i);
_this.locked = true;
return false;
@ -277,19 +187,20 @@ var HTML5AudioSoundManager = new Class({
return true;
});
if (!this.locked)
{
return;
}
var moved = false;
var detectMove = function ()
{
moved = true;
};
*/
/*
var unlock = function ()
{
console.log('unlock->unlock!', moved);
if (moved)
{
moved = false;
@ -299,8 +210,6 @@ var HTML5AudioSoundManager = new Class({
document.body.removeEventListener('touchmove', detectMove);
document.body.removeEventListener('touchend', unlock);
_this.emit(Events.UNLOCKED, _this);
var lockedTags = [];
_this.game.cache.audio.entries.each(function (key, tags)
@ -345,8 +254,6 @@ var HTML5AudioSoundManager = new Class({
this.once(Events.UNLOCKED, function ()
{
console.log('unlocked event handler', this.lockedActionsQueue.length);
this.forEachActiveSound(function (sound)
{
if (sound.currentMarker === null && sound.duration === 0)
@ -357,14 +264,10 @@ var HTML5AudioSoundManager = new Class({
sound.totalDuration = sound.tags[0].duration;
});
var len = this.lockedActionsQueue.length;
for (var i = 0; i < len; i++)
while (this.lockedActionsQueue.length)
{
var lockedAction = this.lockedActionsQueue.shift();
console.log(lockedAction);
if (lockedAction.sound[lockedAction.prop].apply)
{
lockedAction.sound[lockedAction.prop].apply(lockedAction.sound, lockedAction.value || []);
@ -374,33 +277,11 @@ var HTML5AudioSoundManager = new Class({
lockedAction.sound[lockedAction.prop] = lockedAction.value;
}
}
}, this);
console.log('unlock added listeners');
}, this);
document.body.addEventListener('touchmove', detectMove, false);
document.body.addEventListener('touchend', unlock, false);
*/
},
/**
* Adds a new sound into the sound manager.
*
* @method Phaser.Sound.HTML5AudioSoundManager#add
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {Phaser.Sound.HTML5AudioSound} The new sound instance.
*/
add: function (key, config)
{
var sound = new HTML5AudioSound(this, key, config);
this.sounds.push(sound);
return sound;
},
/**
@ -441,6 +322,21 @@ var HTML5AudioSoundManager = new Class({
this.onBlurPausedSounds.length = 0;
},
/**
* Calls Phaser.Sound.BaseSoundManager#destroy method
* and cleans up all HTML5 Audio related stuff.
*
* @method Phaser.Sound.HTML5AudioSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
BaseSoundManager.prototype.destroy.call(this);
this.onBlurPausedSounds.length = 0;
this.onBlurPausedSounds = null;
},
/**
* Method used internally by Phaser.Sound.HTML5AudioSound class methods and property setters
* to check if sound manager is locked and then either perform action immediately or queue it
@ -490,6 +386,33 @@ var HTML5AudioSoundManager = new Class({
return this;
},
/**
* @name Phaser.Sound.HTML5AudioSoundManager#mute
* @type {boolean}
* @fires Phaser.Sound.Events#GLOBAL_MUTE
* @since 3.0.0
*/
mute: {
get: function ()
{
return this._mute;
},
set: function (value)
{
this._mute = value;
this.forEachActiveSound(function (sound)
{
sound.updateMute();
});
this.emit(Events.GLOBAL_MUTE, this, value);
}
},
/**
* Sets the volume of this Sound Manager.
*
@ -533,48 +456,6 @@ var HTML5AudioSoundManager = new Class({
this.emit(Events.GLOBAL_VOLUME, this, value);
}
},
/**
* @name Phaser.Sound.HTML5AudioSoundManager#mute
* @type {boolean}
* @fires Phaser.Sound.Events#GLOBAL_MUTE
* @since 3.0.0
*/
mute: {
get: function ()
{
return this._mute;
},
set: function (value)
{
this._mute = value;
this.forEachActiveSound(function (sound)
{
sound.updateMute();
});
this.emit(Events.GLOBAL_MUTE, this, value);
}
},
/**
* Calls Phaser.Sound.BaseSoundManager#destroy method
* and cleans up all HTML5 Audio related stuff.
*
* @method Phaser.Sound.HTML5AudioSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
BaseSoundManager.prototype.destroy.call(this);
this.onBlurPausedSounds.length = 0;
this.onBlurPausedSounds = null;
}
});

View file

@ -55,91 +55,14 @@ var NoAudioSound = new Class({
EventEmitter.call(this);
/**
* Local reference to the sound manager.
*
* @name Phaser.Sound.NoAudioSound#manager
* @type {Phaser.Sound.BaseSoundManager}
* @private
* @since 3.0.0
*/
this.manager = manager;
/**
* Asset key for the sound.
*
* @name Phaser.Sound.NoAudioSound#key
* @type {string}
* @readonly
* @since 3.0.0
*/
this.key = key;
/**
* Flag indicating if sound is currently playing.
*
* @name Phaser.Sound.NoAudioSound#isPlaying
* @type {boolean}
* @default false
* @readonly
* @since 3.0.0
*/
this.isPlaying = false;
/**
* Flag indicating if sound is currently paused.
*
* @name Phaser.Sound.NoAudioSound#isPaused
* @type {boolean}
* @default false
* @readonly
* @since 3.0.0
*/
this.isPaused = false;
/**
* A property that holds the value of sound's actual playback rate,
* after its rate and detune values has been combined with global
* rate and detune values.
*
* @name Phaser.Sound.NoAudioSound#totalRate
* @type {number}
* @default 1
* @readonly
* @since 3.0.0
*/
this.totalRate = 1;
/**
* A value representing the duration, in seconds.
* It could be total sound duration or a marker duration.
*
* @name Phaser.Sound.NoAudioSound#duration
* @type {number}
* @readonly
* @since 3.0.0
*/
this.duration = 0;
/**
* The total duration of the sound in seconds.
*
* @name Phaser.Sound.NoAudioSound#totalDuration
* @type {number}
* @readonly
* @since 3.0.0
*/
this.totalDuration = 0;
/**
* A config object used to store default sound settings' values.
* Default values will be set by properties' setters.
*
* @name Phaser.Sound.NoAudioSound#config
* @type {Phaser.Types.Sound.SoundConfig}
* @private
* @since 3.0.0
*/
this.config = Extend({
mute: false,
volume: 1,
@ -151,163 +74,61 @@ var NoAudioSound = new Class({
pan: 0
}, config);
/**
* Reference to the currently used config.
* It could be default config or marker config.
*
* @name Phaser.Sound.BaseSound#currentConfig
* @type {Phaser.Types.Sound.SoundConfig}
* @private
* @since 3.0.0
*/
this.currentConfig = this.config;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#mute
* @type {boolean}
* @readonly
* @since 3.0.0
*/
this.mute = false;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#volume
* @type {number}
* @readonly
* @since 3.0.0
*/
this.volume = 1;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#rate
* @type {number}
* @readonly
* @since 3.0.0
*/
this.rate = 1;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#detune
* @type {number}
* @readonly
* @since 3.0.0
*/
this.detune = 0;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#seek
* @type {number}
* @readonly
* @since 3.0.0
*/
this.seek = 0;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#loop
* @type {boolean}
* @readonly
* @since 3.0.0
*/
this.loop = false;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#pan
* @type {number}
* @readonly
* @since 3.0.0
*/
this.pan = 0;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#markers
* @type {Object.<string, Phaser.Types.Sound.SoundMarker>}
* @default {}
* @readonly
* @since 3.0.0
*/
this.markers = {};
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#currentMarker
* @type {Phaser.Types.Sound.SoundMarker}
* @default null
* @readonly
* @since 3.0.0
*/
this.currentMarker = null;
/**
* This property is un-used in a NoAudioSound object.
*
* @name Phaser.Sound.NoAudioSound#pendingRemove
* @type {boolean}
* @readonly
* @since 3.0.0
*/
this.pendingRemove = false;
},
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#addMarker
* @since 3.0.0
*
* @param {Phaser.Types.Sound.SoundMarker} marker - Marker object.
*
* @return {boolean} false
*/
addMarker: returnFalse,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#updateMarker
* @since 3.0.0
*
* @param {Phaser.Types.Sound.SoundMarker} marker - Marker object with updated values.
*
* @return {boolean} false
*/
updateMarker: returnFalse,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#removeMarker
* @since 3.0.0
*
* @param {string} markerName - The name of the marker to remove.
*
* @return {null} null
*/
removeMarker: returnNull,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#play
* @since 3.0.0
*
* @param {(string|Phaser.Types.Sound.SoundConfig)} [markerName=''] - If you want to play a marker then provide the marker name here. Alternatively, this parameter can be a SoundConfig object.
* @param {Phaser.Types.Sound.SoundConfig} [config] - Optional sound config object to be applied to this marker or entire sound if no marker name is provided. It gets memorized for future plays of current section of the sound.
*
* @return {boolean} false
*/
play: returnFalse,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#pause
* @since 3.0.0
*
@ -316,7 +137,7 @@ var NoAudioSound = new Class({
pause: returnFalse,
/**
* This method is empty in a NoAudioSound object.
* Resumes the sound.
*
* @method Phaser.Sound.NoAudioSound#resume
* @since 3.0.0
@ -326,7 +147,7 @@ var NoAudioSound = new Class({
resume: returnFalse,
/**
* This method is empty in a NoAudioSound object.
* Stop playing this sound.
*
* @method Phaser.Sound.NoAudioSound#stop
* @since 3.0.0
@ -347,74 +168,18 @@ var NoAudioSound = new Class({
BaseSound.prototype.destroy.call(this);
},
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setMute
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setMute: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setVolume
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setVolume: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setRate
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setRate: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setDetune
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setDetune: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setSeek
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setSeek: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setLoop
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setLoop: returnThis,
/**
* This method is empty in a NoAudioSound object.
*
* @method Phaser.Sound.NoAudioSound#setPan
* @since 3.0.0
*
* @return {this} This Sound instance.
*/
setPan: returnThis
});

View file

@ -38,105 +38,16 @@ var NoAudioSoundManager = new Class({
{
EventEmitter.call(this);
/**
* Local reference to game.
*
* @name Phaser.Sound.NoAudioSoundManager#game
* @type {Phaser.Game}
* @readonly
* @since 3.0.0
*/
this.game = game;
/**
* An array containing all added sounds.
*
* Always empty for NoAudioSoundManager.
*
* @name Phaser.Sound.NoAudioSoundManager#sounds
* @type {Phaser.Sound.BaseSound[]}
* @default []
* @private
* @since 3.0.0
*/
this.sounds = [];
/**
* Global mute setting.
*
* @name Phaser.Sound.NoAudioSoundManager#mute
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.mute = false;
/**
* Global volume setting.
*
* @name Phaser.Sound.NoAudioSoundManager#volume
* @type {number}
* @default 1
* @since 3.0.0
*/
this.volume = 1;
/**
* Property that actually holds the value of global playback rate.
*
* @name Phaser.Sound.NoAudioSoundManager#rate
* @type {number}
* @default 1
* @since 3.0.0
*/
this.rate = 1;
/**
* Property that actually holds the value of global detune.
*
* @name Phaser.Sound.NoAudioSoundManager#detune
* @type {number}
* @default 0
* @since 3.0.0
*/
this.detune = 0;
/**
* Flag indicating if sounds should be paused when game looses focus,
* for instance when user switches to another tab/program/app.
*
* @name Phaser.Sound.NoAudioSoundManager#pauseOnBlur
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.pauseOnBlur = true;
/**
* Mobile devices require sounds to be triggered from an explicit user action,
* such as a tap, before any sound can be loaded/played on a web page.
* Set to true if the audio system is currently locked awaiting user interaction.
*
* @name Phaser.Sound.NoAudioSoundManager#locked
* @type {boolean}
* @readonly
* @since 3.0.0
*/
this.locked = false;
},
/**
* Adds a new sound into this sound manager.
*
* @method Phaser.Sound.NoAudioSoundManager#add
* @override
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {Phaser.Sound.NoAudioSound} The new sound instance.
*/
add: function (key, config)
{
var sound = new NoAudioSound(this, key, config);
@ -146,19 +57,6 @@ var NoAudioSoundManager = new Class({
return sound;
},
/**
* Adds a new audio sprite sound into the sound manager.
* Audio Sprites are a combination of audio files and a JSON configuration.
* The JSON follows the format of that created by https://github.com/tonistiigi/audiosprite
*
* @method Phaser.Sound.NoAudioSoundManager#addAudioSprite
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {Phaser.Sound.NoAudioSound} The new audio sprite sound instance.
*/
addAudioSprite: function (key, config)
{
var sound = this.add(key, config);
@ -168,171 +66,42 @@ var NoAudioSoundManager = new Class({
return sound;
},
/**
* Adds a new sound to the sound manager and plays it.
*
* The sound will be automatically removed (destroyed) once playback ends.
* This lets you play a new sound on the fly without the need to keep a reference to it.
*
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#play
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {(Phaser.Types.Sound.SoundConfig|Phaser.Types.Sound.SoundMarker)} [extra] - An optional additional object containing settings to be applied to the sound. It could be either config or marker object.
*
* @return {boolean} Always returns false.
*/
// eslint-disable-next-line no-unused-vars
play: function (key, extra)
{
return false;
},
/**
* Adds a new audio sprite sound to the sound manager and plays it.
*
* The sprite will be automatically removed (destroyed) once playback ends.
*
* This lets you play a new sound on the fly without the need to keep a reference to it.
*
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#playAudioSprite
* @since 3.0.0
*
* @param {string} key - Asset key for the sound.
* @param {string} spriteName - The name of the sound sprite to play.
* @param {Phaser.Types.Sound.SoundConfig} [config] - An optional config object containing default sound settings.
*
* @return {boolean} Always returns false.
*/
// eslint-disable-next-line no-unused-vars
playAudioSprite: function (key, spriteName, config)
{
return false;
},
/**
* Removes a sound from the sound manager.
*
* The removed sound is destroyed before removal.
*
* @method Phaser.Sound.NoAudioSoundManager#remove
* @since 3.0.0
*
* @param {Phaser.Sound.NoAudioSound} sound - The sound object to remove.
*
* @return {boolean} True if the sound was removed successfully, otherwise false.
*/
remove: function (sound)
{
return BaseSoundManager.prototype.remove.call(this, sound);
},
/**
* Removes all sounds from the sound manager that have an asset key matching the given value.
*
* The removed sounds are destroyed before removal.
*
* @method Phaser.Sound.NoAudioSoundManager#removeByKey
* @since 3.0.0
*
* @param {string} key - The key to match when removing sound objects.
*
* @return {number} The number of matching sound objects that were removed.
*/
removeByKey: function (key)
{
return BaseSoundManager.prototype.removeByKey.call(this, key);
},
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#pauseAll
* @since 3.0.0
*/
pauseAll: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#resumeAll
* @since 3.0.0
*/
resumeAll: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#stopAll
* @since 3.0.0
*/
stopAll: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#update
* @since 3.0.0
*/
update: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#setRate
* @since 3.0.0
*/
setRate: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#setDetune
* @since 3.0.0
*/
setDetune: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#setMute
* @since 3.0.0
*/
setMute: NOOP,
/**
* This method does nothing in the No Audio Sound Manager.
*
* @method Phaser.Sound.NoAudioSoundManager#setVolume
* @since 3.0.0
*/
setVolume: NOOP,
/**
* Method used internally for iterating only over active sounds and skipping sounds that are marked for removal.
*
* @method Phaser.Sound.NoAudioSoundManager#forEachActiveSound
* @private
* @since 3.0.0
*
* @param {Phaser.Types.Sound.EachActiveSoundCallback} callback - Callback function. (manager: Phaser.Sound.BaseSoundManager, sound: Phaser.Sound.BaseSound, index: number, sounds: Phaser.Manager.BaseSound[]) => void
* @param {*} [scope] - Callback context.
*/
forEachActiveSound: function (callbackfn, scope)
{
BaseSoundManager.prototype.forEachActiveSound.call(this, callbackfn, scope);
},
/**
* Destroys all the sounds in the game and all associated events.
*
* @method Phaser.Sound.NoAudioSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
BaseSoundManager.prototype.destroy.call(this);

View file

@ -40,7 +40,12 @@ var WebAudioSound = new Class({
* @type {AudioBuffer}
* @since 3.0.0
*/
this.audioBuffer;
this.audioBuffer = manager.game.cache.audio.get(key);
if (!this.audioBuffer)
{
throw new Error('Audio key "' + key + '" missing from cache');
}
/**
* A reference to an audio source node used for playing back audio from
@ -70,7 +75,7 @@ var WebAudioSound = new Class({
* @type {GainNode}
* @since 3.0.0
*/
this.muteNode;
this.muteNode = manager.context.createGain();
/**
* Gain node responsible for controlling this sound's volume.
@ -79,7 +84,7 @@ var WebAudioSound = new Class({
* @type {GainNode}
* @since 3.0.0
*/
this.volumeNode;
this.volumeNode = manager.context.createGain();
/**
* Panner node responsible for controlling this sound's pan.
@ -164,45 +169,11 @@ var WebAudioSound = new Class({
*/
this.hasLooped = false;
this.pendingPlay = false;
BaseSound.call(this, manager, key, config);
if (manager.unlocked)
{
this.init();
}
else
{
manager.once(Events.UNLOCKED, this.init, this);
}
},
/**
* This internal method handles the creation of the mute, volume and panner nodes
* as well as the setting of the audio buffer, or requesting the decode of the audio
* if it's not already decoded.
*
* You should not call this method directly. If you do, you should make sure you only
* call it once the SoundManager context has been unlocked.
*
* @method Phaser.Sound.WebAudioSound#init
* @since 3.60.0
*/
init: function ()
{
var manager = this.manager;
var context = manager.context;
this.muteNode = context.createGain();
this.volumeNode = context.createGain();
this.muteNode.connect(this.volumeNode);
if (context.createStereoPanner)
if (manager.context.createStereoPanner)
{
this.pannerNode = context.createStereoPanner();
this.pannerNode = manager.context.createStereoPanner();
this.volumeNode.connect(this.pannerNode);
@ -213,53 +184,11 @@ var WebAudioSound = new Class({
this.volumeNode.connect(manager.destination);
}
// AudioBuffer
var key = this.key;
this.duration = this.audioBuffer.duration;
var buffer = manager.cache.get(key);
this.totalDuration = this.audioBuffer.duration;
if (buffer)
{
this.audioBuffer = buffer;
this.duration = buffer.duration;
this.totalDuration = buffer.duration;
}
else if (manager.decodeQueue.has(key))
{
manager.once(Events.DECODED_KEY + key, this.setAudioBuffer, this);
manager.decodeAudioQueue(key);
}
else
{
throw new Error('Missing Audio: "' + key + '"');
}
},
/**
* Sets the AudioBuffer that this Sound instance will use for playback.
*
* Calling this also sets the `duration` and `totalDuration` properties.
*
* @method Phaser.Sound.WebAudioSound#setAudioBuffer
* @since 3.60.0
*
* @param {AudioBuffer} audioBuffer - The fully decoded AudioBuffer this Sound instance will use.
*/
setAudioBuffer: function (audioBuffer)
{
this.audioBuffer = audioBuffer;
this.duration = audioBuffer.duration;
this.totalDuration = audioBuffer.duration;
var pending = this.pendingPlay;
if (pending)
{
this.pendingPlay = null;
this.play(pending.markerName, pending.config);
}
BaseSound.call(this, manager, key, config);
},
/**
@ -268,15 +197,6 @@ var WebAudioSound = new Class({
* It always plays the sound from the start. If you want to start playback from a specific time
* you can set 'seek' setting of the config object, provided to this call, to that value.
*
* If the audio has not yet been decoded it will first be passed to the Web Audio context and playback
* will not start until the decoding is complete. This may introduce a brief moment of silence, the
* duration of which will vary based on how long it takes the browser to decode the audio.
*
* If you need immediate playback with no decoding time, please ensure you call the method
* `WebAudioSoundManager.processQueue` to ensure the audio has decoded already. Note, this will
* *always* require a user-gesture before it can happen (either a click, touch or key press) and there
* is no way to circumvent this.
*
* @method Phaser.Sound.WebAudioSound#play
* @fires Phaser.Sound.Events#PLAY
* @since 3.0.0
@ -288,18 +208,6 @@ var WebAudioSound = new Class({
*/
play: function (markerName, config)
{
if (!this.audioBuffer)
{
this.pendingPlay = { markerName: markerName, config: config };
if (this.manager.unlocked)
{
this.init();
}
return true;
}
if (!BaseSound.prototype.play.call(this, markerName, config))
{
return false;
@ -325,7 +233,7 @@ var WebAudioSound = new Class({
*/
pause: function ()
{
if (!this.manager.context || this.manager.context.currentTime < this.startTime)
if (this.manager.context.currentTime < this.startTime)
{
return false;
}
@ -355,7 +263,7 @@ var WebAudioSound = new Class({
*/
resume: function ()
{
if (!this.manager.context || this.manager.context.currentTime < this.startTime)
if (this.manager.context.currentTime < this.startTime)
{
return false;
}
@ -581,6 +489,34 @@ var WebAudioSound = new Class({
}
},
/**
* Calls Phaser.Sound.BaseSound#destroy method
* and cleans up all Web Audio API related stuff.
*
* @method Phaser.Sound.WebAudioSound#destroy
* @since 3.0.0
*/
destroy: function ()
{
BaseSound.prototype.destroy.call(this);
this.audioBuffer = null;
this.stopAndRemoveBufferSource();
this.muteNode.disconnect();
this.muteNode = null;
this.volumeNode.disconnect();
this.volumeNode = null;
if (this.pannerNode)
{
this.pannerNode.disconnect();
this.pannerNode = null;
}
this.rateUpdates.length = 0;
this.rateUpdates = null;
},
/**
* Method used internally to calculate total playback rate of the sound.
*
@ -666,138 +602,6 @@ var WebAudioSound = new Class({
return this.playTime + lastRateUpdate.time + (this.duration - lastRateUpdateCurrentTime) / lastRateUpdate.rate;
},
/**
* Sets the playback rate of this Sound.
*
* For example, a value of 1.0 plays the audio at full speed, 0.5 plays the audio at half speed
* and 2.0 doubles the audios playback speed.
*
* @method Phaser.Sound.WebAudioSound#setRate
* @fires Phaser.Sound.Events#RATE
* @since 3.3.0
*
* @param {number} value - The playback rate at of this Sound.
*
* @return {this} This Sound instance.
*/
setRate: function (value)
{
this.rate = value;
return this;
},
/**
* Sets the detune value of this Sound, given in [cents](https://en.wikipedia.org/wiki/Cent_%28music%29).
* The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
*
* @method Phaser.Sound.WebAudioSound#setDetune
* @fires Phaser.Sound.Events#DETUNE
* @since 3.3.0
*
* @param {number} value - The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
*
* @return {this} This Sound instance.
*/
setDetune: function (value)
{
this.detune = value;
return this;
},
/**
* Sets the muted state of this Sound.
*
* @method Phaser.Sound.WebAudioSound#setMute
* @fires Phaser.Sound.Events#MUTE
* @since 3.4.0
*
* @param {boolean} value - `true` to mute this sound, `false` to unmute it.
*
* @return {this} This Sound instance.
*/
setMute: function (value)
{
this.mute = value;
return this;
},
/**
* Sets the volume of this Sound.
*
* @method Phaser.Sound.WebAudioSound#setVolume
* @fires Phaser.Sound.Events#VOLUME
* @since 3.4.0
*
* @param {number} value - The volume of the sound.
*
* @return {this} This Sound instance.
*/
setVolume: function (value)
{
this.volume = value;
return this;
},
/**
* Seeks to a specific point in this sound.
*
* @method Phaser.Sound.WebAudioSound#setSeek
* @fires Phaser.Sound.Events#SEEK
* @since 3.4.0
*
* @param {number} value - The point in the sound to seek to.
*
* @return {this} This Sound instance.
*/
setSeek: function (value)
{
this.seek = value;
return this;
},
/**
* Sets the loop state of this Sound.
*
* @method Phaser.Sound.WebAudioSound#setLoop
* @fires Phaser.Sound.Events#LOOP
* @since 3.4.0
*
* @param {boolean} value - `true` to loop this sound, `false` to not loop it.
*
* @return {this} This Sound instance.
*/
setLoop: function (value)
{
this.loop = value;
return this;
},
/**
* Sets the pan of this sound, a value between -1 (full left pan) and 1 (full right pan).
*
* Note: iOS / Safari doesn't support the stereo panner node.
*
* @method Phaser.Sound.WebAudioSound#setPan
* @fires Phaser.Sound.Events#PAN
* @since 3.50.0
*
* @param {number} value - The pan of the sound. A value between -1 (full left pan) and 1 (full right pan).
*
* @return {this} This Sound instance.
*/
setPan: function (value)
{
this.pan = value;
return this;
},
/**
* Rate at which this Sound will be played.
* Value of 1.0 plays the audio at full speed, 0.5 plays the audio at half speed
@ -827,6 +631,27 @@ var WebAudioSound = new Class({
},
/**
* Sets the playback rate of this Sound.
*
* For example, a value of 1.0 plays the audio at full speed, 0.5 plays the audio at half speed
* and 2.0 doubles the audios playback speed.
*
* @method Phaser.Sound.WebAudioSound#setRate
* @fires Phaser.Sound.Events#RATE
* @since 3.3.0
*
* @param {number} value - The playback rate at of this Sound.
*
* @return {this} This Sound instance.
*/
setRate: function (value)
{
this.rate = value;
return this;
},
/**
* The detune value of this Sound, given in [cents](https://en.wikipedia.org/wiki/Cent_%28music%29).
* The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
@ -855,6 +680,25 @@ var WebAudioSound = new Class({
},
/**
* Sets the detune value of this Sound, given in [cents](https://en.wikipedia.org/wiki/Cent_%28music%29).
* The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
*
* @method Phaser.Sound.WebAudioSound#setDetune
* @fires Phaser.Sound.Events#DETUNE
* @since 3.3.0
*
* @param {number} value - The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
*
* @return {this} This Sound instance.
*/
setDetune: function (value)
{
this.detune = value;
return this;
},
/**
* Boolean indicating whether the sound is muted or not.
* Gets or sets the muted state of this sound.
@ -869,26 +713,39 @@ var WebAudioSound = new Class({
get: function ()
{
return (this.muteNode && this.muteNode.gain.value === 0);
return (this.muteNode.gain.value === 0);
},
set: function (value)
{
if (this.muteNode)
{
this.currentConfig.mute = value;
this.muteNode.gain.setValueAtTime(value ? 0 : 1, 0);
this.currentConfig.mute = value;
this.muteNode.gain.setValueAtTime(value ? 0 : 1, 0);
this.emit(Events.MUTE, this, value);
}
this.emit(Events.MUTE, this, value);
}
},
/**
* Gets or sets the volume of this sound, a value between 0 (silence) and 1 (full volume).
* Sets the muted state of this Sound.
*
* If this returns -1 it means this sound has no volume node.
* @method Phaser.Sound.WebAudioSound#setMute
* @fires Phaser.Sound.Events#MUTE
* @since 3.4.0
*
* @param {boolean} value - `true` to mute this sound, `false` to unmute it.
*
* @return {this} This Sound instance.
*/
setMute: function (value)
{
this.mute = value;
return this;
},
/**
* Gets or sets the volume of this sound, a value between 0 (silence) and 1 (full volume).
*
* @name Phaser.Sound.WebAudioSound#volume
* @type {number}
@ -900,29 +757,36 @@ var WebAudioSound = new Class({
get: function ()
{
if (this.volumeNode)
{
return this.volumeNode.gain.value;
}
else
{
return -1;
}
return this.volumeNode.gain.value;
},
set: function (value)
{
if (this.volumeNode)
{
this.currentConfig.volume = value;
this.currentConfig.volume = value;
this.volumeNode.gain.setValueAtTime(value, 0);
this.volumeNode.gain.setValueAtTime(value, 0);
this.emit(Events.VOLUME, this, value);
}
this.emit(Events.VOLUME, this, value);
}
},
/**
* Sets the volume of this Sound.
*
* @method Phaser.Sound.WebAudioSound#setVolume
* @fires Phaser.Sound.Events#VOLUME
* @since 3.4.0
*
* @param {number} value - The volume of the sound.
*
* @return {this} This Sound instance.
*/
setVolume: function (value)
{
this.volume = value;
return this;
},
/**
* Property representing the position of playback for this sound, in seconds.
* Setting it to a specific value moves current playback to that position.
@ -959,7 +823,7 @@ var WebAudioSound = new Class({
set: function (value)
{
if (!this.manager.context || this.manager.context.currentTime < this.startTime)
if (this.manager.context.currentTime < this.startTime)
{
return;
}
@ -981,6 +845,24 @@ var WebAudioSound = new Class({
}
},
/**
* Seeks to a specific point in this sound.
*
* @method Phaser.Sound.WebAudioSound#setSeek
* @fires Phaser.Sound.Events#SEEK
* @since 3.4.0
*
* @param {number} value - The point in the sound to seek to.
*
* @return {this} This Sound instance.
*/
setSeek: function (value)
{
this.seek = value;
return this;
},
/**
* Flag indicating whether or not the sound or current sound marker will loop.
*
@ -1015,6 +897,24 @@ var WebAudioSound = new Class({
}
},
/**
* Sets the loop state of this Sound.
*
* @method Phaser.Sound.WebAudioSound#setLoop
* @fires Phaser.Sound.Events#LOOP
* @since 3.4.0
*
* @param {boolean} value - `true` to loop this sound, `false` to not loop it.
*
* @return {this} This Sound instance.
*/
setLoop: function (value)
{
this.loop = value;
return this;
},
/**
* Gets or sets the pan of this sound, a value between -1 (full left pan) and 1 (full right pan).
*
@ -1054,40 +954,23 @@ var WebAudioSound = new Class({
},
/**
* Calls Phaser.Sound.BaseSound#destroy method
* and cleans up all Web Audio API related stuff.
* Sets the pan of this sound, a value between -1 (full left pan) and 1 (full right pan).
*
* @method Phaser.Sound.WebAudioSound#destroy
* @since 3.0.0
* Note: iOS / Safari doesn't support the stereo panner node.
*
* @method Phaser.Sound.WebAudioSound#setPan
* @fires Phaser.Sound.Events#PAN
* @since 3.50.0
*
* @param {number} value - The pan of the sound. A value between -1 (full left pan) and 1 (full right pan).
*
* @return {this} This Sound instance.
*/
destroy: function ()
setPan: function (value)
{
BaseSound.prototype.destroy.call(this);
this.pan = value;
this.audioBuffer = null;
this.stopAndRemoveBufferSource();
if (this.muteNode)
{
this.muteNode.disconnect();
this.muteNode = null;
}
if (this.volumeNode)
{
this.volumeNode.disconnect();
this.volumeNode = null;
}
if (this.pannerNode)
{
this.pannerNode.disconnect();
this.pannerNode = null;
}
this.rateUpdates.length = 0;
this.rateUpdates = null;
return this;
}
});

View file

@ -9,8 +9,7 @@ var Base64ToArrayBuffer = require('../../utils/base64/Base64ToArrayBuffer');
var BaseSoundManager = require('../BaseSoundManager');
var Class = require('../../utils/Class');
var Events = require('../events');
var GetFastValue = require('../../utils/object/GetFastValue');
var Map = require('../../structs/Map');
var GameEvents = require('../../core/events');
var WebAudioSound = require('./WebAudioSound');
/**
@ -21,8 +20,6 @@ var WebAudioSound = require('./WebAudioSound');
*
* There is a good guide to what's supported: [Cross-browser audio basics: Audio codec support](https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics#Audio_Codec_Support).
*
* Audio cannot be played without a user-gesture in the browser: https://developer.chrome.com/blog/autoplay/
*
* @class WebAudioSoundManager
* @extends Phaser.Sound.BaseSoundManager
* @memberof Phaser.Sound
@ -39,8 +36,6 @@ var WebAudioSoundManager = new Class({
function WebAudioSoundManager (game)
{
this.config = game.config.audio;
/**
* The AudioContext being used for playback.
*
@ -48,7 +43,7 @@ var WebAudioSoundManager = new Class({
* @type {AudioContext}
* @since 3.0.0
*/
this.context;
this.context = this.createAudioContext(game);
/**
* Gain node responsible for controlling global muting.
@ -57,7 +52,7 @@ var WebAudioSoundManager = new Class({
* @type {GainNode}
* @since 3.0.0
*/
this.masterMuteNode;
this.masterMuteNode = this.context.createGain();
/**
* Gain node responsible for controlling global volume.
@ -66,7 +61,11 @@ var WebAudioSoundManager = new Class({
* @type {GainNode}
* @since 3.0.0
*/
this.masterVolumeNode;
this.masterVolumeNode = this.context.createGain();
this.masterMuteNode.connect(this.masterVolumeNode);
this.masterVolumeNode.connect(this.context.destination);
/**
* Destination node for connecting individual sounds to.
@ -75,55 +74,20 @@ var WebAudioSoundManager = new Class({
* @type {AudioNode}
* @since 3.0.0
*/
this.destination;
this.destination = this.masterMuteNode;
/**
* Audio files pending decoding.
*
* @name Phaser.Sound.WebAudioSoundManager#decodeQueue
* @type {Phaser.Structs.Map.<string, Phaser.Types.Sound.WebAudioDecodeEntry>}
* @since 3.60.0
*/
this.decodeQueue = new Map();
/**
* Will audio files be decoded on-demand (i.e. as they are played),
* or as they are loaded? They can only be decoded on load if the
* audio context has been unlocked, otherwise they are stacked in
* the 'decodeQueue' awaiting a user gesture, which once received
* will decode them all at once.
*
* You can also choose to decode any, or all, queued audio by calling
* the `processQueue` function directly. Again, this can only be done
* after the context has been unlocked.
*
* @name Phaser.Sound.WebAudioSoundManager#decodeOnDemand
* @type {boolean}
* @since 3.60.0
*/
this.decodeOnDemand = GetFastValue(this.config, 'decodeOnDemand', true);
/**
* The Audio cache, where decoded audio data is stored.
*
* @name Phaser.Sound.WebAudioSoundManager#cache
* @type {Phaser.Cache.BaseCache}
* @since 3.60.0
*/
this.cache = game.cache.audio;
this.locked = this.context.state === 'suspended' && ('ontouchstart' in window || 'onclick' in window);
BaseSoundManager.call(this, game);
},
/**
* Handles additional processing when this Audio Manager is unlocked.
*
* @method Phaser.Sound.WebAudioSoundManager#unlockHandler
* @since 3.60.0
*/
unlockHandler: function ()
{
this.createAudioContext();
if (this.locked && game.isBooted)
{
this.unlock();
}
else
{
game.events.once(GameEvents.BOOT, this.unlock, this);
}
},
/**
@ -131,53 +95,34 @@ var WebAudioSoundManager = new Class({
* If an instance of an AudioContext class was provided through the game config,
* that instance will be returned instead. This can come in handy if you are reloading
* a Phaser game on a page that never properly refreshes (such as in an SPA project)
* and you want to reuse an already instantiated AudioContext.
* and you want to reuse already instantiated AudioContext.
*
* @method Phaser.Sound.WebAudioSoundManager#createAudioContext
* @since 3.0.0
*
* @param {Phaser.Game} game - Reference to the current game instance.
*
* @return {AudioContext} The AudioContext instance to be used for playback.
*/
createAudioContext: function ()
createAudioContext: function (game)
{
var context;
var audioConfig = this.config;
var audioConfig = game.config.audio;
if (audioConfig.context)
{
audioConfig.context.resume();
context = audioConfig.context;
return audioConfig.context;
}
if (window.hasOwnProperty('AudioContext'))
{
context = new AudioContext({ latencyHint: 'interactive' });
return new AudioContext();
}
else if (window.hasOwnProperty('webkitAudioContext'))
{
try
{
context = new window.webkitAudioContext({ latencyHint: 'interactive' });
}
catch (e)
{
// For iOS10 and legacy devices we create without arguments:
context = new window.webkitAudioContext();
}
return new window.webkitAudioContext();
}
this.setAudioContext(context);
if (this.locked)
{
this.unlocked = true;
this.locked = false;
this.emit(Events.UNLOCKED, this);
}
return context;
},
/**
@ -244,87 +189,22 @@ var WebAudioSoundManager = new Class({
return sound;
},
/**
* This will process either the entire queue of audio awaiting decoding, or,
* if an array of keys are given, just those audio files.
*
* Decoding only starts of the Audio Context has been unlocked via a user
* gesture. If it hasn't, this method will return `false` and no decoding
* will take place.
*
* This will call `AudioContext.decodeAudioData` on the sounds. If they successfully decode, they will
* be added to the audio cache and can be played via the `play` method by passing their key.
*
* If they fail, it will throw an error.
*
* Decoding time varies, based on the audio file format, the encoder used, the browser
* and the device / CPU it is running on. This decoding time is outside of the control of Phaser.
*
* If you need to know when something has decoded, please use the relevant audio events.
*
* @method Phaser.Sound.WebAudioSoundManager#decodeAudioQueue
* @fires Phaser.Sound.Events#DECODED
* @fires Phaser.Sound.Events#DECODED_KEY
* @fires Phaser.Sound.Events#DECODED_ALL
* @since 3.60.0
*
* @param {(string|string[])} [key] - The key, or an array of keys, of the sound to be decoded. If not given, all sounds are decoded.
*
* @return {boolean} `true` if the audio started to decode, otherwise `false`.
*/
decodeAudioQueue: function (key)
{
var context = this.context;
var queue = this.decodeQueue;
if (key && !Array.isArray(key))
{
key = [ key ];
}
var isDecoding = false;
if (context)
{
for (var i = 0; i < key.length; i++)
{
var entry = queue.get(key[i]);
if (entry && !entry.decoding)
{
entry.decoding = true;
context.decodeAudioData(entry.data, entry.success, entry.failure);
isDecoding = true;
}
}
}
return isDecoding;
},
/**
* Decode audio data into a format ready for playback via Web Audio.
*
* The audio data can be a base64 encoded string, an audio media-type data uri, or an ArrayBuffer instance.
*
* The `audioKey` is the key that will be used to save the decoded audio to the audio cache and it
* must be unique within the cache.
* The `audioKey` is the key that will be used to save the decoded audio to the audio cache.
*
* Instead of passing a single entry you can pass an array of `Phaser.Types.Sound.DecodeAudioConfig`
* Instead of passing a single entry you can instead pass an array of `Phaser.Types.Sound.DecodeAudioConfig`
* objects as the first and only argument.
*
* Decoding is an async process, so be sure to listen for the events to know when decoding has completed.
*
* Not all browsers can decode all audio formats, so if you're calling this method with your own audio
* data please ensure you pass only data suitable for the browser, or it will throw an error.
*
* Once the audio has decoded it can be played via its key.
* Once the audio has decoded it can be added to the Sound Manager or played via its key.
*
* @method Phaser.Sound.WebAudioSoundManager#decodeAudio
* @fires Phaser.Sound.Events#DECODED
* @fires Phaser.Sound.Events#DECODED_KEY
* @fires Phaser.Sound.Events#DECODED_ALL
* @since 3.18.0
*
@ -337,7 +217,7 @@ var WebAudioSoundManager = new Class({
if (!Array.isArray(audioKey))
{
audioFiles = [ { key: audioKey, data: audioData, decoding: false } ];
audioFiles = [ { key: audioKey, data: audioData } ];
}
else
{
@ -346,8 +226,6 @@ var WebAudioSoundManager = new Class({
var cache = this.game.cache.audio;
var remaining = audioFiles.length;
var context = this.context;
var queue = this.decodeQueue;
for (var i = 0; i < audioFiles.length; i++)
{
@ -365,8 +243,7 @@ var WebAudioSoundManager = new Class({
{
cache.add(key, audioBuffer);
this.emit(Events.DECODED, key, audioBuffer);
this.emit(Events.DECODED_KEY + key, audioBuffer);
this.emit(Events.DECODED, key);
remaining--;
@ -389,16 +266,54 @@ var WebAudioSoundManager = new Class({
}
}.bind(this, key);
if (!context || this.decodeOnDemand)
{
queue.set(key, { data: data, success: success, failure: failure, decoding: false });
}
else
{
entry.decoding = true;
this.context.decodeAudioData(data, success, failure);
}
},
context.decodeAudioData(data, success, failure);
/**
* Unlocks Web Audio API on the initial input event.
*
* Read more about how this issue is handled here in [this article](https://medium.com/@pgoloskokovic/unlocking-web-audio-the-smarter-way-8858218c0e09).
*
* @method Phaser.Sound.WebAudioSoundManager#unlock
* @since 3.0.0
*/
unlock: function ()
{
var _this = this;
var body = document.body;
var unlockHandler = function unlockHandler ()
{
if (_this.context && body)
{
var bodyRemove = body.removeEventListener;
_this.context.resume().then(function ()
{
bodyRemove('touchstart', unlockHandler);
bodyRemove('touchend', unlockHandler);
bodyRemove('click', unlockHandler);
bodyRemove('keydown', unlockHandler);
_this.unlocked = true;
}, function ()
{
bodyRemove('touchstart', unlockHandler);
bodyRemove('touchend', unlockHandler);
bodyRemove('click', unlockHandler);
bodyRemove('keydown', unlockHandler);
});
}
};
if (body)
{
body.addEventListener('touchstart', unlockHandler, false);
body.addEventListener('touchend', unlockHandler, false);
body.addEventListener('click', unlockHandler, false);
body.addEventListener('keydown', unlockHandler, false);
}
},
@ -412,7 +327,7 @@ var WebAudioSoundManager = new Class({
*/
onBlur: function ()
{
if (!this.locked && this.context)
if (!this.locked)
{
this.context.suspend();
}
@ -430,7 +345,7 @@ var WebAudioSoundManager = new Class({
{
var context = this.context;
if (context && (context.state === 'suspended' || context.state === 'interrupted') && !this.locked)
if ((context.state === 'suspended' || context.state === 'interrupted') && !this.locked)
{
context.resume();
}
@ -461,6 +376,38 @@ var WebAudioSoundManager = new Class({
}
},
/**
* Calls Phaser.Sound.BaseSoundManager#destroy method
* and cleans up all Web Audio API related stuff.
*
* @method Phaser.Sound.WebAudioSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
this.destination = null;
this.masterVolumeNode.disconnect();
this.masterVolumeNode = null;
this.masterMuteNode.disconnect();
this.masterMuteNode = null;
if (this.game.config.audio.context)
{
this.context.suspend();
}
else
{
var _this = this;
this.context.close().then(function ()
{
_this.context = null;
});
}
BaseSoundManager.prototype.destroy.call(this);
},
/**
* Sets the muted state of all this Sound Manager.
*
@ -479,6 +426,28 @@ var WebAudioSoundManager = new Class({
return this;
},
/**
* @name Phaser.Sound.WebAudioSoundManager#mute
* @type {boolean}
* @fires Phaser.Sound.Events#GLOBAL_MUTE
* @since 3.0.0
*/
mute: {
get: function ()
{
return (this.masterMuteNode.gain.value === 0);
},
set: function (value)
{
this.masterMuteNode.gain.setValueAtTime(value ? 0 : 1, 0);
this.emit(Events.GLOBAL_MUTE, this, value);
}
},
/**
* Sets the volume of this Sound Manager.
*
@ -497,31 +466,6 @@ var WebAudioSoundManager = new Class({
return this;
},
/**
* @name Phaser.Sound.WebAudioSoundManager#mute
* @type {boolean}
* @fires Phaser.Sound.Events#GLOBAL_MUTE
* @since 3.0.0
*/
mute: {
get: function ()
{
return (this.masterMuteNode && this.masterMuteNode.gain.value === 0);
},
set: function (value)
{
if (this.masterMuteNode)
{
this.masterMuteNode.gain.setValueAtTime(value ? 0 : 1, 0);
this.emit(Events.GLOBAL_MUTE, this, value);
}
}
},
/**
* @name Phaser.Sound.WebAudioSoundManager#volume
* @type {number}
@ -532,72 +476,16 @@ var WebAudioSoundManager = new Class({
get: function ()
{
if (this.masterVolumeNode)
{
return this.masterVolumeNode.gain.value;
}
else
{
return 0;
}
return this.masterVolumeNode.gain.value;
},
set: function (value)
{
if (this.masterVolumeNode)
{
this.masterVolumeNode.gain.setValueAtTime(value, 0);
this.masterVolumeNode.gain.setValueAtTime(value, 0);
this.emit(Events.GLOBAL_VOLUME, this, value);
}
this.emit(Events.GLOBAL_VOLUME, this, value);
}
},
/**
* Calls Phaser.Sound.BaseSoundManager#destroy method
* and cleans up all Web Audio API related stuff.
*
* @method Phaser.Sound.WebAudioSoundManager#destroy
* @since 3.0.0
*/
destroy: function ()
{
if (this.masterVolumeNode)
{
this.masterVolumeNode.disconnect();
}
if (this.masterMuteNode)
{
this.masterMuteNode.disconnect();
}
this.decodeQueue.clear();
this.destination = null;
this.masterVolumeNode = null;
this.masterMuteNode = null;
this.decodeQueue = null;
if (this.context)
{
if (this.config.context)
{
this.context.suspend();
}
else
{
var _this = this;
this.context.close().then(function ()
{
_this.context = null;
});
}
}
BaseSoundManager.prototype.destroy.call(this);
}
});