mediaelement_rails 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,6 +12,19 @@
12
12
  */
13
13
  if (typeof jQuery != 'undefined') {
14
14
  mejs.$ = jQuery;
15
+ } else if (typeof Zepto != 'undefined') {
16
+ mejs.$ = Zepto;
17
+
18
+ // define `outerWidth` method which has not been realized in Zepto
19
+ Zepto.fn.outerWidth = function(includeMargin) {
20
+ var width = $(this).width();
21
+ if (includeMargin) {
22
+ width += parseInt($(this).css('margin-right'), 10);
23
+ width += parseInt($(this).css('margin-left'), 10);
24
+ }
25
+ return width
26
+ }
27
+
15
28
  } else if (typeof ender != 'undefined') {
16
29
  mejs.$ = ender;
17
30
  }
@@ -60,9 +73,25 @@ if (typeof jQuery != 'undefined') {
60
73
  autoRewind: true,
61
74
  // resize to media dimensions
62
75
  enableAutosize: true,
76
+
77
+ /*
78
+ * Time format to use. Default: 'mm:ss'
79
+ * Supported units:
80
+ * h: hour
81
+ * m: minute
82
+ * s: second
83
+ * f: frame count
84
+ * When using 'hh', 'mm', 'ss' or 'ff' we always display 2 digits.
85
+ * If you use 'h', 'm', 's' or 'f' we display 1 digit if possible.
86
+ *
87
+ * Example to display 75 seconds:
88
+ * Format 'mm:ss': 01:15
89
+ * Format 'm:ss': 1:15
90
+ * Format 'm:s': 1:15
91
+ */
92
+ timeFormat: '',
63
93
  // forces the hour marker (##:00:00)
64
94
  alwaysShowHours: false,
65
-
66
95
  // show framecount in timecode (##:00:00:00)
67
96
  showTimecodeFrameCount: false,
68
97
  // used when showTimecodeFrameCount is set to true
@@ -102,9 +131,9 @@ if (typeof jQuery != 'undefined') {
102
131
  ],
103
132
  action: function(player, media) {
104
133
  if (media.paused || media.ended) {
105
- player.play();
134
+ media.play();
106
135
  } else {
107
- player.pause();
136
+ media.pause();
108
137
  }
109
138
  }
110
139
  },
@@ -217,12 +246,13 @@ if (typeof jQuery != 'undefined') {
217
246
  t.$media = t.$node = $(node);
218
247
  t.node = t.media = t.$media[0];
219
248
 
249
+ if(!t.node) {
250
+ return
251
+ }
252
+
220
253
  // check for existing player
221
254
  if (typeof t.node.player != 'undefined') {
222
255
  return t.node.player;
223
- } else {
224
- // attach player to DOM node for reference
225
- t.node.player = t;
226
256
  }
227
257
 
228
258
 
@@ -234,6 +264,19 @@ if (typeof jQuery != 'undefined') {
234
264
  // extend default options
235
265
  t.options = $.extend({},mejs.MepDefaults,o);
236
266
 
267
+ if (!t.options.timeFormat) {
268
+ // Generate the time format according to options
269
+ t.options.timeFormat = 'mm:ss';
270
+ if (t.options.alwaysShowHours) {
271
+ t.options.timeFormat = 'hh:mm:ss';
272
+ }
273
+ if (t.options.showTimecodeFrameCount) {
274
+ t.options.timeFormat += ':ff';
275
+ }
276
+ }
277
+
278
+ mejs.Utility.calculateTimeFormat(0, t.options, t.options.framesPerSecond || 25);
279
+
237
280
  // unique ID
238
281
  t.id = 'mep_' + mejs.mepIndex++;
239
282
 
@@ -351,6 +394,9 @@ if (typeof jQuery != 'undefined') {
351
394
  // normal way of moving it into place (doesn't work on iOS)
352
395
  t.container.find('.mejs-mediaelement').append(t.$media);
353
396
  }
397
+
398
+ // needs to be assigned here, after iOS remap
399
+ t.node.player = t;
354
400
 
355
401
  // find parts
356
402
  t.controls = t.container.find('.mejs-controls');
@@ -743,6 +789,15 @@ if (typeof jQuery != 'undefined') {
743
789
  t.setControlsSize();
744
790
  }
745
791
  }, false);
792
+
793
+ // Only change the time format when necessary
794
+ var duration = null;
795
+ t.media.addEventListener('timeupdate',function() {
796
+ if (duration !== this.duration) {
797
+ duration = this.duration;
798
+ mejs.Utility.calculateTimeFormat(duration, t.options, t.options.framesPerSecond || 25);
799
+ }
800
+ }, false);
746
801
 
