annotate johndyer-mediaelement-13fa20a/src/js/mep-player.js @ 25:4a4bd554b4c1 tip

Closing this sub branch.
author Daniele Barchiesi <daniele.barchiesi@eecs.qmul.ac.uk>
date Mon, 25 Mar 2013 14:02:54 +0000
parents 032bc65ebafc
children
rev   line source
gyorgy@0 1 (function ($) {
gyorgy@0 2
gyorgy@0 3 // default player values
gyorgy@0 4 mejs.MepDefaults = {
gyorgy@0 5 // url to poster (to fix iOS 3.x)
gyorgy@0 6 poster: '',
gyorgy@0 7 // default if the <video width> is not specified
gyorgy@0 8 defaultVideoWidth: 480,
gyorgy@0 9 // default if the <video height> is not specified
gyorgy@0 10 defaultVideoHeight: 270,
gyorgy@0 11 // if set, overrides <video width>
gyorgy@0 12 videoWidth: -1,
gyorgy@0 13 // if set, overrides <video height>
gyorgy@0 14 videoHeight: -1,
gyorgy@0 15 // width of audio player
gyorgy@0 16 audioWidth: 400,
gyorgy@0 17 // height of audio player
gyorgy@0 18 audioHeight: 30,
gyorgy@0 19 // initial volume when the player starts (overrided by user cookie)
gyorgy@0 20 startVolume: 0.8,
gyorgy@0 21 // useful for <audio> player loops
gyorgy@0 22 loop: false,
gyorgy@0 23 // resize to media dimensions
gyorgy@0 24 enableAutosize: true,
gyorgy@0 25 // forces the hour marker (##:00:00)
gyorgy@0 26 alwaysShowHours: false,
gyorgy@0 27 // Hide controls when playing and mouse is not over the video
gyorgy@0 28 alwaysShowControls: false,
gyorgy@0 29 // force iPad's native controls
gyorgy@0 30 iPadUseNativeControls: true,
gyorgy@0 31 // features to show
gyorgy@0 32 features: ['playpause','current','progress','duration','tracks','volume','fullscreen']
gyorgy@0 33 };
gyorgy@0 34
gyorgy@0 35 mejs.mepIndex = 0;
gyorgy@0 36
gyorgy@0 37 // wraps a MediaElement object in player controls
gyorgy@0 38 mejs.MediaElementPlayer = function(node, o) {
gyorgy@0 39 // enforce object, even without "new" (via John Resig)
gyorgy@0 40 if ( !(this instanceof mejs.MediaElementPlayer) ) {
gyorgy@0 41 return new mejs.MediaElementPlayer(node, o);
gyorgy@0 42 }
gyorgy@0 43
gyorgy@0 44 var
gyorgy@0 45 t = this,
gyorgy@0 46 mf = mejs.MediaFeatures;
gyorgy@0 47
gyorgy@0 48 // create options
gyorgy@0 49 t.options = $.extend({},mejs.MepDefaults,o);
gyorgy@0 50
gyorgy@0 51 // these will be reset after the MediaElement.success fires
gyorgy@0 52 t.$media = t.$node = $(node);
gyorgy@0 53 t.node = t.media = t.$media[0];
gyorgy@0 54
gyorgy@0 55 // check for existing player
gyorgy@0 56 if (typeof t.node.player != 'undefined') {
gyorgy@0 57 return t.node.player;
gyorgy@0 58 } else {
gyorgy@0 59 // attach player to DOM node for reference
gyorgy@0 60 t.node.player = t;
gyorgy@0 61 }
gyorgy@0 62
gyorgy@0 63 t.init();
gyorgy@0 64
gyorgy@0 65 return t;
gyorgy@0 66 };
gyorgy@0 67
gyorgy@0 68 // actual player
gyorgy@0 69 mejs.MediaElementPlayer.prototype = {
gyorgy@0 70 init: function() {
gyorgy@0 71
gyorgy@0 72 var
gyorgy@0 73 t = this,
gyorgy@0 74 mf = mejs.MediaFeatures,
gyorgy@0 75 // options for MediaElement (shim)
gyorgy@0 76 meOptions = $.extend(true, {}, t.options, {
gyorgy@0 77 success: function(media, domNode) { t.meReady(media, domNode); },
gyorgy@0 78 error: function(e) { t.handleError(e);}
gyorgy@0 79 });
gyorgy@0 80
gyorgy@0 81 t.isVideo = (t.media.tagName.toLowerCase() !== 'audio' && !t.options.isVideo);
gyorgy@0 82
gyorgy@0 83 // use native controls in iPad, iPhone, and Android
gyorgy@0 84 if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) {
gyorgy@0 85 // add controls and stop
gyorgy@0 86 t.$media.attr('controls', 'controls');
gyorgy@0 87
gyorgy@0 88 // attempt to fix iOS 3 bug
gyorgy@0 89 t.$media.removeAttr('poster');
gyorgy@0 90
gyorgy@0 91 // override Apple's autoplay override for iPads
gyorgy@0 92 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
gyorgy@0 93 t.media.load();
gyorgy@0 94 t.media.play();
gyorgy@0 95 }
gyorgy@0 96
gyorgy@0 97 } else if (mf.isAndroid && t.isVideo) {
gyorgy@0 98
gyorgy@0 99 // leave default player
gyorgy@0 100
gyorgy@0 101 } else {
gyorgy@0 102
gyorgy@0 103 // DESKTOP: use MediaElementPlayer controls
gyorgy@0 104
gyorgy@0 105 // remove native controls
gyorgy@0 106 t.$media.removeAttr('controls');
gyorgy@0 107
gyorgy@0 108 // unique ID
gyorgy@0 109 t.id = 'mep_' + mejs.mepIndex++;
gyorgy@0 110
gyorgy@0 111 // build container
gyorgy@0 112 t.container =
gyorgy@0 113 $('<div id="' + t.id + '" class="mejs-container">'+
gyorgy@0 114 '<div class="mejs-inner">'+
gyorgy@0 115 '<div class="mejs-mediaelement"></div>'+
gyorgy@0 116 '<div class="mejs-layers"></div>'+
gyorgy@0 117 '<div class="mejs-controls"></div>'+
gyorgy@0 118 '<div class="mejs-clear"></div>'+
gyorgy@0 119 '</div>' +
gyorgy@0 120 '</div>')
gyorgy@0 121 .addClass(t.$media[0].className)
gyorgy@0 122 .insertBefore(t.$media);
gyorgy@0 123
gyorgy@0 124 // move the <video/video> tag into the right spot
gyorgy@0 125 t.container.find('.mejs-mediaelement').append(t.$media);
gyorgy@0 126
gyorgy@0 127 // find parts
gyorgy@0 128 t.controls = t.container.find('.mejs-controls');
gyorgy@0 129 t.layers = t.container.find('.mejs-layers');
gyorgy@0 130
gyorgy@0 131 // determine the size
gyorgy@0 132 if (t.isVideo) {
gyorgy@0 133 // priority = videoWidth (forced), width attribute, defaultVideoWidth
gyorgy@0 134 t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth;
gyorgy@0 135 t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight;
gyorgy@0 136 } else {
gyorgy@0 137 t.width = t.options.audioWidth;
gyorgy@0 138 t.height = t.options.audioHeight;
gyorgy@0 139 }
gyorgy@0 140
gyorgy@0 141 // set the size, while we wait for the plugins to load below
gyorgy@0 142 t.setPlayerSize(t.width, t.height);
gyorgy@0 143
gyorgy@0 144 // create MediaElementShim
gyorgy@0 145 meOptions.pluginWidth = t.height;
gyorgy@0 146 meOptions.pluginHeight = t.width;
gyorgy@0 147 }
gyorgy@0 148
gyorgy@0 149 // create MediaElement shim
gyorgy@0 150 mejs.MediaElement(t.$media[0], meOptions);
gyorgy@0 151 },
gyorgy@0 152
gyorgy@0 153 // Sets up all controls and events
gyorgy@0 154 meReady: function(media, domNode) {
gyorgy@0 155
gyorgy@0 156
gyorgy@0 157 var t = this,
gyorgy@0 158 mf = mejs.MediaFeatures,
gyorgy@0 159 f,
gyorgy@0 160 feature;
gyorgy@0 161
gyorgy@0 162 // make sure it can't create itself again if a plugin reloads
gyorgy@0 163 if (t.created)
gyorgy@0 164 return;
gyorgy@0 165 else
gyorgy@0 166 t.created = true;
gyorgy@0 167
gyorgy@0 168 t.media = media;
gyorgy@0 169 t.domNode = domNode;
gyorgy@0 170
gyorgy@0 171 if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) {
gyorgy@0 172
gyorgy@0 173 // two built in features
gyorgy@0 174 t.buildposter(t, t.controls, t.layers, t.media);
gyorgy@0 175 t.buildoverlays(t, t.controls, t.layers, t.media);
gyorgy@0 176
gyorgy@0 177 // grab for use by features
gyorgy@0 178 t.findTracks();
gyorgy@0 179
gyorgy@0 180 // add user-defined features/controls
gyorgy@0 181 for (f in t.options.features) {
gyorgy@0 182 feature = t.options.features[f];
gyorgy@0 183 if (t['build' + feature]) {
gyorgy@0 184 //try {
gyorgy@0 185 t['build' + feature](t, t.controls, t.layers, t.media);
gyorgy@0 186 //} catch (e) {
gyorgy@0 187 // TODO: report control error
gyorgy@0 188 //throw e;
gyorgy@0 189 //console.log('error building ' + feature);
gyorgy@0 190 //console.log(e);
gyorgy@0 191 //}
gyorgy@0 192 }
gyorgy@0 193 }
gyorgy@0 194
gyorgy@0 195 t.container.trigger('controlsready');
gyorgy@0 196
gyorgy@0 197 // reset all layers and controls
gyorgy@0 198 t.setPlayerSize(t.width, t.height);
gyorgy@0 199 t.setControlsSize();
gyorgy@0 200
gyorgy@0 201
gyorgy@0 202 // controls fade
gyorgy@0 203 if (t.isVideo) {
gyorgy@0 204 // show/hide controls
gyorgy@0 205 t.container
gyorgy@0 206 .bind('mouseenter', function () {
gyorgy@0 207 if (!t.options.alwaysShowControls) {
gyorgy@0 208 t.controls.css('visibility','visible');
gyorgy@0 209 t.controls.stop(true, true).fadeIn(200);
gyorgy@0 210 }
gyorgy@0 211 })
gyorgy@0 212 .bind('mouseleave', function () {
gyorgy@0 213 if (!t.media.paused && !t.options.alwaysShowControls) {
gyorgy@0 214 t.controls.stop(true, true).fadeOut(200, function() {
gyorgy@0 215 $(this).css('visibility','hidden');
gyorgy@0 216 $(this).css('display','block');
gyorgy@0 217 });
gyorgy@0 218 }
gyorgy@0 219 });
gyorgy@0 220
gyorgy@0 221 // check for autoplay
gyorgy@0 222 if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) {
gyorgy@0 223 t.controls.css('visibility','hidden');
gyorgy@0 224 }
gyorgy@0 225
gyorgy@0 226 // resizer
gyorgy@0 227 if (t.options.enableAutosize) {
gyorgy@0 228 t.media.addEventListener('loadedmetadata', function(e) {
gyorgy@0 229 // if the <video height> was not set and the options.videoHeight was not set
gyorgy@0 230 // then resize to the real dimensions
gyorgy@0 231 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
gyorgy@0 232 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
gyorgy@0 233 t.setControlsSize();
gyorgy@0 234 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
gyorgy@0 235 }
gyorgy@0 236 }, false);
gyorgy@0 237 }
gyorgy@0 238 }
gyorgy@0 239
gyorgy@0 240 // ended for all
gyorgy@0 241 t.media.addEventListener('ended', function (e) {
gyorgy@0 242 t.media.setCurrentTime(0);
gyorgy@0 243 t.media.pause();
gyorgy@0 244
gyorgy@0 245 if (t.setProgressRail)
gyorgy@0 246 t.setProgressRail();
gyorgy@0 247 if (t.setCurrentRail)
gyorgy@0 248 t.setCurrentRail();
gyorgy@0 249
gyorgy@0 250 if (t.options.loop) {
gyorgy@0 251 t.media.play();
gyorgy@0 252 } else if (!t.options.alwaysShowControls) {
gyorgy@0 253 t.controls.css('visibility','visible');
gyorgy@0 254 }
gyorgy@0 255 }, true);
gyorgy@0 256
gyorgy@0 257 // resize on the first play
gyorgy@0 258 t.media.addEventListener('loadedmetadata', function(e) {
gyorgy@0 259 if (t.updateDuration) {
gyorgy@0 260 t.updateDuration();
gyorgy@0 261 }
gyorgy@0 262 if (t.updateCurrent) {
gyorgy@0 263 t.updateCurrent();
gyorgy@0 264 }
gyorgy@0 265
gyorgy@0 266 t.setControlsSize();
gyorgy@0 267 }, true);
gyorgy@0 268
gyorgy@0 269
gyorgy@0 270 // webkit has trouble doing this without a delay
gyorgy@0 271 setTimeout(function () {
gyorgy@0 272 t.setControlsSize();
gyorgy@0 273 t.setPlayerSize(t.width, t.height);
gyorgy@0 274 }, 50);
gyorgy@0 275
gyorgy@0 276 }
gyorgy@0 277
gyorgy@0 278
gyorgy@0 279 if (t.options.success) {
gyorgy@0 280 t.options.success(t.media, t.domNode);
gyorgy@0 281 }
gyorgy@0 282 },
gyorgy@0 283
gyorgy@0 284 handleError: function(e) {
gyorgy@0 285 // Tell user that the file cannot be played
gyorgy@0 286 if (this.options.error) {
gyorgy@0 287 this.options.error(e);
gyorgy@0 288 }
gyorgy@0 289 },
gyorgy@0 290
gyorgy@0 291 setPlayerSize: function(width,height) {
gyorgy@0 292 var t = this;
gyorgy@0 293
gyorgy@0 294 // ie9 appears to need this (jQuery bug?)
gyorgy@0 295 t.width = parseInt(width, 10);
gyorgy@0 296 t.height = parseInt(height, 10);
gyorgy@0 297
gyorgy@0 298 t.container
gyorgy@0 299 .width(t.width)
gyorgy@0 300 .height(t.height);
gyorgy@0 301
gyorgy@0 302 t.layers.children('.mejs-layer')
gyorgy@0 303 .width(t.width)
gyorgy@0 304 .height(t.height);
gyorgy@0 305 },
gyorgy@0 306
gyorgy@0 307 setControlsSize: function() {
gyorgy@0 308 var t = this,
gyorgy@0 309 usedWidth = 0,
gyorgy@0 310 railWidth = 0,
gyorgy@0 311 rail = t.controls.find('.mejs-time-rail'),
gyorgy@0 312 total = t.controls.find('.mejs-time-total'),
gyorgy@0 313 current = t.controls.find('.mejs-time-current'),
gyorgy@0 314 loaded = t.controls.find('.mejs-time-loaded');
gyorgy@0 315 others = rail.siblings();
gyorgy@0 316
gyorgy@0 317 // find the size of all the other controls besides the rail
gyorgy@0 318 others.each(function() {
gyorgy@0 319 if ($(this).css('position') != 'absolute') {
gyorgy@0 320 usedWidth += $(this).outerWidth(true);
gyorgy@0 321 }
gyorgy@0 322 });
gyorgy@0 323 // fit the rail into the remaining space
gyorgy@0 324 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false));
gyorgy@0 325
gyorgy@0 326 // outer area
gyorgy@0 327 rail.width(railWidth);
gyorgy@0 328 // dark space
gyorgy@0 329 total.width(railWidth - (total.outerWidth(true) - total.width()));
gyorgy@0 330
gyorgy@0 331 if (t.setProgressRail)
gyorgy@0 332 t.setProgressRail();
gyorgy@0 333 if (t.setCurrentRail)
gyorgy@0 334 t.setCurrentRail();
gyorgy@0 335 },
gyorgy@0 336
gyorgy@0 337
gyorgy@0 338 buildposter: function(player, controls, layers, media) {
gyorgy@0 339 var poster =
gyorgy@0 340 $('<div class="mejs-poster mejs-layer">'+
gyorgy@0 341 '<img />'+
gyorgy@0 342 '</div>')
gyorgy@0 343 .appendTo(layers),
gyorgy@0 344 posterUrl = player.$media.attr('poster'),
gyorgy@0 345 posterImg = poster.find('img').width(player.width).height(player.height);
gyorgy@0 346
gyorgy@0 347 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
gyorgy@0 348 if (player.options.poster != '') {
gyorgy@0 349 posterImg.attr('src',player.options.poster);
gyorgy@0 350 // second, try the real poster
gyorgy@0 351 } else if (posterUrl !== '' && posterUrl != null) {
gyorgy@0 352 posterImg.attr('src',posterUrl);
gyorgy@0 353 } else {
gyorgy@0 354 poster.remove();
gyorgy@0 355 }
gyorgy@0 356
gyorgy@0 357 media.addEventListener('play',function() {
gyorgy@0 358 poster.hide();
gyorgy@0 359 }, false);
gyorgy@0 360 },
gyorgy@0 361
gyorgy@0 362 buildoverlays: function(player, controls, layers, media) {
gyorgy@0 363 if (!player.isVideo)
gyorgy@0 364 return;
gyorgy@0 365
gyorgy@0 366 var
gyorgy@0 367 loading =
gyorgy@0 368 $('<div class="mejs-overlay mejs-layer">'+
gyorgy@0 369 '<div class="mejs-overlay-loading"><span></span></div>'+
gyorgy@0 370 '</div>')
gyorgy@0 371 .hide() // start out hidden
gyorgy@0 372 .appendTo(layers),
gyorgy@0 373 error =
gyorgy@0 374 $('<div class="mejs-overlay mejs-layer">'+
gyorgy@0 375 '<div class="mejs-overlay-error"></div>'+
gyorgy@0 376 '</div>')
gyorgy@0 377 .hide() // start out hidden
gyorgy@0 378 .appendTo(layers),
gyorgy@0 379
gyorgy@0 380 // this needs to come last so it's on top
gyorgy@0 381 bigPlay =
gyorgy@0 382 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
gyorgy@0 383 '<div class="mejs-overlay-button"></div>'+
gyorgy@0 384 '</div>')
gyorgy@0 385 .appendTo(layers)
gyorgy@0 386 .click(function() {
gyorgy@0 387 if (media.paused) {
gyorgy@0 388 media.play();
gyorgy@0 389 } else {
gyorgy@0 390 media.pause();
gyorgy@0 391 }
gyorgy@0 392 });
gyorgy@0 393
gyorgy@0 394
gyorgy@0 395 // show/hide big play button
gyorgy@0 396 media.addEventListener('play',function() {
gyorgy@0 397 bigPlay.hide();
gyorgy@0 398 error.hide();
gyorgy@0 399 }, false);
gyorgy@0 400 media.addEventListener('pause',function() {
gyorgy@0 401 bigPlay.show();
gyorgy@0 402 }, false);
gyorgy@0 403
gyorgy@0 404 // show/hide loading
gyorgy@0 405 media.addEventListener('loadstart',function() {
gyorgy@0 406 // for some reason Chrome is firing this event
gyorgy@0 407 if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
gyorgy@0 408 return;
gyorgy@0 409
gyorgy@0 410 loading.show();
gyorgy@0 411 }, false);
gyorgy@0 412 media.addEventListener('canplay',function() {
gyorgy@0 413 loading.hide();
gyorgy@0 414 }, false);
gyorgy@0 415
gyorgy@0 416 // error handling
gyorgy@0 417 media.addEventListener('error',function() {
gyorgy@0 418 loading.hide();
gyorgy@0 419 error.show();
gyorgy@0 420 error.find('mejs-overlay-error').html("Error loading this resource");
gyorgy@0 421 }, false);
gyorgy@0 422 },
gyorgy@0 423
gyorgy@0 424 findTracks: function() {
gyorgy@0 425 var t = this,
gyorgy@0 426 tracktags = t.$media.find('track');
gyorgy@0 427
gyorgy@0 428 // store for use by plugins
gyorgy@0 429 t.tracks = [];
gyorgy@0 430 tracktags.each(function() {
gyorgy@0 431 t.tracks.push({
gyorgy@0 432 srclang: $(this).attr('srclang').toLowerCase(),
gyorgy@0 433 src: $(this).attr('src'),
gyorgy@0 434 kind: $(this).attr('kind'),
gyorgy@0 435 entries: [],
gyorgy@0 436 isLoaded: false
gyorgy@0 437 });
gyorgy@0 438 });
gyorgy@0 439 },
gyorgy@0 440 changeSkin: function(className) {
gyorgy@0 441 this.container[0].className = 'mejs-container ' + className;
gyorgy@0 442 this.setPlayerSize();
gyorgy@0 443 this.setControlsSize();
gyorgy@0 444 },
gyorgy@0 445 play: function() {
gyorgy@0 446 this.media.play();
gyorgy@0 447 },
gyorgy@0 448 pause: function() {
gyorgy@0 449 this.media.pause();
gyorgy@0 450 },
gyorgy@0 451 load: function() {
gyorgy@0 452 this.media.load();
gyorgy@0 453 },
gyorgy@0 454 setMuted: function(muted) {
gyorgy@0 455 this.media.setMuted(muted);
gyorgy@0 456 },
gyorgy@0 457 setCurrentTime: function(time) {
gyorgy@0 458 this.media.setCurrentTime(time);
gyorgy@0 459 },
gyorgy@0 460 getCurrentTime: function() {
gyorgy@0 461 return this.media.currentTime;
gyorgy@0 462 },
gyorgy@0 463 setVolume: function(volume) {
gyorgy@0 464 this.media.setVolume(volume);
gyorgy@0 465 },
gyorgy@0 466 getVolume: function() {
gyorgy@0 467 return this.media.volume;
gyorgy@0 468 },
gyorgy@0 469 setSrc: function(src) {
gyorgy@0 470 this.media.setSrc(src);
gyorgy@0 471 }
gyorgy@0 472 };
gyorgy@0 473
gyorgy@0 474 // turn into jQuery plugin
gyorgy@0 475 if (typeof jQuery != 'undefined') {
gyorgy@0 476 jQuery.fn.mediaelementplayer = function (options) {
gyorgy@0 477 return this.each(function () {
gyorgy@0 478 new mejs.MediaElementPlayer(this, options);
gyorgy@0 479 });
gyorgy@0 480 };
gyorgy@0 481 }
gyorgy@0 482
gyorgy@0 483 // push out to window
gyorgy@0 484 window.MediaElementPlayer = mejs.MediaElementPlayer;
gyorgy@0 485
gyorgy@0 486 })(mejs.$);