mediaelement_rails 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  */