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