1 /** The minplayer namespace. */
  2 minplayer = minplayer || {};
  3 
  4 /** Static array to keep track of all plugins. */
  5 minplayer.plugins = minplayer.plugins || {};
  6 
  7 /** Static array to keep track of queues. */
  8 minplayer.queue = minplayer.queue || [];
  9 
 10 /** Mutex lock to keep multiple triggers from occuring. */
 11 minplayer.lock = false;
 12 
 13 /**
 14  * @constructor
 15  * @class The base class for all plugins.
 16  *
 17  * @param {string} name The name of this plugin.
 18  * @param {object} context The jQuery context.
 19  * @param {object} options This components options.
 20  * @param {object} queue The event queue to pass events around.
 21  */
 22 minplayer.plugin = function(name, context, options, queue) {
 23 
 24   // Make sure we have some options.
 25   this.options = options || {};
 26 
 27   /** The name of this plugin. */
 28   this.name = name;
 29 
 30   /** The ready flag. */
 31   this.pluginReady = false;
 32 
 33   /** The event queue. */
 34   this.queue = queue || {};
 35 
 36   /** Keep track of already triggered events. */
 37   this.triggered = {};
 38 
 39   /** Create a queue lock. */
 40   this.lock = false;
 41 
 42   /** The universally unique ID for this plugin. */
 43   this.uuid = 0;
 44 
 45   // Only call the constructor if we have a context.
 46   if (context) {
 47 
 48     /** Keep track of the context. */
 49     this.context = jQuery(context);
 50 
 51     // Initialize the default options.
 52     var defaults = {};
 53 
 54     // Get the default options.
 55     this.defaultOptions(defaults);
 56 
 57     /** The options for this plugin. */
 58     for (var param in defaults) {
 59       if (!this.options.hasOwnProperty(param)) {
 60         this.options[param] = defaults[param];
 61       }
 62     }
 63 
 64     // Initialize this plugin.
 65     this.initialize();
 66   }
 67 };
 68 
 69 /**
 70  * Initialize function for the plugin.
 71  */
 72 minplayer.plugin.prototype.initialize = function() {
 73 
 74   // Construct this plugin.
 75   this.construct();
 76 };
 77 
 78 /**
 79  * Get the default options for this plugin.
 80  *
 81  * @param {object} options The default options for this plugin.
 82  */
 83 minplayer.plugin.prototype.defaultOptions = function(options) {
 84 };
 85 
 86 /**
 87  * The constructor which is called once the context is set.
 88  * Any class deriving from the plugin class should place all context
 89  * dependant functionality within this function instead of the standard
 90  * constructor function since it is called on object derivation as well
 91  * as object creation.
 92  */
 93 minplayer.plugin.prototype.construct = function() {
 94 
 95   /** Say that we are active. */
 96   this.active = true;
 97 
 98   // Adds this as a plugin.
 99   this.addPlugin();
100 };
101 
102 /**
103  * Destructor.
104  */
105 minplayer.plugin.prototype.destroy = function() {
106 
107   // Unbind all events.
108   this.active = false;
109   this.unbind();
110 };
111 
112 /**
113  * Creates a new plugin within this context.
114  *
115  * @param {string} name The name of the plugin you wish to create.
116  * @param {object} base The base object for this plugin.
117  * @param {object} context The context which you would like to create.
118  * @return {object} The new plugin object.
119  */
120 minplayer.plugin.prototype.create = function(name, base, context) {
121   var plugin = null;
122 
123   // Make sure we have a base object.
124   base = base || 'minplayer';
125   if (!window[base][name]) {
126     base = 'minplayer';
127   }
128 
129   // Make sure there is a context.
130   context = context || this.display;
131 
132   // See if this plugin exists within this object.
133   if (window[base][name]) {
134 
135     // Set the plugin.
136     plugin = window[base][name];
137 
138     // See if a template version of the plugin exists.
139     if (plugin[this.options.template]) {
140 
141       plugin = plugin[this.options.template];
142     }
143 
144     // Make sure the plugin is a function.
145     if (typeof plugin !== 'function') {
146       plugin = window.minplayer[name];
147     }
148 
149     // Make sure it is a function.
150     if (typeof plugin === 'function') {
151       return new plugin(context, this.options);
152     }
153   }
154 
155   return null;
156 };
157 
158 /**
159  * Plugins should call this method when they are ready.
160  */
161 minplayer.plugin.prototype.ready = function() {
162 
163   // Keep this plugin from triggering multiple ready events.
164   if (!this.pluginReady) {
165 
166     // Set the ready flag.
167     this.pluginReady = true;
168 
169     // Now trigger that I am ready.
170     this.trigger('ready');
171 
172     // Check the queue.
173     this.checkQueue();
174   }
175 };
176 
177 /**
178  * Returns if this component is valid.
179  *
180  * @return {boolean} TRUE if the plugin display is valid.
181  */
182 minplayer.plugin.prototype.isValid = function() {
183   return !!this.options.id && this.active;
184 };
185 
186 /**
187  * Allows a plugin to do something when it is added to another plugin.
188  *
189  * @param {object} plugin The plugin that this plugin was added to.
190  */
191 minplayer.plugin.prototype.onAdded = function(plugin) {
192 };
193 
194 /**
195  * Adds a new plugin to this player.
196  *
197  * @param {string} name The name of this plugin.
198  * @param {object} plugin A new plugin object, derived from media.plugin.
199  */
200 minplayer.plugin.prototype.addPlugin = function(name, plugin) {
201   name = name || this.name;
202   plugin = plugin || this;
203 
204   // Make sure the plugin is valid.
205   if (plugin.isValid()) {
206 
207     // If the plugins for this instance do not exist.
208     if (!minplayer.plugins[this.options.id]) {
209 
210       // Initialize the plugins.
211       minplayer.plugins[this.options.id] = {};
212     }
213 
214     if (!minplayer.plugins[this.options.id][name]) {
215 
216       // Add the plugins array.
217       minplayer.plugins[this.options.id][name] = [];
218     }
219 
220     // Add this plugin.
221     var instance = minplayer.plugins[this.options.id][name].push(plugin);
222 
223     // Set the uuid.
224     this.uuid = this.options.id + '__' + name + '__' + instance;
225 
226     // Now check the queue for this plugin.
227     this.checkQueue(plugin);
228 
229     // Now let the plugin do something with this plugin.
230     plugin.onAdded(this);
231   }
232 };
233 
234 /** Create timers for the polling. */
235 minplayer.timers = {};
236 
237 /**
238  * Create a polling timer.
239  *
240  * @param {string} name The name of the timer.
241  * @param {function} callback The function to call when you poll.
242  * @param {integer} interval The interval you would like to poll.
243  * @return {string} The setTimeout ID.
244  */
245 minplayer.plugin.prototype.poll = function(name, callback, interval) {
246   if (minplayer.timers.hasOwnProperty(name)) {
247     clearTimeout(minplayer.timers[name]);
248   }
249   minplayer.timers[name] = setTimeout((function(context) {
250     return function callLater() {
251       if (callback.call(context)) {
252         minplayer.timers[name] = setTimeout(callLater, interval);
253       }
254     };
255   })(this), interval);
256   return minplayer.timers[name];
257 };
258 
259 /**
260  * Gets a plugin by name and calls callback when it is ready.
261  *
262  * @param {string} plugin The plugin of the plugin.
263  * @param {function} callback Called when the plugin is ready.
264  * @return {object} The plugin if no callback is provided.
265  */
266 minplayer.plugin.prototype.get = function(plugin, callback) {
267 
268   // If they pass just a callback, then return all plugins when ready.
269   if (typeof plugin === 'function') {
270     callback = plugin;
271     plugin = null;
272   }
273 
274   // Return the minplayer.get equivalent.
275   return minplayer.get.call(this, this.options.id, plugin, callback);
276 };
277 
278 /**
279  * Check the queue and execute it.
280  *
281  * @param {object} plugin The plugin object to check the queue against.
282  */
283 minplayer.plugin.prototype.checkQueue = function(plugin) {
284 
285   // Initialize our variables.
286   var q = null, i = 0, check = false;
287 
288   // Normalize the plugin variable.
289   plugin = plugin || this;
290 
291   // Set the lock.
292   minplayer.lock = true;
293 
294   // Iterate through all the queues.
295   var length = minplayer.queue.length;
296   for (i = 0; i < length; i++) {
297     if (minplayer.queue.hasOwnProperty(i)) {
298 
299       // Get the queue.
300       q = minplayer.queue[i];
301 
302       // Now check to see if this queue is about us.
303       check = !q.id && !q.plugin;
304       check |= (q.plugin === plugin.name);
305       check &= (!q.id || (q.id === this.options.id));
306 
307       // If the check passes, and hasn't already been added...
308       if (check && !q.addedto.hasOwnProperty(plugin.options.id)) {
309         q.addedto[plugin.options.id] = true;
310         check = minplayer.bind.call(
311           q.context,
312           q.event,
313           this.options.id,
314           plugin.name,
315           q.callback,
316           true
317         );
318       }
319     }
320   }
321 
322   // Release the lock.
323   minplayer.lock = false;
324 };
325 
326 /**
327  * All minplayer event types.
328  */
329 minplayer.eventTypes = {};
330 
331 /**
332  * Determine if an event is of a certain type.
333  *
334  * @param {string} name The full name of the event.
335  * @param {string} type The type of the event.
336  * @return {boolean} If this named event is of type.
337  */
338 minplayer.plugin.prototype.isEvent = function(name, type) {
339   // Static cache for performance.
340   var cacheName = name + '__' + type;
341   if (typeof minplayer.eventTypes[cacheName] !== 'undefined') {
342     return minplayer.eventTypes[cacheName];
343   }
344   else {
345     var regex = new RegExp('^(.*:)?' + type + '$', 'gi');
346     minplayer.eventTypes[cacheName] = (name.match(type) !== null);
347     return minplayer.eventTypes[cacheName];
348   }
349 };
350 
351 /**
352  * Trigger a media event.
353  *
354  * @param {string} type The event type.
355  * @param {object} data The event data object.
356  * @param {boolean} noqueue If this trigger should not be queued.
357  * @return {object} The plugin object.
358  */
359 minplayer.plugin.prototype.trigger = function(type, data, noqueue) {
360 
361   // Don't trigger if this plugin is inactive.
362   if (!this.active) {
363     return this;
364   }
365 
366   // Only queue if they wish it to be so...
367   if (!noqueue) {
368 
369     // Add this to our triggered array.
370     this.triggered[type] = data;
371   }
372 
373   // Iterate through the queue.
374   var i = 0, queue = {}, queuetype = null;
375 
376   // Iterate through all the queue items.
377   for (var name in this.queue) {
378 
379     // See if this is an event we care about.
380     if (this.isEvent(name, type)) {
381 
382       // Set the queuetype.
383       queuetype = this.queue[name];
384 
385       // Iterate through all the callbacks in this queue.
386       for (i in queuetype) {
387 
388         // Check to make sure the queue index exists.
389         if (queuetype.hasOwnProperty(i)) {
390 
391           // Setup the event object, and call the callback.
392           queue = queuetype[i];
393           queue.callback({target: this, data: queue.data}, data);
394         }
395       }
396     }
397   }
398 
399   // Return the plugin object.
400   return this;
401 };
402 
403 /**
404  * Unbind then Bind
405  *
406  * @param {string} type The event type.
407  * @param {object} data The data to bind with the event.
408  * @param {function} fn The callback function.
409  * @return {object} The plugin object.
410  */
411 minplayer.plugin.prototype.ubind = function(type, data, fn) {
412   this.unbind(type);
413   return this.bind(type, data, fn);
414 };
415 
416 /**
417  * Bind to a media event.
418  *
419  * @param {string} type The event type.
420  * @param {object} data The data to bind with the event.
421  * @param {function} fn The callback function.
422  * @return {object} The plugin object.
423  **/
424 minplayer.plugin.prototype.bind = function(type, data, fn) {
425 
426   // Only bind if active.
427   if (!this.active) {
428     return this;
429   }
430 
431   // Allow the data to be the callback.
432   if (typeof data === 'function') {
433     fn = data;
434     data = null;
435   }
436 
437   // You must bind to a specific event and have a callback.
438   if (!type || !fn) {
439     return;
440   }
441 
442   // Initialize the queue for this type.
443   this.queue[type] = this.queue[type] || [];
444 
445   // Now add this event to the queue.
446   this.queue[type].push({
447     callback: fn,
448     data: data
449   });
450 
451   // Now see if this event has already been triggered.
452   for (var name in this.triggered) {
453     if (this.triggered.hasOwnProperty(name)) {
454       if (this.isEvent(type, name)) {
455         fn({target: this, data: data}, this.triggered[name]);
456       }
457     }
458   }
459 
460   // Return the plugin.
461   return this;
462 };
463 
464 /**
465  * Unbind a media event.
466  *
467  * @param {string} type The event type.
468  * @return {object} The plugin object.
469  **/
470 minplayer.plugin.prototype.unbind = function(type) {
471 
472   // If this is locked then try again after 10ms.
473   if (this.lock) {
474     setTimeout((function(plugin) {
475       return function() {
476         plugin.unbind(type);
477       };
478     })(this), 10);
479   }
480 
481   // Set the lock.
482   this.lock = true;
483 
484   if (!type) {
485     this.queue = {};
486   }
487   else if (this.queue.hasOwnProperty(type) && (this.queue[type].length > 0)) {
488     this.queue[type].length = 0;
489   }
490 
491   // Reset the lock.
492   this.lock = false;
493 
494   // Return the plugin.
495   return this;
496 };
497 
498 /**
499  * Adds an item to the queue.
500  *
501  * @param {object} context The context which this is called within.
502  * @param {string} event The event to trigger on.
503  * @param {string} id The player ID.
504  * @param {string} plugin The name of the plugin.
505  * @param {function} callback Called when the event occurs.
506  */
507 minplayer.addQueue = function(context, event, id, plugin, callback) {
508 
509   // See if it is locked...
510   if (!minplayer.lock) {
511     minplayer.queue.push({
512       context: context,
513       id: id,
514       event: event,
515       plugin: plugin,
516       callback: callback,
517       addedto: {}
518     });
519   }
520   else {
521 
522     // If so, then try again after 10 milliseconds.
523     setTimeout(function() {
524       minplayer.addQueue(context, id, event, plugin, callback);
525     }, 10);
526   }
527 };
528 
529 /**
530  * Binds an event to a plugin instance, and if it doesn't exist, then caches
531  * it for a later time.
532  *
533  * @param {string} event The event to trigger on.
534  * @param {string} id The player ID.
535  * @param {string} plugin The name of the plugin.
536  * @param {function} callback Called when the event occurs.
537  * @param {boolean} fromCheck If this is from a checkqueue.
538  * @return {boolean} If the bind was successful.
539  * @this The object in context who called this method.
540  */
541 minplayer.bind = function(event, id, plugin, callback, fromCheck) {
542 
543   // If no callback exists, then just return false.
544   if (!callback) {
545     return false;
546   }
547 
548   // Get the plugins.
549   var plugins = minplayer.plugins, thisPlugin = null, thisId = null;
550 
551   // Determine the selected plugins.
552   var selected = [];
553 
554   // Create a quick add.
555   var addSelected = function(id, plugin) {
556     if (plugins.hasOwnProperty(id) && plugins[id].hasOwnProperty(plugin)) {
557       var i = plugins[id][plugin].length;
558       while (i--) {
559         selected.push(plugins[id][plugin][i]);
560       }
561     }
562   };
563 
564   // If they provide id && plugin
565   if (id && plugin) {
566     addSelected(id, plugin);
567   }
568 
569   // If they provide no id but a plugin.
570   else if (!id && plugin) {
571     for (thisId in plugins) {
572       addSelected(thisId, plugin);
573     }
574   }
575 
576   // If they provide an id but no plugin.
577   else if (id && !plugin && plugins[id]) {
578     for (thisPlugin in plugins[id]) {
579       addSelected(id, thisPlugin);
580     }
581   }
582 
583   // If they provide niether an id or a plugin.
584   else if (!id && !plugin) {
585     for (thisId in plugins) {
586       for (thisPlugin in plugins[thisId]) {
587         addSelected(thisId, thisPlugin);
588       }
589     }
590   }
591 
592   // Iterate through the selected plugins and bind.
593   /* jshint loopfunc: true */
594   var i = selected.length;
595   while (i--) {
596     selected[i].bind(event, (function(context) {
597       return function(event) {
598         callback.call(context, event.target);
599       };
600     })(this));
601   }
602 
603   // Add it to the queue for post bindings...
604   if (!fromCheck) {
605     minplayer.addQueue(this, event, id, plugin, callback);
606   }
607 
608   // Return that this wasn't handled.
609   return (selected.length > 0);
610 };
611 
612 /**
613  * The main API for minPlayer.
614  *
615  * Provided that this function takes three parameters, there are 8 different
616  * ways to use this api.
617  *
618  *   id (0x100) - You want a specific player.
619  *   plugin (0x010) - You want a specific plugin.
620  *   callback (0x001) - You only want it when it is ready.
621  *
622  *   000 - You want all plugins from all players, ready or not.
623  *
624  *          var plugins = minplayer.get();
625  *
626  *   001 - You want all plugins from all players, but only when ready.
627  *
628  *          minplayer.get(function(plugin) {
629  *            // Code goes here.
630  *          });
631  *
632  *   010 - You want a specific plugin from all players, ready or not...
633  *
634  *          var medias = minplayer.get(null, 'media');
635  *
636  *   011 - You want a specific plugin from all players, but only when ready.
637  *
638  *          minplayer.get('player', function(player) {
639  *            // Code goes here.
640  *          });
641  *
642  *   100 - You want all plugins from a specific player, ready or not.
643  *
644  *          var plugins = minplayer.get('player_id');
645  *
646  *   101 - You want all plugins from a specific player, but only when ready.
647  *
648  *          minplayer.get('player_id', null, function(plugin) {
649  *            // Code goes here.
650  *          });
651  *
652  *   110 - You want a specific plugin from a specific player, ready or not.
653  *
654  *          var plugin = minplayer.get('player_id', 'media');
655  *
656  *   111 - You want a specific plugin from a specific player, only when ready.
657  *
658  *          minplayer.get('player_id', 'media', function(media) {
659  *            // Code goes here.
660  *          });
661  *
662  * @this The context in which this function was called.
663  * @param {string} id The ID of the widget to get the plugins from.
664  * @param {string} plugin The name of the plugin.
665  * @param {function} callback Called when the plugin is ready.
666  * @return {object} The plugin object if it is immediately available.
667  */
668 minplayer.get = function(id, plugin, callback) {
669 
670   // Get the parameter types.
671   var idType = typeof id;
672   var pluginType = typeof plugin;
673   var callbackType = typeof callback;
674 
675   // Normalize the arguments for a better interface.
676   if (idType === 'function') {
677     callback = id;
678     plugin = id = null;
679   }
680   else if (pluginType === 'function') {
681     callback = plugin;
682     plugin = id;
683     id = null;
684   }
685   else if ((pluginType === 'undefined') && (callbackType === 'undefined')) {
686     plugin = id;
687     callback = id = null;
688   }
689 
690   // Make sure the callback is a callback.
691   callback = (typeof callback === 'function') ? callback : null;
692 
693   // If a callback was provided, then just go ahead and bind.
694   if (callback) {
695     minplayer.bind.call(this, 'ready', id, plugin, callback);
696     return;
697   }
698 
699   // Get the plugins.
700   var plugins = minplayer.plugins, thisId = null;
701 
702   // 0x000
703   if (!id && !plugin && !callback) {
704     return plugins;
705   }
706   // 0x100
707   else if (id && !plugin && !callback) {
708     return plugins[id];
709   }
710   // 0x110
711   else if (id && plugin && !callback) {
712     return plugins[id][plugin];
713   }
714   // 0x010
715   else if (!id && plugin && !callback) {
716     var plugin_types = [];
717     for (thisId in plugins) {
718       if (plugins.hasOwnProperty(thisId) &&
719           plugins[thisId].hasOwnProperty(plugin)) {
720         var i = plugins[thisId][plugin].length;
721         while (i--) {
722           plugin_types.push(plugins[thisId][plugin][i]);
723         }
724       }
725     }
726     return plugin_types;
727   }
728 };
729