diff --git a/Phaser/Game.ts b/Phaser/Game.ts
index c996087f..9ec94962 100644
--- a/Phaser/Game.ts
+++ b/Phaser/Game.ts
@@ -14,6 +14,7 @@
///
///
///
+///
///
///
///
@@ -298,12 +299,12 @@ module Phaser {
this.stage = new Stage(this, parent, width, height);
this.world = new World(this, width, height);
this.add = new GameObjectFactory(this);
- this.sound = new SoundManager(this);
this.cache = new Cache(this);
this.load = new Loader(this, this.loadComplete);
this.time = new Time(this);
this.tweens = new TweenManager(this);
this.input = new Input(this);
+ this.sound = new SoundManager(this);
this.rnd = new RandomDataGenerator([(Date.now() * Math.random()).toString()]);
this.physics = new Physics.Manager(this);
@@ -391,6 +392,7 @@ module Phaser {
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
if (this.onPausedCallback !== null)
{
@@ -407,6 +409,7 @@ module Phaser {
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
this.physics.update();
this.world.update();
diff --git a/Phaser/Stage.ts b/Phaser/Stage.ts
index d147a03a..1e8dd718 100644
--- a/Phaser/Stage.ts
+++ b/Phaser/Stage.ts
@@ -61,6 +61,8 @@ module Phaser {
document.addEventListener('visibilitychange', (event) => this.visibilityChange(event), false);
document.addEventListener('webkitvisibilitychange', (event) => this.visibilityChange(event), false);
+ document.addEventListener('pagehide', (event) => this.visibilityChange(event), false);
+ document.addEventListener('pageshow', (event) => this.visibilityChange(event), false);
window.onblur = (event) => this.visibilityChange(event);
window.onfocus = (event) => this.visibilityChange(event);
@@ -216,7 +218,7 @@ module Phaser {
return;
}
- if (event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true)
+ if (event.type == 'pagehide' || event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true)
{
if (this._game.paused == false)
{
diff --git a/Phaser/gameobjects/GameObjectFactory.ts b/Phaser/gameobjects/GameObjectFactory.ts
index 30b8f781..78e981a3 100644
--- a/Phaser/gameobjects/GameObjectFactory.ts
+++ b/Phaser/gameobjects/GameObjectFactory.ts
@@ -94,6 +94,10 @@ module Phaser {
return this._world.group.add(new Sprite(this._game, x, y, key, frame, bodyType));
}
+ public audio(key: string, volume?: number = 1, loop?: bool = false) {
+ return this._game.sound.add(key, volume, loop);
+ }
+
/**
* Create a new Sprite with the physics automatically created and set to DYNAMIC. The Sprite position offset is set to its center.
*
diff --git a/Phaser/input/Input.ts b/Phaser/input/Input.ts
index 50596489..b427341c 100644
--- a/Phaser/input/Input.ts
+++ b/Phaser/input/Input.ts
@@ -275,7 +275,7 @@ module Phaser {
* @property recordPointerHistory
* @type {Boolean}
**/
- public recordPointerHistory: bool = true;
+ public recordPointerHistory: bool = false;
/**
* The rate in milliseconds at which the Pointer objects should update their tracking history
@@ -286,7 +286,7 @@ module Phaser {
/**
* The total number of entries that can be recorded into the Pointer objects tracking history.
- * The the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
+ * If the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
* @property recordLimit
* @type {Number}
*/
diff --git a/Phaser/input/Touch.ts b/Phaser/input/Touch.ts
index 8d46b706..34d2e71c 100644
--- a/Phaser/input/Touch.ts
+++ b/Phaser/input/Touch.ts
@@ -79,9 +79,7 @@ module Phaser {
* @param {Any} event
**/
private consumeTouchMove(event) {
-
event.preventDefault();
-
}
/**
diff --git a/Phaser/loader/Cache.ts b/Phaser/loader/Cache.ts
index 7e82516f..c7af99c8 100644
--- a/Phaser/loader/Cache.ts
+++ b/Phaser/loader/Cache.ts
@@ -122,22 +122,40 @@ module Phaser {
* @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
- public addSound(key: string, url: string, data) {
+ public addSound(key: string, url: string, data, webAudio: bool = true, audioTag: bool = false) {
- this._sounds[key] = { url: url, data: data, decoded: false };
+ console.log('Cache addSound', key, url, webAudio, audioTag);
+
+ var decoded: bool = false;
+
+ if (audioTag) {
+ decoded = true;
+ }
+
+ this._sounds[key] = { url: url, data: data, isDecoding: false, decoded: decoded, webAudio: webAudio, audioTag: audioTag };
+
+ }
+
+ public updateSound(key: string, property: string, value) {
+
+ if (this._sounds[key])
+ {
+ this._sounds[key][property] = value;
+ }
}
/**
* Add a new decoded sound.
* @param key {string} Asset key for the sound.
- * @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
public decodedSound(key: string, data) {
+ console.log('decoded sound', key);
this._sounds[key].data = data;
this._sounds[key].decoded = true;
+ this._sounds[key].isDecoding = false;
}
@@ -201,12 +219,28 @@ module Phaser {
}
+ /**
+ * Get sound by key.
+ * @param key Asset key of the sound you want.
+ * @return {object} The sound you want.
+ */
+ public getSound(key: string) {
+
+ if (this._sounds[key])
+ {
+ return this._sounds[key];
+ }
+
+ return null;
+
+ }
+
/**
* Get sound data by key.
* @param key Asset key of the sound you want.
* @return {object} The sound data you want.
*/
- public getSound(key: string) {
+ public getSoundData(key: string) {
if (this._sounds[key])
{
diff --git a/Phaser/loader/Loader.ts b/Phaser/loader/Loader.ts
index 0218c5a8..d37c5cdb 100644
--- a/Phaser/loader/Loader.ts
+++ b/Phaser/loader/Loader.ts
@@ -218,14 +218,15 @@ module Phaser {
/**
* Add a new audio file loading request.
* @param key {string} Unique asset key of the audio file.
- * @param url {string} URL of audio file.
+ * @param urls {Array} An array containing the URLs of the audio files, i.e.: [ 'jump.mp3', 'jump.ogg', 'jump.m4a' ]
+ * @param autoDecode {boolean} When using Web Audio the audio files can either be decoded at load time or run-time. They can't be played until they are decoded, but this let's you control when that happens. Decoding is a non-blocking async process.
*/
- public audio(key: string, url: string) {
+ public audio(key: string, urls: string[], autoDecode: bool = true) {
if (this.checkKeyExists(key) === false)
{
this._queueSize++;
- this._fileList[key] = { type: 'audio', key: key, url: url, data: null, buffer: null, error: false, loaded: false };
+ this._fileList[key] = { type: 'audio', key: key, url: urls, data: null, buffer: null, error: false, loaded: false, autoDecode: autoDecode };
this._keys.push(key);
}
@@ -327,11 +328,32 @@ module Phaser {
break;
case 'audio':
- this._xhr.open("GET", file.url, true);
- this._xhr.responseType = "arraybuffer";
- this._xhr.onload = () => this.fileComplete(file.key);
- this._xhr.onerror = () => this.fileError(file.key);
- this._xhr.send();
+
+ file.url = this.getAudioURL(file.url);
+
+ if (file.url !== null)
+ {
+ // WebAudio or Audio Tag?
+ if (this._game.sound.usingWebAudio)
+ {
+ this._xhr.open("GET", file.url, true);
+ this._xhr.responseType = "arraybuffer";
+ this._xhr.onload = () => this.fileComplete(file.key);
+ this._xhr.onerror = () => this.fileError(file.key);
+ this._xhr.send();
+ }
+ else if (this._game.sound.usingAudioTag)
+ {
+ file.data = new Audio();
+ file.data.name = file.key;
+ file.data.onerror = () => this.fileError(file.key);
+ file.data.preload = 'auto';
+ file.data.src = file.url;
+ file.data.addEventListener('canplaythrough', () => this.fileComplete(file.key), false);
+ file.data.load();
+ }
+ }
+
break;
case 'text':
@@ -345,6 +367,27 @@ module Phaser {
}
+ private getAudioURL(urls): string {
+
+ var extension: string;
+
+ for (var i = 0; i < urls.length; i++)
+ {
+ extension = urls[i].toLowerCase();
+ extension = extension.substr((Math.max(0, extension.lastIndexOf(".")) || Infinity) + 1);
+
+ if (this._game.device.canPlayAudio(extension))
+ {
+ console.log('getAudioURL', urls[i]);
+ return urls[i];
+ }
+
+ }
+
+ return null;
+
+ }
+
/**
* Error occured when load a file.
* @param key {string} Key of the error loading file.
@@ -406,8 +449,32 @@ module Phaser {
break;
case 'audio':
- file.data = this._xhr.response;
- this._game.cache.addSound(file.key, file.url, file.data);
+
+ if (this._game.sound.usingWebAudio)
+ {
+ file.data = this._xhr.response;
+
+ this._game.cache.addSound(file.key, file.url, file.data, true, false);
+
+ if (file.autoDecode)
+ {
+ this._game.cache.updateSound(key, 'isDecoding', true);
+
+ var that = this;
+ var key = file.key;
+
+ this._game.sound.context.decodeAudioData(file.data, function (buffer) {
+ if (buffer)
+ {
+ that._game.cache.decodedSound(key, buffer);
+ }
+ });
+ }
+ }
+ else
+ {
+ this._game.cache.addSound(file.key, file.url, file.data, false, true);
+ }
break;
case 'text':
diff --git a/Phaser/sound/Sound.ts b/Phaser/sound/Sound.ts
index 6f169aec..0fe2a212 100644
--- a/Phaser/sound/Sound.ts
+++ b/Phaser/sound/Sound.ts
@@ -13,56 +13,76 @@ module Phaser {
/**
* Sound constructor
- * @param context {object} The AudioContext instance.
- * @param gainNode {object} Gain node instance.
- * @param data {object} Sound data.
* @param [volume] {number} volume of this sound when playing.
* @param [loop] {boolean} loop this sound when playing? (Default to false)
*/
- constructor(context, gainNode, data, volume?: number = 1, loop?: bool = false) {
+ constructor(game: Phaser.Game, key: string, volume?: number = 1, loop?: bool = false) {
- this._context = context;
- this._gainNode = gainNode;
- this._buffer = data;
- this._volume = volume;
- this.loop = loop;
+ this.game = game;
- // Local volume control
- if (this._context !== null)
+ this.usingWebAudio = this.game.sound.usingWebAudio;
+ this.usingAudioTag = this.game.sound.usingAudioTag;
+ this.key = key;
+
+ if (this.usingWebAudio)
{
- this._localGainNode = this._context.createGainNode();
- this._localGainNode.connect(this._gainNode);
- this._localGainNode.gain.value = this._volume;
- }
+ this.context = this.game.sound.context;
+ this.masterGainNode = this.game.sound.masterGain;
- if (this._buffer === null)
- {
- this.isDecoding = true;
+ if (typeof this.context.createGain === 'undefined')
+ {
+ this.gainNode = this.context.createGainNode();
+ }
+ else
+ {
+ this.gainNode = this.context.createGain();
+ }
+
+ this.gainNode.gain.value = volume * this.game.sound.volume;
+ this.gainNode.connect(this.masterGainNode);
}
else
{
- this.play();
+ this._sound = this.game.cache.getSoundData(key);
+ this.totalDuration = this._sound.duration;
}
+ this._volume = volume;
+ this.loop = loop;
+ this.markers = {};
+
+ this.onDecoded = new Phaser.Signal;
+ this.onPlay = new Phaser.Signal;
+ this.onPause = new Phaser.Signal;
+ this.onResume = new Phaser.Signal;
+ this.onLoop = new Phaser.Signal;
+ this.onStop = new Phaser.Signal;
+ this.onMute = new Phaser.Signal;
+
}
/**
- * Local private reference to AudioContext.
+ * Local reference to the current Phaser.Game.
*/
- private _context;
+ public game: Game;
+
+ /**
+ * Reference to AudioContext instance.
+ */
+ public context = null;
/**
* Reference to gain node of SoundManager.
*/
- private _gainNode;
+ public masterGainNode;
/**
* GainNode of this sound.
*/
- private _localGainNode;
+ public gainNode;
/**
- * Decoded data buffer.
+ * Decoded data buffer / Audio tag.
*/
private _buffer;
@@ -78,43 +98,247 @@ module Phaser {
private _muteVolume: number;
private _muted: bool = false;
+ private _tempPosition: number;
+ private _tempVolume: number;
+ private _tempLoop: bool;
+ private _tempMarker: string;
+ public usingWebAudio: bool = false;
+ public usingAudioTag: bool = false;
+
+ public name: string = '';
+
+ autoplay: bool = false;
+ totalDuration: number = 0;
+ startTime: number = 0;
+ currentTime: number = 0;
+ duration: number = 0;
+ stopTime: number = 0;
+ position: number;
+ paused: bool = false;
loop: bool = false;
- duration: number;
isPlaying: bool = false;
- isDecoding: bool = false;
+ key: string;
+ markers;
+ currentMarker: string = '';
- public setDecodedBuffer(data) {
+ // events
+ public onDecoded: Phaser.Signal;
+ public onPlay: Phaser.Signal;
+ public onPause: Phaser.Signal;
+ public onResume: Phaser.Signal;
+ public onLoop: Phaser.Signal;
+ public onStop: Phaser.Signal;
+ public onMute: Phaser.Signal;
- this._buffer = data;
- this.isDecoding = false;
- //this.play();
+ public pendingPlayback: bool = false;
+
+ public get isDecoding(): bool {
+ return this.game.cache.getSound(this.key).isDecoding;
+ }
+
+ public get isDecoded(): bool {
+ return this.game.cache.isSoundDecoded(this.key);
+ }
+
+ public addMarker(name: string, start: number, stop: number, volume: number = 1, loop: bool = false) {
+ this.markers[name] = { name: name, start: start, stop: stop, volume: volume, duration: stop - start, loop: loop };
+ }
+
+ public removeMarker(name: string) {
+ delete this.markers[name];
+ }
+
+ public update() {
+
+ if (this.pendingPlayback && this.game.cache.isSoundDecoded(this.key))
+ {
+ this.pendingPlayback = false;
+ this.play(this._tempMarker, this._tempPosition, this._tempVolume, this._tempLoop);
+ }
+
+ if (this.isPlaying)
+ {
+ this.currentTime = this.game.time.now - this.startTime;
+
+ if (this.currentTime >= this.duration)
+ {
+ if (this.usingWebAudio)
+ {
+ if (this.loop)
+ {
+ this.onLoop.dispatch(this);
+ this.currentTime = 0;
+ this.startTime = this.game.time.now;
+ }
+ else
+ {
+ this.stop();
+ }
+ }
+ else
+ {
+ if (this.loop)
+ {
+ this.onLoop.dispatch(this);
+ this.play(this.currentMarker, 0, this.volume, true, true);
+ }
+ else
+ {
+ this.stop();
+ }
+ }
+ }
+
+ }
}
/**
- * Play this sound.
+ * Play this sound, or a marked section of it.
+ * @param marker {string} Assets key of the sound you want to play.
+ * @param [volume] {number} volume of the sound you want to play.
+ * @param [loop] {boolean} loop when it finished playing? (Default to false)
+ * @return {Sound} The playing sound object.
*/
- public play() {
+ public play(marker: string = '', position?: number = 0, volume?: number = 1, loop?: bool = false, forceRestart: bool = false) {
- if (this._buffer === null || this.isDecoding === true)
+ if (this.isPlaying == true && forceRestart == false)
{
+ // Use Restart instead
return;
}
- this._sound = this._context.createBufferSource();
- this._sound.buffer = this._buffer;
- this._sound.connect(this._localGainNode);
+ this.currentMarker = marker;
- if (this.loop)
+ if (marker !== '' && this.markers[marker])
{
- this._sound.loop = true;
+ this.loop = this.markers[marker].loop;
+ this.volume = this.markers[marker].volume;
+ this.position = this.markers[marker].start;
+ this.duration = this.markers[marker].duration * 1000;
+ }
+ else
+ {
+ this.loop = loop;
+ this.volume = volume;
+ this.position = position;
+ this.duration = 0;
}
- this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ if (this.usingWebAudio)
+ {
+ // Does the sound need decoding?
+ if (this.game.cache.isSoundDecoded(this.key))
+ {
+ // Do we need to do this every time we play? How about just if the buffer is empty?
+ this._buffer = this.game.cache.getSoundData(this.key);
+ this._sound = this.context.createBufferSource();
+ this._sound.buffer = this._buffer;
+ this._sound.connect(this.gainNode);
+ this.totalDuration = this._sound.buffer.duration;
- this.duration = this._sound.buffer.duration;
- this.isPlaying = true;
+ if (this.duration == 0)
+ {
+ this.duration = this.totalDuration * 1000;
+ }
+
+ if (this.loop)
+ {
+ this._sound.loop = true;
+ }
+
+ // Useful to cache this somewhere perhaps?
+ if (typeof this._sound.start === 'undefined')
+ {
+ this._sound.noteGrainOn(0, this.position, this.duration / 1000);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ }
+ else
+ {
+ this._sound.start(0, this.position, this.duration / 1000);
+ }
+
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ }
+ else
+ {
+ this._tempVolume = volume;
+ this._tempLoop = loop;
+ this._tempPosition = position;
+ this._tempMarker = marker;
+ this.pendingPlayback = true;
+
+ if (this.game.cache.getSound(this.key).isDecoding == false)
+ {
+ this.game.sound.decode(this.key, this);
+ }
+ }
+ }
+ else
+ {
+ if (this._sound.readyState == 4)
+ {
+ this._sound.currentTime = this.position;
+ this._sound.muted = this._muted;
+ this._sound.volume = this._volume;
+ this._sound.play();
+
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ }
+ }
+
+ }
+
+ public restart(marker: string = '', position?: number = 0, volume?: number = 1, loop?: bool = false) {
+ this.play(marker, position, volume, loop, true);
+ }
+
+ public pause() {
+
+ if (this.isPlaying && this._sound)
+ {
+ this.stop();
+ this.isPlaying = false;
+ this.paused = true;
+ this.onPause.dispatch(this);
+ }
+
+ }
+
+ public resume() {
+
+ if (this.isPlaying == false && this._sound)
+ {
+ if (this.usingWebAudio)
+ {
+ if (typeof this._sound.start === 'undefined')
+ {
+ this._sound.noteGrainOn(0, this.position, this.duration);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ }
+ else
+ {
+ this._sound.start(0, this.position, this.duration);
+ }
+ }
+ else
+ {
+ this._sound.play();
+ }
+
+ this.isPlaying = true;
+ this.paused = false;
+ this.onResume.dispatch(this);
+ }
}
@@ -123,11 +347,29 @@ module Phaser {
*/
public stop() {
- if (this.isPlaying === true)
+ if (this.isPlaying && this._sound)
{
+ if (this.usingWebAudio)
+ {
+ if (typeof this._sound.stop === 'undefined')
+ {
+ this._sound.noteOff(0);
+ }
+ else
+ {
+ this._sound.stop(0);
+ }
+ }
+ else if (this.usingAudioTag)
+ {
+ this._sound.pause();
+ this._sound.currentTime = 0;
+ }
+
this.isPlaying = false;
+ this.currentMarker = '';
+ this.onStop.dispatch(this);
- this._sound.noteOff(0);
}
}
@@ -135,37 +377,56 @@ module Phaser {
/**
* Mute sounds.
*/
- public get mute():bool {
+ public get mute(): bool {
return this._muted;
}
public set mute(value: bool) {
- if (value && this._muted == false)
+ if (value)
{
- this._muteVolume = this._localGainNode.gain.value;
- this._localGainNode.gain.value = 0;
this._muted = true;
+
+ if (this.usingWebAudio)
+ {
+ this._muteVolume = this.gainNode.gain.value;
+ this.gainNode.gain.value = 0;
+ }
+ else if (this.usingAudioTag)
+ {
+ this._muteVolume = this._sound.volume;
+ this._sound.volume = 0;
+ }
}
else
{
- this._localGainNode.gain.value = this._muteVolume;
this._muted = false;
+
+ if (this.usingWebAudio)
+ {
+ this.gainNode.gain.value = this._muteVolume;
+ }
+ else if (this.usingAudioTag)
+ {
+ this._sound.volume = this._muteVolume;
+ }
}
+ this.onMute.dispatch(this);
+
}
public set volume(value: number) {
this._volume = value;
- if (this._muted)
+ if (this.usingWebAudio)
{
- this._muteVolume = this._volume;
+ this.gainNode.gain.value = value;
}
- else
+ else if (this.usingAudioTag)
{
- this._localGainNode.gain.value = this._volume;
+ this._sound.volume = value;
}
}
@@ -174,6 +435,30 @@ module Phaser {
return this._volume;
}
+ /**
+ * Renders the Pointer.circle object onto the stage in green if down or red if up.
+ * @method renderDebug
+ */
+ public renderDebug(x: number, y: number) {
+
+ this.game.stage.context.fillStyle = 'rgb(255,255,255)';
+ this.game.stage.context.font = '16px Courier';
+ this.game.stage.context.fillText('Sound: ' + this.key + ' Locked: ' + this.game.sound.touchLocked + ' Pending Playback: ' + this.pendingPlayback, x, y);
+ this.game.stage.context.fillText('Decoded: ' + this.isDecoded + ' Decoding: ' + this.isDecoding, x, y + 20);
+ this.game.stage.context.fillText('Total Duration: ' + this.totalDuration + ' Playing: ' + this.isPlaying, x, y + 40);
+ this.game.stage.context.fillText('Time: ' + this.currentTime, x, y + 60);
+ this.game.stage.context.fillText('Volume: ' + this.volume + ' Muted: ' + this.mute, x, y + 80);
+ this.game.stage.context.fillText('WebAudio: ' + this.usingWebAudio + ' Audio: ' + this.usingAudioTag, x, y + 100);
+
+ if (this.currentMarker !== '')
+ {
+ this.game.stage.context.fillText('Marker: ' + this.currentMarker + ' Duration: ' + this.duration, x, y + 120);
+ this.game.stage.context.fillText('Start: ' + this.markers[this.currentMarker].start + ' Stop: ' + this.markers[this.currentMarker].stop, x, y + 140);
+ this.game.stage.context.fillText('Position: ' + this.position, x, y + 160);
+ }
+
+ }
+
}
}
\ No newline at end of file
diff --git a/Phaser/sound/SoundManager.ts b/Phaser/sound/SoundManager.ts
index 7ced4fbd..364a3be3 100644
--- a/Phaser/sound/SoundManager.ts
+++ b/Phaser/sound/SoundManager.ts
@@ -17,48 +17,106 @@ module Phaser {
*/
constructor(game: Game) {
- this._game = game;
+ this.game = game;
- if (window['PhaserGlobal'] && window['PhaserGlobal'].disableAudio == true)
+ this._volume = 1;
+ this._muted = false;
+ this._sounds = [];
+
+ if (this.game.device.iOS && this.game.device.webAudio == false)
{
- return;
+ this.channels = 1;
}
- if (game.device.webaudio == true)
+ if (window['PhaserGlobal'])
{
- if (!!window['AudioContext'])
+ // Check to see if all audio playback is disabled (i.e. handled by a 3rd party class)
+ if (window['PhaserGlobal'].disableAudio == true)
{
- this._context = new window['AudioContext']();
- }
- else if (!!window['webkitAudioContext'])
- {
- this._context = new window['webkitAudioContext']();
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ return;
}
- if (this._context !== null)
+ // Check if the Web Audio API is disabled (for testing Audio Tag playback during development)
+ if (window['PhaserGlobal'].disableWebAudio == true)
{
- this._gainNode = this._context.createGainNode();
- this._gainNode.connect(this._context.destination);
- this._volume = 1;
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ this.noAudio = false;
+ return;
}
}
+ if (this.game.device.iOS && this.game.device.webAudio)
+ {
+ this.game.input.touch.callbackContext = this;
+ this.game.input.touch.touchStartCallback = this.unlock;
+ this.touchLocked = true;
+ }
+ else
+ {
+ // What about iOS5?
+ this.touchLocked = false;
+ }
+
+ this.usingWebAudio = true;
+ this.noAudio = false;
+
+ if (!!window['AudioContext'])
+ {
+ this.context = new window['AudioContext']();
+ }
+ else if (!!window['webkitAudioContext'])
+ {
+ this.context = new window['webkitAudioContext']();
+ }
+ else if (!!window['Audio'])
+ {
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ }
+ else
+ {
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ }
+
+ if (this.context !== null)
+ {
+ if (typeof this.context.createGain === 'undefined')
+ {
+ this.masterGain = this.context.createGainNode();
+ }
+ else
+ {
+ this.masterGain = this.context.createGain();
+ }
+
+ this.masterGain.gain.value = 1;
+ this.masterGain.connect(this.context.destination);
+ }
+
}
+ public usingWebAudio: bool = false;
+ public usingAudioTag: bool = false;
+ public noAudio: bool = false;
+
/**
- * Local private reference to game.
+ * Local reference to the current Phaser.Game.
*/
- private _game: Game;
+ public game: Game;
/**
* Reference to AudioContext instance.
*/
- private _context = null;
+ public context = null;
/**
- * Gain node created from audio context.
+ * The Master Gain node through which all sounds
*/
- private _gainNode;
+ public masterGain;
/**
* Volume of sounds.
@@ -66,11 +124,37 @@ module Phaser {
*/
private _volume: number;
+ private _sounds: Phaser.Sound[];
+
private _muteVolume: number;
private _muted: bool = false;
+ public channels: number;
+
+ public touchLocked: bool = false;
+
+ private _unlockSource = null;
+
+ public unlock() {
+
+ if (this.touchLocked == false || this.game.device.iOS == false)
+ {
+ return;
+ }
+
+ // Create Audio tag?
+
+ // Create empty buffer and play it
+ var buffer = this.context.createBuffer(1, 1, 22050);
+ this._unlockSource = this.context.createBufferSource();
+ this._unlockSource.buffer = buffer;
+ this._unlockSource.connect(this.context.destination);
+ this._unlockSource.noteOn(0);
+
+ }
+
/**
- * Mute sounds.
+ * A global audio mute toggle.
*/
public get mute():bool {
return this._muted;
@@ -78,104 +162,155 @@ module Phaser {
public set mute(value: bool) {
- if (value && this._muted == false)
+ if (value)
{
- this._muteVolume = this._gainNode.gain.value;
- this._gainNode.gain.value = 0;
+ if (this._muted)
+ {
+ return;
+ }
+
this._muted = true;
+
+ if (this.usingWebAudio)
+ {
+ this._muteVolume = this.masterGain.gain.value;
+ this.masterGain.gain.value = 0;
+ }
+
+ // Loop through sounds
+ for (var i = 0; i < this._sounds.length; i++)
+ {
+ if (this._sounds[i].usingAudioTag)
+ {
+ this._sounds[i].mute = true;
+ }
+ }
}
else
{
- this._gainNode.gain.value = this._muteVolume;
+ if (this._muted == false)
+ {
+ return;
+ }
+
this._muted = false;
+
+ if (this.usingWebAudio)
+ {
+ this.masterGain.gain.value = this._muteVolume;
+ }
+
+ // Loop through sounds
+ for (var i = 0; i < this._sounds.length; i++)
+ {
+ if (this._sounds[i].usingAudioTag)
+ {
+ this._sounds[i].mute = false;
+ }
+ }
}
}
+ /**
+ * The global audio volume. A value between 0 (silence) and 1 (full volume)
+ */
public set volume(value: number) {
+ value = this.game.math.clamp(value, 1, 0);
+
this._volume = value;
- if (this._muted)
+ if (this.usingWebAudio)
{
- this._muteVolume = this._volume;
+ this.masterGain.gain.value = value;
}
- else
+
+ // Loop through the sound cache and change the volume of all html audio tags
+ for (var i = 0; i < this._sounds.length; i++)
{
- this._gainNode.gain.value = this._volume;
+ if (this._sounds[i].usingAudioTag)
+ {
+ this._sounds[i].volume = this._sounds[i].volume * value;
+ }
}
}
public get volume(): number {
- return this._volume;
+
+ if (this.usingWebAudio)
+ {
+ return this.masterGain.gain.value;
+ }
+ else
+ {
+ return this._volume;
+ }
+
}
/**
* Decode a sound with its assets key.
* @param key {string} Assets key of the sound to be decoded.
- * @param callback {function} This will be invoked when finished decoding.
* @param [sound] {Sound} its bufer will be set to decoded data.
*/
- public decode(key: string, callback = null, sound?: Sound = null) {
+ public decode(key: string, sound?: Sound = null) {
- var soundData = this._game.cache.getSound(key);
+ var soundData = this.game.cache.getSoundData(key);
if (soundData)
{
- if (this._game.cache.isSoundDecoded(key) === false)
+ if (this.game.cache.isSoundDecoded(key) === false)
{
+ this.game.cache.updateSound(key, 'isDecoding', true);
+
var that = this;
- this._context.decodeAudioData(soundData, function (buffer) {
- that._game.cache.decodedSound(key, buffer);
+ this.context.decodeAudioData(soundData, function (buffer) {
+
+ that.game.cache.decodedSound(key, buffer);
if (sound)
{
- sound.setDecodedBuffer(buffer);
+ that.onSoundDecode.dispatch(sound);
}
-
- callback();
});
}
}
}
- /**
- * Play a sound with its assets key.
- * @param key {string} Assets key of the sound you want to play.
- * @param [volume] {number} volume of the sound you want to play.
- * @param [loop] {boolean} loop when it finished playing? (Default to false)
- * @return {Sound} The playing sound object.
- */
- public play(key: string, volume?: number = 1, loop?: bool = false): Sound {
+ public onSoundDecode: Phaser.Signal = new Phaser.Signal;
- if (this._context === null)
+ public update() {
+
+ if (this.touchLocked && this._unlockSource !== null)
{
- return;
- }
-
- var soundData = this._game.cache.getSound(key);
-
- if (soundData)
- {
- // Does the sound need decoding?
- if (this._game.cache.isSoundDecoded(key) === true)
+ if ((this._unlockSource.playbackState === this._unlockSource.PLAYING_STATE || this._unlockSource.playbackState === this._unlockSource.FINISHED_STATE))
{
- return new Sound(this._context, this._gainNode, soundData, volume, loop);
- }
- else
- {
- var tempSound: Sound = new Sound(this._context, this._gainNode, null, volume, loop);
-
- // this is an async process, so we can return the Sound object anyway, it just won't be playing yet
- this.decode(key, () => tempSound.play(), tempSound);
-
- return tempSound;
+ this.touchLocked = false;
+ this._unlockSource = null;
+ this.game.input.touch.callbackContext = null;
+ this.game.input.touch.touchStartCallback = null;
}
}
+ for (var i = 0; i < this._sounds.length; i++)
+ {
+ this._sounds[i].update();
+ }
+
+ }
+
+ public add(key: string, volume?: number = 1, loop?: bool = false): Sound {
+
+ var sound: Phaser.Sound = new Sound(this.game, key, volume, loop);
+
+ this._sounds.push(sound);
+
+ return sound;
+
}
}
diff --git a/Phaser/system/Device.ts b/Phaser/system/Device.ts
index 2132bea1..cb6d71b5 100644
--- a/Phaser/system/Device.ts
+++ b/Phaser/system/Device.ts
@@ -191,41 +191,53 @@ module Phaser {
// Audio
/**
- * Is audioData available?
+ * Are Audio tags available?
* @type {boolean}
*/
public audioData: bool = false;
/**
- * Is webaudio available?
+ * Is the WebAudio API available?
* @type {boolean}
*/
- public webaudio: bool = false;
+ public webAudio: bool = false;
/**
- * Is ogg available?
+ * Can this device play ogg files?
* @type {boolean}
*/
public ogg: bool = false;
/**
- * Is mp3 available?
+ * Can this device play opus files?
+ * @type {boolean}
+ */
+ public opus: bool = false;
+
+ /**
+ * Can this device play mp3 files?
* @type {boolean}
*/
public mp3: bool = false;
/**
- * Is wav available?
+ * Can this device play wav files?
* @type {boolean}
*/
public wav: bool = false;
/**
- * Is m4a available?
+ * Can this device play m4a files?
* @type {boolean}
*/
public m4a: bool = false;
+ /**
+ * Can this device play webm files?
+ * @type {boolean}
+ */
+ public webm: bool = false;
+
// Device
/**
@@ -355,7 +367,7 @@ module Phaser {
this.mobileSafari = true;
}
else if (/MSIE (\d+\.\d+);/.test(ua))
- {
+ {
this.ie = true;
this.ieVersion = parseInt(RegExp.$1);
}
@@ -380,6 +392,33 @@ module Phaser {
}
+ public canPlayAudio(type: string): bool {
+
+ if (type == 'mp3' && this.mp3)
+ {
+ return true;
+ }
+ else if (type == 'ogg' && (this.ogg || this.opus))
+ {
+ return true;
+ }
+ else if (type == 'm4a' && this.m4a)
+ {
+ return true;
+ }
+ else if (type == 'wav' && this.wav)
+ {
+ return true;
+ }
+ else if (type == 'webm' && this.webm)
+ {
+ return true;
+ }
+
+ return false;
+
+ }
+
/**
* Check audio support.
* @private
@@ -387,7 +426,7 @@ module Phaser {
private _checkAudio() {
this.audioData = !!(window['Audio']);
- this.webaudio = !!(window['webkitAudioContext'] || window['AudioContext']);
+ this.webAudio = !!(window['webkitAudioContext'] || window['AudioContext']);
var audioElement: HTMLAudioElement = document.createElement('audio');
var result = false;
@@ -401,6 +440,11 @@ module Phaser {
this.ogg = true;
}
+ if (audioElement.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''))
+ {
+ this.opus = true;
+ }
+
if (audioElement.canPlayType('audio/mpeg;').replace(/^no$/, ''))
{
this.mp3 = true;
@@ -418,6 +462,11 @@ module Phaser {
{
this.m4a = true;
}
+
+ if (audioElement.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ''))
+ {
+ this.webm = true;
+ }
}
} catch (e) { }
diff --git a/README.md b/README.md
index efd69115..705e77a8 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,11 @@ TODO:
* When you toggle visible of a button that is over, it doesn't disable that 'over' state (should it? probably yes)
* Stage.opaqueBackground = 'rgb()' or null, alpha, blendMode, filters, mask, rotation+XYZ,scaleXYZ,visible
+* Need a way for Input event to redirect to audio to unlock playback
+* AudioSprite object?
+* How to get web audio to playback from an offset
+* Stage lost to mute
+
V1.0.0
@@ -148,6 +153,12 @@ V1.0.0
* Fixed bug where the hand cursor would be reset on input up, even if the mouse was still over the sprite.
* Fixed bug where pressing down then moving out of the sprite area wouldn't properly reset the input state next time you moved over the sprite.
* Added the Sprite.tween property, really useful to avoid creating new tween vars in your local scope if you don't need them.
+* Added support for pagehide and pageshow events to Stage, hooked in to the pause/resume game methods.
+* Extended Device audio checks to include opus and webm.
+* Updated Loader and Cache so they now support loading of Audio() tags as well as Web Audio.
+* Set Input.recordPointerHistory to false, you now need to enable the pointer tracking if you wish to use it.
+* SoundManager will now automatically handle iOS touch unlocking.
+
V0.9.6
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index f57ac0db..bd659391 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -56,6 +56,14 @@
+
+
+
+ audio sprites 1.ts
+
+
+ play sound 1.ts
+
basic button 2.ts
diff --git a/Tests/assets/mp3/oedipus_wizball_highscore.ogg b/Tests/assets/mp3/oedipus_wizball_highscore.ogg
new file mode 100644
index 00000000..c9f7430f
Binary files /dev/null and b/Tests/assets/mp3/oedipus_wizball_highscore.ogg differ
diff --git a/Tests/assets/mp3/oedipus_wizball_highscore.xmp b/Tests/assets/mp3/oedipus_wizball_highscore.xmp
new file mode 100644
index 00000000..cc00ef74
--- /dev/null
+++ b/Tests/assets/mp3/oedipus_wizball_highscore.xmp
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+ CuePoint Markers
+ Cue
+ f44100
+
+
+ Subclip Markers
+ InOut
+ f44100
+
+
+
+
+
+ 2013-07-16T02:39:59+01:00
+ 2013-07-16T02:39:59+01:00
+
+
+ xmp.iid:CF0377343CEDE211B46AA47009D9EF1D
+ xmp.did:CF0377343CEDE211B46AA47009D9EF1D
+ xmp.did:CE0377343CEDE211B46AA47009D9EF1D
+
+
+
+ saved
+ xmp.iid:CE0377343CEDE211B46AA47009D9EF1D
+ 2013-07-16T02:39:59+01:00
+ Adobe Audition CS6 (Windows)
+ /metadata
+
+
+ saved
+ xmp.iid:CF0377343CEDE211B46AA47009D9EF1D
+ 2013-07-16T02:39:59+01:00
+ Adobe Audition CS6 (Windows)
+ /
+
+
+
+
+ xmp.iid:CE0377343CEDE211B46AA47009D9EF1D
+ xmp.did:CE0377343CEDE211B46AA47009D9EF1D
+ xmp.did:CE0377343CEDE211B46AA47009D9EF1D
+
+
+
+ audio/ogg; codec="vorbis"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tests/audio/audio sprites 1.js b/Tests/audio/audio sprites 1.js
new file mode 100644
index 00000000..dd52c2ee
--- /dev/null
+++ b/Tests/audio/audio sprites 1.js
@@ -0,0 +1,44 @@
+///
+///
+///
+//var PhaserGlobal = { disableWebAudio: true };
+(function () {
+ var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render);
+ function init() {
+ game.load.audio('rabbit', [
+ 'assets/mp3/peter_rabbit.m4a',
+ 'assets/mp3/peter_rabbit.mp3',
+ 'assets/mp3/peter_rabbit.ogg'
+ ]);
+ game.load.spritesheet('button', 'assets/buttons/button_sprite_sheet.png', 193, 71);
+ game.load.start();
+ }
+ audioSprite:
+Phaser.Sound
+ button:
+Phaser.Button
+ pause:
+Phaser.Button
+ function create() {
+ this.audioSprite = game.add.audio('rabbit');
+ this.audioSprite.addMarker('title', 3.00, 5.00, 1, true);
+ this.audioSprite.addMarker('help', 6.00, 12.00);
+ this.audioSprite.addMarker('intro', 14.00, 19.00);
+ this.audioSprite.addMarker('peter', 20.00, 21.50);
+ this.button = game.add.button(game.stage.centerX, 400, 'button', playMusic, this, 2, 1, 0);
+ //this.pause = game.add.button(200, 200, 'button', togglePause, this, 2, 1, 0);
+ }
+ function playMusic() {
+ this.audioSprite.play('help');
+ }
+ function render() {
+ this.audioSprite.renderDebug(32, 32);
+ }
+ function togglePause() {
+ if(this.music.paused) {
+ this.music.resume();
+ } else {
+ this.music.pause();
+ }
+ }
+})();
diff --git a/Tests/audio/audio sprites 1.ts b/Tests/audio/audio sprites 1.ts
new file mode 100644
index 00000000..e24f22da
--- /dev/null
+++ b/Tests/audio/audio sprites 1.ts
@@ -0,0 +1,58 @@
+///
+///
+///
+
+//var PhaserGlobal = { disableWebAudio: true };
+
+(function () {
+
+ var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render);
+
+ function init() {
+
+ game.load.audio('rabbit', ['assets/mp3/peter_rabbit.m4a', 'assets/mp3/peter_rabbit.mp3', 'assets/mp3/peter_rabbit.ogg']);
+ game.load.spritesheet('button', 'assets/buttons/button_sprite_sheet.png', 193, 71);
+ game.load.start();
+
+ }
+
+ audioSprite: Phaser.Sound;
+ button: Phaser.Button;
+ pause: Phaser.Button;
+
+ function create() {
+
+ this.audioSprite = game.add.audio('rabbit');
+ this.audioSprite.addMarker('title', 3.00, 5.00, 1, true);
+ this.audioSprite.addMarker('help', 6.00, 12.00);
+ this.audioSprite.addMarker('intro', 14.00, 19.00);
+ this.audioSprite.addMarker('peter', 20.00, 21.50);
+
+ this.button = game.add.button(game.stage.centerX, 400, 'button', playMusic, this, 2, 1, 0);
+ //this.pause = game.add.button(200, 200, 'button', togglePause, this, 2, 1, 0);
+
+ }
+
+ function playMusic() {
+ this.audioSprite.play('help');
+ }
+
+ function render() {
+ this.audioSprite.renderDebug(32, 32);
+ }
+
+ function togglePause() {
+
+ if (this.music.paused)
+ {
+ this.music.resume();
+ }
+ else
+ {
+ this.music.pause();
+ }
+
+ }
+
+
+})();
diff --git a/Tests/audio/play sound 1.js b/Tests/audio/play sound 1.js
new file mode 100644
index 00000000..9e2c8499
--- /dev/null
+++ b/Tests/audio/play sound 1.js
@@ -0,0 +1,56 @@
+///
+///
+///
+//var PhaserGlobal = { disableWebAudio: true };
+(function () {
+ var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render);
+ function init() {
+ //game.load.audio('wizball', ['assets/mp3/oedipus_wizball_highscore.ogg', 'assets/mp3/oedipus_wizball_highscore.mp3']);
+ game.load.audio('boden', [
+ 'assets/mp3/bodenstaendig_2000_in_rock_4bit.mp3'
+ ]);
+ //game.load.audio('boden', ['assets/mp3/puzzleover.mp3']);
+ game.load.spritesheet('button', 'assets/buttons/button_sprite_sheet.png', 193, 71);
+ game.load.start();
+ }
+ button:
+Phaser.Button
+ music:
+Phaser.Sound
+ volumeUp:
+Phaser.Button
+ volumeDown:
+Phaser.Button
+ pause:
+Phaser.Button
+ function create() {
+ this.music = game.add.audio('boden');
+ this.button = game.add.button(game.stage.centerX, 400, 'button', playMusic, this, 2, 1, 0);
+ this.volumeUp = game.add.button(0, 0, 'button', volUp, this, 2, 1, 0);
+ this.volumeDown = game.add.button(700, 0, 'button', volDown, this, 2, 1, 0);
+ this.pause = game.add.button(200, 200, 'button', togglePause, this, 2, 1, 0);
+ }
+ function render() {
+ this.music.renderDebug(0, 300);
+ }
+ function togglePause() {
+ if(this.music.paused) {
+ this.music.resume();
+ } else {
+ this.music.pause();
+ }
+ }
+ function volUp() {
+ //game.sound.volume += 0.1;
+ this.music.volume += 0.1;
+ console.log('vol up', game.sound.volume);
+ }
+ function volDown() {
+ //game.sound.volume -= 0.1;
+ this.music.volume -= 0.1;
+ console.log('vol down', game.sound.volume);
+ }
+ function playMusic() {
+ this.music.play();
+ }
+})();
diff --git a/Tests/audio/play sound 1.ts b/Tests/audio/play sound 1.ts
new file mode 100644
index 00000000..b77285ee
--- /dev/null
+++ b/Tests/audio/play sound 1.ts
@@ -0,0 +1,73 @@
+///
+///
+///
+
+//var PhaserGlobal = { disableWebAudio: true };
+
+(function () {
+
+ var game = new Phaser.Game(this, 'game', 800, 600, init, create, null, render);
+
+ function init() {
+
+ //game.load.audio('wizball', ['assets/mp3/oedipus_wizball_highscore.ogg', 'assets/mp3/oedipus_wizball_highscore.mp3']);
+ game.load.audio('boden', ['assets/mp3/bodenstaendig_2000_in_rock_4bit.mp3']);
+ //game.load.audio('boden', ['assets/mp3/puzzleover.mp3']);
+ game.load.spritesheet('button', 'assets/buttons/button_sprite_sheet.png', 193, 71);
+ game.load.start();
+
+ }
+
+ button: Phaser.Button;
+ music: Phaser.Sound;
+ volumeUp: Phaser.Button;
+ volumeDown: Phaser.Button;
+ pause: Phaser.Button;
+
+ function create() {
+
+ this.music = game.add.audio('boden');
+
+ this.button = game.add.button(game.stage.centerX, 400, 'button', playMusic, this, 2, 1, 0);
+ this.volumeUp = game.add.button(0, 0, 'button', volUp, this, 2, 1, 0);
+ this.volumeDown = game.add.button(700, 0, 'button', volDown, this, 2, 1, 0);
+ this.pause = game.add.button(200, 200, 'button', togglePause, this, 2, 1, 0);
+
+ }
+
+ function render() {
+
+ this.music.renderDebug(0, 300);
+
+ }
+
+ function togglePause() {
+
+ if (this.music.paused)
+ {
+ this.music.resume();
+ }
+ else
+ {
+ this.music.pause();
+ }
+
+ }
+
+ function volUp() {
+ //game.sound.volume += 0.1;
+ this.music.volume += 0.1;
+ console.log('vol up', game.sound.volume);
+ }
+
+ function volDown() {
+ //game.sound.volume -= 0.1;
+ this.music.volume -= 0.1;
+ console.log('vol down', game.sound.volume);
+ }
+
+ function playMusic() {
+ this.music.play();
+ }
+
+})();
diff --git a/Tests/phaser.js b/Tests/phaser.js
index d4732c21..c616e6f0 100644
--- a/Tests/phaser.js
+++ b/Tests/phaser.js
@@ -7243,7 +7243,7 @@ var Phaser;
this.cameraView = new Phaser.Rectangle(x, y, this.width, this.height);
this.transform.setCache();
this.outOfBounds = false;
- this.outOfBoundsAction = Phaser.Types.OUT_OF_BOUNDS_KILL;
+ this.outOfBoundsAction = Phaser.Types.OUT_OF_BOUNDS_PERSIST;
// Handy proxies
this.scale = this.transform.scale;
this.alpha = this.texture.alpha;
@@ -9134,19 +9134,22 @@ var Phaser;
Loader.prototype.audio = /**
* Add a new audio file loading request.
* @param key {string} Unique asset key of the audio file.
- * @param url {string} URL of audio file.
+ * @param urls {Array} An array containing the URLs of the audio files, i.e.: [ 'jump.mp3', 'jump.ogg', 'jump.m4a' ]
+ * @param autoDecode {boolean} When using Web Audio the audio files can either be decoded at load time or run-time. They can't be played until they are decoded, but this let's you control when that happens. Decoding is a non-blocking async process.
*/
- function (key, url) {
+ function (key, urls, autoDecode) {
+ if (typeof autoDecode === "undefined") { autoDecode = true; }
if(this.checkKeyExists(key) === false) {
this._queueSize++;
this._fileList[key] = {
type: 'audio',
key: key,
- url: url,
+ url: urls,
data: null,
buffer: null,
error: false,
- loaded: false
+ loaded: false,
+ autoDecode: autoDecode
};
this._keys.push(key);
}
@@ -9234,15 +9237,33 @@ var Phaser;
file.data.src = file.url;
break;
case 'audio':
- this._xhr.open("GET", file.url, true);
- this._xhr.responseType = "arraybuffer";
- this._xhr.onload = function () {
- return _this.fileComplete(file.key);
- };
- this._xhr.onerror = function () {
- return _this.fileError(file.key);
- };
- this._xhr.send();
+ file.url = this.getAudioURL(file.url);
+ if(file.url !== null) {
+ // WebAudio or Audio Tag?
+ if(this._game.sound.usingWebAudio) {
+ this._xhr.open("GET", file.url, true);
+ this._xhr.responseType = "arraybuffer";
+ this._xhr.onload = function () {
+ return _this.fileComplete(file.key);
+ };
+ this._xhr.onerror = function () {
+ return _this.fileError(file.key);
+ };
+ this._xhr.send();
+ } else if(this._game.sound.usingAudioTag) {
+ file.data = new Audio();
+ file.data.name = file.key;
+ file.data.onerror = function () {
+ return _this.fileError(file.key);
+ };
+ file.data.preload = 'auto';
+ file.data.src = file.url;
+ file.data.addEventListener('canplaythrough', function () {
+ return _this.fileComplete(file.key);
+ }, false);
+ file.data.load();
+ }
+ }
break;
case 'text':
this._xhr.open("GET", file.url, true);
@@ -9257,6 +9278,18 @@ var Phaser;
break;
}
};
+ Loader.prototype.getAudioURL = function (urls) {
+ var extension;
+ for(var i = 0; i < urls.length; i++) {
+ extension = urls[i].toLowerCase();
+ extension = extension.substr((Math.max(0, extension.lastIndexOf(".")) || Infinity) + 1);
+ if(this._game.device.canPlayAudio(extension)) {
+ console.log('getAudioURL', urls[i]);
+ return urls[i];
+ }
+ }
+ return null;
+ };
Loader.prototype.fileError = /**
* Error occured when load a file.
* @param key {string} Key of the error loading file.
@@ -9306,8 +9339,22 @@ var Phaser;
}
break;
case 'audio':
- file.data = this._xhr.response;
- this._game.cache.addSound(file.key, file.url, file.data);
+ if(this._game.sound.usingWebAudio) {
+ file.data = this._xhr.response;
+ this._game.cache.addSound(file.key, file.url, file.data, true, false);
+ if(file.autoDecode) {
+ this._game.cache.updateSound(key, 'isDecoding', true);
+ var that = this;
+ var key = file.key;
+ this._game.sound.context.decodeAudioData(file.data, function (buffer) {
+ if(buffer) {
+ that._game.cache.decodedSound(key, buffer);
+ }
+ });
+ }
+ } else {
+ this._game.cache.addSound(file.key, file.url, file.data, false, true);
+ }
break;
case 'text':
file.data = this._xhr.response;
@@ -9492,22 +9539,38 @@ var Phaser;
* @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
- function (key, url, data) {
+ function (key, url, data, webAudio, audioTag) {
+ if (typeof webAudio === "undefined") { webAudio = true; }
+ if (typeof audioTag === "undefined") { audioTag = false; }
+ console.log('Cache addSound', key, url, webAudio, audioTag);
+ var decoded = false;
+ if(audioTag) {
+ decoded = true;
+ }
this._sounds[key] = {
url: url,
data: data,
- decoded: false
+ isDecoding: false,
+ decoded: decoded,
+ webAudio: webAudio,
+ audioTag: audioTag
};
};
+ Cache.prototype.updateSound = function (key, property, value) {
+ if(this._sounds[key]) {
+ this._sounds[key][property] = value;
+ }
+ };
Cache.prototype.decodedSound = /**
* Add a new decoded sound.
* @param key {string} Asset key for the sound.
- * @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
function (key, data) {
+ console.log('decoded sound', key);
this._sounds[key].data = data;
this._sounds[key].decoded = true;
+ this._sounds[key].isDecoding = false;
};
Cache.prototype.addText = /**
* Add a new text data.
@@ -9555,6 +9618,17 @@ var Phaser;
return null;
};
Cache.prototype.getSound = /**
+ * Get sound by key.
+ * @param key Asset key of the sound you want.
+ * @return {object} The sound you want.
+ */
+ function (key) {
+ if(this._sounds[key]) {
+ return this._sounds[key];
+ }
+ return null;
+ };
+ Cache.prototype.getSoundData = /**
* Get sound data by key.
* @param key Asset key of the sound you want.
* @return {object} The sound data you want.
@@ -13794,6 +13868,11 @@ var Phaser;
if (typeof bodyType === "undefined") { bodyType = Phaser.Types.BODY_DISABLED; }
return this._world.group.add(new Phaser.Sprite(this._game, x, y, key, frame, bodyType));
};
+ GameObjectFactory.prototype.audio = function (key, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ return this._game.sound.add(key, volume, loop);
+ };
GameObjectFactory.prototype.physicsSprite = /**
* Create a new Sprite with the physics automatically created and set to DYNAMIC. The Sprite position offset is set to its center.
*
@@ -13889,13 +13968,15 @@ var Phaser;
return this._world.group.add(new Phaser.Tilemap(this._game, key, mapData, format, resizeWorld, tileWidth, tileHeight));
};
GameObjectFactory.prototype.tween = /**
- * Create a tween object for a specific object.
+ * Create a tween object for a specific object. The object can be any JavaScript object or Phaser object such as Sprite.
*
- * @param obj Object you wish the tween will affect.
+ * @param obj {object} Object the tween will be run on.
+ * @param [localReference] {bool} If true the tween will be stored in the object.tween property so long as it exists. If already set it'll be over-written.
* @return {Phaser.Tween} The newly created tween object.
*/
- function (obj) {
- return this._game.tweens.create(obj);
+ function (obj, localReference) {
+ if (typeof localReference === "undefined") { localReference = false; }
+ return this._game.tweens.create(obj, localReference);
};
GameObjectFactory.prototype.existingSprite = /**
* Add an existing Sprite to the current world.
@@ -13983,90 +14064,313 @@ var Phaser;
var Sound = (function () {
/**
* Sound constructor
- * @param context {object} The AudioContext instance.
- * @param gainNode {object} Gain node instance.
- * @param data {object} Sound data.
* @param [volume] {number} volume of this sound when playing.
* @param [loop] {boolean} loop this sound when playing? (Default to false)
*/
- function Sound(context, gainNode, data, volume, loop) {
+ function Sound(game, key, volume, loop) {
if (typeof volume === "undefined") { volume = 1; }
if (typeof loop === "undefined") { loop = false; }
+ /**
+ * Reference to AudioContext instance.
+ */
+ this.context = null;
+ this._muted = false;
+ this.usingWebAudio = false;
+ this.usingAudioTag = false;
+ this.name = '';
+ this.autoplay = false;
+ this.totalDuration = 0;
+ this.startTime = 0;
+ this.currentTime = 0;
+ this.duration = 0;
+ this.stopTime = 0;
+ this.paused = false;
this.loop = false;
this.isPlaying = false;
- this.isDecoding = false;
- this._context = context;
- this._gainNode = gainNode;
- this._buffer = data;
+ this.currentMarker = '';
+ this.pendingPlayback = false;
+ this.game = game;
+ this.usingWebAudio = this.game.sound.usingWebAudio;
+ this.usingAudioTag = this.game.sound.usingAudioTag;
+ this.key = key;
+ if(this.usingWebAudio) {
+ this.context = this.game.sound.context;
+ this.masterGainNode = this.game.sound.masterGain;
+ if(typeof this.context.createGain === 'undefined') {
+ this.gainNode = this.context.createGainNode();
+ } else {
+ this.gainNode = this.context.createGain();
+ }
+ this.gainNode.gain.value = volume * this.game.sound.volume;
+ this.gainNode.connect(this.masterGainNode);
+ } else {
+ this._sound = this.game.cache.getSoundData(key);
+ this.totalDuration = this._sound.duration;
+ }
this._volume = volume;
this.loop = loop;
- // Local volume control
- if(this._context !== null) {
- this._localGainNode = this._context.createGainNode();
- this._localGainNode.connect(this._gainNode);
- this._localGainNode.gain.value = this._volume;
- }
- if(this._buffer === null) {
- this.isDecoding = true;
- } else {
- this.play();
- }
+ this.markers = {
+ };
+ this.onDecoded = new Phaser.Signal();
+ this.onPlay = new Phaser.Signal();
+ this.onPause = new Phaser.Signal();
+ this.onResume = new Phaser.Signal();
+ this.onLoop = new Phaser.Signal();
+ this.onStop = new Phaser.Signal();
+ this.onMute = new Phaser.Signal();
}
- Sound.prototype.setDecodedBuffer = function (data) {
- this._buffer = data;
- this.isDecoding = false;
- //this.play();
- };
+ Object.defineProperty(Sound.prototype, "isDecoding", {
+ get: function () {
+ return this.game.cache.getSound(this.key).isDecoding;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Object.defineProperty(Sound.prototype, "isDecoded", {
+ get: function () {
+ return this.game.cache.isSoundDecoded(this.key);
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Sound.prototype.addMarker = function (name, start, stop, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ this.markers[name] = {
+ name: name,
+ start: start,
+ stop: stop,
+ volume: volume,
+ duration: stop - start,
+ loop: loop
+ };
+ };
+ Sound.prototype.removeMarker = function (name) {
+ delete this.markers[name];
+ };
+ Sound.prototype.update = function () {
+ if(this.pendingPlayback && this.game.cache.isSoundDecoded(this.key)) {
+ this.pendingPlayback = false;
+ this.play(this._tempMarker, this._tempPosition, this._tempVolume, this._tempLoop);
+ }
+ if(this.isPlaying) {
+ this.currentTime = this.game.time.now - this.startTime;
+ if(this.currentTime >= this.duration) {
+ if(this.usingWebAudio) {
+ if(this.loop) {
+ this.onLoop.dispatch(this);
+ this.currentTime = 0;
+ this.startTime = this.game.time.now;
+ } else {
+ this.stop();
+ }
+ } else {
+ if(this.loop) {
+ this.onLoop.dispatch(this);
+ this.play(this.currentMarker, 0, this.volume, true, true);
+ } else {
+ this.stop();
+ }
+ }
+ }
+ }
+ };
Sound.prototype.play = /**
- * Play this sound.
+ * Play this sound, or a marked section of it.
+ * @param marker {string} Assets key of the sound you want to play.
+ * @param [volume] {number} volume of the sound you want to play.
+ * @param [loop] {boolean} loop when it finished playing? (Default to false)
+ * @return {Sound} The playing sound object.
*/
- function () {
- if(this._buffer === null || this.isDecoding === true) {
+ function (marker, position, volume, loop, forceRestart) {
+ if (typeof marker === "undefined") { marker = ''; }
+ if (typeof position === "undefined") { position = 0; }
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ if (typeof forceRestart === "undefined") { forceRestart = false; }
+ if(this.isPlaying == true && forceRestart == false) {
+ // Use Restart instead
return;
}
- this._sound = this._context.createBufferSource();
- this._sound.buffer = this._buffer;
- this._sound.connect(this._localGainNode);
- if(this.loop) {
- this._sound.loop = true;
+ this.currentMarker = marker;
+ if(marker !== '' && this.markers[marker]) {
+ this.loop = this.markers[marker].loop;
+ this.volume = this.markers[marker].volume;
+ this.position = this.markers[marker].start;
+ this.duration = this.markers[marker].duration * 1000;
+ } else {
+ this.loop = loop;
+ this.volume = volume;
+ this.position = position;
+ this.duration = 0;
+ }
+ if(this.usingWebAudio) {
+ // Does the sound need decoding?
+ if(this.game.cache.isSoundDecoded(this.key)) {
+ // Do we need to do this every time we play? How about just if the buffer is empty?
+ this._buffer = this.game.cache.getSoundData(this.key);
+ this._sound = this.context.createBufferSource();
+ this._sound.buffer = this._buffer;
+ this._sound.connect(this.gainNode);
+ this.totalDuration = this._sound.buffer.duration;
+ if(this.duration == 0) {
+ this.duration = this.totalDuration * 1000;
+ }
+ if(this.loop) {
+ this._sound.loop = true;
+ }
+ // Useful to cache this somewhere perhaps?
+ if(typeof this._sound.start === 'undefined') {
+ this._sound.noteGrainOn(0, this.position, this.duration / 1000);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ } else {
+ this._sound.start(0, this.position, this.duration / 1000);
+ }
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ } else {
+ this._tempVolume = volume;
+ this._tempLoop = loop;
+ this._tempPosition = position;
+ this._tempMarker = marker;
+ this.pendingPlayback = true;
+ if(this.game.cache.getSound(this.key).isDecoding == false) {
+ this.game.sound.decode(this.key, this);
+ }
+ }
+ } else {
+ if(this._sound.readyState == 4) {
+ this._sound.currentTime = this.position;
+ this._sound.muted = this._muted;
+ this._sound.volume = this._volume;
+ this._sound.play();
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ }
+ }
+ };
+ Sound.prototype.restart = function (marker, position, volume, loop) {
+ if (typeof marker === "undefined") { marker = ''; }
+ if (typeof position === "undefined") { position = 0; }
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ this.play(marker, position, volume, loop, true);
+ };
+ Sound.prototype.pause = function () {
+ if(this.isPlaying && this._sound) {
+ this.stop();
+ this.isPlaying = false;
+ this.paused = true;
+ this.onPause.dispatch(this);
+ }
+ };
+ Sound.prototype.resume = function () {
+ if(this.isPlaying == false && this._sound) {
+ if(this.usingWebAudio) {
+ if(typeof this._sound.start === 'undefined') {
+ this._sound.noteGrainOn(0, this.position, this.duration);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ } else {
+ this._sound.start(0, this.position, this.duration);
+ }
+ } else {
+ this._sound.play();
+ }
+ this.isPlaying = true;
+ this.paused = false;
+ this.onResume.dispatch(this);
}
- this._sound.noteOn(0)// the zero is vitally important, crashes iOS6 without it
- ;
- this.duration = this._sound.buffer.duration;
- this.isPlaying = true;
};
Sound.prototype.stop = /**
* Stop playing this sound.
*/
function () {
- if(this.isPlaying === true) {
+ if(this.isPlaying && this._sound) {
+ if(this.usingWebAudio) {
+ if(typeof this._sound.stop === 'undefined') {
+ this._sound.noteOff(0);
+ } else {
+ this._sound.stop(0);
+ }
+ } else if(this.usingAudioTag) {
+ this._sound.pause();
+ this._sound.currentTime = 0;
+ }
this.isPlaying = false;
- this._sound.noteOff(0);
+ this.currentMarker = '';
+ this.onStop.dispatch(this);
}
};
- Sound.prototype.mute = /**
- * Mute the sound.
- */
- function () {
- this._localGainNode.gain.value = 0;
- };
- Sound.prototype.unmute = /**
- * Enable the sound.
- */
- function () {
- this._localGainNode.gain.value = this._volume;
- };
+ Object.defineProperty(Sound.prototype, "mute", {
+ get: /**
+ * Mute sounds.
+ */
+ function () {
+ return this._muted;
+ },
+ set: function (value) {
+ if(value) {
+ this._muted = true;
+ if(this.usingWebAudio) {
+ this._muteVolume = this.gainNode.gain.value;
+ this.gainNode.gain.value = 0;
+ } else if(this.usingAudioTag) {
+ this._muteVolume = this._sound.volume;
+ this._sound.volume = 0;
+ }
+ } else {
+ this._muted = false;
+ if(this.usingWebAudio) {
+ this.gainNode.gain.value = this._muteVolume;
+ } else if(this.usingAudioTag) {
+ this._sound.volume = this._muteVolume;
+ }
+ }
+ this.onMute.dispatch(this);
+ },
+ enumerable: true,
+ configurable: true
+ });
Object.defineProperty(Sound.prototype, "volume", {
get: function () {
return this._volume;
},
set: function (value) {
this._volume = value;
- this._localGainNode.gain.value = this._volume;
+ if(this.usingWebAudio) {
+ this.gainNode.gain.value = value;
+ } else if(this.usingAudioTag) {
+ this._sound.volume = value;
+ }
},
enumerable: true,
configurable: true
});
+ Sound.prototype.renderDebug = /**
+ * Renders the Pointer.circle object onto the stage in green if down or red if up.
+ * @method renderDebug
+ */
+ function (x, y) {
+ this.game.stage.context.fillStyle = 'rgb(255,255,255)';
+ this.game.stage.context.font = '16px Courier';
+ this.game.stage.context.fillText('Sound: ' + this.key + ' Locked: ' + this.game.sound.touchLocked + ' Pending Playback: ' + this.pendingPlayback, x, y);
+ this.game.stage.context.fillText('Decoded: ' + this.isDecoded + ' Decoding: ' + this.isDecoding, x, y + 20);
+ this.game.stage.context.fillText('Total Duration: ' + this.totalDuration + ' Playing: ' + this.isPlaying, x, y + 40);
+ this.game.stage.context.fillText('Time: ' + this.currentTime, x, y + 60);
+ this.game.stage.context.fillText('Volume: ' + this.volume + ' Muted: ' + this.mute, x, y + 80);
+ this.game.stage.context.fillText('WebAudio: ' + this.usingWebAudio + ' Audio: ' + this.usingAudioTag, x, y + 100);
+ if(this.currentMarker !== '') {
+ this.game.stage.context.fillText('Marker: ' + this.currentMarker + ' Duration: ' + this.duration, x, y + 120);
+ this.game.stage.context.fillText('Start: ' + this.markers[this.currentMarker].start + ' Stop: ' + this.markers[this.currentMarker].stop, x, y + 140);
+ this.game.stage.context.fillText('Position: ' + this.position, x, y + 160);
+ }
+ };
return Sound;
})();
Phaser.Sound = Sound;
@@ -14086,46 +14390,147 @@ var Phaser;
* Create a new SoundManager.
*/
function SoundManager(game) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = false;
+ this.noAudio = false;
/**
* Reference to AudioContext instance.
*/
- this._context = null;
- this._game = game;
- if(window['PhaserGlobal'] && window['PhaserGlobal'].disableAudio == true) {
- return;
+ this.context = null;
+ this._muted = false;
+ this.touchLocked = false;
+ this._unlockSource = null;
+ this.onSoundDecode = new Phaser.Signal();
+ this.game = game;
+ this._volume = 1;
+ this._muted = false;
+ this._sounds = [];
+ if(this.game.device.iOS && this.game.device.webAudio == false) {
+ this.channels = 1;
}
- if(game.device.webaudio == true) {
- if(!!window['AudioContext']) {
- this._context = new window['AudioContext']();
- } else if(!!window['webkitAudioContext']) {
- this._context = new window['webkitAudioContext']();
+ if(window['PhaserGlobal']) {
+ // Check to see if all audio playback is disabled (i.e. handled by a 3rd party class)
+ if(window['PhaserGlobal'].disableAudio == true) {
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ return;
}
- if(this._context !== null) {
- this._gainNode = this._context.createGainNode();
- this._gainNode.connect(this._context.destination);
- this._volume = 1;
+ // Check if the Web Audio API is disabled (for testing Audio Tag playback during development)
+ if(window['PhaserGlobal'].disableWebAudio == true) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ this.noAudio = false;
+ return;
}
}
+ if(this.game.device.iOS && this.game.device.webAudio) {
+ this.game.input.touch.callbackContext = this;
+ this.game.input.touch.touchStartCallback = this.unlock;
+ this.touchLocked = true;
+ } else {
+ // What about iOS5?
+ this.touchLocked = false;
+ }
+ this.usingWebAudio = true;
+ this.noAudio = false;
+ if(!!window['AudioContext']) {
+ this.context = new window['AudioContext']();
+ } else if(!!window['webkitAudioContext']) {
+ this.context = new window['webkitAudioContext']();
+ } else if(!!window['Audio']) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ } else {
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ }
+ if(this.context !== null) {
+ if(typeof this.context.createGain === 'undefined') {
+ this.masterGain = this.context.createGainNode();
+ } else {
+ this.masterGain = this.context.createGain();
+ }
+ this.masterGain.gain.value = 1;
+ this.masterGain.connect(this.context.destination);
+ }
}
- SoundManager.prototype.mute = /**
- * Mute sounds.
- */
- function () {
- this._gainNode.gain.value = 0;
+ SoundManager.prototype.unlock = function () {
+ if(this.touchLocked == false || this.game.device.iOS == false) {
+ return;
+ }
+ // Create Audio tag?
+ // Create empty buffer and play it
+ var buffer = this.context.createBuffer(1, 1, 22050);
+ this._unlockSource = this.context.createBufferSource();
+ this._unlockSource.buffer = buffer;
+ this._unlockSource.connect(this.context.destination);
+ this._unlockSource.noteOn(0);
};
- SoundManager.prototype.unmute = /**
- * Enable sounds.
- */
- function () {
- this._gainNode.gain.value = this._volume;
- };
- Object.defineProperty(SoundManager.prototype, "volume", {
- get: function () {
- return this._volume;
+ Object.defineProperty(SoundManager.prototype, "mute", {
+ get: /**
+ * A global audio mute toggle.
+ */
+ function () {
+ return this._muted;
},
set: function (value) {
+ if(value) {
+ if(this._muted) {
+ return;
+ }
+ this._muted = true;
+ if(this.usingWebAudio) {
+ this._muteVolume = this.masterGain.gain.value;
+ this.masterGain.gain.value = 0;
+ }
+ // Loop through sounds
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].mute = true;
+ }
+ }
+ } else {
+ if(this._muted == false) {
+ return;
+ }
+ this._muted = false;
+ if(this.usingWebAudio) {
+ this.masterGain.gain.value = this._muteVolume;
+ }
+ // Loop through sounds
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].mute = false;
+ }
+ }
+ }
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Object.defineProperty(SoundManager.prototype, "volume", {
+ get: function () {
+ if(this.usingWebAudio) {
+ return this.masterGain.gain.value;
+ } else {
+ return this._volume;
+ }
+ },
+ set: /**
+ * The global audio volume. A value between 0 (silence) and 1 (full volume)
+ */
+ function (value) {
+ value = this.game.math.clamp(value, 1, 0);
this._volume = value;
- this._gainNode.gain.value = this._volume;
+ if(this.usingWebAudio) {
+ this.masterGain.gain.value = value;
+ }
+ // Loop through the sound cache and change the volume of all html audio tags
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].volume = this._sounds[i].volume * value;
+ }
+ }
},
enumerable: true,
configurable: true
@@ -14133,53 +14538,43 @@ var Phaser;
SoundManager.prototype.decode = /**
* Decode a sound with its assets key.
* @param key {string} Assets key of the sound to be decoded.
- * @param callback {function} This will be invoked when finished decoding.
* @param [sound] {Sound} its bufer will be set to decoded data.
*/
- function (key, callback, sound) {
- if (typeof callback === "undefined") { callback = null; }
+ function (key, sound) {
if (typeof sound === "undefined") { sound = null; }
- var soundData = this._game.cache.getSound(key);
+ var soundData = this.game.cache.getSoundData(key);
if(soundData) {
- if(this._game.cache.isSoundDecoded(key) === false) {
+ if(this.game.cache.isSoundDecoded(key) === false) {
+ this.game.cache.updateSound(key, 'isDecoding', true);
var that = this;
- this._context.decodeAudioData(soundData, function (buffer) {
- that._game.cache.decodedSound(key, buffer);
+ this.context.decodeAudioData(soundData, function (buffer) {
+ that.game.cache.decodedSound(key, buffer);
if(sound) {
- sound.setDecodedBuffer(buffer);
+ that.onSoundDecode.dispatch(sound);
}
- callback();
});
}
}
};
- SoundManager.prototype.play = /**
- * Play a sound with its assets key.
- * @param key {string} Assets key of the sound you want to play.
- * @param [volume] {number} volume of the sound you want to play.
- * @param [loop] {boolean} loop when it finished playing? (Default to false)
- * @return {Sound} The playing sound object.
- */
- function (key, volume, loop) {
- if (typeof volume === "undefined") { volume = 1; }
- if (typeof loop === "undefined") { loop = false; }
- if(this._context === null) {
- return;
- }
- var soundData = this._game.cache.getSound(key);
- if(soundData) {
- // Does the sound need decoding?
- if(this._game.cache.isSoundDecoded(key) === true) {
- return new Phaser.Sound(this._context, this._gainNode, soundData, volume, loop);
- } else {
- var tempSound = new Phaser.Sound(this._context, this._gainNode, null, volume, loop);
- // this is an async process, so we can return the Sound object anyway, it just won't be playing yet
- this.decode(key, function () {
- return tempSound.play();
- }, tempSound);
- return tempSound;
+ SoundManager.prototype.update = function () {
+ if(this.touchLocked && this._unlockSource !== null) {
+ if((this._unlockSource.playbackState === this._unlockSource.PLAYING_STATE || this._unlockSource.playbackState === this._unlockSource.FINISHED_STATE)) {
+ this.touchLocked = false;
+ this._unlockSource = null;
+ this.game.input.touch.callbackContext = null;
+ this.game.input.touch.touchStartCallback = null;
}
}
+ for(var i = 0; i < this._sounds.length; i++) {
+ this._sounds[i].update();
+ }
+ };
+ SoundManager.prototype.add = function (key, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ var sound = new Phaser.Sound(this.game, key, volume, loop);
+ this._sounds.push(sound);
+ return sound;
};
return SoundManager;
})();
@@ -14868,6 +15263,12 @@ var Phaser;
document.addEventListener('webkitvisibilitychange', function (event) {
return _this.visibilityChange(event);
}, false);
+ document.addEventListener('pagehide', function (event) {
+ return _this.visibilityChange(event);
+ }, false);
+ document.addEventListener('pageshow', function (event) {
+ return _this.visibilityChange(event);
+ }, false);
window.onblur = function (event) {
return _this.visibilityChange(event);
};
@@ -14915,7 +15316,7 @@ var Phaser;
if(this.disablePauseScreen) {
return;
}
- if(event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) {
+ if(event.type == 'pagehide' || event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) {
if(this._game.paused == false) {
this.pauseGame();
}
@@ -15251,13 +15652,20 @@ var Phaser;
this._tweens.length = 0;
};
TweenManager.prototype.create = /**
- * Create a tween object for a specific object.
+ * Create a tween object for a specific object. The object can be any JavaScript object or Phaser object such as Sprite.
*
- * @param object {object} Object you wish the tween will affect.
+ * @param obj {object} Object the tween will be run on.
+ * @param [localReference] {bool} If true the tween will be stored in the object.tween property so long as it exists. If already set it'll be over-written.
* @return {Phaser.Tween} The newly created tween object.
*/
- function (object) {
- return new Phaser.Tween(object, this._game);
+ function (object, localReference) {
+ if (typeof localReference === "undefined") { localReference = false; }
+ if(localReference) {
+ object['tween'] = new Phaser.Tween(object, this._game);
+ return object['tween'];
+ } else {
+ return new Phaser.Tween(object, this._game);
+ }
};
TweenManager.prototype.add = /**
* Add a new tween into the TweenManager.
@@ -15899,35 +16307,45 @@ var Phaser;
this.webApp = false;
// Audio
/**
- * Is audioData available?
+ * Are Audio tags available?
* @type {boolean}
*/
this.audioData = false;
/**
- * Is webaudio available?
+ * Is the WebAudio API available?
* @type {boolean}
*/
- this.webaudio = false;
+ this.webAudio = false;
/**
- * Is ogg available?
+ * Can this device play ogg files?
* @type {boolean}
*/
this.ogg = false;
/**
- * Is mp3 available?
+ * Can this device play opus files?
+ * @type {boolean}
+ */
+ this.opus = false;
+ /**
+ * Can this device play mp3 files?
* @type {boolean}
*/
this.mp3 = false;
/**
- * Is wav available?
+ * Can this device play wav files?
* @type {boolean}
*/
this.wav = false;
/**
- * Is m4a available?
+ * Can this device play m4a files?
* @type {boolean}
*/
this.m4a = false;
+ /**
+ * Can this device play webm files?
+ * @type {boolean}
+ */
+ this.webm = false;
// Device
/**
* Is running on iPhone?
@@ -16032,13 +16450,27 @@ var Phaser;
this.webApp = true;
}
};
+ Device.prototype.canPlayAudio = function (type) {
+ if(type == 'mp3' && this.mp3) {
+ return true;
+ } else if(type == 'ogg' && (this.ogg || this.opus)) {
+ return true;
+ } else if(type == 'm4a' && this.m4a) {
+ return true;
+ } else if(type == 'wav' && this.wav) {
+ return true;
+ } else if(type == 'webm' && this.webm) {
+ return true;
+ }
+ return false;
+ };
Device.prototype._checkAudio = /**
* Check audio support.
* @private
*/
function () {
this.audioData = !!(window['Audio']);
- this.webaudio = !!(window['webkitAudioContext'] || window['AudioContext']);
+ this.webAudio = !!(window['webkitAudioContext'] || window['AudioContext']);
var audioElement = document.createElement('audio');
var result = false;
try {
@@ -16046,6 +16478,9 @@ var Phaser;
if(audioElement.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '')) {
this.ogg = true;
}
+ if(audioElement.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, '')) {
+ this.opus = true;
+ }
if(audioElement.canPlayType('audio/mpeg;').replace(/^no$/, '')) {
this.mp3 = true;
}
@@ -16058,6 +16493,9 @@ var Phaser;
if(audioElement.canPlayType('audio/x-m4a;') || audioElement.canPlayType('audio/aac;').replace(/^no$/, '')) {
this.m4a = true;
}
+ if(audioElement.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')) {
+ this.webm = true;
+ }
}
} catch (e) {
}
@@ -16644,6 +17082,12 @@ var Phaser;
**/
this.totalTouches = 0;
/**
+ * The number of miliseconds since the last click
+ * @property msSinceLastClick
+ * @type {Number}
+ **/
+ this.msSinceLastClick = Number.MAX_VALUE;
+ /**
* The Game Object this Pointer is currently over / touching / dragging.
* @property targetObject
* @type {Any}
@@ -16711,6 +17155,8 @@ var Phaser;
this.withinGame = true;
this.isDown = true;
this.isUp = false;
+ // Work out how long it has been since the last click
+ this.msSinceLastClick = this.game.time.now - this.timeDown;
this.timeDown = this.game.time.now;
this._holdSent = false;
// This sets the x/y and other local values
@@ -17768,7 +18214,7 @@ var Phaser;
* @property recordPointerHistory
* @type {Boolean}
**/
- this.recordPointerHistory = true;
+ this.recordPointerHistory = false;
/**
* The rate in milliseconds at which the Pointer objects should update their tracking history
* @property recordRate
@@ -17777,7 +18223,7 @@ var Phaser;
this.recordRate = 100;
/**
* The total number of entries that can be recorded into the Pointer objects tracking history.
- * The the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
+ * If the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
* @property recordLimit
* @type {Number}
*/
@@ -19020,6 +19466,7 @@ var Phaser;
///
///
///
+///
///
///
///
@@ -19190,12 +19637,12 @@ var Phaser;
this.stage = new Phaser.Stage(this, parent, width, height);
this.world = new Phaser.World(this, width, height);
this.add = new Phaser.GameObjectFactory(this);
- this.sound = new Phaser.SoundManager(this);
this.cache = new Phaser.Cache(this);
this.load = new Phaser.Loader(this, this.loadComplete);
this.time = new Phaser.Time(this);
this.tweens = new Phaser.TweenManager(this);
this.input = new Phaser.Input(this);
+ this.sound = new Phaser.SoundManager(this);
this.rnd = new Phaser.RandomDataGenerator([
(Date.now() * Math.random()).toString()
]);
@@ -19257,6 +19704,7 @@ var Phaser;
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
if(this.onPausedCallback !== null) {
this.onPausedCallback.call(this.callbackContext);
}
@@ -19268,6 +19716,7 @@ var Phaser;
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
this.physics.update();
this.world.update();
if(this._loadComplete && this.onUpdateCallback) {
diff --git a/build/phaser.d.ts b/build/phaser.d.ts
index b25b4535..959a53e9 100644
--- a/build/phaser.d.ts
+++ b/build/phaser.d.ts
@@ -4299,9 +4299,10 @@ module Phaser {
/**
* Add a new audio file loading request.
* @param key {string} Unique asset key of the audio file.
- * @param url {string} URL of audio file.
+ * @param urls {Array} An array containing the URLs of the audio files, i.e.: [ 'jump.mp3', 'jump.ogg', 'jump.m4a' ]
+ * @param autoDecode {boolean} When using Web Audio the audio files can either be decoded at load time or run-time. They can't be played until they are decoded, but this let's you control when that happens. Decoding is a non-blocking async process.
*/
- public audio(key: string, url: string): void;
+ public audio(key: string, urls: string[], autoDecode?: bool): void;
/**
* Add a new text file loading request.
* @param key {string} Unique asset key of the text file.
@@ -4327,6 +4328,7 @@ module Phaser {
* Load files. Private method ONLY used by loader.
*/
private loadFile();
+ private getAudioURL(urls);
/**
* Error occured when load a file.
* @param key {string} Key of the error loading file.
@@ -4436,11 +4438,11 @@ module Phaser {
* @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
- public addSound(key: string, url: string, data): void;
+ public addSound(key: string, url: string, data, webAudio?: bool, audioTag?: bool): void;
+ public updateSound(key: string, property: string, value): void;
/**
* Add a new decoded sound.
* @param key {string} Asset key for the sound.
- * @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
public decodedSound(key: string, data): void;
@@ -4470,11 +4472,17 @@ module Phaser {
*/
public getFrameData(key: string): FrameData;
/**
+ * Get sound by key.
+ * @param key Asset key of the sound you want.
+ * @return {object} The sound you want.
+ */
+ public getSound(key: string);
+ /**
* Get sound data by key.
* @param key Asset key of the sound you want.
* @return {object} The sound data you want.
*/
- public getSound(key: string);
+ public getSoundData(key: string);
/**
* Check whether an asset is decoded sound.
* @param key Asset key of the sound you want.
@@ -6806,6 +6814,7 @@ module Phaser {
* @returns {Sprite} The newly created sprite object.
*/
public sprite(x: number, y: number, key?: string, frame?, bodyType?: number): Sprite;
+ public audio(key: string, volume?: number, loop?: bool): Sound;
/**
* Create a new Sprite with the physics automatically created and set to DYNAMIC. The Sprite position offset is set to its center.
*
@@ -6938,27 +6947,28 @@ module Phaser {
class Sound {
/**
* Sound constructor
- * @param context {object} The AudioContext instance.
- * @param gainNode {object} Gain node instance.
- * @param data {object} Sound data.
* @param [volume] {number} volume of this sound when playing.
* @param [loop] {boolean} loop this sound when playing? (Default to false)
*/
- constructor(context, gainNode, data, volume?: number, loop?: bool);
+ constructor(game: Game, key: string, volume?: number, loop?: bool);
/**
- * Local private reference to AudioContext.
+ * Local reference to the current Phaser.Game.
*/
- private _context;
+ public game: Game;
+ /**
+ * Reference to AudioContext instance.
+ */
+ public context;
/**
* Reference to gain node of SoundManager.
*/
- private _gainNode;
+ public masterGainNode;
/**
* GainNode of this sound.
*/
- private _localGainNode;
+ public gainNode;
/**
- * Decoded data buffer.
+ * Decoded data buffer / Audio tag.
*/
private _buffer;
/**
@@ -6969,28 +6979,66 @@ module Phaser {
* The real sound object (buffer source).
*/
private _sound;
- public loop: bool;
+ private _muteVolume;
+ private _muted;
+ private _tempPosition;
+ private _tempVolume;
+ private _tempLoop;
+ private _tempMarker;
+ public usingWebAudio: bool;
+ public usingAudioTag: bool;
+ public name: string;
+ public autoplay: bool;
+ public totalDuration: number;
+ public startTime: number;
+ public currentTime: number;
public duration: number;
+ public stopTime: number;
+ public position: number;
+ public paused: bool;
+ public loop: bool;
public isPlaying: bool;
- public isDecoding: bool;
- public setDecodedBuffer(data): void;
+ public key: string;
+ public markers;
+ public currentMarker: string;
+ public onDecoded: Signal;
+ public onPlay: Signal;
+ public onPause: Signal;
+ public onResume: Signal;
+ public onLoop: Signal;
+ public onStop: Signal;
+ public onMute: Signal;
+ public pendingPlayback: bool;
+ public isDecoding : bool;
+ public isDecoded : bool;
+ public addMarker(name: string, start: number, stop: number, volume?: number, loop?: bool): void;
+ public removeMarker(name: string): void;
+ public update(): void;
/**
- * Play this sound.
+ * Play this sound, or a marked section of it.
+ * @param marker {string} Assets key of the sound you want to play.
+ * @param [volume] {number} volume of the sound you want to play.
+ * @param [loop] {boolean} loop when it finished playing? (Default to false)
+ * @return {Sound} The playing sound object.
*/
- public play(): void;
+ public play(marker?: string, position?: number, volume?: number, loop?: bool, forceRestart?: bool): void;
+ public restart(marker?: string, position?: number, volume?: number, loop?: bool): void;
+ public pause(): void;
+ public resume(): void;
/**
* Stop playing this sound.
*/
public stop(): void;
/**
- * Mute the sound.
+ * Mute sounds.
*/
- public mute(): void;
- /**
- * Enable the sound.
- */
- public unmute(): void;
+ public mute : bool;
public volume : number;
+ /**
+ * Renders the Pointer.circle object onto the stage in green if down or red if up.
+ * @method renderDebug
+ */
+ public renderDebug(x: number, y: number): void;
}
}
/**
@@ -7005,47 +7053,50 @@ module Phaser {
* Create a new SoundManager.
*/
constructor(game: Game);
+ public usingWebAudio: bool;
+ public usingAudioTag: bool;
+ public noAudio: bool;
/**
- * Local private reference to game.
+ * Local reference to the current Phaser.Game.
*/
- private _game;
+ public game: Game;
/**
* Reference to AudioContext instance.
*/
- private _context;
+ public context;
/**
- * Gain node created from audio context.
+ * The Master Gain node through which all sounds
*/
- private _gainNode;
+ public masterGain;
/**
* Volume of sounds.
* @type {number}
*/
private _volume;
+ private _sounds;
+ private _muteVolume;
+ private _muted;
+ public channels: number;
+ public touchLocked: bool;
+ private _unlockSource;
+ public unlock(): void;
/**
- * Mute sounds.
+ * A global audio mute toggle.
*/
- public mute(): void;
+ public mute : bool;
/**
- * Enable sounds.
+ * The global audio volume. A value between 0 (silence) and 1 (full volume)
*/
- public unmute(): void;
public volume : number;
/**
* Decode a sound with its assets key.
* @param key {string} Assets key of the sound to be decoded.
- * @param callback {function} This will be invoked when finished decoding.
* @param [sound] {Sound} its bufer will be set to decoded data.
*/
- public decode(key: string, callback?, sound?: Sound): void;
- /**
- * Play a sound with its assets key.
- * @param key {string} Assets key of the sound you want to play.
- * @param [volume] {number} volume of the sound you want to play.
- * @param [loop] {boolean} loop when it finished playing? (Default to false)
- * @return {Sound} The playing sound object.
- */
- public play(key: string, volume?: number, loop?: bool): Sound;
+ public decode(key: string, sound?: Sound): void;
+ public onSoundDecode: Signal;
+ public update(): void;
+ public add(key: string, volume?: number, loop?: bool): Sound;
}
}
/**
@@ -8106,36 +8157,46 @@ module Phaser {
public safari: bool;
public webApp: bool;
/**
- * Is audioData available?
+ * Are Audio tags available?
* @type {boolean}
*/
public audioData: bool;
/**
- * Is webaudio available?
+ * Is the WebAudio API available?
* @type {boolean}
*/
- public webaudio: bool;
+ public webAudio: bool;
/**
- * Is ogg available?
+ * Can this device play ogg files?
* @type {boolean}
*/
public ogg: bool;
/**
- * Is mp3 available?
+ * Can this device play opus files?
+ * @type {boolean}
+ */
+ public opus: bool;
+ /**
+ * Can this device play mp3 files?
* @type {boolean}
*/
public mp3: bool;
/**
- * Is wav available?
+ * Can this device play wav files?
* @type {boolean}
*/
public wav: bool;
/**
- * Is m4a available?
+ * Can this device play m4a files?
* @type {boolean}
*/
public m4a: bool;
/**
+ * Can this device play webm files?
+ * @type {boolean}
+ */
+ public webm: bool;
+ /**
* Is running on iPhone?
* @type {boolean}
*/
@@ -8170,6 +8231,7 @@ module Phaser {
* @private
*/
private _checkBrowser();
+ public canPlayAudio(type: string): bool;
/**
* Check audio support.
* @private
@@ -8405,6 +8467,9 @@ module Phaser {
* @return {Phaser.Pointer} This object.
*/
constructor(game: Game, id: number);
+ private _highestRenderOrderID;
+ private _highestRenderObject;
+ private _highestInputPriorityID;
/**
* Local private reference to game.
* @property game
@@ -8582,6 +8647,12 @@ module Phaser {
**/
public totalTouches: number;
/**
+ * The number of miliseconds since the last click
+ * @property msSinceLastClick
+ * @type {Number}
+ **/
+ public msSinceLastClick: number;
+ /**
* How long the Pointer has been depressed on the touchscreen. If not currently down it returns -1.
* @property duration
* @type {Number}
@@ -8610,9 +8681,6 @@ module Phaser {
*/
public start(event): Pointer;
public update(): void;
- private _highestRenderOrderID;
- private _highestRenderObject;
- private _highestInputPriorityID;
/**
* Called when the Pointer is moved on the touchscreen
* @method move
@@ -9265,7 +9333,7 @@ module Phaser {
public recordRate: number;
/**
* The total number of entries that can be recorded into the Pointer objects tracking history.
- * The the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
+ * If the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
* @property recordLimit
* @type {Number}
*/
diff --git a/build/phaser.js b/build/phaser.js
index 3952482c..c616e6f0 100644
--- a/build/phaser.js
+++ b/build/phaser.js
@@ -7243,7 +7243,7 @@ var Phaser;
this.cameraView = new Phaser.Rectangle(x, y, this.width, this.height);
this.transform.setCache();
this.outOfBounds = false;
- this.outOfBoundsAction = Phaser.Types.OUT_OF_BOUNDS_KILL;
+ this.outOfBoundsAction = Phaser.Types.OUT_OF_BOUNDS_PERSIST;
// Handy proxies
this.scale = this.transform.scale;
this.alpha = this.texture.alpha;
@@ -9134,19 +9134,22 @@ var Phaser;
Loader.prototype.audio = /**
* Add a new audio file loading request.
* @param key {string} Unique asset key of the audio file.
- * @param url {string} URL of audio file.
+ * @param urls {Array} An array containing the URLs of the audio files, i.e.: [ 'jump.mp3', 'jump.ogg', 'jump.m4a' ]
+ * @param autoDecode {boolean} When using Web Audio the audio files can either be decoded at load time or run-time. They can't be played until they are decoded, but this let's you control when that happens. Decoding is a non-blocking async process.
*/
- function (key, url) {
+ function (key, urls, autoDecode) {
+ if (typeof autoDecode === "undefined") { autoDecode = true; }
if(this.checkKeyExists(key) === false) {
this._queueSize++;
this._fileList[key] = {
type: 'audio',
key: key,
- url: url,
+ url: urls,
data: null,
buffer: null,
error: false,
- loaded: false
+ loaded: false,
+ autoDecode: autoDecode
};
this._keys.push(key);
}
@@ -9234,15 +9237,33 @@ var Phaser;
file.data.src = file.url;
break;
case 'audio':
- this._xhr.open("GET", file.url, true);
- this._xhr.responseType = "arraybuffer";
- this._xhr.onload = function () {
- return _this.fileComplete(file.key);
- };
- this._xhr.onerror = function () {
- return _this.fileError(file.key);
- };
- this._xhr.send();
+ file.url = this.getAudioURL(file.url);
+ if(file.url !== null) {
+ // WebAudio or Audio Tag?
+ if(this._game.sound.usingWebAudio) {
+ this._xhr.open("GET", file.url, true);
+ this._xhr.responseType = "arraybuffer";
+ this._xhr.onload = function () {
+ return _this.fileComplete(file.key);
+ };
+ this._xhr.onerror = function () {
+ return _this.fileError(file.key);
+ };
+ this._xhr.send();
+ } else if(this._game.sound.usingAudioTag) {
+ file.data = new Audio();
+ file.data.name = file.key;
+ file.data.onerror = function () {
+ return _this.fileError(file.key);
+ };
+ file.data.preload = 'auto';
+ file.data.src = file.url;
+ file.data.addEventListener('canplaythrough', function () {
+ return _this.fileComplete(file.key);
+ }, false);
+ file.data.load();
+ }
+ }
break;
case 'text':
this._xhr.open("GET", file.url, true);
@@ -9257,6 +9278,18 @@ var Phaser;
break;
}
};
+ Loader.prototype.getAudioURL = function (urls) {
+ var extension;
+ for(var i = 0; i < urls.length; i++) {
+ extension = urls[i].toLowerCase();
+ extension = extension.substr((Math.max(0, extension.lastIndexOf(".")) || Infinity) + 1);
+ if(this._game.device.canPlayAudio(extension)) {
+ console.log('getAudioURL', urls[i]);
+ return urls[i];
+ }
+ }
+ return null;
+ };
Loader.prototype.fileError = /**
* Error occured when load a file.
* @param key {string} Key of the error loading file.
@@ -9306,8 +9339,22 @@ var Phaser;
}
break;
case 'audio':
- file.data = this._xhr.response;
- this._game.cache.addSound(file.key, file.url, file.data);
+ if(this._game.sound.usingWebAudio) {
+ file.data = this._xhr.response;
+ this._game.cache.addSound(file.key, file.url, file.data, true, false);
+ if(file.autoDecode) {
+ this._game.cache.updateSound(key, 'isDecoding', true);
+ var that = this;
+ var key = file.key;
+ this._game.sound.context.decodeAudioData(file.data, function (buffer) {
+ if(buffer) {
+ that._game.cache.decodedSound(key, buffer);
+ }
+ });
+ }
+ } else {
+ this._game.cache.addSound(file.key, file.url, file.data, false, true);
+ }
break;
case 'text':
file.data = this._xhr.response;
@@ -9492,22 +9539,38 @@ var Phaser;
* @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
- function (key, url, data) {
+ function (key, url, data, webAudio, audioTag) {
+ if (typeof webAudio === "undefined") { webAudio = true; }
+ if (typeof audioTag === "undefined") { audioTag = false; }
+ console.log('Cache addSound', key, url, webAudio, audioTag);
+ var decoded = false;
+ if(audioTag) {
+ decoded = true;
+ }
this._sounds[key] = {
url: url,
data: data,
- decoded: false
+ isDecoding: false,
+ decoded: decoded,
+ webAudio: webAudio,
+ audioTag: audioTag
};
};
+ Cache.prototype.updateSound = function (key, property, value) {
+ if(this._sounds[key]) {
+ this._sounds[key][property] = value;
+ }
+ };
Cache.prototype.decodedSound = /**
* Add a new decoded sound.
* @param key {string} Asset key for the sound.
- * @param url {string} URL of this sound file.
* @param data {object} Extra sound data.
*/
function (key, data) {
+ console.log('decoded sound', key);
this._sounds[key].data = data;
this._sounds[key].decoded = true;
+ this._sounds[key].isDecoding = false;
};
Cache.prototype.addText = /**
* Add a new text data.
@@ -9555,6 +9618,17 @@ var Phaser;
return null;
};
Cache.prototype.getSound = /**
+ * Get sound by key.
+ * @param key Asset key of the sound you want.
+ * @return {object} The sound you want.
+ */
+ function (key) {
+ if(this._sounds[key]) {
+ return this._sounds[key];
+ }
+ return null;
+ };
+ Cache.prototype.getSoundData = /**
* Get sound data by key.
* @param key Asset key of the sound you want.
* @return {object} The sound data you want.
@@ -13794,6 +13868,11 @@ var Phaser;
if (typeof bodyType === "undefined") { bodyType = Phaser.Types.BODY_DISABLED; }
return this._world.group.add(new Phaser.Sprite(this._game, x, y, key, frame, bodyType));
};
+ GameObjectFactory.prototype.audio = function (key, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ return this._game.sound.add(key, volume, loop);
+ };
GameObjectFactory.prototype.physicsSprite = /**
* Create a new Sprite with the physics automatically created and set to DYNAMIC. The Sprite position offset is set to its center.
*
@@ -13985,90 +14064,313 @@ var Phaser;
var Sound = (function () {
/**
* Sound constructor
- * @param context {object} The AudioContext instance.
- * @param gainNode {object} Gain node instance.
- * @param data {object} Sound data.
* @param [volume] {number} volume of this sound when playing.
* @param [loop] {boolean} loop this sound when playing? (Default to false)
*/
- function Sound(context, gainNode, data, volume, loop) {
+ function Sound(game, key, volume, loop) {
if (typeof volume === "undefined") { volume = 1; }
if (typeof loop === "undefined") { loop = false; }
+ /**
+ * Reference to AudioContext instance.
+ */
+ this.context = null;
+ this._muted = false;
+ this.usingWebAudio = false;
+ this.usingAudioTag = false;
+ this.name = '';
+ this.autoplay = false;
+ this.totalDuration = 0;
+ this.startTime = 0;
+ this.currentTime = 0;
+ this.duration = 0;
+ this.stopTime = 0;
+ this.paused = false;
this.loop = false;
this.isPlaying = false;
- this.isDecoding = false;
- this._context = context;
- this._gainNode = gainNode;
- this._buffer = data;
+ this.currentMarker = '';
+ this.pendingPlayback = false;
+ this.game = game;
+ this.usingWebAudio = this.game.sound.usingWebAudio;
+ this.usingAudioTag = this.game.sound.usingAudioTag;
+ this.key = key;
+ if(this.usingWebAudio) {
+ this.context = this.game.sound.context;
+ this.masterGainNode = this.game.sound.masterGain;
+ if(typeof this.context.createGain === 'undefined') {
+ this.gainNode = this.context.createGainNode();
+ } else {
+ this.gainNode = this.context.createGain();
+ }
+ this.gainNode.gain.value = volume * this.game.sound.volume;
+ this.gainNode.connect(this.masterGainNode);
+ } else {
+ this._sound = this.game.cache.getSoundData(key);
+ this.totalDuration = this._sound.duration;
+ }
this._volume = volume;
this.loop = loop;
- // Local volume control
- if(this._context !== null) {
- this._localGainNode = this._context.createGainNode();
- this._localGainNode.connect(this._gainNode);
- this._localGainNode.gain.value = this._volume;
- }
- if(this._buffer === null) {
- this.isDecoding = true;
- } else {
- this.play();
- }
+ this.markers = {
+ };
+ this.onDecoded = new Phaser.Signal();
+ this.onPlay = new Phaser.Signal();
+ this.onPause = new Phaser.Signal();
+ this.onResume = new Phaser.Signal();
+ this.onLoop = new Phaser.Signal();
+ this.onStop = new Phaser.Signal();
+ this.onMute = new Phaser.Signal();
}
- Sound.prototype.setDecodedBuffer = function (data) {
- this._buffer = data;
- this.isDecoding = false;
- //this.play();
- };
+ Object.defineProperty(Sound.prototype, "isDecoding", {
+ get: function () {
+ return this.game.cache.getSound(this.key).isDecoding;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Object.defineProperty(Sound.prototype, "isDecoded", {
+ get: function () {
+ return this.game.cache.isSoundDecoded(this.key);
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Sound.prototype.addMarker = function (name, start, stop, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ this.markers[name] = {
+ name: name,
+ start: start,
+ stop: stop,
+ volume: volume,
+ duration: stop - start,
+ loop: loop
+ };
+ };
+ Sound.prototype.removeMarker = function (name) {
+ delete this.markers[name];
+ };
+ Sound.prototype.update = function () {
+ if(this.pendingPlayback && this.game.cache.isSoundDecoded(this.key)) {
+ this.pendingPlayback = false;
+ this.play(this._tempMarker, this._tempPosition, this._tempVolume, this._tempLoop);
+ }
+ if(this.isPlaying) {
+ this.currentTime = this.game.time.now - this.startTime;
+ if(this.currentTime >= this.duration) {
+ if(this.usingWebAudio) {
+ if(this.loop) {
+ this.onLoop.dispatch(this);
+ this.currentTime = 0;
+ this.startTime = this.game.time.now;
+ } else {
+ this.stop();
+ }
+ } else {
+ if(this.loop) {
+ this.onLoop.dispatch(this);
+ this.play(this.currentMarker, 0, this.volume, true, true);
+ } else {
+ this.stop();
+ }
+ }
+ }
+ }
+ };
Sound.prototype.play = /**
- * Play this sound.
+ * Play this sound, or a marked section of it.
+ * @param marker {string} Assets key of the sound you want to play.
+ * @param [volume] {number} volume of the sound you want to play.
+ * @param [loop] {boolean} loop when it finished playing? (Default to false)
+ * @return {Sound} The playing sound object.
*/
- function () {
- if(this._buffer === null || this.isDecoding === true) {
+ function (marker, position, volume, loop, forceRestart) {
+ if (typeof marker === "undefined") { marker = ''; }
+ if (typeof position === "undefined") { position = 0; }
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ if (typeof forceRestart === "undefined") { forceRestart = false; }
+ if(this.isPlaying == true && forceRestart == false) {
+ // Use Restart instead
return;
}
- this._sound = this._context.createBufferSource();
- this._sound.buffer = this._buffer;
- this._sound.connect(this._localGainNode);
- if(this.loop) {
- this._sound.loop = true;
+ this.currentMarker = marker;
+ if(marker !== '' && this.markers[marker]) {
+ this.loop = this.markers[marker].loop;
+ this.volume = this.markers[marker].volume;
+ this.position = this.markers[marker].start;
+ this.duration = this.markers[marker].duration * 1000;
+ } else {
+ this.loop = loop;
+ this.volume = volume;
+ this.position = position;
+ this.duration = 0;
+ }
+ if(this.usingWebAudio) {
+ // Does the sound need decoding?
+ if(this.game.cache.isSoundDecoded(this.key)) {
+ // Do we need to do this every time we play? How about just if the buffer is empty?
+ this._buffer = this.game.cache.getSoundData(this.key);
+ this._sound = this.context.createBufferSource();
+ this._sound.buffer = this._buffer;
+ this._sound.connect(this.gainNode);
+ this.totalDuration = this._sound.buffer.duration;
+ if(this.duration == 0) {
+ this.duration = this.totalDuration * 1000;
+ }
+ if(this.loop) {
+ this._sound.loop = true;
+ }
+ // Useful to cache this somewhere perhaps?
+ if(typeof this._sound.start === 'undefined') {
+ this._sound.noteGrainOn(0, this.position, this.duration / 1000);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ } else {
+ this._sound.start(0, this.position, this.duration / 1000);
+ }
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ } else {
+ this._tempVolume = volume;
+ this._tempLoop = loop;
+ this._tempPosition = position;
+ this._tempMarker = marker;
+ this.pendingPlayback = true;
+ if(this.game.cache.getSound(this.key).isDecoding == false) {
+ this.game.sound.decode(this.key, this);
+ }
+ }
+ } else {
+ if(this._sound.readyState == 4) {
+ this._sound.currentTime = this.position;
+ this._sound.muted = this._muted;
+ this._sound.volume = this._volume;
+ this._sound.play();
+ this.isPlaying = true;
+ this.startTime = this.game.time.now;
+ this.currentTime = 0;
+ this.stopTime = this.startTime + this.duration;
+ this.onPlay.dispatch(this);
+ }
+ }
+ };
+ Sound.prototype.restart = function (marker, position, volume, loop) {
+ if (typeof marker === "undefined") { marker = ''; }
+ if (typeof position === "undefined") { position = 0; }
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ this.play(marker, position, volume, loop, true);
+ };
+ Sound.prototype.pause = function () {
+ if(this.isPlaying && this._sound) {
+ this.stop();
+ this.isPlaying = false;
+ this.paused = true;
+ this.onPause.dispatch(this);
+ }
+ };
+ Sound.prototype.resume = function () {
+ if(this.isPlaying == false && this._sound) {
+ if(this.usingWebAudio) {
+ if(typeof this._sound.start === 'undefined') {
+ this._sound.noteGrainOn(0, this.position, this.duration);
+ //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
+ } else {
+ this._sound.start(0, this.position, this.duration);
+ }
+ } else {
+ this._sound.play();
+ }
+ this.isPlaying = true;
+ this.paused = false;
+ this.onResume.dispatch(this);
}
- this._sound.noteOn(0)// the zero is vitally important, crashes iOS6 without it
- ;
- this.duration = this._sound.buffer.duration;
- this.isPlaying = true;
};
Sound.prototype.stop = /**
* Stop playing this sound.
*/
function () {
- if(this.isPlaying === true) {
+ if(this.isPlaying && this._sound) {
+ if(this.usingWebAudio) {
+ if(typeof this._sound.stop === 'undefined') {
+ this._sound.noteOff(0);
+ } else {
+ this._sound.stop(0);
+ }
+ } else if(this.usingAudioTag) {
+ this._sound.pause();
+ this._sound.currentTime = 0;
+ }
this.isPlaying = false;
- this._sound.noteOff(0);
+ this.currentMarker = '';
+ this.onStop.dispatch(this);
}
};
- Sound.prototype.mute = /**
- * Mute the sound.
- */
- function () {
- this._localGainNode.gain.value = 0;
- };
- Sound.prototype.unmute = /**
- * Enable the sound.
- */
- function () {
- this._localGainNode.gain.value = this._volume;
- };
+ Object.defineProperty(Sound.prototype, "mute", {
+ get: /**
+ * Mute sounds.
+ */
+ function () {
+ return this._muted;
+ },
+ set: function (value) {
+ if(value) {
+ this._muted = true;
+ if(this.usingWebAudio) {
+ this._muteVolume = this.gainNode.gain.value;
+ this.gainNode.gain.value = 0;
+ } else if(this.usingAudioTag) {
+ this._muteVolume = this._sound.volume;
+ this._sound.volume = 0;
+ }
+ } else {
+ this._muted = false;
+ if(this.usingWebAudio) {
+ this.gainNode.gain.value = this._muteVolume;
+ } else if(this.usingAudioTag) {
+ this._sound.volume = this._muteVolume;
+ }
+ }
+ this.onMute.dispatch(this);
+ },
+ enumerable: true,
+ configurable: true
+ });
Object.defineProperty(Sound.prototype, "volume", {
get: function () {
return this._volume;
},
set: function (value) {
this._volume = value;
- this._localGainNode.gain.value = this._volume;
+ if(this.usingWebAudio) {
+ this.gainNode.gain.value = value;
+ } else if(this.usingAudioTag) {
+ this._sound.volume = value;
+ }
},
enumerable: true,
configurable: true
});
+ Sound.prototype.renderDebug = /**
+ * Renders the Pointer.circle object onto the stage in green if down or red if up.
+ * @method renderDebug
+ */
+ function (x, y) {
+ this.game.stage.context.fillStyle = 'rgb(255,255,255)';
+ this.game.stage.context.font = '16px Courier';
+ this.game.stage.context.fillText('Sound: ' + this.key + ' Locked: ' + this.game.sound.touchLocked + ' Pending Playback: ' + this.pendingPlayback, x, y);
+ this.game.stage.context.fillText('Decoded: ' + this.isDecoded + ' Decoding: ' + this.isDecoding, x, y + 20);
+ this.game.stage.context.fillText('Total Duration: ' + this.totalDuration + ' Playing: ' + this.isPlaying, x, y + 40);
+ this.game.stage.context.fillText('Time: ' + this.currentTime, x, y + 60);
+ this.game.stage.context.fillText('Volume: ' + this.volume + ' Muted: ' + this.mute, x, y + 80);
+ this.game.stage.context.fillText('WebAudio: ' + this.usingWebAudio + ' Audio: ' + this.usingAudioTag, x, y + 100);
+ if(this.currentMarker !== '') {
+ this.game.stage.context.fillText('Marker: ' + this.currentMarker + ' Duration: ' + this.duration, x, y + 120);
+ this.game.stage.context.fillText('Start: ' + this.markers[this.currentMarker].start + ' Stop: ' + this.markers[this.currentMarker].stop, x, y + 140);
+ this.game.stage.context.fillText('Position: ' + this.position, x, y + 160);
+ }
+ };
return Sound;
})();
Phaser.Sound = Sound;
@@ -14088,46 +14390,147 @@ var Phaser;
* Create a new SoundManager.
*/
function SoundManager(game) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = false;
+ this.noAudio = false;
/**
* Reference to AudioContext instance.
*/
- this._context = null;
- this._game = game;
- if(window['PhaserGlobal'] && window['PhaserGlobal'].disableAudio == true) {
- return;
+ this.context = null;
+ this._muted = false;
+ this.touchLocked = false;
+ this._unlockSource = null;
+ this.onSoundDecode = new Phaser.Signal();
+ this.game = game;
+ this._volume = 1;
+ this._muted = false;
+ this._sounds = [];
+ if(this.game.device.iOS && this.game.device.webAudio == false) {
+ this.channels = 1;
}
- if(game.device.webaudio == true) {
- if(!!window['AudioContext']) {
- this._context = new window['AudioContext']();
- } else if(!!window['webkitAudioContext']) {
- this._context = new window['webkitAudioContext']();
+ if(window['PhaserGlobal']) {
+ // Check to see if all audio playback is disabled (i.e. handled by a 3rd party class)
+ if(window['PhaserGlobal'].disableAudio == true) {
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ return;
}
- if(this._context !== null) {
- this._gainNode = this._context.createGainNode();
- this._gainNode.connect(this._context.destination);
- this._volume = 1;
+ // Check if the Web Audio API is disabled (for testing Audio Tag playback during development)
+ if(window['PhaserGlobal'].disableWebAudio == true) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ this.noAudio = false;
+ return;
}
}
+ if(this.game.device.iOS && this.game.device.webAudio) {
+ this.game.input.touch.callbackContext = this;
+ this.game.input.touch.touchStartCallback = this.unlock;
+ this.touchLocked = true;
+ } else {
+ // What about iOS5?
+ this.touchLocked = false;
+ }
+ this.usingWebAudio = true;
+ this.noAudio = false;
+ if(!!window['AudioContext']) {
+ this.context = new window['AudioContext']();
+ } else if(!!window['webkitAudioContext']) {
+ this.context = new window['webkitAudioContext']();
+ } else if(!!window['Audio']) {
+ this.usingWebAudio = false;
+ this.usingAudioTag = true;
+ } else {
+ this.usingWebAudio = false;
+ this.noAudio = true;
+ }
+ if(this.context !== null) {
+ if(typeof this.context.createGain === 'undefined') {
+ this.masterGain = this.context.createGainNode();
+ } else {
+ this.masterGain = this.context.createGain();
+ }
+ this.masterGain.gain.value = 1;
+ this.masterGain.connect(this.context.destination);
+ }
}
- SoundManager.prototype.mute = /**
- * Mute sounds.
- */
- function () {
- this._gainNode.gain.value = 0;
+ SoundManager.prototype.unlock = function () {
+ if(this.touchLocked == false || this.game.device.iOS == false) {
+ return;
+ }
+ // Create Audio tag?
+ // Create empty buffer and play it
+ var buffer = this.context.createBuffer(1, 1, 22050);
+ this._unlockSource = this.context.createBufferSource();
+ this._unlockSource.buffer = buffer;
+ this._unlockSource.connect(this.context.destination);
+ this._unlockSource.noteOn(0);
};
- SoundManager.prototype.unmute = /**
- * Enable sounds.
- */
- function () {
- this._gainNode.gain.value = this._volume;
- };
- Object.defineProperty(SoundManager.prototype, "volume", {
- get: function () {
- return this._volume;
+ Object.defineProperty(SoundManager.prototype, "mute", {
+ get: /**
+ * A global audio mute toggle.
+ */
+ function () {
+ return this._muted;
},
set: function (value) {
+ if(value) {
+ if(this._muted) {
+ return;
+ }
+ this._muted = true;
+ if(this.usingWebAudio) {
+ this._muteVolume = this.masterGain.gain.value;
+ this.masterGain.gain.value = 0;
+ }
+ // Loop through sounds
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].mute = true;
+ }
+ }
+ } else {
+ if(this._muted == false) {
+ return;
+ }
+ this._muted = false;
+ if(this.usingWebAudio) {
+ this.masterGain.gain.value = this._muteVolume;
+ }
+ // Loop through sounds
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].mute = false;
+ }
+ }
+ }
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Object.defineProperty(SoundManager.prototype, "volume", {
+ get: function () {
+ if(this.usingWebAudio) {
+ return this.masterGain.gain.value;
+ } else {
+ return this._volume;
+ }
+ },
+ set: /**
+ * The global audio volume. A value between 0 (silence) and 1 (full volume)
+ */
+ function (value) {
+ value = this.game.math.clamp(value, 1, 0);
this._volume = value;
- this._gainNode.gain.value = this._volume;
+ if(this.usingWebAudio) {
+ this.masterGain.gain.value = value;
+ }
+ // Loop through the sound cache and change the volume of all html audio tags
+ for(var i = 0; i < this._sounds.length; i++) {
+ if(this._sounds[i].usingAudioTag) {
+ this._sounds[i].volume = this._sounds[i].volume * value;
+ }
+ }
},
enumerable: true,
configurable: true
@@ -14135,53 +14538,43 @@ var Phaser;
SoundManager.prototype.decode = /**
* Decode a sound with its assets key.
* @param key {string} Assets key of the sound to be decoded.
- * @param callback {function} This will be invoked when finished decoding.
* @param [sound] {Sound} its bufer will be set to decoded data.
*/
- function (key, callback, sound) {
- if (typeof callback === "undefined") { callback = null; }
+ function (key, sound) {
if (typeof sound === "undefined") { sound = null; }
- var soundData = this._game.cache.getSound(key);
+ var soundData = this.game.cache.getSoundData(key);
if(soundData) {
- if(this._game.cache.isSoundDecoded(key) === false) {
+ if(this.game.cache.isSoundDecoded(key) === false) {
+ this.game.cache.updateSound(key, 'isDecoding', true);
var that = this;
- this._context.decodeAudioData(soundData, function (buffer) {
- that._game.cache.decodedSound(key, buffer);
+ this.context.decodeAudioData(soundData, function (buffer) {
+ that.game.cache.decodedSound(key, buffer);
if(sound) {
- sound.setDecodedBuffer(buffer);
+ that.onSoundDecode.dispatch(sound);
}
- callback();
});
}
}
};
- SoundManager.prototype.play = /**
- * Play a sound with its assets key.
- * @param key {string} Assets key of the sound you want to play.
- * @param [volume] {number} volume of the sound you want to play.
- * @param [loop] {boolean} loop when it finished playing? (Default to false)
- * @return {Sound} The playing sound object.
- */
- function (key, volume, loop) {
- if (typeof volume === "undefined") { volume = 1; }
- if (typeof loop === "undefined") { loop = false; }
- if(this._context === null) {
- return;
- }
- var soundData = this._game.cache.getSound(key);
- if(soundData) {
- // Does the sound need decoding?
- if(this._game.cache.isSoundDecoded(key) === true) {
- return new Phaser.Sound(this._context, this._gainNode, soundData, volume, loop);
- } else {
- var tempSound = new Phaser.Sound(this._context, this._gainNode, null, volume, loop);
- // this is an async process, so we can return the Sound object anyway, it just won't be playing yet
- this.decode(key, function () {
- return tempSound.play();
- }, tempSound);
- return tempSound;
+ SoundManager.prototype.update = function () {
+ if(this.touchLocked && this._unlockSource !== null) {
+ if((this._unlockSource.playbackState === this._unlockSource.PLAYING_STATE || this._unlockSource.playbackState === this._unlockSource.FINISHED_STATE)) {
+ this.touchLocked = false;
+ this._unlockSource = null;
+ this.game.input.touch.callbackContext = null;
+ this.game.input.touch.touchStartCallback = null;
}
}
+ for(var i = 0; i < this._sounds.length; i++) {
+ this._sounds[i].update();
+ }
+ };
+ SoundManager.prototype.add = function (key, volume, loop) {
+ if (typeof volume === "undefined") { volume = 1; }
+ if (typeof loop === "undefined") { loop = false; }
+ var sound = new Phaser.Sound(this.game, key, volume, loop);
+ this._sounds.push(sound);
+ return sound;
};
return SoundManager;
})();
@@ -14870,6 +15263,12 @@ var Phaser;
document.addEventListener('webkitvisibilitychange', function (event) {
return _this.visibilityChange(event);
}, false);
+ document.addEventListener('pagehide', function (event) {
+ return _this.visibilityChange(event);
+ }, false);
+ document.addEventListener('pageshow', function (event) {
+ return _this.visibilityChange(event);
+ }, false);
window.onblur = function (event) {
return _this.visibilityChange(event);
};
@@ -14917,7 +15316,7 @@ var Phaser;
if(this.disablePauseScreen) {
return;
}
- if(event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) {
+ if(event.type == 'pagehide' || event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) {
if(this._game.paused == false) {
this.pauseGame();
}
@@ -15908,35 +16307,45 @@ var Phaser;
this.webApp = false;
// Audio
/**
- * Is audioData available?
+ * Are Audio tags available?
* @type {boolean}
*/
this.audioData = false;
/**
- * Is webaudio available?
+ * Is the WebAudio API available?
* @type {boolean}
*/
- this.webaudio = false;
+ this.webAudio = false;
/**
- * Is ogg available?
+ * Can this device play ogg files?
* @type {boolean}
*/
this.ogg = false;
/**
- * Is mp3 available?
+ * Can this device play opus files?
+ * @type {boolean}
+ */
+ this.opus = false;
+ /**
+ * Can this device play mp3 files?
* @type {boolean}
*/
this.mp3 = false;
/**
- * Is wav available?
+ * Can this device play wav files?
* @type {boolean}
*/
this.wav = false;
/**
- * Is m4a available?
+ * Can this device play m4a files?
* @type {boolean}
*/
this.m4a = false;
+ /**
+ * Can this device play webm files?
+ * @type {boolean}
+ */
+ this.webm = false;
// Device
/**
* Is running on iPhone?
@@ -16041,13 +16450,27 @@ var Phaser;
this.webApp = true;
}
};
+ Device.prototype.canPlayAudio = function (type) {
+ if(type == 'mp3' && this.mp3) {
+ return true;
+ } else if(type == 'ogg' && (this.ogg || this.opus)) {
+ return true;
+ } else if(type == 'm4a' && this.m4a) {
+ return true;
+ } else if(type == 'wav' && this.wav) {
+ return true;
+ } else if(type == 'webm' && this.webm) {
+ return true;
+ }
+ return false;
+ };
Device.prototype._checkAudio = /**
* Check audio support.
* @private
*/
function () {
this.audioData = !!(window['Audio']);
- this.webaudio = !!(window['webkitAudioContext'] || window['AudioContext']);
+ this.webAudio = !!(window['webkitAudioContext'] || window['AudioContext']);
var audioElement = document.createElement('audio');
var result = false;
try {
@@ -16055,6 +16478,9 @@ var Phaser;
if(audioElement.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '')) {
this.ogg = true;
}
+ if(audioElement.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, '')) {
+ this.opus = true;
+ }
if(audioElement.canPlayType('audio/mpeg;').replace(/^no$/, '')) {
this.mp3 = true;
}
@@ -16067,6 +16493,9 @@ var Phaser;
if(audioElement.canPlayType('audio/x-m4a;') || audioElement.canPlayType('audio/aac;').replace(/^no$/, '')) {
this.m4a = true;
}
+ if(audioElement.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')) {
+ this.webm = true;
+ }
}
} catch (e) {
}
@@ -16653,6 +17082,12 @@ var Phaser;
**/
this.totalTouches = 0;
/**
+ * The number of miliseconds since the last click
+ * @property msSinceLastClick
+ * @type {Number}
+ **/
+ this.msSinceLastClick = Number.MAX_VALUE;
+ /**
* The Game Object this Pointer is currently over / touching / dragging.
* @property targetObject
* @type {Any}
@@ -16720,6 +17155,8 @@ var Phaser;
this.withinGame = true;
this.isDown = true;
this.isUp = false;
+ // Work out how long it has been since the last click
+ this.msSinceLastClick = this.game.time.now - this.timeDown;
this.timeDown = this.game.time.now;
this._holdSent = false;
// This sets the x/y and other local values
@@ -17777,7 +18214,7 @@ var Phaser;
* @property recordPointerHistory
* @type {Boolean}
**/
- this.recordPointerHistory = true;
+ this.recordPointerHistory = false;
/**
* The rate in milliseconds at which the Pointer objects should update their tracking history
* @property recordRate
@@ -17786,7 +18223,7 @@ var Phaser;
this.recordRate = 100;
/**
* The total number of entries that can be recorded into the Pointer objects tracking history.
- * The the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
+ * If the Pointer is tracking one event every 100ms, then a trackLimit of 100 would store the last 10 seconds worth of history.
* @property recordLimit
* @type {Number}
*/
@@ -19029,6 +19466,7 @@ var Phaser;
///
///
///
+///
///
///
///
@@ -19199,12 +19637,12 @@ var Phaser;
this.stage = new Phaser.Stage(this, parent, width, height);
this.world = new Phaser.World(this, width, height);
this.add = new Phaser.GameObjectFactory(this);
- this.sound = new Phaser.SoundManager(this);
this.cache = new Phaser.Cache(this);
this.load = new Phaser.Loader(this, this.loadComplete);
this.time = new Phaser.Time(this);
this.tweens = new Phaser.TweenManager(this);
this.input = new Phaser.Input(this);
+ this.sound = new Phaser.SoundManager(this);
this.rnd = new Phaser.RandomDataGenerator([
(Date.now() * Math.random()).toString()
]);
@@ -19266,6 +19704,7 @@ var Phaser;
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
if(this.onPausedCallback !== null) {
this.onPausedCallback.call(this.callbackContext);
}
@@ -19277,6 +19716,7 @@ var Phaser;
this.tweens.update();
this.input.update();
this.stage.update();
+ this.sound.update();
this.physics.update();
this.world.update();
if(this._loadComplete && this.onUpdateCallback) {