/**
* @module Learning Media
* @namespace springroll.pbskids
* @requires Core, Learning, Sound, Captions
*/
(function()
{
var Debug = include('springroll.Debug', false);
var StringFilters = include('springroll.StringFilters');
/**
* This class contains a bunch of media playing class
* to provide convenience around using the Learning Dispatcher
* @class LearningMedia
*/
var LearningMedia = function()
{
/**
* Reference to the StringFilters
* @property {springroll.StringFilters} filters
*/
this.filters = null;
/**
* Reference to the Learning Dispatcher
* @property {springroll.Learning} learning
*/
this.learning = null;
/**
* Reference to the VO Player
* @property {springroll.VOPlayer} voPlayer
*/
this.voPlayer = null;
/**
* Reference to the main display
* @property {createjs.Display} display
*/
this.display = null;
/**
* Reference to the animator instance
* @property {springroll.Animator} animator
*/
this.animator = null;
};
//Reference to the prototype
var p = extend(LearningMedia);
/**
* Intiailize the media
* @method init
* @param {springroll.Application} app
*/
p.init = function(app)
{
this.filters = app.filters;
this.learning = app.learning;
this.voPlayer = app.voPlayer;
this.display = app.display;
this.animator = app.animator;
};
p._filterEvents = function(events)
{
if (typeof events == "string")
return this.filters.filter(events);
else
{
if (!Array.isArray(events))
events = [events];
for (var i = 0; i < events.length; ++i)
{
var eventInfo = events[i];
switch (typeof eventInfo)
{
case "string":
eventInfo = this.filters.filter(eventInfo);
break;
case "object":
//we passed an object
eventInfo.anim = this.filters.filter(eventInfo.anim);
if (eventInfo.audio)
{
if (typeof eventInfo.audio == 'object')
{
eventInfo.audio.alias = this.filters.filter(eventInfo.audio.alias);
}
else
{
eventInfo.audio = this.filters.filter(eventInfo.audio);
}
}
break;
}
events[i] = eventInfo;
}
return events;
}
};
/**
* Plays animation or list of animations using springroll.Animator,
* firing startMovie and endMovie or skipMovie Learning events.
* @method playMovie
* @param {MovieClip} instance The MovieClip to animate.
* @param {String|Array|Object} events Event or list of events to animate. See
* springroll.Animator.play() docs for details.
* @param {Function} [onComplete] VO/Animation Ended callback
* @param {Function|Boolean} [onCancel] VO/Animation Cancelled (interrupted) callback.
* If set to 'true' (Boolean), will use same callback as 'onComplete'
* If omited, no callback will be fired on interruption.
* @return {springroll.AnimatorTimeline} AnimatorTimeline of the played animation.
*/
p.playMovie = function(instance, events, onComplete, onCancel)
{
events = this._filterEvents(events);
if (!this.learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger Movie events");
}
return this.animator.play(instance, events, options);
}
return this.triggerMoviePlay(
instance,
events,
onComplete,
onCancel,
"movie"
);
};
/**
* Plays events/aliases using Animator or VOPlayer, and fires startInstruction and endInstruction Learning events
*
* Example Animator usage:
* game.playInstruction(someMovieClip, {"anim":"frameLabel", "audio":"soundAlias"}, doneFunction, interruptedFunction);
*
* Example VOPlayer usage:
* game.playInstruction("soundAlias", doneFunction, interruptedFunction);
*
* @method playInstruction
* @param {MovieClip} [instance] createjs.MovieClip instance to play with Animator.
* Omit this parameter to play alias(es) with VOPlayer instead
* @param {String|Array|Object} event If 'instance' is omitted, Alias or Array of aliases for VO lines to play with VOPlayer.
* See VOPlayer.play docs for options.
* If 'instance' is present, Event or list of events to animate with createjs.Animator.
* See springroll.Animator.play() docs for options
* @param {Function} [onComplete] VO/Animation Ended callback
* @param {Function|Boolean} [onCancel] VO/Animation Cancelled (interrupted) callback.
* If set to 'true' (Boolean), will use same callback as 'onComplete'
* If omited, no callback will be fired on interruption.
* @return {springroll.AnimatorTimeline|undefined} AnimatorTimeline of the played animation, or nothing if VO only.
*/
p.playInstruction = function(instance, events, onComplete, onCancel)
{
var learning = this.learning;
var animator = this.animator;
if (animator.canAnimate(instance)) //use Animator
{
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger Instruction events");
}
return animator.play(instance, events, onComplete, onCancel);
}
return this.triggerMoviePlay(
instance,
events,
onComplete,
onCancel,
"instruction"
);
}
else //use VOPlayer
{
onCancel = onComplete;
onComplete = events;
events = instance;
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger Instruction events");
}
this.voPlayer.play(events, onComplete, onCancel);
return;
}
this.triggerVOPlay(
events,
learning.startInstruction,
learning.endInstruction,
onComplete,
onCancel
);
}
};
/**
* Plays events/aliases using Animator or VOPlayer, and fires startIncorrectFeedback and endIncorrectFeedback Learning events
*
* Example Animator usage:
* game.playIncorrectFeedback(someMovieClip, {"anim":"frameLabel", "audio":"soundAlias"}, doneFunction, interruptedFunction);
*
* Example VOPlayer usage:
* game.playIncorrectFeedback("soundAlias", doneFunction, interruptedFunction);
*
* @method playIncorrectFeedback
* @param {MovieClip} [instance] createjs.MovieClip instance to play with Animator.
* Omit this parameter to play alias(es) with VOPlayer instead
* @param {String|Array|Object} events If 'instance' is omitted, Alias or Array of aliases for VO lines to play with VOPlayer.
* See VOPlayer.play docs for options.
* If 'instance' is present, Event or list of events to animate with createjs.Animator.
* See springroll.Animator.play() docs for options
* @param {Function} [onComplete] VO/Animation Ended callback
* @param {Function|Boolean} [onCancel] VO/Animation Cancelled (interrupted) callback.
* If set to 'true' (Boolean), will use same callback as 'onComplete'
* If omited, no callback will be fired on interruption.
* @return {springroll.AnimatorTimeline|undefined} AnimatorTimeline of the played animation, or nothing if VO only.
*/
p.playIncorrectFeedback = function(instance, events, onComplete, onCancel)
{
var learning = this.learning;
var animator = this.animator;
if (animator.canAnimate(instance)) //use Animator
{
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger IncorrectFeedback events");
}
return animator.play(instance, events, onComplete, onCancel);
}
return this.triggerMoviePlay(
instance,
events,
onComplete,
onCancel,
"incorrect"
);
}
else //use VOPlayer
{
onCancel = onComplete;
onComplete = events;
events = instance;
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger IncorrectFeedback events");
}
this.voPlayer.play(events, onComplete, onCancel);
return;
}
this.triggerVOPlay(
events,
learning.startIncorrectFeedback,
learning.endIncorrectFeedback,
onComplete,
onCancel
);
}
};
/**
* Plays events/aliases using Animator or VOPlayer, and fires startCorrectFeedback and endCorrectFeedback Learning events
*
* Example Animator usage:
* game.playCorrectFeedback(someMovieClip, {"anim":"frameLabel", "audio":"soundAlias"}, doneFunction, interruptedFunction);
*
* Example VOPlayer usage:
* game.playCorrectFeedback("soundAlias", doneFunction, interruptedFunction);
*
* @method playCorrectFeedback
* @param {MovieClip} [instance] createjs.MovieClip instance to play with Animator.
* Omit this parameter to play alias(es) with VOPlayer instead
* @param {String|Array|Object} event If 'instance' is omitted, Alias or Array of aliases for VO lines to play with VOPlayer.
* See VOPlayer.play docs for options.
* If 'instance' is present, Event or list of events to animate with createjs.Animator.
* See springroll.Animator.play() docs for options
* @param {Function} [onComplete] VO/Animation Ended callback
* @param {Function|Boolean} [onCancel] VO/Animation Cancelled (interrupted) callback.
* If set to 'true' (Boolean), will use same callback as 'onComplete'.
* If omited, no callback will be fired on interruption.
* @return {springroll.AnimatorTimeline|undefined} AnimatorTimeline of the played animation, or nothing if VO only.
*/
p.playCorrectFeedback = function(instance, events, onComplete, onCancel)
{
var learning = this.learning,
animator = this.animator;
if (animator.canAnimate(instance)) //use Animator
{
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger CorrectFeedback events");
}
return animator.play(instance, events, onComplete, onCancel);
}
return this.triggerMoviePlay(
instance,
events,
onComplete,
onCancel,
"correct"
);
}
else //use VOPlayer
{
onCancel = onComplete;
onComplete = events;
events = instance;
events = this._filterEvents(events);
if (!learning.spec)
{
if (DEBUG && Debug)
{
Debug.warn("Learning is not available and will not trigger CorrectFeedback events");
}
this.voPlayer.play(events, onComplete, onCancel);
return;
}
this.triggerVOPlay(
events,
learning.startCorrectFeedback,
learning.endCorrectFeedback,
onComplete,
onCancel
);
}
};
/**
* Generalized method for playing either feedback or instructions
* @method triggerVOPlay
* @protected
* @param {String|Array} alias Alias or Array of aliases for VO lines to play
* @param {Function} learningStart The learning to call while starting
* @param {Function} learningEnd The learning call to call after finishing/canceling VO
* @param {Function} [onComplete] VO Ended callback
* @param {Function} [onCancel] VO Cancelled (interrupted) callback
*/
p.triggerVOPlay = function(alias, learningStart, learningEnd, onComplete, onCancel)
{
var animator = this.animator;
//stop any previously playing stuff
this.voPlayer.stop();
if (this._learningAnimatorInstance)
{
animator.stop(this._learningAnimatorInstance);
}
var captions = this.voPlayer.captions;
//Callback function for ending or canceling the VO
var callback = function(finish)
{
//quit early if LearningMedia has been destroyed.
if (!this.learning) return;
learningEnd.call(this.learning);
if (finish) finish();
};
if (onCancel === true)
onCancel = onComplete;
//Play the audio
this.voPlayer.play(
alias,
callback.bind(this, onComplete),
callback.bind(this, onCancel)
);
//Trigger the start event
learningStart.call(
this.learning,
captions.getFullCaption(alias),
aliasToString(alias),
"audio",
captions.getLength(alias)
);
};
/**
* Handles learning events for triggered Animator calls.
* @method triggerMoviePlay
* @protected
* @param {MovieClip} instance The MovieClip to animate.
* @param {String|Array} events Event or list of events to animate. See
* springroll.Animator.play() docs for details.
* @param {Object} options Additional options. See springroll.Animator.play() docs
* for details.
* @param {String} learningEvent Learning VO/animation event type
* ("movie", "instruction", "incorrect", or "correct").
* @return {springroll.AnimatorTimeline} AnimatorTimeline of animation.
*/
p.triggerMoviePlay = function(instance, events, onComplete, onCancel, learningEvent)
{
//Localized instance of learning
var learning = this.learning;
var animator = this.animator;
//stop any previously playing stuff
this.voPlayer.stop();
if (this._learningAnimatorInstance)
{
animator.stop(this._learningAnimatorInstance);
}
if (!Array.isArray(events))
{
events = [events];
}
var duration = 0; //Event "duration"
var fullCaption = ""; //Event "description"
var alias = ""; //Event "id"
var captions = this.voPlayer.captions;
var eventInfo, anim, audio, learningEnd, learningCancel;
//Current loop iteration Caption and ID/alias
var thisCaption, thisID;
for (var i = 0, len = events.length; i < len; ++i)
{
eventInfo = events[i];
thisID = null;
thisCaption = null;
switch (typeof eventInfo)
{
case "number":
{
duration += eventInfo;
break;
}
case "string":
{
//we passed in a string - the audio alias should be assumed
//to be the same as the animation alias
duration += animator.getDuration(instance, eventInfo);
thisID = eventInfo;
//if no audio, will return event alias.
thisCaption = captions.getFullCaption(eventInfo);
//convert to Animator friendly format
events[i] = {
anim: eventInfo,
audio: eventInfo
};
break;
}
case "object":
{
//we passed an object
anim = eventInfo.anim;
if (eventInfo.audio)
{
if (typeof eventInfo.audio == 'object')
{
audio = eventInfo.audio.alias;
}
else
{
audio = eventInfo.audio;
}
}
else
audio = null;
duration += animator.getDuration(instance, anim);
//if no audio, use the anim label instead
if (!audio)
{
audio = anim;
}
else if (typeof audio != "string")
{
audio = audio.alias;
}
thisCaption = captions.getFullCaption(audio);
thisID = audio;
break;
}
}
if (thisID)
{
if (alias.length > 0)
{
alias += ",";
}
alias += thisID;
}
if (thisCaption)
{
if (fullCaption.length > 0)
{
fullCaption += " ";
}
fullCaption += thisCaption;
}
}
duration = duration | 0; //make it an int
switch (learningEvent)
{
case "instruction":
{
learning.startInstruction(fullCaption, alias, "animation", duration);
learningEnd = learning.endInstruction.bind(learning);
break;
}
case "correct":
{
learning.startCorrectFeedback(fullCaption, alias, "animation", duration);
learningEnd = learning.endCorrectFeedback.bind(learning);
break;
}
case "incorrect":
{
learning.startIncorrectFeedback(fullCaption, alias, "animation", duration);
learningEnd = learning.endIncorrectFeedback.bind(learning);
break;
}
case "movie":
{
learning.startMovie(alias, duration, fullCaption);
learningEnd = learning.endMovie.bind(learning);
learningCancel = learning.skipMovie.bind(learning);
break;
}
}
var callback = function(learningCall, otherCall)
{
//quit early if LearningMedia has been destroyed
if (!this.learning) return;
this._learningAnimatorInstance = null;
if (learningCall) //learning end event
{
learningCall();
}
if (otherCall) //original callback
{
otherCall();
}
};
//Setup callbacks
var onCompleteCallback = callback.bind(this, learningEnd, onComplete);
var onCancelCallback = callback.bind(
this,
learningCancel ? learningCancel : learningEnd,
onCancel === true ? onComplete : onCancel
);
this._learningAnimatorInstance = instance;
return animator.play(instance, events, onCompleteCallback, onCancelCallback);
};
/**
* Get an alias or group of aliases as a string
* @method aliasToString
* @private
* @param {Array|String} alias The alias to convert
* @return {String} The alias as string
*/
var aliasToString = function(alias)
{
if (Array.isArray(alias))
{
var output = "";
for (var i = 0, len = alias.length; i < len; ++i)
{
if (typeof alias[i] == "string")
{
if (output.length > 0)
{
output += ",";
}
output += alias[i];
}
}
return output;
}
else
{
return String(alias);
}
};
/**
* Stops the currently playing animation or VO.
* @method stop
*/
p.stop = function()
{
if (this._learningAnimatorInstance)
{
this.animator.stop(this._learningAnimatorInstance);
}
else
{
this.voPlayer.stop();
}
};
/**
* Whether or not VOPlayer or Animator are currently playing
* @method isPlaying
* @return {Boolean} Whether or not VOPlayer or Animator are currently playing
*/
p.isPlaying = function()
{
return this.voPlayer.playing || !!this._learningAnimatorInstance;
};
/**
* Destroy and don't use after this
* @method destroy
*/
p.destroy = function()
{
this.learning = null;
this.voPlayer = null;
this.display = null;
this.filters = null;
};
//Assign to namespace
namespace('springroll.pbskids').LearningMedia = LearningMedia;
}());