747
802
  t.container.focusout(function (e) {
748
803
  if( e.relatedTarget ) { //FF is working on supporting focusout https://bugzilla.mozilla.org/show_bug.cgi?id=687787
@@ -823,7 +878,7 @@ if (typeof jQuery != 'undefined') {
823
878
  }
824
879
 
825
880
  // detect 100% mode - use currentStyle for IE since css() doesn't return percentages
826
- if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%' || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) {
881
+ if (t.height.toString().indexOf('%') > 0 || (t.$node.css('max-width') !== 'none' && t.$node.css('max-width') !== 't.width') || (t.$node[0].currentStyle && t.$node[0].currentStyle.maxWidth === '100%')) {
827
882
 
828
883
  // do we have the native dimensions yet?
829
884
  var nativeWidth = (function() {
@@ -864,7 +919,7 @@ if (typeof jQuery != 'undefined') {
864
919
  newHeight = parentHeight;
865
920
  }
866
921
 
867
- if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
922
+ if (t.container.parent().length > 0 && t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) {
868
923
  parentWidth = $(window).width();
869
924
  newHeight = $(window).height();
870
925
  }
@@ -907,13 +962,6 @@ if (typeof jQuery != 'undefined') {
907
962
 
908
963
  }
909
964
 
910
- // special case for big play button so it doesn't go over the controls area
911
- var playLayer = t.layers.find('.mejs-overlay-play'),
912
- playButton = playLayer.find('.mejs-overlay-button');
913
-
914
- playLayer.height(t.container.height() - t.controls.height());
915
- playButton.css('margin-top', '-' + (playButton.height()/2 - t.controls.height()/2).toString() + 'px' );
916
-
917
965
  },
918
966
 
919
967
  setControlsSize: function() {
@@ -922,8 +970,6 @@ if (typeof jQuery != 'undefined') {
922
970
  railWidth = 0,
923
971
  rail = t.controls.find('.mejs-time-rail'),
924
972
  total = t.controls.find('.mejs-time-total'),
925
- current = t.controls.find('.mejs-time-current'),
926
- loaded = t.controls.find('.mejs-time-loaded'),
927
973
  others = rail.siblings(),
928
974
  lastControl = others.last(),
929
975
  lastControlPosition = null;
@@ -966,15 +1012,12 @@ if (typeof jQuery != 'undefined') {
966
1012
  total.width(railWidth - (total.outerWidth(true) - total.width()));
967
1013
 
968
1014
  if (lastControl.css('position') != 'absolute') {
969
- lastControlPosition = lastControl.position();
1015
+ lastControlPosition = lastControl.length ? lastControl.position() : null;
970
1016
  railWidth--;
971
1017
  }
972
1018
  } while (lastControlPosition !== null && lastControlPosition.top > 0 && railWidth > 0);
973
1019
 
974
- if (t.setProgressRail)
975
- t.setProgressRail();
976
- if (t.setCurrentRail)
977
- t.setCurrentRail();
1020
+ t.container.trigger('controlsresize');
978
1021
  },
979
1022
 
980
1023
 
@@ -1015,7 +1058,7 @@ if (typeof jQuery != 'undefined') {
1015
1058
  posterImg = posterDiv.find('img');
1016
1059
 
1017
1060
  if (posterImg.length === 0) {
1018
- posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv);
1061
+ posterImg = $('<img width="100%" height="100%" alt="" />').appendTo(posterDiv);
1019
1062
  }
1020
1063
 
1021
1064
  posterImg.attr('src', url);
@@ -1127,11 +1170,12 @@ if (typeof jQuery != 'undefined') {
1127
1170
  }, false);
1128
1171
 
1129
1172
  // error handling
1130
- media.addEventListener('error',function() {
1173
+ media.addEventListener('error',function(e) {
1174
+ t.handleError(e);
1131
1175
  loading.hide();
1132
- controls.find('.mejs-time-buffering').hide();
1176
+ bigPlay.hide();
1133
1177
  error.show();
1134
- error.find('mejs-overlay-error').html("Error loading this resource");
1178
+ error.find('.mejs-overlay-error').html("Error loading this resource");
1135
1179
  }, false);
1136
1180
 
1137
1181
  media.addEventListener('keydown', function(e) {
@@ -1239,6 +1283,8 @@ if (typeof jQuery != 'undefined') {
1239
1283
  },
1240
1284
  remove: function() {
1241
1285
  var t = this, featureIndex, feature;
1286
+
1287
+ t.container.prev('.mejs-offscreen').remove();
1242
1288
 
1243
1289
  // invoke features cleanup
1244
1290
  for (featureIndex in t.options.features) {
@@ -1284,6 +1330,15 @@ if (typeof jQuery != 'undefined') {
1284
1330
  var t = this;
1285
1331
  t.findTracks();
1286
1332
  t.buildtracks(t, t.controls, t.layers, t.media);
1333
+ },
1334
+ resetSize: function(){
1335
+ var t = this;
1336
+ // webkit has trouble doing this without a delay
1337
+ setTimeout(function () {
1338
+ //
1339
+ t.setPlayerSize(t.width, t.height);
1340
+ t.setControlsSize();
1341
+ }, 50);
1287
1342
  }
1288
1343
  };
1289
1344
 
@@ -1433,9 +1488,9 @@ if (typeof jQuery != 'undefined') {
1433
1488
  // STOP BUTTON
1434
1489
  $.extend(MediaElementPlayer.prototype, {
1435
1490
  buildstop: function(player, controls, layers, media) {
1436
- var t = this,
1437
- stop =
1438
- $('<div class="mejs-button mejs-stop-button mejs-stop">' +
1491
+ var t = this;
1492
+
1493
+ $('<div class="mejs-button mejs-stop-button mejs-stop">' +
1439
1494
  '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' +
1440
1495
  '</div>')
1441
1496
  .appendTo(controls)
@@ -1448,8 +1503,8 @@ if (typeof jQuery != 'undefined') {
1448
1503
  media.pause();
1449
1504
  controls.find('.mejs-time-current').width('0px');
1450
1505
  controls.find('.mejs-time-handle').css('left', '0px');
1451
- controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) );
1452
- controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) );
1506
+ controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0, player.options));
1507
+ controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0, player.options));
1453
1508
  layers.find('.mejs-poster').show();
1454
1509
  }
1455
1510
  });
@@ -1472,15 +1527,16 @@ if (typeof jQuery != 'undefined') {
1472
1527
  $('<div class="mejs-time-rail">' +
1473
1528
  '<span class="mejs-time-total mejs-time-slider">' +
1474
1529
  //'<span class="mejs-offscreen">' + this.options.progessHelpText + '</span>' +
1475
- '<span class="mejs-time-buffering"></span>' +
1476
- '<span class="mejs-time-loaded"></span>' +
1477
- '<span class="mejs-time-current"></span>' +
1478
- '<span class="mejs-time-handle"></span>' +
1479
- '<span class="mejs-time-float">' +
1480
- '<span class="mejs-time-float-current">00:00</span>' +
1481
- '<span class="mejs-time-float-corner"></span>' +
1530
+ '<span class="mejs-time-buffering"></span>' +
1531
+ '<span class="mejs-time-loaded"></span>' +
1532
+ '<span class="mejs-time-current"></span>' +
1533
+ '<span class="mejs-time-handle"></span>' +
1534
+ '<span class="mejs-time-float">' +
1535
+ '<span class="mejs-time-float-current">00:00</span>' +
1536
+ '<span class="mejs-time-float-corner"></span>' +
1537
+ '</span>' +
1482
1538
  '</span>' +
1483
- '</div>')
1539
+ '</div>')
1484
1540
  .appendTo(controls);
1485
1541
  controls.find('.mejs-time-buffering').hide();
1486
1542
 
@@ -1503,9 +1559,11 @@ if (typeof jQuery != 'undefined') {
1503
1559
  x;
1504
1560
 
1505
1561
  // mouse or touch position relative to the object
1506
- if (e.originalEvent.changedTouches) {
1562
+ if (e.originalEvent && e.originalEvent.changedTouches) {
1507
1563
  x = e.originalEvent.changedTouches[0].pageX;
1508
- }else{
1564
+ } else if (e.changedTouches) { // for Zepto
1565
+ x = e.changedTouches[0].pageX;
1566
+ } else {
1509
1567
  x = e.pageX;
1510
1568
  }
1511
1569
 
@@ -1528,7 +1586,7 @@ if (typeof jQuery != 'undefined') {
1528
1586
  // position floating time box
1529
1587
  if (!mejs.MediaFeatures.hasTouch) {
1530
1588
  timefloat.css('left', pos);
1531
- timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) );
1589
+ timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime, player.options) );
1532
1590
  timefloat.show();
1533
1591
  }
1534
1592
  }
@@ -1543,7 +1601,7 @@ if (typeof jQuery != 'undefined') {
1543
1601
 
1544
1602
  var seconds = media.currentTime,
1545
1603
  timeSliderText = mejs.i18n.t('Time Slider'),
1546
- time = mejs.Utility.secondsToTimeCode(seconds),
1604
+ time = mejs.Utility.secondsToTimeCode(seconds, player.options),
1547
1605
  duration = media.duration;
1548
1606
 
1549
1607
  slider.attr({
@@ -1678,6 +1736,10 @@ if (typeof jQuery != 'undefined') {
1678
1736
  updateSlider(e);
1679
1737
  }, false);
1680
1738
 
1739
+ t.container.on('controlsresize', function() {
1740
+ player.setProgressRail();
1741
+ player.setCurrentRail();
1742
+ });
1681
1743
 
1682
1744
  // store for later use
1683
1745
  t.loaded = loaded;
@@ -1694,8 +1756,8 @@ if (typeof jQuery != 'undefined') {
1694
1756
 
1695
1757
  // newest HTML5 spec has buffered array (FF4, Webkit)
1696
1758
  if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) {
1697
- // TODO: account for a real array with multiple values (only Firefox 4 has this so far)
1698
- percent = target.buffered.end(0) / target.duration;
1759
+ // account for a real array with multiple values - always read the end of the last buffer
1760
+ percent = target.buffered.end(target.buffered.length - 1) / target.duration;
1699
1761
  }
1700
1762
  // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
1701
1763
  // to be anything other than 0. If the byte count is available we use this instead.
@@ -1738,6 +1800,7 @@ if (typeof jQuery != 'undefined') {
1738
1800
  }
1739
1801
  });
1740
1802
  })(mejs.$);
1803
+
1741
1804
  (function($) {
1742
1805
 
1743
1806
  // options
@@ -1754,8 +1817,7 @@ if (typeof jQuery != 'undefined') {
1754
1817
 
1755
1818
  $('<div class="mejs-time" role="timer" aria-live="off">' +
1756
1819
  '<span class="mejs-currenttime">' +
1757
- (player.options.alwaysShowHours ? '00:' : '') +
1758
- (player.options.showTimecodeFrameCount? '00:00:00':'00:00') +
1820
+ mejs.Utility.secondsToTimeCode(0, player.options) +
1759
1821
  '</span>'+
1760
1822
  '</div>')
1761
1823
  .appendTo(controls);
@@ -1774,10 +1836,7 @@ if (typeof jQuery != 'undefined') {
1774
1836
  if (controls.children().last().find('.mejs-currenttime').length > 0) {
1775
1837
  $(t.options.timeAndDurationSeparator +
1776
1838
  '<span class="mejs-duration">' +
1777
- (t.options.duration > 0 ?
1778
- mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) :
1779
- ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00'))
1780
- ) +
1839
+ mejs.Utility.secondsToTimeCode(t.options.duration, t.options) +
1781
1840
  '</span>')
1782
1841
  .appendTo(controls.find('.mejs-time'));
1783
1842
  } else {
@@ -1787,10 +1846,7 @@ if (typeof jQuery != 'undefined') {
1787
1846
 
1788
1847
  $('<div class="mejs-time mejs-duration-container">'+
1789
1848
  '<span class="mejs-duration">' +
1790
- (t.options.duration > 0 ?
1791
- mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) :
1792
- ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00'))
1793
- ) +
1849
+ mejs.Utility.secondsToTimeCode(t.options.duration, t.options) +
1794
1850
  '</span>' +
1795
1851
  '</div>')
1796
1852
  .appendTo(controls);
@@ -1807,7 +1863,7 @@ if (typeof jQuery != 'undefined') {
1807
1863
  var t = this;
1808
1864
 
1809
1865
  if (t.currenttime) {
1810
- t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25));
1866
+ t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options));
1811
1867
  }
1812
1868
  },
1813
1869
 
@@ -1818,7 +1874,7 @@ if (typeof jQuery != 'undefined') {
1818
1874
  t.container.toggleClass("mejs-long-video", t.media.duration > 3600);
1819
1875
 
1820
1876
  if (t.durationD && (t.options.duration > 0 || t.media.duration)) {
1821
- t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25));
1877
+ t.durationD.html(mejs.Utility.secondsToTimeCode(t.options.duration > 0 ? t.options.duration : t.media.duration, t.options));
1822
1878
  }
