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