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 if (!this.options.autoload) { 127 this.onLoaded(); 128 } 129 }); 130 this.addPlayerEvent('loadeddata', function() { 131 this.onLoaded(); 132 }); 133 this.addPlayerEvent('loadedmetadata', function() { 134 this.onLoaded(); 135 }); 136 this.addPlayerEvent('canplaythrough', function() { 137 this.onLoaded(); 138 }); 139 this.addPlayerEvent('ended', function() { 140 this.onComplete(); 141 }); 142 this.addPlayerEvent('pause', function() { 143 this.onPaused(); 144 }); 145 this.addPlayerEvent('play', function() { 146 this.onPlaying(); 147 }); 148 this.addPlayerEvent('playing', function() { 149 this.onPlaying(); 150 }); 151 152 var errorSent = false; 153 this.addPlayerEvent('error', function() { 154 if (!errorSent) { 155 errorSent = true; 156 this.trigger('error', 'An error occured - ' + this.player.error.code); 157 } 158 }); 159 160 this.addPlayerEvent('waiting', function() { 161 this.onWaiting(); 162 }); 163 this.addPlayerEvent('durationchange', function() { 164 this.duration.set(this.player.duration); 165 this.trigger('durationchange', {duration: this.player.duration}); 166 }); 167 this.addPlayerEvent('progress', function(event) { 168 this.bytesTotal.set(event.total); 169 this.bytesLoaded.set(event.loaded); 170 }); 171 return true; 172 } 173 174 return false; 175 }; 176 177 /** 178 * @see minplayer.players.base#onReady 179 */ 180 minplayer.players.html5.prototype.onReady = function() { 181 minplayer.players.base.prototype.onReady.call(this); 182 183 // Android just say we are loaded here. 184 if (minplayer.isAndroid) { 185 this.onLoaded(); 186 } 187 188 // iOS devices are strange in that they don't autoload. 189 if (minplayer.isIDevice) { 190 setTimeout((function(player) { 191 return function() { 192 player.pause(); 193 player.onLoaded(); 194 }; 195 })(this), 1); 196 } 197 }; 198 199 /** 200 * @see minplayer.players.base#playerFound 201 * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise. 202 */ 203 minplayer.players.html5.prototype.playerFound = function() { 204 return (this.display.find(this.mediaFile.type).length > 0); 205 }; 206 207 /** 208 * @see minplayer.players.base#create 209 * @return {object} The media player entity. 210 */ 211 minplayer.players.html5.prototype.create = function() { 212 minplayer.players.base.prototype.create.call(this); 213 var element = jQuery(document.createElement(this.mediaFile.type)) 214 .attr(this.options.attributes) 215 .append( 216 jQuery(document.createElement('source')).attr({ 217 'src': this.mediaFile.path 218 }) 219 ); 220 221 // Fix the fluid width and height. 222 element.eq(0)[0].setAttribute('width', '100%'); 223 element.eq(0)[0].setAttribute('height', '100%'); 224 var option = this.options.autoload ? 'metadata' : 'none'; 225 option = minplayer.isIDevice ? 'metadata' : option; 226 element.eq(0)[0].setAttribute('preload', option); 227 228 // Make sure that we trigger onReady if autoload is false. 229 if (!this.options.autoload) { 230 element.eq(0)[0].setAttribute('autobuffer', false); 231 } 232 233 return element; 234 }; 235 236 /** 237 * @see minplayer.players.base#getPlayer 238 * @return {object} The media player object. 239 */ 240 minplayer.players.html5.prototype.getPlayer = function() { 241 return this.elements.media.eq(0)[0]; 242 }; 243 244 /** 245 * @see minplayer.players.base#load 246 * @return {boolean} If this action was performed. 247 */ 248 minplayer.players.html5.prototype.load = function(file) { 249 250 // See if a load is even necessary. 251 if (minplayer.players.base.prototype.load.call(this, file)) { 252 253 // Get the current source. 254 var src = this.elements.media.attr('src'); 255 if (!src) { 256 src = jQuery('source', this.elements.media).eq(0).attr('src'); 257 } 258 259 // Only swap out if the new file is different from the source. 260 if (src != file.path) { 261 262 // Add a new player. 263 this.addPlayer(); 264 265 // Set the new player. 266 this.player = this.getPlayer(); 267 268 // Add the events again. 269 this.addPlayerEvents(); 270 271 // Change the source... 272 var code = '<source src="' + file.path + '"></source>'; 273 this.elements.media.removeAttr('src').empty().html(code); 274 return true; 275 } 276 } 277 278 return false; 279 }; 280 281 /** 282 * @see minplayer.players.base#play 283 * @return {boolean} If this action was performed. 284 */ 285 minplayer.players.html5.prototype.play = function() { 286 if (minplayer.players.base.prototype.play.call(this)) { 287 this.player.play(); 288 return true; 289 } 290 291 return false; 292 }; 293 294 /** 295 * @see minplayer.players.base#pause 296 * @return {boolean} If this action was performed. 297 */ 298 minplayer.players.html5.prototype.pause = function() { 299 if (minplayer.players.base.prototype.pause.call(this)) { 300 this.player.pause(); 301 return true; 302 } 303 304 return false; 305 }; 306 307 /** 308 * @see minplayer.players.base#stop 309 * @return {boolean} If this action was performed. 310 */ 311 minplayer.players.html5.prototype.stop = function() { 312 if (minplayer.players.base.prototype.stop.call(this)) { 313 this.player.pause(); 314 this.player.src = ''; 315 return true; 316 } 317 318 return false; 319 }; 320 321 /** 322 * @see minplayer.players.base#seek 323 * @return {boolean} If this action was performed. 324 */ 325 minplayer.players.html5.prototype.seek = function(pos) { 326 if (minplayer.players.base.prototype.seek.call(this, pos)) { 327 this.player.currentTime = pos; 328 return true; 329 } 330 331 return false; 332 }; 333 334 /** 335 * @see minplayer.players.base#setVolume 336 * @return {boolean} If this action was performed. 337 */ 338 minplayer.players.html5.prototype.setVolume = function(vol) { 339 if (minplayer.players.base.prototype.setVolume.call(this, vol)) { 340 this.player.volume = vol; 341 return true; 342 } 343 344 return false; 345 }; 346 347 /** 348 * @see minplayer.players.base#getVolume 349 */ 350 minplayer.players.html5.prototype.getVolume = function(callback) { 351 if (this.isReady()) { 352 callback(this.player.volume); 353 } 354 }; 355 356 /** 357 * @see minplayer.players.base#getDuration 358 */ 359 minplayer.players.html5.prototype.getDuration = function(callback) { 360 if (this.isReady()) { 361 this.duration.get(callback); 362 if (this.player.duration) { 363 this.duration.set(this.player.duration); 364 } 365 } 366 }; 367 368 /** 369 * @see minplayer.players.base#getCurrentTime 370 */ 371 minplayer.players.html5.prototype.getCurrentTime = function(callback) { 372 if (this.isReady()) { 373 callback(this.player.currentTime); 374 } 375 }; 376 377 /** 378 * @see minplayer.players.base#getBytesLoaded 379 */ 380 minplayer.players.html5.prototype.getBytesLoaded = function(callback) { 381 if (this.isReady()) { 382 var loaded = 0; 383 384 // Check several different possibilities. 385 if (this.bytesLoaded.value) { 386 loaded = this.bytesLoaded.value; 387 } 388 else if (this.player.buffered && 389 this.player.buffered.length > 0 && 390 this.player.buffered.end && 391 this.player.duration) { 392 loaded = this.player.buffered.end(0); 393 } 394 else if (this.player.bytesTotal != undefined && 395 this.player.bytesTotal > 0 && 396 this.player.bufferedBytes != undefined) { 397 loaded = this.player.bufferedBytes; 398 } 399 400 // Return the loaded amount. 401 callback(loaded); 402 } 403 }; 404 405 /** 406 * @see minplayer.players.base#getBytesTotal 407 */ 408 minplayer.players.html5.prototype.getBytesTotal = function(callback) { 409 if (this.isReady()) { 410 411 var total = 0; 412 413 // Check several different possibilities. 414 if (this.bytesTotal.value) { 415 total = this.bytesTotal.value; 416 } 417 else if (this.player.buffered && 418 this.player.buffered.length > 0 && 419 this.player.buffered.end && 420 this.player.duration) { 421 total = this.player.duration; 422 } 423 else if (this.player.bytesTotal != undefined && 424 this.player.bytesTotal > 0 && 425 this.player.bufferedBytes != undefined) { 426 total = this.player.bytesTotal; 427 } 428 429 // Return the loaded amount. 430 callback(total); 431 } 432 }; 433