diff --git a/CHANGELOG.md b/CHANGELOG.md index fbad53cc4..73861995a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ * The RandomDataGenerator classes randomness has been improved thanks to the correct caching of a class property. Fix #3289 (thanks @migiyubi) * The RandomDataGenerator `sign` property had a method collision. Fix #3323 (thanks @vinerz and @samme) * In Arcade Physics World if you collided a group with itself it would call a missing method (`collideGroupVsSelf`), it now calls `collideGroupVsGroup` correctly (thanks @patrickgalbraith) +* The HTML5 Sound Manager would unlock the Sound API on a touch event but only if the audio files were loaded in the first Scene, if they were loaded in a subsequent Scene the audio system would never unlock. It now unlocks only if there are audio files in the cache. Fix #3311 (thanks @chancezeus) ### Updates diff --git a/src/sound/html5/HTML5AudioSoundManager.js b/src/sound/html5/HTML5AudioSoundManager.js index e2038ee2e..5548fa670 100644 --- a/src/sound/html5/HTML5AudioSoundManager.js +++ b/src/sound/html5/HTML5AudioSoundManager.js @@ -3,8 +3,9 @@ * @copyright 2018 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ -var Class = require('../../utils/Class'); + var BaseSoundManager = require('../BaseSoundManager'); +var Class = require('../../utils/Class'); var HTML5AudioSound = require('./HTML5AudioSound'); /** @@ -20,8 +21,12 @@ var HTML5AudioSound = require('./HTML5AudioSound'); * @param {Phaser.Game} game - Reference to the current game instance. */ var HTML5AudioSoundManager = new Class({ + Extends: BaseSoundManager, - initialize: function HTML5AudioSoundManager (game) + + initialize: + + function HTML5AudioSoundManager (game) { /** * Flag indicating whether if there are no idle instances of HTML5 Audio tag, @@ -77,6 +82,7 @@ var HTML5AudioSoundManager = new Class({ * @since 3.0.0 */ this.onBlurPausedSounds = []; + this.locked = 'ontouchstart' in window; /** @@ -115,6 +121,7 @@ var HTML5AudioSoundManager = new Class({ * @since 3.0.0 */ this._volume = 1; + BaseSoundManager.call(this, game); }, @@ -132,7 +139,9 @@ var HTML5AudioSoundManager = new Class({ add: function (key, config) { var sound = new HTML5AudioSound(this, key, config); + this.sounds.push(sound); + return sound; }, @@ -147,21 +156,32 @@ var HTML5AudioSoundManager = new Class({ unlock: function () { var _this = this; + var moved = false; + var detectMove = function () { moved = true; }; + var unlock = function () { + if (!_this.game.cache.audio.entries.size) + { + return; + } + if (moved) { moved = false; return; } + document.body.removeEventListener('touchmove', detectMove); document.body.removeEventListener('touchend', unlock); + var allTags = []; + _this.game.cache.audio.entries.each(function (key, tags) { for (var i = 0; i < tags.length; i++) @@ -170,25 +190,30 @@ var HTML5AudioSoundManager = new Class({ } return true; }); + var lastTag = allTags[allTags.length - 1]; + lastTag.oncanplaythrough = function () { lastTag.oncanplaythrough = null; _this.unlocked = true; }; + allTags.forEach(function (tag) { tag.load(); }); }; + this.once('unlocked', function () { - _this.forEachActiveSound(function (sound) + this.forEachActiveSound(function (sound) { sound.duration = sound.tags[0].duration; sound.totalDuration = sound.tags[0].duration; }); - _this.lockedActionsQueue.forEach(function (lockedAction) + + this.lockedActionsQueue.forEach(function (lockedAction) { if (lockedAction.sound[lockedAction.prop].apply) { @@ -199,9 +224,11 @@ var HTML5AudioSoundManager = new Class({ lockedAction.sound[lockedAction.prop] = lockedAction.value; } }); - _this.lockedActionsQueue.length = 0; - _this.lockedActionsQueue = null; - }); + + this.lockedActionsQueue.length = 0; + this.lockedActionsQueue = null; + }, this); + document.body.addEventListener('touchmove', detectMove, false); document.body.addEventListener('touchend', unlock, false); }, @@ -240,6 +267,7 @@ var HTML5AudioSoundManager = new Class({ { sound.onFocus(); }); + this.onBlurPausedSounds.length = 0; }, @@ -253,6 +281,7 @@ var HTML5AudioSoundManager = new Class({ destroy: function () { BaseSoundManager.prototype.destroy.call(this); + this.onBlurPausedSounds.length = 0; this.onBlurPausedSounds = null; }, @@ -281,51 +310,79 @@ var HTML5AudioSoundManager = new Class({ prop: prop, value: value }); + return true; } + return false; - } -}); -Object.defineProperty(HTML5AudioSoundManager.prototype, 'mute', { - get: function () - { - return this._mute; }, - set: function (value) - { - this._mute = value; - this.forEachActiveSound(function (sound) - { - sound.setMute(); - }); - /** - * @event Phaser.Sound.HTML5AudioSoundManager#mute - * @param {Phaser.Sound.HTML5AudioSoundManager} soundManager - Reference to the sound manager that emitted event. - * @param {boolean} value - An updated value of Phaser.Sound.HTML5AudioSoundManager#mute property. - */ - this.emit('mute', this, value); - } -}); -Object.defineProperty(HTML5AudioSoundManager.prototype, 'volume', { - get: function () - { - return this._volume; + /** + * @event Phaser.Sound.HTML5AudioSoundManager#MuteEvent + * @param {Phaser.Sound.HTML5AudioSoundManager} soundManager - Reference to the sound manager that emitted event. + * @param {boolean} value - An updated value of Phaser.Sound.HTML5AudioSoundManager#mute property. + */ + + /** + * @name Phaser.Sound.HTML5AudioSoundManager#mute + * @type {boolean} + * @fires Phaser.Sound.HTML5AudioSoundManager#MuteEvent + * @since 3.0.0 + */ + mute: { + + get: function () + { + return this._mute; + }, + + set: function (value) + { + this._mute = value; + + this.forEachActiveSound(function (sound) + { + sound.setMute(); + }); + + this.emit('mute', this, value); + } + }, - set: function (value) - { - this._volume = value; - this.forEachActiveSound(function (sound) - { - sound.setVolume(); - }); - /** - * @event Phaser.Sound.HTML5AudioSoundManager#volume - * @param {Phaser.Sound.HTML5AudioSoundManager} soundManager - Reference to the sound manager that emitted event. - * @param {number} value - An updated value of Phaser.Sound.HTML5AudioSoundManager#volume property. - */ - this.emit('volume', this, value); + /** + * @event Phaser.Sound.HTML5AudioSoundManager#VolumeEvent + * @param {Phaser.Sound.HTML5AudioSoundManager} soundManager - Reference to the sound manager that emitted event. + * @param {number} value - An updated value of Phaser.Sound.HTML5AudioSoundManager#volume property. + */ + + /** + * @name Phaser.Sound.HTML5AudioSoundManager#volume + * @type {number} + * @fires Phaser.Sound.HTML5AudioSoundManager#VolumeEvent + * @since 3.0.0 + */ + volume: { + + get: function () + { + return this._volume; + }, + + set: function (value) + { + this._volume = value; + + this.forEachActiveSound(function (sound) + { + sound.setVolume(); + }); + + this.emit('volume', this, value); + } + } + }); + module.exports = HTML5AudioSoundManager; diff --git a/src/sound/webaudio/WebAudioSoundManager.js b/src/sound/webaudio/WebAudioSoundManager.js index 95c97e7d3..ed36e9f50 100644 --- a/src/sound/webaudio/WebAudioSoundManager.js +++ b/src/sound/webaudio/WebAudioSoundManager.js @@ -3,8 +3,9 @@ * @copyright 2018 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ -var Class = require('../../utils/Class'); + var BaseSoundManager = require('../BaseSoundManager'); +var Class = require('../../utils/Class'); var WebAudioSound = require('./WebAudioSound'); /** @@ -21,8 +22,12 @@ var WebAudioSound = require('./WebAudioSound'); * @param {Phaser.Game} game - Reference to the current game instance. */ var WebAudioSoundManager = new Class({ + Extends: BaseSoundManager, - initialize: function WebAudioSoundManager (game) + + initialize: + + function WebAudioSoundManager (game) { /** * The AudioContext being used for playback. @@ -53,7 +58,9 @@ var WebAudioSoundManager = new Class({ * @since 3.0.0 */ this.masterVolumeNode = this.context.createGain(); + this.masterMuteNode.connect(this.masterVolumeNode); + this.masterVolumeNode.connect(this.context.destination); /** @@ -65,7 +72,9 @@ var WebAudioSoundManager = new Class({ * @since 3.0.0 */ this.destination = this.masterMuteNode; + this.locked = this.context.state === 'suspended' && 'ontouchstart' in window; + BaseSoundManager.call(this, game); }, @@ -87,11 +96,13 @@ var WebAudioSoundManager = new Class({ createAudioContext: function (game) { var audioConfig = game.config.audio; + if (audioConfig && audioConfig.context) { audioConfig.context.resume(); return audioConfig.context; } + return new AudioContext(); }, @@ -108,8 +119,11 @@ var WebAudioSoundManager = new Class({ */ add: function (key, config) { + var sound = new WebAudioSound(this, key, config); + this.sounds.push(sound); + return sound; }, @@ -125,6 +139,7 @@ var WebAudioSoundManager = new Class({ unlock: function () { var _this = this; + var unlock = function () { _this.context.resume().then(function () @@ -134,6 +149,7 @@ var WebAudioSoundManager = new Class({ _this.unlocked = true; }); }; + document.body.addEventListener('touchstart', unlock, false); document.body.addEventListener('touchend', unlock, false); }, @@ -178,6 +194,7 @@ var WebAudioSoundManager = new Class({ this.masterVolumeNode = null; this.masterMuteNode.disconnect(); this.masterMuteNode = null; + if (this.game.config.audio && this.game.config.audio.context) { this.context.suspend(); @@ -186,42 +203,68 @@ var WebAudioSoundManager = new Class({ { this.context.close(); } + this.context = null; + BaseSoundManager.prototype.destroy.call(this); - } -}); -Object.defineProperty(WebAudioSoundManager.prototype, 'mute', { - get: function () - { - return this.masterMuteNode.gain.value === 0; }, - set: function (value) - { - this.masterMuteNode.gain.setValueAtTime(value ? 0 : 1, 0); - /** - * @event Phaser.Sound.WebAudioSoundManager#mute - * @param {Phaser.Sound.WebAudioSoundManager} soundManager - Reference to the sound manager that emitted event. - * @param {boolean} value - An updated value of Phaser.Sound.WebAudioSoundManager#mute property. - */ - this.emit('mute', this, value); - } -}); -Object.defineProperty(WebAudioSoundManager.prototype, 'volume', { - get: function () - { - return this.masterVolumeNode.gain.value; + /** + * @event Phaser.Sound.WebAudioSoundManager#MuteEvent + * @param {Phaser.Sound.WebAudioSoundManager} soundManager - Reference to the sound manager that emitted event. + * @param {boolean} value - An updated value of Phaser.Sound.WebAudioSoundManager#mute property. + */ + + /** + * @name Phaser.Sound.WebAudioSoundManager#mute + * @type {boolean} + * @fires Phaser.Sound.WebAudioSoundManager#MuteEvent + * @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('mute', this, value); + } + }, - set: function (value) - { - this.masterVolumeNode.gain.setValueAtTime(value, 0); - /** - * @event Phaser.Sound.WebAudioSoundManager#volume - * @param {Phaser.Sound.WebAudioSoundManager} soundManager - Reference to the sound manager that emitted event. - * @param {number} value - An updated value of Phaser.Sound.WebAudioSoundManager#volume property. - */ - this.emit('volume', this, value); + /** + * @event Phaser.Sound.WebAudioSoundManager#VolumeEvent + * @param {Phaser.Sound.WebAudioSoundManager} soundManager - Reference to the sound manager that emitted event. + * @param {number} value - An updated value of Phaser.Sound.WebAudioSoundManager#volume property. + */ + + /** + * @name Phaser.Sound.WebAudioSoundManager#volume + * @type {number} + * @fires Phaser.Sound.WebAudioSoundManager#VolumeEvent + * @since 3.0.0 + */ + volume: { + + get: function () + { + return this.masterVolumeNode.gain.value; + }, + + set: function (value) + { + this.masterVolumeNode.gain.setValueAtTime(value, 0); + + this.emit('volume', this, value); + } + } + }); + module.exports = WebAudioSoundManager;