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           true
259         );
260       }
261 
262       // Add the queue back if it doesn't check out.
263       if (!check) {
264 
265         // Add this back to the queue.
266         newqueue.push(q);
267       }
268     }
269   }
270 
271   // Set the old queue to the new queue.
272   minplayer.queue = newqueue;
273 
274   // Release the lock.
275   minplayer.lock = false;
276 };
277 
278 /**
279  * Trigger a media event.
280  *
281  * @param {string} type The event type.
282  * @param {object} data The event data object.
283  * @return {object} The plugin object.
284  */
285 minplayer.plugin.prototype.trigger = function(type, data) {
286 
287   // Don't trigger if this plugin is inactive.
288   if (!this.active) {
289     return this;
290   }
291 
292   // Add this to our triggered array.
293   this.triggered[type] = data;
294 
295   // Check to make sure the queue for this type exists.
296   if (this.queue.hasOwnProperty(type)) {
297 
298     var i = 0, queue = {}, queuetype = this.queue[type];
299 
300     // Iterate through all the callbacks in this queue.
301     for (i in queuetype) {
302 
303       // Check to make sure the queue index exists.
304       if (queuetype.hasOwnProperty(i)) {
305 
306         // Setup the event object, and call the callback.
307         queue = queuetype[i];
308         queue.callback({target: this, data: queue.data}, data);
309       }
310     }
311   }
312 
313   // Return the plugin object.
314   return this;
315 };
316 
317 /**
318  * Bind to a media event.
319  *
320  * @param {string} type The event type.
321  * @param {object} data The data to bind with the event.
322  * @param {function} fn The callback function.
323  * @return {object} The plugin object.
324  **/
325 minplayer.plugin.prototype.bind = function(type, data, fn) {
326 
327   // Only bind if active.
328   if (!this.active) {
329     return this;
330   }
331 
332   // Allow the data to be the callback.
333   if (typeof data === 'function') {
334     fn = data;
335     data = null;
336   }
337 
338   // You must bind to a specific event and have a callback.
339   if (!type || !fn) {
340     return;
341   }
342 
343   // Initialize the queue for this type.
344   this.queue[type] = this.queue[type] || [];
345 
346   // Unbind any existing equivalent events.
347   this.unbind(type, fn);
348 
349   // Now add this event to the queue.
350   this.queue[type].push({
351     callback: fn,
352     data: data
353   });
354 
355   // Now see if this event has already been triggered.
356   if (this.triggered.hasOwnProperty(type)) {
357 
358     // Go ahead and trigger the event.
359     fn({target: this, data: data}, this.triggered[type]);
360   }
361 
362   // Return the plugin.
363   return this;
364 };
365 
366 /**
367  * Unbind a media event.
368  *
369  * @param {string} type The event type.
370  * @param {function} fn The callback function.
371  * @return {object} The plugin object.
372  **/
373 minplayer.plugin.prototype.unbind = function(type, fn) {
374 
375   // If this is locked then try again after 10ms.
376   if (this.lock) {
377     setTimeout((function(plugin) {
378       return function() {
379         plugin.unbind(type, fn);
380       };
381     })(this), 10);
382   }
383 
384   // Set the lock.
385   this.lock = true;
386 
387   // Get the queue type.
388   var queuetype = this.queue.hasOwnProperty(type) ? this.queue[type] : null;
389 
390   if (!type) {
391     this.queue = {};
392   }
393   else if (!fn) {
394     this.queue[type] = [];
395   }
396   else if (queuetype) {
397     // Iterate through all the callbacks and search for equal callbacks.
398     var i = 0, queue = {};
399     for (i in queuetype) {
400       if (queuetype.hasOwnProperty(i)) {
401         if (queuetype[i].callback === fn) {
402           queue = this.queue[type].splice(i, 1);
403           delete queue;
404         }
405       }
406     }
407   }
408 
409   // Reset the lock.
410   this.lock = false;
411 
412   // Return the plugin.
413   return this;
414 };
415 
416 /**
417  * Adds an item to the queue.
418  *
419  * @param {object} context The context which this is called within.
420  * @param {string} event The event to trigger on.
421  * @param {string} id The player ID.
422  * @param {string} plugin The name of the plugin.
423  * @param {function} callback Called when the event occurs.
424  */
425 minplayer.addQueue = function(context, event, id, plugin, callback) {
426 
427   // See if it is locked...
428   if (!minplayer.lock) {
429     minplayer.queue.push({
430       context: context,
431       id: id,
432       event: event,
433       plugin: plugin,
434       callback: callback
435     });
436   }
437   else {
438 
439     // If so, then try again after 10 milliseconds.
440     setTimeout(function() {
441       minplayer.addQueue(context, id, event, plugin, callback);
442     }, 10);
443   }
444 };
445 
446 /**
447  * Binds an event to a plugin instance, and if it doesn't exist, then caches
448  * it for a later time.
449  *
450  * @param {string} event The event to trigger on.
451  * @param {string} id The player ID.
452  * @param {string} plugin The name of the plugin.
453  * @param {function} callback Called when the event occurs.
454  * @param {boolean} fromCheck If this is from a checkqueue.
455  * @return {boolean} If the bind was successful.
456  * @this The object in context who called this method.
457  */
458 minplayer.bind = function(event, id, plugin, callback, fromCheck) {
459 
460   // If no callback exists, then just return false.
461   if (!callback) {
462     return false;
463   }
464 
465   // Get the plugins.
466   var plugins = minplayer.plugins;
467 
468   // Determine the selected plugins.
469   var selected = [];
470 
471   // Create a quick add.
472   var addSelected = function(id, plugin) {
473     if (plugins.hasOwnProperty(id) && plugins[id].hasOwnProperty(plugin)) {
474       var i = plugins[id][plugin].length;
475       while (i--) {
476         selected.push(plugins[id][plugin][i]);
477       }
478     }
479   };
480 
481   // If they provide id && plugin
482   if (id && plugin) {
483     addSelected(id, plugin);
484   }
485 
486   // If they provide no id but a plugin.
487   else if (!id && plugin) {
488     for (var id in plugins) {
489       addSelected(id, plugin);
490     }
491   }
492 
493   // If they provide an id but no plugin.
494   else if (id && !plugin && plugins[id]) {
495     for (var plugin in plugins[id]) {
496       addSelected(id, plugin);
497     }
498   }
499 
500   // If they provide niether an id or a plugin.
501   else if (!id && !plugin) {
502     for (var id in plugins) {
503       for (var plugin in plugins[id]) {
504         addSelected(id, plugin);
505       }
506     }
507   }
508 
509   // Iterate through the selected plugins and bind.
510   var i = selected.length;
511   while (i--) {
512     selected[i].bind(event, (function(context) {
513       return function(event) {
514         callback.call(context, event.target);
515       };
516     })(this));
517   }
518 
519   // Add it to the queue for post bindings...
520   if ((selected.length == 0) && !fromCheck) {
521     minplayer.addQueue(this, event, id, plugin, callback);
522   }
523 
524   // Return that this wasn't handled.
525   return (selected.length > 0);
526 };
527 
528 /**
529  * The main API for minPlayer.
530  *
531  * Provided that this function takes three parameters, there are 8 different
532  * ways to use this api.
533  *
534  *   id (0x100) - You want a specific player.
535  *   plugin (0x010) - You want a specific plugin.
536  *   callback (0x001) - You only want it when it is ready.
537  *
538  *   000 - You want all plugins from all players, ready or not.
539  *
540  *          var plugins = minplayer.get();
541  *
542  *   001 - You want all plugins from all players, but only when ready.
543  *
544  *          minplayer.get(function(plugin) {
545  *            // Code goes here.
546  *          });
547  *
548  *   010 - You want a specific plugin from all players, ready or not...
549  *
550  *          var medias = minplayer.get(null, 'media');
551  *
552  *   011 - You want a specific plugin from all players, but only when ready.
553  *
554  *          minplayer.get('player', function(player) {
555  *            // Code goes here.
556  *          });
557  *
558  *   100 - You want all plugins from a specific player, ready or not.
559  *
560  *          var plugins = minplayer.get('player_id');
561  *
562  *   101 - You want all plugins from a specific player, but only when ready.
563  *
564  *          minplayer.get('player_id', null, function(plugin) {
565  *            // Code goes here.
566  *          });
567  *
568  *   110 - You want a specific plugin from a specific player, ready or not.
569  *
570  *          var plugin = minplayer.get('player_id', 'media');
571  *
572  *   111 - You want a specific plugin from a specific player, only when ready.
573  *
574  *          minplayer.get('player_id', 'media', function(media) {
575  *            // Code goes here.
576  *          });
577  *
578  * @this The context in which this function was called.
579  * @param {string} id The ID of the widget to get the plugins from.
580  * @param {string} plugin The name of the plugin.
581  * @param {function} callback Called when the plugin is ready.
582  * @return {object} The plugin object if it is immediately available.
583  */
584 minplayer.get = function(id, plugin, callback) {
585 
586   // Normalize the arguments for a better interface.
587   if (typeof id === 'function') {
588     callback = id;
589     plugin = id = null;
590   }
591 
592   if (typeof plugin === 'function') {
593     callback = plugin;
594     plugin = id;
595     id = null;
596   }
597 
598   // Make sure the callback is a callback.
599   callback = (typeof callback === 'function') ? callback : null;
600 
601   // If a callback was provided, then just go ahead and bind.
602   if (callback) {
603     minplayer.bind.call(this, 'ready', id, plugin, callback);
604     return;
605   }
606 
607   // Get the plugins.
608   var plugins = minplayer.plugins;
609 
610   // 0x000
611   if (!id && !plugin && !callback) {
612     return plugins;
613   }
614   // 0x100
615   else if (id && !plugin && !callback) {
616     return plugins[id];
617   }
618   // 0x110
619   else if (id && plugin && !callback) {
620     return plugins[id][plugin];
621   }
622   // 0x010
623   else if (!id && plugin && !callback) {
624     var plugin_types = [];
625     for (var id in plugins) {
626       if (plugins.hasOwnProperty(id) && plugins[id].hasOwnProperty(plugin)) {
627         var i = plugins[id][plugin].length;
628         while (i--) {
629           plugin_types.push(plugins[id][plugin][i]);
630         }
631       }
632     }
633     return plugin_types;
634   }
635 };
636