diff --git a/README.md b/README.md index b0f02abc..5950f3e3 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Updates: * Loader.setPreloadSprite() will now set sprite.visible = true once the crop has been applied. Should help avoid issues (#430) on super-slow connections. * Updated the way the page visibility is checked, should now be more compatible across more browsers. * Phaser.Input.Key.isUp now defaults to 'true', as does GamepadButton.isUp (#474) +* Vastly improved visibility API support + pageshow/pagehide + focus/blur. Working across Chrome, IE, Firefox, iOS, Android (also fixes #161) Bug Fixes: diff --git a/examples/wip/index.php b/examples/wip/index.php index 3c5511da..37c4f2d1 100644 --- a/examples/wip/index.php +++ b/examples/wip/index.php @@ -77,7 +77,7 @@ phaser - + + + + + + + + diff --git a/src/core/Game.js b/src/core/Game.js index 0366f248..83c09b93 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -86,20 +86,6 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this.state = null; - /** - * @property {boolean} _paused - Is game paused? - * @private - * @default - */ - this._paused = false; - - /** - * @property {boolean} _loadComplete - Whether load complete loading or not. - * @private - * @default - */ - this._loadComplete = false; - /** * @property {boolean} isBooted - Whether the game engine is booted, aka available. * @default @@ -246,6 +232,27 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this.stepCount = 0; + /** + * @property {boolean} _paused - Is game paused? + * @private + * @default + */ + this._paused = false; + + /** + * @property {boolean} _codePaused - Was the game paused via code or a visibility change? + * @private + * @default + */ + this._codePaused = false; + + /** + * @property {boolean} _loadComplete - Whether load complete loading or not. + * @private + * @default + */ + this._loadComplete = false; + // Parse the configuration object (if any) if (arguments.length === 1 && typeof arguments[0] === 'object') { @@ -605,9 +612,16 @@ Phaser.Game.prototype = { if (this._paused) { - this.renderer.render(this.stage); - this.plugins.render(); - this.state.render(); + this.input.update(); + + if (this.renderType !== Phaser.HEADLESS) + { + this.renderer.render(this.stage); + this.plugins.render(); + this.state.render(); + + this.plugins.postRender(); + } } else { @@ -708,6 +722,43 @@ Phaser.Game.prototype = { this.world = null; this.isBooted = false; + }, + + /** + * Called by the Stage visibility handler. + * + * @method Phaser.Game#gamePaused + */ + gamePaused: function (time) { + + // If the game is already paused it was done via game code, so don't re-pause it + if (!this._paused) + { + this._paused = true; + this.time.gamePaused(time); + this.sound.mute = true; + this.onPause.dispatch(this); + } + + }, + + /** + * Called by the Stage visibility handler. + * + * @method Phaser.Game#gameResumed + */ + gameResumed: function (time) { + + // Game is paused, but wasn't paused via code, so resume it + if (this._paused && !this._codePaused) + { + this._paused = false; + this.time.gameResumed(time); + this.input.reset(); + this.sound.mute = false; + this.onResume.dispatch(this); + } + } }; @@ -733,6 +784,9 @@ Object.defineProperty(Phaser.Game.prototype, "paused", { if (this._paused === false) { this._paused = true; + this._codePaused = true; + this.sound.mute = true; + this.time.gamePaused(); this.onPause.dispatch(this); } } @@ -741,7 +795,10 @@ Object.defineProperty(Phaser.Game.prototype, "paused", { if (this._paused) { this._paused = false; + this._codePaused = false; this.input.reset(); + this.sound.mute = false; + this.time.gameResumed(); this.onResume.dispatch(this); } } diff --git a/src/core/Stage.js b/src/core/Stage.js index f9661c1f..db8c4de2 100644 --- a/src/core/Stage.js +++ b/src/core/Stage.js @@ -246,27 +246,37 @@ Phaser.Stage.prototype.boot = function () { */ Phaser.Stage.prototype.checkVisibility = function () { - var supportsVisibilityApi = false; - var prefixes = [ "", "moz", "ms", "webkit" ]; - - while (prefixes.length) + if (document.webkitHidden != undefined) { - prefix = prefixes.pop(); - this._hiddenVar = prefix ? prefix + "Hidden" : "hidden"; - - if (this._hiddenVar in document) - { - supportsVisibilityApi = true; - break; - } + this._hiddenVar = 'webkitvisibilitychange'; + } + else if (document.mozHidden != undefined) + { + this._hiddenVar = 'mozvisibilitychange'; + } + else if (document.msHidden != undefined) + { + this._hiddenVar = 'msvisibilitychange'; + } + else if (document.hidden != undefined) + { + this._hiddenVar = 'visibilitychange'; + } + else + { + this._hiddenVar = null; } // Does browser support it? If not (like in IE9 or old Android) we need to fall back to blur/focus - if (supportsVisibilityApi) + if (this._hiddenVar) { document.addEventListener(this._hiddenVar, this._onChange, false); - document.addEventListener('pagehide', this._onChange, false); - document.addEventListener('pageshow', this._onChange, false); + } + + if (window['onpagehide']) + { + window.onpagehide = this._onChange; + window.onpageshow = this._onChange; } window.onblur = this._onChange; @@ -304,13 +314,27 @@ Phaser.Stage.prototype.visibilityChange = function (event) { return; } - if (this.game.paused === false && (event.type === 'pagehide' || event.type === 'blur' || document[this._hiddenVar] === true)) + if (event.type === 'pagehide' || event.type === 'blur' || event.type === 'pageshow' || event.type === 'focus') { - this.game.paused = true; + if (event.type === 'pagehide' || event.type === 'blur') + { + this.game.gamePaused(event.timeStamp); + } + else if (event.type === 'pageshow' || event.type === 'focus') + { + this.game.gameResumed(event.timeStamp); + } + + return; + } + + if (document.hidden || document.mozHidden || document.msHidden || document.webkitHidden) + { + this.game.gamePaused(event.timeStamp); } else { - this.game.paused = false; + this.game.gameResumed(event.timeStamp); } } diff --git a/src/input/Input.js b/src/input/Input.js index 774f67c2..40967a4b 100644 --- a/src/input/Input.js +++ b/src/input/Input.js @@ -457,6 +457,7 @@ Phaser.Input.prototype = { if (this.pointer10) { this.pointer10.update(); } this._pollCounter = 0; + }, /** diff --git a/src/input/Keyboard.js b/src/input/Keyboard.js index 17e5eb80..a738aa5d 100644 --- a/src/input/Keyboard.js +++ b/src/input/Keyboard.js @@ -71,7 +71,7 @@ Phaser.Keyboard = function (game) { Phaser.Keyboard.prototype = { /** - * Add callbacks to the Keyboard handler so that each time a key is pressed down or releases the callbacks are activated. + * Add callbacks to the Keyboard handler so that each time a key is pressed down or released the callbacks are activated. * * @method Phaser.Keyboard#addCallbacks * @param {Object} context - The context under which the callbacks are run. diff --git a/src/input/Pointer.js b/src/input/Pointer.js index 20ec3e86..874e69f6 100644 --- a/src/input/Pointer.js +++ b/src/input/Pointer.js @@ -209,8 +209,8 @@ Phaser.Pointer.prototype = { // Fix to stop rogue browser plugins from blocking the visibility state event if (this.game.stage.disableVisibilityChange === false && this.game.paused && this.game.scale.incorrectOrientation === false) { - this.game.paused = false; - return this; + // this.game.paused = false; + // return this; } this._history.length = 0; diff --git a/src/time/Time.js b/src/time/Time.js index f3fe853a..f71ba1f3 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -143,8 +143,8 @@ Phaser.Time = function (game) { this._i = 0; // Listen for game pause/resume events - this.game.onPause.add(this.gamePaused, this); - this.game.onResume.add(this.gameResumed, this); + // this.game.onPause.add(this.gamePaused, this); + // this.game.onResume.add(this.gameResumed, this); }; @@ -218,6 +218,7 @@ Phaser.Time.prototype = { this.elapsed = this.now - this.time; + /* this.msMin = this.game.math.min(this.msMin, this.elapsed); this.msMax = this.game.math.max(this.msMax, this.elapsed); @@ -231,9 +232,12 @@ Phaser.Time.prototype = { this._timeLastSecond = this.now; this.frames = 0; } + */ this.time = this.now; this.lastTime = time + this.timeToCall; + + /* this.physicsElapsed = 1.0 * (this.elapsed / 1000); // Clamp the delta @@ -241,11 +245,12 @@ Phaser.Time.prototype = { { this.physicsElapsed = 0.05; } + */ - // Paused? + // Paused but still running? if (this.game.paused) { - this.pausedTime = this.now - this._pauseStarted; + // this.pausedTime = this.now - this._pauseStarted; } else { @@ -278,13 +283,22 @@ Phaser.Time.prototype = { * @method Phaser.Time#gamePaused * @private */ - gamePaused: function () { + gamePaused: function (time) { - this._pauseStarted = this.now; + if (typeof time === 'undefined') + { + this._pauseStarted = this.now; + } + else + { + this._pauseStarted = time; + } this.events.pause(); - for (var i = 0; i < this._timers.length; i++) + var i = this._timers.length; + + while (i--) { this._timers[i].pause(); } @@ -296,11 +310,21 @@ Phaser.Time.prototype = { * @method Phaser.Time#gameResumed * @private */ - gameResumed: function () { + gameResumed: function (time) { + + if (typeof time === 'undefined') + { + this.pauseDuration = this.now - this._pauseStarted; + } + else + { + this.pauseDuration = time - this._pauseStarted; + } // Level out the elapsed timer to avoid spikes + this.time = Date.now(); - this.pauseDuration = this.pausedTime; + this._justResumed = true; },