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