1 /** The minplayer namespace. */ 2 var minplayer = minplayer || {}; 3 4 /** All the media player implementations */ 5 minplayer.players = minplayer.players || {}; 6 7 /** 8 * @constructor 9 * @extends minplayer.display 10 * @class The base media player class where all media players derive from. 11 * 12 * @param {object} context The jQuery context. 13 * @param {object} options This components options. 14 * @param {object} queue The event queue to pass events around. 15 */ 16 minplayer.players.base = function(context, options, queue) { 17 18 // Derive from display 19 minplayer.display.call(this, 'media', context, options, queue); 20 }; 21 22 /** Derive from minplayer.display. */ 23 minplayer.players.base.prototype = new minplayer.display(); 24 25 /** Reset the constructor. */ 26 minplayer.players.base.prototype.constructor = minplayer.players.base; 27 28 /** 29 * @see minplayer.display.getElements 30 * @this minplayer.players.base 31 * @return {object} The elements for this display. 32 */ 33 minplayer.players.base.prototype.getElements = function() { 34 var elements = minplayer.display.prototype.getElements.call(this); 35 return jQuery.extend(elements, { 36 media: this.options.mediaelement 37 }); 38 }; 39 40 41 /** 42 * Get the default options for this plugin. 43 * 44 * @param {object} options The default options for this plugin. 45 */ 46 minplayer.players.base.prototype.defaultOptions = function(options) { 47 options.range = {min: 0, max: 0}; 48 minplayer.display.prototype.defaultOptions.call(this, options); 49 }; 50 51 /** 52 * Get the priority of this media player. 53 * 54 * @param {object} file A {@link minplayer.file} object. 55 * @return {number} The priority of this media player. 56 */ 57 minplayer.players.base.getPriority = function(file) { 58 return 0; 59 }; 60 61 /** 62 * Returns the ID for the media being played. 63 * 64 * @param {object} file A {@link minplayer.file} object. 65 * @return {string} The ID for the provided media. 66 */ 67 minplayer.players.base.getMediaId = function(file) { 68 return ''; 69 }; 70 71 /** 72 * Determine if we can play the media file. 73 * 74 * @param {object} file A {@link minplayer.file} object. 75 * @return {boolean} If this player can play this media type. 76 */ 77 minplayer.players.base.canPlay = function(file) { 78 return false; 79 }; 80 81 /** 82 * @see minplayer.plugin.construct 83 * @this minplayer.players.base 84 */ 85 minplayer.players.base.prototype.construct = function() { 86 87 // Call the media display constructor. 88 minplayer.display.prototype.construct.call(this); 89 90 // Set the poster if it exists. 91 if (this.elements.media) { 92 this.poster = this.elements.media.attr('poster'); 93 } 94 95 // Set the plugin name within the options. 96 this.options.pluginName = 'basePlayer'; 97 98 /** The ready queue for this player. */ 99 this.readyQueue = []; 100 101 /** The currently loaded media file. */ 102 this.mediaFile = this.options.file; 103 104 // Clear the media player. 105 this.clear(); 106 107 // Now setup the media player. 108 this.setupPlayer(); 109 }; 110 111 /** 112 * Sets up a new media player. 113 */ 114 minplayer.players.base.prototype.setupPlayer = function() { 115 116 // Get the player display object. 117 if (!this.playerFound()) { 118 119 // Add the new player. 120 this.addPlayer(); 121 } 122 123 // Get the player object... 124 this.player = this.getPlayer(); 125 126 // Toggle playing if they click. 127 minplayer.click(this.display, (function(player) { 128 return function() { 129 if (player.playing) { 130 player.pause(); 131 } 132 else { 133 player.play(); 134 } 135 }; 136 })(this)); 137 138 // Bind to key events... 139 jQuery(document).bind('keydown', (function(player) { 140 return function(event) { 141 if (player.hasFocus) { 142 event.preventDefault(); 143 switch (event.keyCode) { 144 case 32: // SPACE 145 case 179: // GOOGLE play/pause button. 146 if (player.playing) { 147 player.pause(); 148 } 149 else { 150 player.play(); 151 } 152 break; 153 case 38: // UP 154 player.setVolumeRelative(0.1); 155 break; 156 case 40: // DOWN 157 player.setVolumeRelative(-0.1); 158 break; 159 case 37: // LEFT 160 case 227: // GOOGLE TV REW 161 player.seekRelative(-0.05); 162 break; 163 case 39: // RIGHT 164 case 228: // GOOGLE TV FW 165 player.seekRelative(0.05); 166 break; 167 } 168 } 169 }; 170 })(this)); 171 }; 172 173 /** 174 * Adds the media player. 175 */ 176 minplayer.players.base.prototype.addPlayer = function() { 177 178 // Remove the media element if found 179 if (this.elements.media) { 180 this.elements.media.remove(); 181 } 182 183 // Create a new media player element. 184 this.elements.media = jQuery(this.createPlayer()); 185 this.display.html(this.elements.media); 186 }; 187 188 /** 189 * @see minplayer.plugin.destroy. 190 */ 191 minplayer.players.base.prototype.destroy = function() { 192 minplayer.plugin.prototype.destroy.call(this); 193 this.clear(); 194 }; 195 196 /** 197 * Clears the media player. 198 */ 199 minplayer.players.base.prototype.clear = function() { 200 201 // Reset the ready flag. 202 this.playerReady = false; 203 204 // Reset the player. 205 this.reset(); 206 207 // If the player exists, then unbind all events. 208 if (this.player) { 209 jQuery(this.player).remove(); 210 this.player = null; 211 } 212 }; 213 214 /** 215 * Resets all variables. 216 */ 217 minplayer.players.base.prototype.reset = function() { 218 219 // The duration of the player. 220 this.realDuration = 0; 221 this.duration = new minplayer.async(); 222 223 // The current play time of the player. 224 this.currentTime = new minplayer.async(); 225 226 // The amount of bytes loaded in the player. 227 this.bytesLoaded = new minplayer.async(); 228 229 // The total amount of bytes for the media. 230 this.bytesTotal = new minplayer.async(); 231 232 // The bytes that the download started with. 233 this.bytesStart = new minplayer.async(); 234 235 // The current volume of the player. 236 this.volume = new minplayer.async(); 237 238 // Reset focus. 239 this.hasFocus = false; 240 241 // We are not playing. 242 this.playing = false; 243 244 // We are not loading. 245 this.loading = false; 246 247 // If we are loaded. 248 this.loaded = false; 249 250 // Tell everyone else we reset. 251 this.trigger('pause', null, true); 252 this.trigger('waiting', null, true); 253 this.trigger('progress', {loaded: 0, total: 0, start: 0}, true); 254 this.trigger('timeupdate', {currentTime: 0, duration: 0}, true); 255 }; 256 257 /** 258 * Called when the player is ready to recieve events and commands. 259 */ 260 minplayer.players.base.prototype.onReady = function() { 261 262 // Only continue if we are not already ready. 263 if (this.playerReady) { 264 return; 265 } 266 267 // Set the start and stop of the player. 268 this.setStartStop(); 269 270 // Set the ready flag. 271 this.playerReady = true; 272 273 // Set the volume to the default. 274 this.setVolume(this.options.volume / 100); 275 276 // Setup the progress interval. 277 this.loading = true; 278 279 // Create a poll to get the progress. 280 this.poll('progress', (function(player) { 281 return function() { 282 283 // Only do this if the play interval is set. 284 if (player.loading) { 285 286 // Get the bytes loaded asynchronously. 287 player.getBytesLoaded(function(bytesLoaded) { 288 289 // Get the bytes total asynchronously. 290 player.getBytesTotal(function(bytesTotal) { 291 292 // Trigger an event about the progress. 293 if (bytesLoaded || bytesTotal) { 294 295 // Get the bytes start, but don't require it. 296 var bytesStart = 0; 297 player.getBytesStart(function(val) { 298 bytesStart = val; 299 }); 300 301 // Trigger a progress event. 302 player.trigger('progress', { 303 loaded: bytesLoaded, 304 total: bytesTotal, 305 start: bytesStart 306 }); 307 308 // Say we are not longer loading if they are equal. 309 if (bytesLoaded >= bytesTotal) { 310 player.loading = false; 311 } 312 } 313 }); 314 }); 315 } 316 317 // Keep polling as long as its loading... 318 return player.loading; 319 }; 320 })(this), 1000); 321 322 // We are now ready. 323 this.ready(); 324 325 // Make sure the player is ready or errors will occur. 326 if (this.isReady()) { 327 328 // Iterate through our ready queue. 329 for (var i in this.readyQueue) { 330 this.readyQueue[i].call(this); 331 } 332 333 // Empty the ready queue. 334 this.readyQueue.length = 0; 335 this.readyQueue = []; 336 337 if (!this.loaded) { 338 339 // If we are still loading, then trigger that the load has started. 340 this.trigger('loadstart'); 341 } 342 } 343 else { 344 345 // Empty the ready queue. 346 this.readyQueue.length = 0; 347 this.readyQueue = []; 348 } 349 }; 350 351 /** 352 * Parses a time value into seconds. 353 * 354 * @param string time 355 * The time to parse to seconds. 356 * 357 * @returns {number} 358 * The number of seconds this time represents. 359 */ 360 minplayer.players.base.prototype.parseTime = function(time) { 361 var seconds = 0, minutes = 0, hours = 0; 362 363 if (!time) { 364 return 0; 365 } 366 367 // Convert to string if we need to. 368 if (typeof time != 'string') { 369 time = String(time); 370 } 371 372 // Get the seconds. 373 seconds = time.match(/([0-9]+)s/i); 374 if (seconds) { 375 seconds = parseInt(seconds[1], 10); 376 } 377 378 // Get the minutes. 379 minutes = time.match(/([0-9]+)m/i); 380 if (minutes) { 381 seconds += (parseInt(minutes[1], 10) * 60); 382 } 383 384 // Get the hours. 385 hours = time.match(/([0-9]+)h/i); 386 if (hours) { 387 seconds += (parseInt(hours[1], 10) * 3600); 388 } 389 390 // If no seconds were found, then just use the raw value. 391 if (!seconds) { 392 seconds = time; 393 } 394 395 // Return the seconds from the time. 396 return Number(seconds); 397 }; 398 399 /** 400 * Sets the start and stop points for the media. 401 * 402 * @return {number} The number of seconds we should seek. 403 */ 404 minplayer.players.base.prototype.setStartStop = function() { 405 if (this.startTime) { 406 return this.startTime; 407 } 408 409 this.startTime = 0; 410 this.offsetTime = this.parseTime(this.options.range.min); 411 412 // First check the url for the seek time. 413 if (minplayer.urlVars) { 414 this.startTime = this.parseTime(minplayer.urlVars.seek); 415 } 416 417 // Then check the options range parameter. 418 if (!this.startTime) { 419 this.startTime = this.offsetTime; 420 } 421 422 // Get the stop time. 423 this.stopTime = this.options.range.max ? this.parseTime(this.options.range.max) : 0; 424 425 // Calculate the range. 426 this.mediaRange = this.stopTime - this.offsetTime; 427 if (this.mediaRange < 0) { 428 this.mediaRange = 0; 429 } 430 431 // Return the start time. 432 return this.startTime; 433 }; 434 435 /** 436 * Should be called when the media is playing. 437 */ 438 minplayer.players.base.prototype.onPlaying = function() { 439 440 // See if we need to autoseek. 441 if (!this.playing) { 442 var self = this; 443 this.getDuration(function(duration) { 444 if (self.startTime && (self.startTime < duration)) { 445 self.seek(self.startTime, null, true); 446 if (self.options.autoplay) { 447 self.play(); 448 } 449 } 450 }); 451 } 452 453 // Trigger an event that we are playing. 454 this.trigger('playing'); 455 456 // Say that this player has focus. 457 this.hasFocus = true; 458 459 // Set the playInterval to true. 460 this.playing = true; 461 this.loaded = true; 462 463 // Create a poll to get the timeupate. 464 this.poll('timeupdate', (function(player) { 465 return function() { 466 467 // Only do this if the play interval is set. 468 if (player.playing) { 469 470 // Get the current time asyncrhonously. 471 player.getCurrentTime(function(currentTime) { 472 473 // Get the duration asynchronously. 474 player.getDuration(function(duration) { 475 476 // Convert these to floats. 477 currentTime = parseFloat(currentTime); 478 duration = parseFloat(duration); 479 480 // Trigger an event about the progress. 481 if (currentTime || duration) { 482 483 // Trigger an update event. 484 player.trigger('timeupdate', { 485 currentTime: currentTime, 486 duration: duration 487 }); 488 } 489 }); 490 }); 491 } 492 493 // Keep polling as long as it is playing. 494 return player.playing; 495 }; 496 })(this), 500); 497 }; 498 499 /** 500 * Should be called when the media is paused. 501 */ 502 minplayer.players.base.prototype.onPaused = function() { 503 504 // Trigger an event that we are paused. 505 this.trigger('pause'); 506 507 // Remove focus. 508 this.hasFocus = false; 509 510 // Say we are not playing. 511 this.playing = false; 512 }; 513 514 /** 515 * Should be called when the media is complete. 516 */ 517 minplayer.players.base.prototype.onComplete = function() { 518 if (this.playing) { 519 this.onPaused(); 520 } 521 522 // Stop the intervals. 523 this.playing = false; 524 this.loading = false; 525 this.hasFocus = false; 526 this.trigger('ended'); 527 }; 528 529 /** 530 * Should be called when the media is done loading. 531 */ 532 minplayer.players.base.prototype.onLoaded = function() { 533 534 // See if we are loaded. 535 var isLoaded = this.loaded; 536 537 // If we should autoplay, then just play now. 538 if (!this.loaded && this.options.autoplay) { 539 this.play(); 540 } 541 542 // We are now loaded. 543 this.loaded = true; 544 545 // Trigger this event. 546 this.trigger('loadeddata'); 547 }; 548 549 /** 550 * Should be called when the player is waiting. 551 */ 552 minplayer.players.base.prototype.onWaiting = function() { 553 this.trigger('waiting'); 554 }; 555 556 /** 557 * Called when an error occurs. 558 * 559 * @param {string} errorCode The error that was triggered. 560 */ 561 minplayer.players.base.prototype.onError = function(errorCode) { 562 this.hasFocus = false; 563 this.trigger('error', errorCode); 564 }; 565 566 /** 567 * @see minplayer.players.base#isReady 568 * @return {boolean} Checks to see if the Flash is ready. 569 */ 570 minplayer.players.base.prototype.isReady = function() { 571 572 // Return that the player is set and the ready flag is good. 573 return (this.player && this.playerReady); 574 }; 575 576 /** 577 * Calls the callback when this player is ready. 578 * 579 * @param {function} callback Called when it is done performing this operation. 580 */ 581 minplayer.players.base.prototype.whenReady = function(callback) { 582 583 // If the player is ready, then call the callback. 584 if (this.isReady()) { 585 callback.call(this); 586 } 587 else { 588 589 // Add this to the ready queue. 590 this.readyQueue.push(callback); 591 } 592 }; 593 594 /** 595 * Determines if the player should show the playloader. 596 * 597 * @param {string} preview The preview image. 598 * @return {bool} If this player implements its own playLoader. 599 */ 600 minplayer.players.base.prototype.hasPlayLoader = function(preview) { 601 return false; 602 }; 603 604 /** 605 * Determines if the player should show the controller. 606 * 607 * @return {bool} If this player implements its own controller. 608 */ 609 minplayer.players.base.prototype.hasController = function() { 610 return false; 611 }; 612 613 /** 614 * Returns if the media player is already within the DOM. 615 * 616 * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise. 617 */ 618 minplayer.players.base.prototype.playerFound = function() { 619 return false; 620 }; 621 622 /** 623 * Creates the media player and inserts it in the DOM. 624 * 625 * @return {object} The media player entity. 626 */ 627 minplayer.players.base.prototype.createPlayer = function() { 628 this.reset(); 629 return null; 630 }; 631 632 /** 633 * Returns the media player object. 634 * 635 * @return {object} The media player object. 636 */ 637 minplayer.players.base.prototype.getPlayer = function() { 638 return this.player; 639 }; 640 641 /** 642 * Loads a new media player. 643 * 644 * @param {object} file A {@link minplayer.file} object. 645 * @param {function} callback Called when it is done performing this operation. 646 */ 647 minplayer.players.base.prototype.load = function(file, callback) { 648 649 // Store the media file for future lookup. 650 var isString = (typeof this.mediaFile === 'string'); 651 var path = isString ? this.mediaFile : this.mediaFile.path; 652 if (file && (file.path !== path)) { 653 654 // If the player is not ready, then setup. 655 if (!this.isReady()) { 656 this.setupPlayer(); 657 } 658 659 // Reset the media and set the media file. 660 this.reset(); 661 this.mediaFile = file; 662 if (callback) { 663 callback.call(this); 664 } 665 } 666 667 // We still want to play the song if it isn't playing but has autoplay enabled. 668 else if (this.options.autoplay && !this.playing) { 669 this.play(); 670 } 671 }; 672 673 /** 674 * Play the loaded media file. 675 * 676 * @param {function} callback Called when it is done performing this operation. 677 */ 678 minplayer.players.base.prototype.play = function(callback) { 679 this.options.autoload = true; 680 this.options.autoplay = true; 681 this.whenReady(callback); 682 }; 683 684 /** 685 * Pause the loaded media file. 686 * 687 * @param {function} callback Called when it is done performing this operation. 688 */ 689 minplayer.players.base.prototype.pause = function(callback) { 690 this.whenReady(callback); 691 }; 692 693 /** 694 * Stop the loaded media file. 695 * 696 * @param {function} callback Called when it is done performing this operation. 697 */ 698 minplayer.players.base.prototype.stop = function(callback) { 699 this.playing = false; 700 this.loading = false; 701 this.hasFocus = false; 702 this.whenReady(callback); 703 }; 704 705 /** 706 * Seeks to relative position. 707 * 708 * @param {number} pos Relative position. -1 to 1 (percent), > 1 (seconds). 709 */ 710 minplayer.players.base.prototype.seekRelative = function(pos) { 711 712 // Get the current time asyncrhonously. 713 this.getCurrentTime((function(player) { 714 return function(currentTime) { 715 716 // Get the duration asynchronously. 717 player.getDuration(function(duration) { 718 719 // Only do this if we have a duration. 720 if (duration) { 721 722 // Get the position. 723 var seekPos = 0; 724 if ((pos > -1) && (pos < 1)) { 725 seekPos = ((currentTime / duration) + parseFloat(pos)) * duration; 726 } 727 else { 728 seekPos = (currentTime + parseFloat(pos)); 729 } 730 731 // Set the seek value. 732 player.seek(seekPos); 733 } 734 }); 735 }; 736 })(this)); 737 }; 738 739 /** 740 * Seek the loaded media. 741 * 742 * @param {number} pos The position to seek the minplayer. 0 to 1. 743 * @param {function} callback Called when it is done performing this operation. 744 */ 745 minplayer.players.base.prototype.seek = function(pos, callback, noOffset) { 746 this.whenReady(function() { 747 pos = Number(pos); 748 if (!noOffset) { 749 pos += this.offsetTime; 750 } 751 this._seek(pos); 752 if (callback) { 753 callback.call(this); 754 } 755 }); 756 }; 757 758 minplayer.players.base.prototype._seek = function(pos) {}; 759 760 /** 761 * Set the volume of the loaded minplayer. 762 * 763 * @param {number} vol -1 to 1 - The relative amount to increase or decrease. 764 */ 765 minplayer.players.base.prototype.setVolumeRelative = function(vol) { 766 767 // Get the volume 768 this.getVolume((function(player) { 769 return function(newVol) { 770 newVol += parseFloat(vol); 771 newVol = (newVol < 0) ? 0 : newVol; 772 newVol = (newVol > 1) ? 1 : newVol; 773 player.setVolume(newVol); 774 }; 775 })(this)); 776 }; 777 778 /** 779 * Set the volume of the loaded minplayer. 780 * 781 * @param {number} vol The volume to set the media. 0 to 1. 782 * @param {function} callback Called when it is done performing this operation. 783 */ 784 minplayer.players.base.prototype.setVolume = function(vol, callback) { 785 this.trigger('volumeupdate', vol); 786 this.whenReady(callback); 787 }; 788 789 /** 790 * Gets a value from the player. 791 * 792 * @param {string} getter The getter method on the player. 793 * @param {string} prop The property to use when getting. 794 * @param {function} callback The callback function. 795 */ 796 minplayer.players.base.prototype.getValue = function(method, prop, callback) { 797 this.whenReady(function() { 798 var self = this; 799 this[method](function(value) { 800 if (value !== null) { 801 callback.call(self, value); 802 } 803 else { 804 self[prop].get(callback); 805 } 806 }); 807 }); 808 }; 809 810 /** 811 * Get the volume from the loaded media. 812 * 813 * @param {function} callback Called when the volume is determined. 814 * @return {number} The volume of the media; 0 to 1. 815 */ 816 minplayer.players.base.prototype.getVolume = function(callback) { 817 this.getValue('_getVolume', 'volume', callback); 818 }; 819 820 /** 821 * Implemented by the players to get the current time. 822 * 823 * @param callback 824 * @private 825 */ 826 minplayer.players.base.prototype._getVolume = function(callback) { 827 callback(null); 828 }; 829 830 /** 831 * Get the current time for the media being played. 832 * 833 * @param {function} callback Called when the time is determined. 834 * @return {number} The volume of the media; 0 to 1. 835 */ 836 minplayer.players.base.prototype.getCurrentTime = function(callback) { 837 var self = this; 838 this.getValue('_getCurrentTime', 'currentTime', function(currentTime) { 839 self.setStartStop(); 840 if (self.stopTime && (currentTime > self.stopTime)) { 841 self.stop(function() { 842 self.onComplete(); 843 }); 844 } 845 currentTime -= self.offsetTime; 846 callback(currentTime); 847 }); 848 }; 849 850 /** 851 * Implemented by the players to get the current time. 852 * 853 * @param callback 854 * @private 855 */ 856 minplayer.players.base.prototype._getCurrentTime = function(callback) { 857 callback(null); 858 }; 859 860 /** 861 * Return the duration of the loaded media. 862 * 863 * @param {function} callback Called when the duration is determined. 864 * @return {number} The duration of the loaded media. 865 */ 866 minplayer.players.base.prototype.getDuration = function(callback) { 867 if (this.options.duration) { 868 callback(this.options.duration); 869 } 870 else { 871 var self = this; 872 this.getValue('_getDuration', 'duration', function(duration) { 873 self.setStartStop(); 874 self.realDuration = duration; 875 callback(self.mediaRange ? self.mediaRange : duration); 876 }); 877 } 878 }; 879 880 /** 881 * Implemented by the players to get the duration. 882 * 883 * @param callback 884 * @private 885 */ 886 minplayer.players.base.prototype._getDuration = function(callback) { 887 callback(null); 888 }; 889 890 /** 891 * Return the start bytes for the loaded media. 892 * 893 * @param {function} callback Called when the start bytes is determined. 894 * @return {int} The bytes that were started. 895 */ 896 minplayer.players.base.prototype.getBytesStart = function(callback) { 897 this.getValue('_getBytesStart', 'bytesStart', callback); 898 }; 899 900 /** 901 * Implemented by the players to get the start bytes. 902 * 903 * @param callback 904 * @private 905 */ 906 minplayer.players.base.prototype._getBytesStart = function(callback) { 907 callback(null); 908 }; 909 910 /** 911 * Return the bytes of media loaded. 912 * 913 * @param {function} callback Called when the bytes loaded is determined. 914 * @return {int} The amount of bytes loaded. 915 */ 916 minplayer.players.base.prototype.getBytesLoaded = function(callback) { 917 this.getValue('_getBytesLoaded', 'bytesLoaded', callback); 918 }; 919 920 /** 921 * Implemented by the players to get the loaded bytes. 922 * 923 * @param callback 924 * @private 925 */ 926 minplayer.players.base.prototype._getBytesLoaded = function(callback) { 927 callback(null); 928 }; 929 930 /** 931 * Return the total amount of bytes. 932 * 933 * @param {function} callback Called when the bytes total is determined. 934 * @return {int} The total amount of bytes for this media. 935 */ 936 minplayer.players.base.prototype.getBytesTotal = function(callback) { 937 this.getValue('_getBytesTotal', 'bytesTotal', callback); 938 }; 939 940 /** 941 * Implemented by the players to get the total bytes. 942 * 943 * @param callback 944 * @private 945 */ 946 minplayer.players.base.prototype._getBytesTotal = function(callback) { 947 callback(null); 948 }; 949