1 /** The minplayer namespace. */
  2 minplayer = minplayer || {};
  3 
  4 /** Static array to keep track of plugin instances. */
  5 minplayer.instances = minplayer.instances || {};
  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  */
 21 minplayer.plugin = function(name, context, options) {
 22 
 23   /** The name of this plugin. */
 24   this.name = name;
 25 
 26   /** The ready flag. */
 27   this.pluginReady = false;
 28 
 29   /** The options for this plugin. */
 30   this.options = options;
 31 
 32   /** The event queue. */
 33   this.queue = {};
 34 
 35   /** Keep track of already triggered events. */
 36   this.triggered = {};
 37 
 38   /** Create a queue lock. */
 39   this.lock = false;
 40 
 41   // Only call the constructor if we have a context.
 42   if (context) {
 43 
 44     // Construct this plugin.
 45     this.construct();
 46   }
 47 };
 48 
 49 /**
 50  * The constructor which is called once the context is set.
 51  * Any class deriving from the plugin class should place all context
 52  * dependant functionality within this function instead of the standard
 53  * constructor function since it is called on object derivation as well
 54  * as object creation.
 55  */
 56 minplayer.plugin.prototype.construct = function() {
 57 
 58   // Adds this as a plugin.
 59   this.addPlugin();
 60 };
 61 
 62 /**
 63  * Destructor.
 64  */
 65 minplayer.plugin.prototype.destroy = function() {
 66 
 67   // Unbind all events.
 68   this.unbind();
 69 };
 70 
 71 /**
 72  * Loads all of the available plugins.
 73  */
 74 minplayer.plugin.prototype.loadPlugins = function() {
 75 
 76   // Get all the plugins to load.
 77   var instance = '';
 78 
 79   // Iterate through all the plugins.
 80   for (var name in this.options.plugins) {
 81 
 82     // Only load if it does not already exist.
 83     if (!minplayer.instances[this.options.id][name]) {
 84 
 85       // Get the instance name from the setting.
 86       instance = this.options.plugins[name];
 87 
 88       // If this object exists.
 89       if (minplayer[name][instance]) {
 90 
 91         // Declare a new object.
 92         new minplayer[name][instance](this.display, this.options);
 93       }
 94     }
 95   }
 96 };
 97 
 98 /**
 99  * Plugins should call this method when they are ready.
100  */
101 minplayer.plugin.prototype.ready = function() {
102 
103   // Keep this plugin from triggering multiple ready events.
104   if (!this.pluginReady) {
105 
106     // Set the ready flag.
107     this.pluginReady = true;
108 
109     // Now trigger that I am ready.
110     this.trigger('ready');
111 
112     // Check the queue.
113     this.checkQueue();
114   }
115 };
116 
117 /**
118  * Adds a new plugin to this player.
119  *
120  * @param {string} name The name of this plugin.
121  * @param {object} plugin A new plugin object, derived from media.plugin.
122  */
123 minplayer.plugin.prototype.addPlugin = function(name, plugin) {
124   name = name || this.name;
125   plugin = plugin || this;
126 
127   // Make sure the plugin is valid.
128   if (plugin.isValid()) {
129 
130     // If the plugins for this instance do not exist.
131     if (!minplayer.instances[this.options.id]) {
132 
133       // Initialize the instances.
134       minplayer.instances[this.options.id] = {};
135     }
136 
137     // Add this plugin.
138     minplayer.instances[this.options.id][name] = plugin;
139   }
140 };
141 
142 /**
143  * Gets a plugin by name and calls callback when it is ready.
144  *
145  * @param {string} plugin The plugin of the plugin.
146  * @param {function} callback Called when the plugin is ready.
147  * @return {object} The plugin if no callback is provided.
148  */
149 minplayer.plugin.prototype.get = function(plugin, callback) {
150 
151   // Allow this to be called on itself with a single callback.
152   if (typeof plugin === 'function') {
153     this.get(this.name, plugin);
154     return;
155   }
156 
157   // Return the minplayer.get equivalent.
158   return minplayer.get.call(this, this.options.id, plugin, callback);
159 };
160 
161 /**
162  * Check the queue and execute it.
163  */
164 minplayer.plugin.prototype.checkQueue = function() {
165 
166   // Initialize our variables.
167   var q = null, i = 0, check = false, newqueue = [];
168 
169   // Set the lock.
170   minplayer.lock = true;
171 
172   // Iterate through all the queues.
173   var length = minplayer.queue.length;
174   for (i = 0; i < length; i++) {
175 
176     // Get the queue.
177     q = minplayer.queue[i];
178 
179     // Now check to see if this queue is about us.
180     check = !q.id && !q.plugin;
181     check |= (q.plugin == this.name) && (!q.id || (q.id == this.options.id));
182 
183     // If the check passes...
184     if (check) {
185       check = minplayer.bind.call(
186         q.context,
187         q.event,
188         this.options.id,
189         this.name,
190         q.callback
191       );
192     }
193 
194     // Add the queue back if it doesn't check out.
195     if (!check) {
196 
197       // Add this back to the queue.
198       newqueue.push(q);
199     }
200   }
201 
202   // Set the old queue to the new queue.
203   minplayer.queue = newqueue;
204 
205   // Release the lock.
206   minplayer.lock = false;
207 };
208 
209 /**
210  * Trigger a media event.
211  *
212  * @param {string} type The event type.
213  * @param {object} data The event data object.
214  * @return {object} The plugin object.
215  */
216 minplayer.plugin.prototype.trigger = function(type, data) {
217   data = data || {};
218   data.plugin = this;
219 
220   // Add this to our triggered array.
221   this.triggered[type] = data;
222 
223   // Check to make sure the queue for this type exists.
224   if (this.queue[type]) {
225 
226     var i = 0, queue = {};
227 
228     // Iterate through all the callbacks in this queue.
229     for (i in this.queue[type]) {
230 
231       // Setup the event object, and call the callback.
232       queue = this.queue[type][i];
233       queue.callback({target: this, data: queue.data}, data);
234     }
235   }
236 
237   // Return the plugin object.
238   return this;
239 };
240 
241 /**
242  * Bind to a media event.
243  *
244  * @param {string} type The event type.
245  * @param {object} data The data to bind with the event.
246  * @param {function} fn The callback function.
247  * @return {object} The plugin object.
248  **/
249 minplayer.plugin.prototype.bind = function(type, data, fn) {
250 
251   // Allow the data to be the callback.
252   if (typeof data === 'function') {
253     fn = data;
254     data = null;
255   }
256 
257   // You must bind to a specific event and have a callback.
258   if (!type || !fn) {
259     return;
260   }
261 
262   // Initialize the queue for this type.
263   this.queue[type] = this.queue[type] || [];
264 
265   // Unbind any existing equivalent events.
266   this.unbind(type, fn);
267 
268   // Now add this event to the queue.
269   this.queue[type].push({
270     callback: fn,
271     data: data
272   });
273 
274   // Now see if this event has already been triggered.
275   if (this.triggered[type]) {
276 
277     // Go ahead and trigger the event.
278     fn({target: this, data: data}, this.triggered[type]);
279   }
280 
281   // Return the plugin.
282   return this;
283 };
284 
285 /**
286  * Unbind a media event.
287  *
288  * @param {string} type The event type.
289  * @param {function} fn The callback function.
290  * @return {object} The plugin object.
291  **/
292 minplayer.plugin.prototype.unbind = function(type, fn) {
293 
294   // If this is locked then try again after 10ms.
295   if (this.lock) {
296     setTimeout(function() {
297       this.unbind(type, fn);
298     }, 10);
299   }
300 
301   // Set the lock.
302   this.lock = true;
303 
304   if (!type) {
305     this.queue = {};
306   }
307   else if (!fn) {
308     this.queue[type] = [];
309   }
310   else {
311     // Iterate through all the callbacks and search for equal callbacks.
312     var i = 0, queue = {};
313     for (i in this.queue[type]) {
314       if (this.queue[type][i].callback === fn) {
315         queue = this.queue[type].splice(1, 1);
316         delete queue;
317       }
318     }
319   }
320 
321   // Reset the lock.
322   this.lock = false;
323 
324   // Return the plugin.
325   return this;
326 };
327 
328 /**
329  * Adds an item to the queue.
330  *
331  * @param {object} context The context which this is called within.
332  * @param {string} event The event to trigger on.
333  * @param {string} id The player ID.
334  * @param {string} plugin The name of the plugin.
335  * @param {function} callback Called when the event occurs.
336  */
337 minplayer.addQueue = function(context, event, id, plugin, callback) {
338 
339   // See if it is locked...
340   if (!minplayer.lock) {
341     minplayer.queue.push({
342       context: context,
343       id: id,
344       event: event,
345       plugin: plugin,
346       callback: callback
347     });
348   }
349   else {
350 
351     // If so, then try again after 10 milliseconds.
352     setTimeout(function() {
353       minplayer.addQueue(context, id, event, plugin, callback);
354     }, 10);
355   }
356 };
357 
358 /**
359  * Binds an event to a plugin instance, and if it doesn't exist, then caches
360  * it for a later time.
361  *
362  * @param {string} event The event to trigger on.
363  * @param {string} id The player ID.
364  * @param {string} plugin The name of the plugin.
365  * @param {function} callback Called when the event occurs.
366  * @return {boolean} If the bind was successful.
367  * @this The object in context who called this method.
368  */
369 minplayer.bind = function(event, id, plugin, callback) {
370 
371   // If no callback exists, then just return false.
372   if (!callback) {
373     return false;
374   }
375 
376   // Get the instances.
377   var inst = minplayer.instances;
378 
379   // See if this plugin exists.
380   if (inst[id][plugin]) {
381 
382     // If so, then bind the event to this plugin.
383     inst[id][plugin].bind(event, {context: this}, function(event, data) {
384       callback.call(event.data.context, data.plugin);
385     });
386     return true;
387   }
388 
389   // If not, then add it to the queue to bind later.
390   minplayer.addQueue(this, event, id, plugin, callback);
391 
392   // Return that this wasn't handled.
393   return false;
394 };
395 
396 /**
397  * The main API for minPlayer.
398  *
399  * Provided that this function takes three parameters, there are 8 different
400  * ways to use this api.
401  *
402  *   id (0x100) - You want a specific player.
403  *   plugin (0x010) - You want a specific plugin.
404  *   callback (0x001) - You only want it when it is ready.
405  *
406  *   000 - You want all plugins from all players, ready or not.
407  *
408  *          var instances = minplayer.get();
409  *
410  *   001 - You want all plugins from all players, but only when ready.
411  *
412  *          minplayer.get(function(plugin) {
413  *            // Code goes here.
414  *          });
415  *
416  *   010 - You want a specific plugin from all players, ready or not...
417  *
418  *          var medias = minplayer.get(null, 'media');
419  *
420  *   011 - You want a specific plugin from all players, but only when ready.
421  *
422  *          minplayer.get('player', function(player) {
423  *            // Code goes here.
424  *          });
425  *
426  *   100 - You want all plugins from a specific player, ready or not.
427  *
428  *          var plugins = minplayer.get('player_id');
429  *
430  *   101 - You want all plugins from a specific player, but only when ready.
431  *
432  *          minplayer.get('player_id', null, function(plugin) {
433  *            // Code goes here.
434  *          });
435  *
436  *   110 - You want a specific plugin from a specific player, ready or not.
437  *
438  *          var plugin = minplayer.get('player_id', 'media');
439  *
440  *   111 - You want a specific plugin from a specific player, only when ready.
441  *
442  *          minplayer.get('player_id', 'media', function(media) {
443  *            // Code goes here.
444  *          });
445  *
446  * @this The context in which this function was called.
447  * @param {string} id The ID of the widget to get the plugins from.
448  * @param {string} plugin The name of the plugin.
449  * @param {function} callback Called when the plugin is ready.
450  * @return {object} The plugin object if it is immediately available.
451  */
452 minplayer.get = function(id, plugin, callback) {
453 
454   // Normalize the arguments for a better interface.
455   if (typeof id === 'function') {
456     callback = id;
457     plugin = id = null;
458   }
459 
460   if (typeof plugin === 'function') {
461     callback = plugin;
462     plugin = id;
463     id = null;
464   }
465 
466   // Make sure the callback is a callback.
467   callback = (typeof callback === 'function') ? callback : null;
468 
469   // Get the instances.
470   var inst = minplayer.instances;
471 
472   // 0x000
473   if (!id && !plugin && !callback) {
474     return inst;
475   }
476   // 0x100
477   else if (id && !plugin && !callback) {
478     return inst[id];
479   }
480   // 0x110
481   else if (id && plugin && !callback) {
482     return inst[id][plugin];
483   }
484   // 0x111
485   else if (id && plugin && callback) {
486     minplayer.bind.call(this, 'ready', id, plugin, callback);
487   }
488   // 0x011
489   else if (!id && plugin && callback) {
490     for (var id in inst) {
491       minplayer.bind.call(this, 'ready', id, plugin, callback);
492     }
493   }
494   // 0x101
495   else if (id && !plugin && callback) {
496     for (var plugin in inst[id]) {
497       minplayer.bind.call(this, 'ready', id, plugin, callback);
498     }
499   }
500   // 0x010
501   else if (!id && plugin && !callback) {
502     var plugins = {};
503     for (var id in inst) {
504       if (inst.hasOwnProperty(id) && inst[id].hasOwnProperty(plugin)) {
505         plugins[id] = inst[id][plugin];
506       }
507     }
508     return plugins;
509   }
510   // 0x001
511   else {
512     for (var id in inst) {
513       for (var plugin in inst[id]) {
514         minplayer.bind.call(this, 'ready', id, plugin, callback);
515       }
516     }
517   }
518 };
519