/** * @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; }());