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  */
 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.plugins[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.plugins[this.options.id]) {
132 
133       // Initialize the plugins.
134       minplayer.plugins[this.options.id] = {};
135     }
136 
137     // Add this plugin.
138     minplayer.plugins[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   // If they pass just a callback, then return all plugins when ready.
152   if (typeof plugin === 'function') {
153     callback = plugin;
154     plugin = null;
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     var _this = this;
297     setTimeout(function() {
298       _this.unbind(type, fn);
299     }, 10);
300   }
301 
302   // Set the lock.
303   this.lock = true;
304 
305   if (!type) {
306     this.queue = {};
307   }
308   else if (!fn) {
309     this.queue[type] = [];
310   }
311   else {
312     // Iterate through all the callbacks and search for equal callbacks.
313     var i = 0, queue = {};
314     for (i in this.queue[type]) {
315       if (this.queue[type][i].callback === fn) {
316         queue = this.queue[type].splice(1, 1);
317         delete queue;
318       }
319     }
320   }
321 
322   // Reset the lock.
323   this.lock = false;
324 
325   // Return the plugin.
326   return this;
327 };
328 
329 /**
330  * Adds an item to the queue.
331  *
332  * @param {object} context The context which this is called within.
333  * @param {string} event The event to trigger on.
334  * @param {string} id The player ID.
335  * @param {string} plugin The name of the plugin.
336  * @param {function} callback Called when the event occurs.
337  */
338 minplayer.addQueue = function(context, event, id, plugin, callback) {
339 
340   // See if it is locked...
341   if (!minplayer.lock) {
342     minplayer.queue.push({
343       context: context,
344       id: id,
345       event: event,
346       plugin: plugin,
347       callback: callback
348     });
349   }
350   else {
351 
352     // If so, then try again after 10 milliseconds.
353     setTimeout(function() {
354       minplayer.addQueue(context, id, event, plugin, callback);
355     }, 10);
356   }
357 };
358 
359 /**
360  * Binds an event to a plugin instance, and if it doesn't exist, then caches
361  * it for a later time.
362  *
363  * @param {string} event The event to trigger on.
364  * @param {string} id The player ID.
365  * @param {string} plugin The name of the plugin.
366  * @param {function} callback Called when the event occurs.
367  * @return {boolean} If the bind was successful.
368  * @this The object in context who called this method.
369  */
370 minplayer.bind = function(event, id, plugin, callback) {
371 
372   // If no callback exists, then just return false.
373   if (!callback) {
374     return false;
375   }
376 
377   // Get the plugins.
378   var inst = minplayer.plugins;
379 
380   // See if this plugin exists.
381   if (inst[id][plugin]) {
382 
383     // If so, then bind the event to this plugin.
384     inst[id][plugin].bind(event, {context: this}, function(event, data) {
385       callback.call(event.data.context, data.plugin);
386     });
387     return true;
388   }
389 
390   // If not, then add it to the queue to bind later.
391   minplayer.addQueue(this, event, id, plugin, callback);
392 
393   // Return that this wasn't handled.
394   return false;
395 };
396 
397 /**
398  * The main API for minPlayer.
399  *
400  * Provided that this function takes three parameters, there are 8 different
401  * ways to use this api.
402  *
403  *   id (0x100) - You want a specific player.
404  *   plugin (0x010) - You want a specific plugin.
405  *   callback (0x001) - You only want it when it is ready.
406  *
407  *   000 - You want all plugins from all players, ready or not.
408  *
409  *          var plugins = minplayer.get();
410  *
411  *   001 - You want all plugins from all players, but only when ready.
412  *
413  *          minplayer.get(function(plugin) {
414  *            // Code goes here.
415  *          });
416  *
417  *   010 - You want a specific plugin from all players, ready or not...
418  *
419  *          var medias = minplayer.get(null, 'media');
420  *
421  *   011 - You want a specific plugin from all players, but only when ready.
422  *
423  *          minplayer.get('player', function(player) {
424  *            // Code goes here.
425  *          });
426  *
427  *   100 - You want all plugins from a specific player, ready or not.
428  *
429  *          var plugins = minplayer.get('player_id');
430  *
431  *   101 - You want all plugins from a specific player, but only when ready.
432  *
433  *          minplayer.get('player_id', null, function(plugin) {
434  *            // Code goes here.
435  *          });
436  *
437  *   110 - You want a specific plugin from a specific player, ready or not.
438  *
439  *          var plugin = minplayer.get('player_id', 'media');
440  *
441  *   111 - You want a specific plugin from a specific player, only when ready.
442  *
443  *          minplayer.get('player_id', 'media', function(media) {
444  *            // Code goes here.
445  *          });
446  *
447  * @this The context in which this function was called.
448  * @param {string} id The ID of the widget to get the plugins from.
449  * @param {string} plugin The name of the plugin.
450  * @param {function} callback Called when the plugin is ready.
451  * @return {object} The plugin object if it is immediately available.
452  */
453 minplayer.get = function(id, plugin, callback) {
454 
455   // Normalize the arguments for a better interface.
456   if (typeof id === 'function') {
457     callback = id;
458     plugin = id = null;
459   }
460 
461   if (typeof plugin === 'function') {
462     callback = plugin;
463     plugin = id;
464     id = null;
465   }
466 
467   // Make sure the callback is a callback.
468   callback = (typeof callback === 'function') ? callback : null;
469 
470   // Get the plugins.
471   var plugins = minplayer.plugins;
472 
473   // 0x000
474   if (!id && !plugin && !callback) {
475     return plugins;
476   }
477   // 0x100
478   else if (id && !plugin && !callback) {
479     return plugins[id];
480   }
481   // 0x110
482   else if (id && plugin && !callback) {
483     return plugins[id][plugin];
484   }
485   // 0x111
486   else if (id && plugin && callback) {
487     minplayer.bind.call(this, 'ready', id, plugin, callback);
488   }
489   // 0x011
490   else if (!id && plugin && callback) {
491     for (var id in plugins) {
492       minplayer.bind.call(this, 'ready', id, plugin, callback);
493     }
494   }
495   // 0x101
496   else if (id && !plugin && callback) {
497     for (var plugin in plugins[id]) {
498       minplayer.bind.call(this, 'ready', id, plugin, callback);
499     }
500   }
501   // 0x010
502   else if (!id && plugin && !callback) {
503     var plugin_types = {};
504     for (var id in plugins) {
505       if (plugins.hasOwnProperty(id) && plugins[id].hasOwnProperty(plugin)) {
506         plugin_types[id] = plugins[id][plugin];
507       }
508     }
509     return plugin_types;
510   }
511   // 0x001
512   else {
513     for (var id in plugins) {
514       for (var plugin in plugins[id]) {
515         minplayer.bind.call(this, 'ready', id, plugin, callback);
516       }
517     }
518   }
519 };
520