1823
1879
  }
1824
1880
  });
@@ -1898,8 +1954,10 @@ if (typeof jQuery != 'undefined') {
1898
1954
  // ajust mute button style
1899
1955
  if (volume === 0) {
1900
1956
  mute.removeClass('mejs-mute').addClass('mejs-unmute');
1957
+ mute.children('button').attr('title', mejs.i18n.t('Unmute')).attr('aria-label', mejs.i18n.t('Unmute'));
1901
1958
  } else {
1902
1959
  mute.removeClass('mejs-unmute').addClass('mejs-mute');
1960
+ mute.children('button').attr('title', mejs.i18n.t('Mute')).attr('aria-label', mejs.i18n.t('Mute'));
1903
1961
  }
1904
1962
 
1905
1963
  // top/left of full size volume slider background
@@ -1945,7 +2003,6 @@ if (typeof jQuery != 'undefined') {
1945
2003
 
1946
2004
  var
1947
2005
  railHeight = volumeTotal.height(),
1948
- totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
1949
2006
  newY = e.pageY - totalOffset.top;
1950
2007
 
1951
2008
  volume = (railHeight - newY) / railHeight;
@@ -2078,25 +2135,25 @@ if (typeof jQuery != 'undefined') {
2078
2135
  }
2079
2136
  updateVolumeSlider(e);
2080
2137
  }, false);
