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