      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