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