      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 HTML5 media player implementation.
     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.html5 = function(context, options, queue) {
     17
     18   // Derive players base.
     19   minplayer.players.base.call(this, context, options, queue);
     20 };
     21
     22 /** Derive from minplayer.players.base. */
     23 minplayer.players.html5.prototype = new minplayer.players.base();
     24
     25 /** Reset the constructor. */
     26 minplayer.players.html5.prototype.constructor = minplayer.players.html5;
     27
     28 /**
     29  * @see minplayer.players.base#getPriority
     30  * @param {object} file A {@link minplayer.file} object.
     31  * @return {number} The priority of this media player.
     32  */
     33 minplayer.players.html5.getPriority = function(file) {
     34   return 10;
     35 };
     36
     37 /**
     38  * @see minplayer.players.base#canPlay
     39  * @return {boolean} If this player can play this media type.
     40  */
     41 minplayer.players.html5.canPlay = function(file) {
     42   switch (file.mimetype) {
     43     case 'video/ogg':
     44       return !!minplayer.playTypes.videoOGG;
     45     case 'video/mp4':
     46     case 'video/x-mp4':
     47     case 'video/m4v':
     48     case 'video/x-m4v':
     49       return !!minplayer.playTypes.videoH264;
     50     case 'video/x-webm':
     51     case 'video/webm':
     52     case 'application/octet-stream':
     53       return !!minplayer.playTypes.videoWEBM;
     54     case 'audio/ogg':
     55       return !!minplayer.playTypes.audioOGG;
     56     case 'audio/mpeg':
     57       return !!minplayer.playTypes.audioMP3;
     58     case 'audio/mp4':
     59       return !!minplayer.playTypes.audioMP4;
     60     default:
     61       return false;
     62   }
     63 };
     64
     65 /**
     66  * @see minplayer.plugin.construct
     67  */
     68 minplayer.players.html5.prototype.construct = function() {
     69
     70   // Call base constructor.
     71   minplayer.players.base.prototype.construct.call(this);
     72
     73   // Set the plugin name within the options.
     74   this.options.pluginName = 'html5';
     75
     76   // Add the player events.
     77   this.addPlayerEvents();
     78 };
     79
     80 /**
     81  * Adds a new player event.
     82  *
     83  * @param {string} type The type of event being fired.
     84  * @param {function} callback Called when the event is fired.
     85  */
     86 minplayer.players.html5.prototype.addPlayerEvent = function(type, callback) {
     87   if (this.player) {
     88
     89     // Add an event listener for this event type.
     90     this.player.addEventListener(type, (function(player) {
     91
     92       // Get the function name.
     93       var func = type + 'Event';
     94
     95       // If the callback already exists, then remove it from the player.
     96       if (player[func]) {
     97         player.player.removeEventListener(type, player[func], false);
     98       }
     99
    100       // Create a new callback.
    101       player[func] = function(event) {
    102         callback.call(player, event);
    103       };
    104
    105       // Return the callback.
    106       return player[func];
    107
    108     })(this), false);
    109   }
    110 };
    111
    112 /**
    113  * Add events.
    114  * @return {boolean} If this action was performed.
    115  */
    116 minplayer.players.html5.prototype.addPlayerEvents = function() {
    117
    118   // Check if the player exists.
    119   if (this.player) {
    120
    121     this.addPlayerEvent('abort', function() {
    122       this.trigger('abort');
    123     });
    124     this.addPlayerEvent('loadstart', function() {
    125       this.onReady();
    126     });
    127     this.addPlayerEvent('loadeddata', function() {
    128       this.onLoaded();
    129     });
    130     this.addPlayerEvent('loadedmetadata', function() {
    131       this.onLoaded();
    132     });
    133     this.addPlayerEvent('canplaythrough', function() {
    134       this.onLoaded();
    135     });
    136     this.addPlayerEvent('ended', function() {
    137       this.onComplete();
    138     });
    139     this.addPlayerEvent('pause', function() {
    140       this.onPaused();
    141     });
    142     this.addPlayerEvent('play', function() {
    143       this.onPlaying();
    144     });
    145     this.addPlayerEvent('playing', function() {
    146       this.onPlaying();
    147     });
    148
    149     var errorSent = false;
    150     this.addPlayerEvent('error', function() {
    151       if (!errorSent) {
    152         errorSent = true;
    153         this.trigger('error', 'An error occured - ' + this.player.error.code);
    154       }
    155     });
    156
    157     this.addPlayerEvent('waiting', function() {
    158       this.onWaiting();
    159     });
    160     this.addPlayerEvent('durationchange', function() {
    161       this.duration.set(this.player.duration);
    162       this.trigger('durationchange', {duration: this.player.duration});
    163     });
    164     this.addPlayerEvent('progress', function(event) {
    165       this.bytesTotal.set(event.total);
    166       this.bytesLoaded.set(event.loaded);
    167     });
    168     return true;
    169   }
    170
    171   return false;
    172 };
    173
    174 /**
    175  * @see minplayer.players.base#onReady
    176  */
    177 minplayer.players.html5.prototype.onReady = function() {
    178   minplayer.players.base.prototype.onReady.call(this);
    179
    180   // Android just say we are loaded here.
    181   if (minplayer.isAndroid) {
    182     this.onLoaded();
    183   }
    184
    185   // iOS devices are strange in that they don't autoload.
    186   if (minplayer.isIDevice) {
    187     this.play();
    188     setTimeout((function(player) {
    189       return function() {
    190         player.pause();
    191         player.onLoaded();
    192       };
    193     })(this), 1);
    194   }
    195 };
    196
    197 /**
    198  * @see minplayer.players.base#playerFound
    199  * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
    200  */
    201 minplayer.players.html5.prototype.playerFound = function() {
    202   return (this.display.find(this.mediaFile.type).length > 0);
    203 };
    204
    205 /**
    206  * @see minplayer.players.base#create
    207  * @return {object} The media player entity.
    208  */
    209 minplayer.players.html5.prototype.create = function() {
    210   minplayer.players.base.prototype.create.call(this);
    211   var element = jQuery(document.createElement(this.mediaFile.type))
    212   .attr(this.options.attributes)
    213   .append(
    214     jQuery(document.createElement('source')).attr({
    215       'src': this.mediaFile.path
    216     })
    217   );
    218
    219   // Fix the fluid width and height.
    220   element.eq(0)[0].setAttribute('width', '100%');
    221   element.eq(0)[0].setAttribute('height', '100%');
    222   element.eq(0)[0].setAttribute('autobuffer', true);
    223   var option = this.options.autoload ? 'auto' : 'metadata';
    224   option = minplayer.isIDevice ? 'metadata' : option;
    225   element.eq(0)[0].setAttribute('preload', option);
    226   return element;
    227 };
    228
    229 /**
    230  * @see minplayer.players.base#getPlayer
    231  * @return {object} The media player object.
    232  */
    233 minplayer.players.html5.prototype.getPlayer = function() {
    234   return this.elements.media.eq(0)[0];
    235 };
    236
    237 /**
    238  * @see minplayer.players.base#load
    239  * @return {boolean} If this action was performed.
    240  */
    241 minplayer.players.html5.prototype.load = function(file) {
    242
    243   // See if a load is even necessary.
    244   if (minplayer.players.base.prototype.load.call(this, file)) {
    245
    246     // Get the current source.
    247     var src = this.elements.media.attr('src');
    248     if (!src) {
    249       src = jQuery('source', this.elements.media).eq(0).attr('src');
    250     }
    251
    252     // Only swap out if the new file is different from the source.
    253     if (src != file.path) {
    254
    255       // Add a new player.
    256       this.addPlayer();
    257
    258       // Set the new player.
    259       this.player = this.getPlayer();
    260
    261       // Add the events again.
    262       this.addPlayerEvents();
    263
    264       // Change the source...
    265       var code = '<source src="' + file.path + '"></source>';
    266       this.elements.media.removeAttr('src').empty().html(code);
    267       return true;
    268     }
    269   }
    270
    271   return false;
    272 };
    273
    274 /**
    275  * @see minplayer.players.base#play
    276  * @return {boolean} If this action was performed.
    277  */
    278 minplayer.players.html5.prototype.play = function() {
    279   if (minplayer.players.base.prototype.play.call(this)) {
    280     this.player.play();
    281     return true;
    282   }
    283
    284   return false;
    285 };
    286
    287 /**
    288  * @see minplayer.players.base#pause
    289  * @return {boolean} If this action was performed.
    290  */
    291 minplayer.players.html5.prototype.pause = function() {
    292   if (minplayer.players.base.prototype.pause.call(this)) {
    293     this.player.pause();
    294     return true;
    295   }
    296
    297   return false;
    298 };
    299
    300 /**
    301  * @see minplayer.players.base#stop
    302  * @return {boolean} If this action was performed.
    303  */
    304 minplayer.players.html5.prototype.stop = function() {
    305   if (minplayer.players.base.prototype.stop.call(this)) {
    306     this.player.pause();
    307     this.player.src = '';
    308     return true;
    309   }
    310
    311   return false;
    312 };
    313
    314 /**
    315  * @see minplayer.players.base#seek
    316  * @return {boolean} If this action was performed.
    317  */
    318 minplayer.players.html5.prototype.seek = function(pos) {
    319   if (minplayer.players.base.prototype.seek.call(this, pos)) {
    320     this.player.currentTime = pos;
    321     return true;
    322   }
    323
    324   return false;
    325 };
    326
    327 /**
    328  * @see minplayer.players.base#setVolume
    329  * @return {boolean} If this action was performed.
    330  */
    331 minplayer.players.html5.prototype.setVolume = function(vol) {
    332   if (minplayer.players.base.prototype.setVolume.call(this, vol)) {
    333     this.player.volume = vol;
    334     return true;
    335   }
    336
    337   return false;
    338 };
    339
    340 /**
    341  * @see minplayer.players.base#getVolume
    342  */
    343 minplayer.players.html5.prototype.getVolume = function(callback) {
    344   if (this.isReady()) {
    345     callback(this.player.volume);
    346   }
    347 };
    348
    349 /**
    350  * @see minplayer.players.base#getDuration
    351  */
    352 minplayer.players.html5.prototype.getDuration = function(callback) {
    353   if (this.isReady()) {
    354     callback(this.player.duration);
    355   }
    356 };
    357
    358 /**
    359  * @see minplayer.players.base#getCurrentTime
    360  */
    361 minplayer.players.html5.prototype.getCurrentTime = function(callback) {
    362   if (this.isReady()) {
    363     callback(this.player.currentTime);
    364   }
    365 };
    366
    367 /**
    368  * @see minplayer.players.base#getBytesLoaded
    369  */
    370 minplayer.players.html5.prototype.getBytesLoaded = function(callback) {
    371   if (this.isReady()) {
    372     var loaded = 0;
    373
    374     // Check several different possibilities.
    375     if (this.bytesLoaded.value) {
    376       loaded = this.bytesLoaded.value;
    377     }
    378     else if (this.player.buffered &&
    379         this.player.buffered.length > 0 &&
    380         this.player.buffered.end &&
    381         this.player.duration) {
    382       loaded = this.player.buffered.end(0);
    383     }
    384     else if (this.player.bytesTotal != undefined &&
    385              this.player.bytesTotal > 0 &&
    386              this.player.bufferedBytes != undefined) {
    387       loaded = this.player.bufferedBytes;
    388     }
    389
    390     // Return the loaded amount.
    391     callback(loaded);
    392   }
    393 };
    394
    395 /**
    396  * @see minplayer.players.base#getBytesTotal
    397  */
    398 minplayer.players.html5.prototype.getBytesTotal = function(callback) {
    399   if (this.isReady()) {
    400
    401     var total = 0;
    402
    403     // Check several different possibilities.
    404     if (this.bytesTotal.value) {
    405       total = this.bytesTotal.value;
    406     }
    407     else if (this.player.buffered &&
    408         this.player.buffered.length > 0 &&
    409         this.player.buffered.end &&
    410         this.player.duration) {
    411       total = this.player.duration;
    412     }
    413     else if (this.player.bytesTotal != undefined &&
    414              this.player.bytesTotal > 0 &&
    415              this.player.bufferedBytes != undefined) {
    416       total = this.player.bytesTotal;
    417     }
    418
    419     // Return the loaded amount.
    420     callback(total);
    421   }
    422 };
    423