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