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