File:HintsPlayer.js

/**
 * @module Hints
 * @namespace springroll
 * @requires Core, Sound, Learning
 */
(function()
{
	//Include classes
	var EventDispatcher = include('springroll.EventDispatcher'),
		AnimatorHint = include('springroll.AnimatorHint'),
		FunctionHint = include('springroll.FunctionHint'),
		VOHint = include('springroll.VOHint'),
		GroupHint = include('springroll.GroupHint');

	/**
	 * Design to handle the setting and playing of hints
	 * @class HintsPlayer
	 * @constructor
	 * @param {springroll.Application} app Reference to the current app
	 */
	var HintsPlayer = function(app)
	{
		EventDispatcher.call(this);

		/**
		 * Reference to the current app
		 * @property {springroll.Application} _app
		 * @private
		 */
		this._app = app;

		/**
		 * The currently selected hint
		 * @property {springroll.AbstractHint} _hint
		 * @private
		 */
		this._hint = null;

		/**
		 * The total number of milliseconds until playing
		 * @property {int} _duration
		 * @private
		 */
		this._duration = 0;

		/**
		 * The countdown in milliseconds
		 * @property {int} _timer
		 * @private
		 */
		this._timer = 0;

		/**
		 * Time in ms between timeout lines
		 * @property {Number} timerDuration
		 * @default  12000
		 */
		this.timerDuration = 12000;

		//Bind functions
		this._update = this._update.bind(this);
		this._done = this._done.bind(this);
		this.play = this.play.bind(this);

		/**
		 * If a hint is currently playing
		 * @property {Boolean} _playing
		 * @default false
		 * @private
		 */
		this._playing = false;

		/**
		 * Contains previously set hints to be cleaned up after the new hint plays,
		 * to prevent erasing callbacks too soon.
		 * @property {Array} _oldHints
		 */
		this._oldHints = [];
	};

	//Reference to the prototype
	var s = EventDispatcher.prototype;
	var p = EventDispatcher.extend(HintsPlayer);

	/**
	 * Play an animation event
	 * @event start
	 * @param {springroll.AbstractHint} hint The hint being played
	 */

	/**
	 * Play an animation event
	 * @event anim
	 * @param {Object} data The event data
	 * @param {createjs.MovieClip} data.instance The movieclip instance
	 * @param {String|Array} data.events The Animator events
	 * @param {Function} data.complete Callback when complete
	 * @param {Function} data.cancel Callback when canceled
	 */

	/**
	 * Play an Voice-Over event
	 * @event vo
	 * @param {Object} data The event data
	 * @param {String|Array} data.events The VO alias or array of aliases/times/etc
	 * @param {Function} data.complete Callback when complete
	 * @param {Function} data.cancel Callback when canceled
	 */

	/**
	 * Event when the enabled status of the hint changes
	 * @event enabled
	 * @param {Boolean} enabled If the player is enabled
	 */

	/**
	 * Add a VO hint to the player.
	 * @method vo
	 * @param {String|Array} idOrList The list of VO element, see VOPlayer.play
	 * @param {Function} onComplete Call when the VO is done playing
	 * @param {Function|Boolean} [onCancel] Call when the VO is cancelled playing,
	 *       a value of true sets onComplete to also be the onCancelled callback.
	 * @return {springroll.VOHint} The newly added hint
	 */
	p.vo = function(idOrList, onComplete, onCancel)
	{
		return this.set(new VOHint(
			this,
			this._done,
			idOrList,
			onComplete,
			onCancel
		));
	};

	/**
	 * Add an animator hint to the player
	 * @method anim
	 * @param {createjs.MovieClip|*} instance The instance of the clip to play with Animator
	 * @param {String|Array|Object} events The event aliases to play, see Animator.play
	 * @param {Function} onComplete Call when the VO is done playing
	 * @param {Function|Boolean} [onCancel] Call when the VO is cancelled playing,
	 *       a value of true sets onComplete to also be the onCancelled callback.
	 * @return {springroll.AnimatorHint} The newly added hint
	 */
	p.anim = function(instance, events, onComplete, onCancel)
	{
		return this.set(new AnimatorHint(
			this,
			this._done,
			instance,
			events,
			onComplete,
			onCancel
		));
	};

	/**
	 * Add an animator hint to the player. If you use this hinting method, you
	 * NEED to re-enable the hinting when it's done. Whereas the VO and ANIM methods
	 * with automatically re-enable the hinting button.
	 * @method func
	 * @param {Function} onStart The function to call when hint is played.
	 *                           Should accept 2 arguments (callbacks): onComplete, onCancelled
	 *                           and call them when complete or cancelled
	 * @return {springroll.FunctionHint} The newly added hint
	 */
	p.func = function(onStart)
	{
		return this.set(new FunctionHint(this, this._done, onStart));
	};

	/**
	 * Create the new group hint for randomizing hints or for tiered hinting.
	 * You can save this group hint for later and assign using HintsPlayer.set()
	 * @method group
	 * @return {springroll.GroupHint} The new group hint
	 */
	p.group = function()
	{
		return this.set(new GroupHint(this, this._done));
	};

	/**
	 * Set the current method to use
	 * @method set
	 * @param {springroll.AbstractHint} hint The new hint to add
	 * @return {springroll.AbstractHint} Instance of the player, for chaining
	 */
	p.set = function(hint)
	{
		//Remove any existing hint
		this.clear();
		this.enabled = true;
		this._hint = hint;
		return hint;
	};

	/**
	 * Removes the current hint
	 * @method clear
	 */
	p.clear = function()
	{
		this._playing = false;
		this.removeTimer();
		this.enabled = false;
		if (this._hint)
		{
			this._oldHints.push(this._hint); //we'll destroy these when it's safe
		}
		this._hint = null;
	};

	/**
	 * Manually play the current hint
	 * @method play
	 * @return {springroll.HintsPlayer} instance of the player for chaining
	 */
	p.play = function()
	{
		if (this._hint)
		{
			// Keep track of the playing status
			this._playing = true;

			// Start playing the hint
			this._hint.play();

			// it is now safe to destroy old hints since 
			// their callbacks have already fired
			this._clearOldHints();

			// Trigger start event
			this.trigger('start', this._hint);
		}
		return this;
	};

	/**
	 * Start a timer
	 * @method startTimer
	 * @param {int} [duration=12000] The number of milliseconds before playing hint
	 * @return {springroll.HintsPlayer} instance of the player for chaining
	 */
	p.startTimer = function(duration)
	{
		this._timer = this._duration = duration || this.timerDuration;
		this._app.off('update', this._update).on('update', this._update);
		return this;
	};

	/**
	 * Stop the timer and remove update listener
	 * @method stopTimer
	 * @return {springroll.HintsPlayer} instance of the player for chaining
	 */

	/**
	 * Stop the timer and remove update listener.
	 * Alias for stopTimer
	 * @method removeTimer
	 * @return {springroll.HintsPlayer} instance of the player for chaining
	 */
	p.stopTimer = p.removeTimer = function()
	{
		if (this._app) this._app.off('update', this._update);
		this._timer = this._duration = 0;
		return this;
	};

	/**
	 * Reset the timer to start over
	 * @method resetTimer
	 * @return {springroll.HintsPlayer} instance of the player for chaining
	 */
	p.resetTimer = function()
	{
		this._app.off('update', this._update).on('update', this._update);
		this._timer = this._duration;
		return this;
	};

	/**
	 * If the help button is enabled
	 * @property {Boolean} enabled
	 */
	Object.defineProperty(p, 'enabled',
	{
		set: function(enabled)
		{
			this.trigger('enabled', enabled);
		}
	});

	/**
	 * Handle the update function
	 * @method _update
	 * @private
	 * @param {int} elapsed Number of milliseconds since the last update
	 */
	p._update = function(elapsed)
	{
		if (this._playing) return;

		if (this._timer > 0)
		{
			this._timer -= elapsed;

			if (this._timer <= 0)
			{
				this._app.off('update', this._update);
				this.play();
			}
		}
	};
	/**
	 * Call this when a FunctionHint is done playing to reset HintsPlayer
	 * @method funcDone
	 * @param {Boolean} [cancelled=false] If the function was interrupted by the user or something else.
	 */
	/**
	 * Internal callback when a hint is done playing
	 * @method _done
	 * @private
	 * @param {Boolean} [cancelled=false] If the function was interrupted by the user or something else.
	 */
	p.funcDone = p._done = function(cancelled)
	{
		this._playing = false;
		this.resetTimer();

		//Enable the button to play again
		this.enabled = !cancelled;

		//After playing the current tier, goto the next tier
		if (this._hint instanceof GroupHint)
		{
			this._hint.nextTier();
		}
	};

	/**
	 * Destroys old hints
	 * @method _clearOldHints
	 * @private
	 */
	p._clearOldHints = function()
	{
		if (this._oldHints.length)
		{
			for (var i = 0; i < this._oldHints.length; i++)
			{
				this._oldHints[i].destroy();
			}
			this._oldHints.length = 0;
		}
	};

	/**
	 * Destroy, don't use after this
	 * @method destroy
	 */
	p.destroy = function()
	{
		this.clear();
		this._clearOldHints();
		this._app = null;
		s.destroy.call(this);
	};

	//Assign to namespace
	namespace('springroll').HintsPlayer = HintsPlayer;
}());