2081
-
2082
- if (t.container.is(':visible')) {
2083
- // set initial volume
2084
- positionVolumeHandle(player.options.startVolume);
2085
-
2086
- // mutes the media and sets the volume icon muted if the initial volume is set to 0
2087
- if (player.options.startVolume === 0) {
2088
- media.setMuted(true);
2089
- }
2090
-
2091
- // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
2092
- if (media.pluginType === 'native') {
2093
- media.setVolume(player.options.startVolume);
2094
- }
2138
+
2139
+ // mutes the media and sets the volume icon muted if the initial volume is set to 0
2140
+ if (player.options.startVolume === 0) {
2141
+ media.setMuted(true);
2095
2142
  }
2143
+
2144
+ // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
2145
+ if (media.pluginType === 'native') {
2146
+ media.setVolume(player.options.startVolume);
2147
+ }
2148
+
2149
+ t.container.on('controlsresize', function() {
2150
+ positionVolumeHandle(media.volume);
2151
+ });
2096
2152
  }
2097
2153
  });
2098
2154
 
2099
2155
  })(mejs.$);
2156
+
2100
2157
  (function($) {
2101
2158
 
2102
2159
  $.extend(mejs.MepDefaults, {
@@ -2143,9 +2200,6 @@ if (typeof jQuery != 'undefined') {
2143
2200
  }
2144
2201
 
2145
2202
  var t = this,
2146
- normalHeight = 0,
2147
- normalWidth = 0,
2148
- container = player.container,
2149
2203
  fullscreenBtn =
2150
2204
  $('<div class="mejs-button mejs-fullscreen-button">' +
2151
2205
  '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' +
@@ -2375,6 +2429,9 @@ if (typeof jQuery != 'undefined') {
2375
2429
  player.exitFullScreen();
2376
2430
  }
2377
2431
  });
2432
+
2433
+ t.normalHeight = 0;
2434
+ t.normalWidth = 0;
2378
2435
 
2379
2436
  },
2380
2437
 
@@ -2399,8 +2456,8 @@ if (typeof jQuery != 'undefined') {
2399
2456
  $(document.documentElement).addClass('mejs-fullscreen');
2400
2457
 
2401
2458
  // store sizing
2402
- normalHeight = t.container.height();
2403
- normalWidth = t.container.width();
2459
+ t.normalHeight = t.container.height();
2460
+ t.normalWidth = t.container.width();
2404
2461
 
2405
2462
  // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now)
2406
2463
  if (t.media.pluginType === 'native') {
@@ -2415,13 +2472,25 @@ if (typeof jQuery != 'undefined') {
2415
2472
  setTimeout(function checkFullscreen() {
2416
2473
 
2417
2474
  if (t.isNativeFullScreen) {
2418
- var zoomMultiplier = window["devicePixelRatio"] || 1;
2475
+ var zoomMultiplier = window["devicePixelRatio"] || 1,
2419
2476
  // Use a percent error margin since devicePixelRatio is a float and not exact.
2420
- var percentErrorMargin = 0.002; // 0.2%
2421
- var windowWidth = zoomMultiplier * $(window).width();
2422
- var screenWidth = screen.width;
2423
- var absDiff = Math.abs(screenWidth - windowWidth);
2424
- var marginError = screenWidth * percentErrorMargin;
2477
+ percentErrorMargin = 0.002, // 0.2%
2478
+ windowWidth = zoomMultiplier * $(window).width(),
2479
+ screenWidth = screen.width,
2480
+ // ** 13twelve
2481
+ // Screen width is sort of useless: http://www.quirksmode.org/blog/archives/2013/11/screenwidth_is.html
2482
+ // My rMBP ignores devicePixelRatio when returning the values, so fullscreen would always fail the "suddenly not fullscreen" test
2483
+ // Theory: the gap between reported values should give us an indication of browser behavior with screen.width and devicePixelRatio
2484
+ zoomedWindowWidth = zoomMultiplier * windowWidth;
2485
+
2486
+ if (Math.abs(screenWidth-windowWidth) > Math.abs(screenWidth-zoomedWindowWidth)) {
2487
+ // screen.width is likely true pixels, not CSS pixels, so we need to use the zoomed window width for comparison
2488
+ windowWidth = zoomedWindowWidth;
2489
+ }
2490
+ // ** / 13twelve
2491
+
2492
+ var absDiff = Math.abs(screenWidth - windowWidth),
2493
+ marginError = screenWidth * percentErrorMargin;
2425
2494
 
2426
2495
  // check if the video is suddenly not really fullscreen
2427
2496
  if (absDiff > marginError) {
@@ -2432,9 +2501,8 @@ if (typeof jQuery != 'undefined') {
2432
2501
  setTimeout(checkFullscreen, 500);
2433
2502
  }
2434
2503
  }
2435
-
2436
-
2437
- }, 500);
2504
+
2505
+ }, 1000);
2438
2506
  }
2439
2507
 
2440
2508
  } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) {
@@ -2516,6 +2584,8 @@ if (typeof jQuery != 'undefined') {
2516
2584
 
2517
2585
  t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%');
2518
2586
  t.container.find('.mejs-captions-position').css('bottom', '45px');
2587
+
2588
+ t.container.trigger('enteredfullscreen');
2519
2589
  },
2520
2590
 
2521
2591
  exitFullScreen: function() {
@@ -2542,25 +2612,24 @@ if (typeof jQuery != 'undefined') {
2542
2612
 
2543
2613
  t.container
2544
2614
  .removeClass('mejs-container-fullscreen')
2545
- .width(normalWidth)
2546
- .height(normalHeight);
2547
- //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1});
2615
+ .width(t.normalWidth)
2616
+ .height(t.normalHeight);
2548
2617
 
2549
2618
  if (t.media.pluginType === 'native') {
2550
2619
  t.$media
2551
- .width(normalWidth)
2552
- .height(normalHeight);
2620
+ .width(t.normalWidth)
2621
+ .height(t.normalHeight);
2553
2622
  } else {
2554
2623
  t.container.find('.mejs-shim')
2555
- .width(normalWidth)
2556
- .height(normalHeight);
2624
+ .width(t.normalWidth)
2625
+ .height(t.normalHeight);
2557
2626
 
2558
- t.media.setVideoSize(normalWidth, normalHeight);
2627
+ t.media.setVideoSize(t.normalWidth, t.normalHeight);
2559
2628
  }
2560
2629
 
2561
2630
  t.layers.children('div')
2562
- .width(normalWidth)
2563
- .height(normalHeight);
2631
+ .width(t.normalWidth)
2632
+ .height(t.normalHeight);
2564
2633
 
2565
2634
  t.fullscreenBtn
2566
2635
  .removeClass('mejs-unfullscreen')
@@ -2571,6 +2640,8 @@ if (typeof jQuery != 'undefined') {
2571
2640
 
2572
2641
  t.container.find('.mejs-captions-text').css('font-size','');
2573
2642
  t.container.find('.mejs-captions-position').css('bottom', '');
2643
+
2644
+ t.container.trigger('exitedfullscreen');
2574
2645
  }
2575
2646
  });
2576
2647
 
@@ -2581,6 +2652,8 @@ if (typeof jQuery != 'undefined') {
2581
2652
  // Speed
2582
2653
  $.extend(mejs.MepDefaults, {
2583
2654
 
2655
+ // We also support to pass object like this:
2656
+ // [{name: 'Slow', value: '0.75'}, {name: 'Normal', value: '1.00'}, ...]
2584
2657
  speeds: ['2.00', '1.50', '1.25', '1.00', '0.75'],
2585
2658
 
2586
2659
  defaultSpeed: '1.00',
@@ -2599,53 +2672,90 @@ if (typeof jQuery != 'undefined') {
2599
2672
  speedButton = null,
2600
2673
  speedSelector = null,
2601
2674
  playbackSpeed = null,
2602
- html = '<div class="mejs-button mejs-speed-button">' +
2603
- '<button type="button">' + t.options.defaultSpeed + t.options.speedChar + '</button>' +
2604
- '<div class="mejs-speed-selector">' +
2605
- '<ul>';
2606
-
2607
- if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) {
2608
- t.options.speeds.push(t.options.defaultSpeed);
2675
+ inputId = null;
2676
+
2677
+ var speeds = [];
2678
+ var defaultInArray = false;
2679
+ for (var i=0, len=t.options.speeds.length; i < len; i++) {
2680
+ var s = t.options.speeds[i];
2681
+ if (typeof(s) === 'string'){
2682
+ speeds.push({
2683
+ name: s + t.options.speedChar,
2684
+ value: s
2685
+ });
2686
+ if(s === t.options.defaultSpeed) {
2687
+ defaultInArray = true;
2688
+ }
2689
+ }
2690
+ else {
2691
+ speeds.push(s);
2692
+ if(s.value === t.options.defaultSpeed) {
2693
+ defaultInArray = true;
2694
+ }
2695
+ }
2696
+ }
2697
+
2698
+ if (!defaultInArray) {
2699
+ speeds.push({
2700
+ name: t.options.defaultSpeed + t.options.speedChar,
2701
+ value: t.options.defaultSpeed
2702
+ });
2609
2703
  }
2610
2704
 
2611
- t.options.speeds.sort(function(a, b) {
2612
- return parseFloat(b) - parseFloat(a);
2705
+ speeds.sort(function(a, b) {
2706
+ return parseFloat(b.value) - parseFloat(a.value);
2613
2707
  });
2614
2708
 
2615
- for (var i = 0, il = t.options.speeds.length; i<il; i++) {
2709
+ var getSpeedNameFromValue = function(value) {
2710
+ for(i=0,len=speeds.length; i <len; i++) {
2711
+ if (speeds[i].value === value) {
2712
+ return speeds[i].name;
2713
+ }
2714
+ }
2715
+ };
2716
+
2717
+ var html = '<div class="mejs-button mejs-speed-button">' +
2718
+ '<button type="button">' + getSpeedNameFromValue(t.options.defaultSpeed) + '</button>' +
2719
+ '<div class="mejs-speed-selector">' +
2720
+ '<ul>';
2721
+
2722
+ for (i = 0, il = speeds.length; i<il; i++) {
2723
+ inputId = t.id + '-speed-' + speeds[i].value;
2616
2724
  html += '<li>' +
2617
2725
  '<input type="radio" name="speed" ' +
2618
- 'value="' + t.options.speeds[i] + '" ' +
2619
- 'id="' + t.options.speeds[i] + '" ' +
2620
- (t.options.speeds[i] == t.options.defaultSpeed ? ' checked' : '') +
2726
+ 'value="' + speeds[i].value + '" ' +
2727
+ 'id="' + inputId + '" ' +
2728
+ (speeds[i].value === t.options.defaultSpeed ? ' checked' : '') +
2621
2729
  ' />' +
2622
- '<label for="' + t.options.speeds[i] + '" ' +
2623
- (t.options.speeds[i] == t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') +
2624
- '>' + t.options.speeds[i] + t.options.speedChar + '</label>' +
2730
+ '<label for="' + inputId + '" ' +
2731
+ (speeds[i].value === t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') +
2732
+ '>' + speeds[i].name + '</label>' +
2625
2733
  '</li>';
2626
2734
  }
2627
2735
  html += '</ul></div></div>';
2628
2736
 
2629
2737
  speedButton = $(html).appendTo(controls);
2630
- speedSelector = speedButton.find('.mejs-speed-selector');
2738
+ speedSelector = speedButton.find('.mejs-speed-selector');
2631
2739
 
2632
- playbackspeed = t.options.defaultSpeed;
2740
+ playbackSpeed = t.options.defaultSpeed;
2633
2741
 
2634
2742
  speedSelector
2635
2743
  .on('click', 'input[type="radio"]', function() {
2636
2744
  var newSpeed = $(this).attr('value');
2637
- playbackspeed = newSpeed;
2745
+ playbackSpeed = newSpeed;
2638
2746
  media.playbackRate = parseFloat(newSpeed);
2639
- speedButton.find('button').html(newSpeed + t.options.speedChar);
2747
+ speedButton.find('button').html(getSpeedNameFromValue(newSpeed));
2640
2748
  speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected');
2641
2749
  speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected');
2642
2750
  });
2643
-
2644
- speedSelector
2645
- .height(
2646
- speedButton.find('.mejs-speed-selector ul').outerHeight(true) +
2647
- speedButton.find('.mejs-speed-translations').outerHeight(true))
2648
- .css('top', (-1 * speedSelector.height()) + 'px');
2751
+ speedButton
2752
+ .one( 'mouseenter focusin', function() {
2753
+ speedSelector
2754
+ .height(
2755
+ speedButton.find('.mejs-speed-selector ul').outerHeight(true) +
2756
+ speedButton.find('.mejs-speed-translations').outerHeight(true))
2757
+ .css('top', (-1 * speedSelector.height()) + 'px');
2758
+ });
2649
2759
  }
2650
2760
  }
2651
2761
  });
@@ -2661,6 +2771,10 @@ if (typeof jQuery != 'undefined') {
2661
2771
 
2662
2772
  tracksText: mejs.i18n.t('Captions/Subtitles'),
2663
2773
 
2774
+ // By default, no WAI-ARIA live region - don't make a
2775
+ // screen reader speak captions over an audio track.
2776
+ tracksAriaLive: false,
2777
+
2664
2778
  // option to remove the [cc] button when no <track kind="subtitles"> are present
2665
2779
  hideCaptionsButtonWhenEmpty: true,
2666
2780
 
@@ -2688,8 +2802,9 @@ if (typeof jQuery != 'undefined') {
2688
2802
  return;
2689
2803
 
2690
2804
  var t = this,
2691
- i,
2692
- options = '';
2805
+ attr = t.options.tracksAriaLive ?
2806
+ 'role="log" aria-live="assertive" aria-atomic="false"' : '',
2807
+ i;
2693
2808
 
2694
2809
  if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide
2695
2810
  for (i = t.domNode.textTracks.length - 1; i >= 0; i--) {
@@ -2701,7 +2816,8 @@ if (typeof jQuery != 'undefined') {
2701
2816
  $('<div class="mejs-chapters mejs-layer"></div>')
2702
2817
  .prependTo(layers).hide();
2703
2818
  player.captions =
2704
- $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover" role="log" aria-live="assertive" aria-atomic="false"><span class="mejs-captions-text"></span></div></div>')
2819
+ $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover" ' +
2820
+ attr + '><span class="mejs-captions-text"></span></div></div>')
2705
2821
  .prependTo(layers).hide();
2706
2822
  player.captionsText = player.captions.find('.mejs-captions-text');
2707
2823
  player.captionsButton =
@@ -2821,6 +2937,10 @@ if (typeof jQuery != 'undefined') {
2821
2937
  }
2822
2938
  });
2823
2939
 
2940
+ t.container.on('controlsresize', function() {
2941
+ t.adjustLanguageBox();
2942
+ });
2943
+
2824
2944
  // check for autoplay
2825
2945
  if (player.node.getAttribute('autoplay') !== null) {
2826
2946
  player.chapters.css('visibility','hidden');
@@ -2872,8 +2992,6 @@ if (typeof jQuery != 'undefined') {
2872
2992
 
2873
2993
  track.isLoaded = true;
2874
2994
 
2875
- // create button
2876
- //t.addTrackButton(track.srclang);
2877
2995
  t.enableTrackButton(track.srclang, track.label);
2878
2996
 
2879
2997
  t.loadNextTrack();
@@ -2908,6 +3026,7 @@ if (typeof jQuery != 'undefined') {
2908
3026
  }
2909
3027
  },
2910
3028
  error: function() {
3029
+ t.removeTrackButton(track.srclang);
2911
3030
  t.loadNextTrack();
2912
3031
  }
2913
3032
  });
@@ -2933,6 +3052,14 @@ if (typeof jQuery != 'undefined') {
2933
3052
 
2934
3053
  t.adjustLanguageBox();
2935
3054
  },
3055
+
3056
+ removeTrackButton: function(lang) {
3057
+ var t = this;
3058
+
3059
+ t.captionsButton.find('input[value=' + lang + ']').closest('li').remove();
3060
+
3061
+ t.adjustLanguageBox();
3062
+ },
2936
3063
 
2937
3064
  addTrackButton: function(lang, label) {
2938
3065
  var t = this;
@@ -2970,7 +3097,7 @@ if (typeof jQuery != 'undefined') {
2970
3097
  // check if any subtitles
2971
3098
  if (t.options.hideCaptionsButtonWhenEmpty) {
2972
3099
  for (i=0; i<t.tracks.length; i++) {
2973
- if (t.tracks[i].kind == 'subtitles') {
3100
+ if (t.tracks[i].kind == 'subtitles' && t.tracks[i].isLoaded) {
2974
3101
  hasSubtitles = true;
2975
3102
  break;
2976
3103
  }
@@ -3116,7 +3243,7 @@ if (typeof jQuery != 'undefined') {
3116
3243
  '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' +
3117
3244
  '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' +
3118
3245
  '<span class="ch-title">' + chapters.entries.text[i] + '</span>' +
3119
- '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' +
3246
+ '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start, t.options) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop, t.options) + '</span>' +
3120
3247
  '</div>' +
3121
3248
  '</div>'));
3122
3249
  usedPercent += percent;
@@ -3266,8 +3393,6 @@ if (typeof jQuery != 'undefined') {
3266
3393
  lines = container.find("p"),
3267
3394
  styleNode = trackText.find("#" + container.attr("style")),
3268
3395
  styles,
3269
- begin,
3270
- end,
3271
3396
  text,
3272
3397
  entries = {text:[], times:[]};
3273
3398
 
@@ -3536,6 +3661,38 @@ $.extend(mejs.MepDefaults,
3536
3661
  });
3537
3662
 
3538
3663
  })(mejs.$);
3664
+ (function($) {
3665
+ // skip back button
3666
+
3667
+ $.extend(mejs.MepDefaults, {
3668
+ skipBackInterval: 30,
3669
+ // %1 will be replaced with skipBackInterval in this string
3670
+ skipBackText: mejs.i18n.t('Skip back %1 seconds')
3671
+ });
3672
+
3673
+ $.extend(MediaElementPlayer.prototype, {
3674
+ buildskipback: function(player, controls, layers, media) {
3675
+ var
3676
+ t = this,
3677
+ // Replace %1 with skip back interval
3678
+ backText = t.options.skipBackText.replace('%1', t.options.skipBackInterval),
3679
+ // create the loop button
3680
+ loop =
3681
+ $('<div class="mejs-button mejs-skip-back-button">' +
3682
+ '<button type="button" aria-controls="' + t.id + '" title="' + backText + '" aria-label="' + backText + '">' + t.options.skipBackInterval + '</button>' +
3683
+ '</div>')
3684
+ // append it to the toolbar
3685
+ .appendTo(controls)
3686
+ // add a click toggle event
3687
+ .click(function() {
3688
+ media.setCurrentTime(Math.max(media.currentTime - t.options.skipBackInterval, 0));
3689
+ $(this).find('button').blur();
3690
+ });
3691
+ }
3692
+ });
3693
+
3694
+ })(mejs.$);
3695
+
3539
3696
  /**
3540
3697
  * Postroll plugin
3541
3698
  */