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