Skip to content

Commit dfee828

Browse files
committed
Sound.loop even when set for WebAudio wouldn't use the AudioContext loop property because Sound.start was being invoked with an offset and duration. Now if loop is true and no marker is being used it will use the native Web Audio loop support (phaserjs#1431)
SoundManager.setDecodedCallback lets you specify a list of Sound files, or keys, and a callback. Once all of the Sound files have finished decoding the callback will be invoked. The amount of time spent decoding depends on the codec used and file size. If all of the files given have already decoded the callback is triggered immediately. Sound.loopFull is a new method that will start playback of the Sound and set it to loop in its entirety.
1 parent 18ff048 commit dfee828

3 files changed

Lines changed: 131 additions & 12 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ Thanks to @pnstickne for vast majority of this update.
9898
* Tilemap.createFromObjects now checks for a `rotation` property on the Object and if present will set it as the Sprite.angle (#1433)
9999
* If for whatever reason you wish to hide the Phaser banner in the console.log you can set `window.PhaserGlobal.hideBanner` to `true` and it will skip the output. Honestly I'd rather if you didn't, but the option is now there.
100100
* TilemapLayer.setScale will allow you to apply scaling to a specific Tilemap layer, i.e. `layer.setScale(2)` would double the size of the layer. The way the Camera responds to the layer is adjusted accordingly based on the scale, as is Arcade collision (thanks @mickez #1605)
101+
* SoundManager.setDecodedCallback lets you specify a list of Sound files, or keys, and a callback. Once all of the Sound files have finished decoding the callback will be invoked. The amount of time spent decoding depends on the codec used and file size. If all of the files given have already decoded the callback is triggered immediately.
102+
* Sound.loopFull is a new method that will start playback of the Sound and set it to loop in its entirety.
101103

102104
### Updates
103105

@@ -140,6 +142,7 @@ Thanks to @pnstickne for vast majority of this update.
140142
* Sprite.loadTexture and Image.loadTexture now no longer call `updateTexture` if the texture given is a RenderTexture. This fixes issues with RetroFonts in IE11 WebGL as well as other RenderTexture related IE11 problems (#1310 #1381 #1523)
141143
* You can now tint animated Sprites in Canvas mode. Or change the texture atlas frame of a tinted Sprite or Image. Please note that this is pretty expensive (depending in the browser), as the tint is re-applied every time the *frame changes*. The Pixi tint cache has also been removed to allow for subtle tint color shifts and to avoid blowing up memory. So use this feature sparingly! But at least it does now work (#1070)
142144
* ArcadePhysics.moveToPointer no longer goes crazy if the maxTime parameter is given and the Sprite is positioned in a larger game world (thanks @AnderbergE #1472)
145+
* Sound.loop even when set for WebAudio wouldn't use the AudioContext loop property because Sound.start was being invoked with an offset and duration. Now if `loop` is true and no marker is being used it will use the native Web Audio loop support (#1431)
143146

144147
For changes in previous releases please see the extensive [Version History](https://github.com/photonstorm/phaser/blob/master/CHANGELOG.md).
145148

src/sound/Sound.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ Phaser.Sound.prototype = {
405405
{
406406
if (this.loop)
407407
{
408+
// console.log('Sound update loop: ' + this.currentTime + ' m: ' + this.currentMarker);
409+
408410
// won't work with markers, needs to reset the position
409411
this.onLoop.dispatch(this);
410412

@@ -442,13 +444,27 @@ Phaser.Sound.prototype = {
442444
}
443445
},
444446

447+
/**
448+
* Loops this entire sound. If you need to loop a section of it then use Sound.play and the marker and loop parameters.
449+
*
450+
* @method Phaser.Sound#loopFull
451+
* @param {number} [volume=1] - Volume of the sound you want to play. If none is given it will use the volume given to the Sound when it was created (which defaults to 1 if none was specified).
452+
* @return {Phaser.Sound} This sound instance.
453+
*/
454+
loopFull: function (volume) {
455+
456+
this.play(null, 0, volume, true);
457+
458+
},
459+
445460
/**
446461
* Play this sound, or a marked section of it.
462+
*
447463
* @method Phaser.Sound#play
448464
* @param {string} [marker=''] - If you want to play a marker then give the key here, otherwise leave blank to play the full sound.
449465
* @param {number} [position=0] - The starting position to play the sound from - this is ignored if you provide a marker.
450466
* @param {number} [volume=1] - Volume of the sound you want to play. If none is given it will use the volume given to the Sound when it was created (which defaults to 1 if none was specified).
451-
* @param {boolean} [loop=false] - Loop when it finished playing?
467+
* @param {boolean} [loop=false] - Loop when finished playing? If not using a marker / audio sprite the looping will be done via the WebAudio loop property, otherwise it's time based.
452468
* @param {boolean} [forceRestart=true] - If the sound is already playing you can set forceRestart to restart it from the beginning.
453469
* @return {Phaser.Sound} This sound instance.
454470
*/
@@ -574,6 +590,11 @@ Phaser.Sound.prototype = {
574590
this._sound.connect(this.gainNode);
575591
}
576592

593+
if (this.loop && marker === '')
594+
{
595+
this._sound.loop = true;
596+
}
597+
577598
this.totalDuration = this._sound.buffer.duration;
578599

579600
if (this.duration === 0)
@@ -583,22 +604,22 @@ Phaser.Sound.prototype = {
583604
this.durationMS = this.totalDuration * 1000;
584605
}
585606

586-
if (this.loop && marker === '')
587-
{
588-
this._sound.loop = true;
589-
}
590-
591607
// Useful to cache this somewhere perhaps?
592608
if (typeof this._sound.start === 'undefined')
593609
{
594610
this._sound.noteGrainOn(0, this.position, this.duration);
595-
// this._sound.noteGrainOn(0, this.position, this.duration / 1000);
596611
//this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
597612
}
598613
else
599614
{
600-
// this._sound.start(0, this.position, this.duration / 1000);
601-
this._sound.start(0, this.position, this.duration);
615+
if (this.loop && marker === '')
616+
{
617+
this._sound.start(0);
618+
}
619+
else
620+
{
621+
this._sound.start(0, this.position, this.duration);
622+
}
602623
}
603624

604625
this.isPlaying = true;

src/sound/SoundManager.js

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,30 @@ Phaser.SoundManager = function (game) {
6464
*/
6565
this._sounds = [];
6666

67+
/**
68+
* @property {Phaser.ArraySet} _watchList - An array set containing all the sounds being monitored for decoding status.
69+
* @private
70+
*/
71+
this._watchList = new Phaser.ArraySet();
72+
73+
/**
74+
* @property {boolean} _watching - Is the SoundManager monitoring the watchList?
75+
* @private
76+
*/
77+
this._watching = false;
78+
79+
/**
80+
* @property {function} _watchCallback - The callback to invoke once the watchlist is clear.
81+
* @private
82+
*/
83+
this._watchCallback = null;
84+
85+
/**
86+
* @property {object} _watchContext - The context in which to call the watchlist callback.
87+
* @private
88+
*/
89+
this._watchContext = null;
90+
6791
/**
6892
* @property {AudioContext} context - The AudioContext being used for playback.
6993
* @default
@@ -301,7 +325,7 @@ Phaser.SoundManager.prototype = {
301325
},
302326

303327
/**
304-
* Decode a sound by its assets key.
328+
* Decode a sound by its asset key.
305329
*
306330
* @method Phaser.SoundManager#decode
307331
* @param {string} key - Assets key of the sound to be decoded.
@@ -313,6 +337,8 @@ Phaser.SoundManager.prototype = {
313337

314338
var soundData = this.game.cache.getSoundData(key);
315339

340+
// console.log(key, 'soundData', soundData);
341+
316342
if (soundData)
317343
{
318344
if (this.game.cache.isSoundDecoded(key) === false)
@@ -322,9 +348,10 @@ Phaser.SoundManager.prototype = {
322348
var that = this;
323349

324350
this.context.decodeAudioData(soundData, function (buffer) {
325-
that.game.cache.decodedSound(key, buffer);
326-
if (sound)
351+
352+
if (buffer)
327353
{
354+
that.game.cache.decodedSound(key, buffer);
328355
that.onSoundDecode.dispatch(key, sound);
329356
}
330357
});
@@ -333,6 +360,53 @@ Phaser.SoundManager.prototype = {
333360

334361
},
335362

363+
/**
364+
* This method allows you to give the SoundManager a list of Sound files, or keys, and a callback.
365+
* Once all of the Sound files have finished decoding the callback will be invoked.
366+
* The amount of time spent decoding depends on the codec used and file size.
367+
* If all of the files given have already decoded the callback is triggered immediately.
368+
*
369+
* @method Phaser.SoundManager#setDecodedCallback
370+
* @param {string|array} files - An array containing either Phaser.Sound objects or their key strings as found in the Phaser.Cache.
371+
* @param {Function} callback - The callback which will be invoked once all files have finished decoding.
372+
* @param {Object} callbackContext - The context in which the callback will run.
373+
*/
374+
setDecodedCallback: function (files, callback, callbackContext) {
375+
376+
if (typeof files === 'string')
377+
{
378+
files = [ files ];
379+
}
380+
381+
this._watchList.reset();
382+
383+
for (var i = 0; i < files.length; i++)
384+
{
385+
if (files[i] instanceof Phaser.Sound && !this.game.cache.isSoundDecoded(files[i].key))
386+
{
387+
this._watchList.add(files[i].key);
388+
}
389+
else if (!this.game.cache.isSoundDecoded(files[i]))
390+
{
391+
this._watchList.add(files[i]);
392+
}
393+
}
394+
395+
// All decoded already?
396+
if (this._watchList.total === 0)
397+
{
398+
this._watching = false;
399+
callback.call(callbackContext);
400+
}
401+
else
402+
{
403+
this._watching = true;
404+
this._watchCallback = callback;
405+
this._watchContext = callbackContext;
406+
}
407+
408+
},
409+
336410
/**
337411
* Updates every sound in the game.
338412
*
@@ -359,6 +433,27 @@ Phaser.SoundManager.prototype = {
359433
this._sounds[i].update();
360434
}
361435

436+
if (this._watching)
437+
{
438+
var key = this._watchList.first;
439+
440+
while (key)
441+
{
442+
if (this.game.cache.isSoundDecoded(key))
443+
{
444+
this._watchList.remove(key);
445+
}
446+
447+
key = this._watchList.next;
448+
}
449+
450+
if (this._watchList.total === 0)
451+
{
452+
this._watching = false;
453+
this._watchCallback.call(this._watchContext);
454+
}
455+
}
456+
362457
},
363458

364459
/**

0 commit comments

Comments
 (0)