videojs_rails 4.6.4 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80a4f30faab8c97afa8b8b44242e568a3139cbca
4
- data.tar.gz: 56b077ebc2e96e658bd682030c5350ac8d1c0573
3
+ metadata.gz: c91579dfef47310e8313775c8ce6e3802316052f
4
+ data.tar.gz: b6a514289bfd81adff7796b8d6cf352a62bd2dda
5
5
  SHA512:
6
- metadata.gz: 8d4aac4ce74e012b2fd30731a9378805e04472a02d95a49cb3b3909ea46ce5c08148131a19a0647664672903be1628b9c2ee1263f72b13672818ccd35c92679a
7
- data.tar.gz: 2274ede9d80bfdc4f1d198078d05c0ec51e8f62dad233e047f6cef91d69ada4974707c38bfaaea2de8216e49f911a46f47e55fbb4a2fa65a09e80226b316113e
6
+ metadata.gz: 7edd09f2fe6c10c64475e83a16f65f22349f3b11ddecd213ae23b26e83b0b68f6eb36dfa19925da0a1c3478869c6efd35138a760bc711731aa589e5f828a4281
7
+ data.tar.gz: 340981a79f646701e48ea8a986559ee7c8e6e2a6c102da3cb1bcaf9eeb843c2eebc1f79fde1dc2445d8a2837611b9ad22250b08d093e849cc57e81094c0e9d6e
@@ -1,3 +1,3 @@
1
1
  module VideojsRails
2
- VERSION = '4.6.4'
2
+ VERSION = '4.7.0'
3
3
  end
@@ -60,11 +60,10 @@ var vjs = function(id, options, ready){
60
60
  };
61
61
 
62
62
  // Extended name, also available externally, window.videojs
63
- var videojs = vjs;
64
- window.videojs = window.vjs = vjs;
63
+ var videojs = window['videojs'] = vjs;
65
64
 
66
65
  // CDN Version. Used to target right flash swf.
67
- vjs.CDN_VERSION = '4.6';
66
+ vjs.CDN_VERSION = '4.7';
68
67
  vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
69
68
 
70
69
  /**
@@ -104,12 +103,17 @@ vjs.options = {
104
103
  'errorDisplay': {}
105
104
  },
106
105
 
106
+ 'language': document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
107
+
108
+ // locales and their language translations
109
+ 'languages': {},
110
+
107
111
  // Default message to show when a video cannot be played.
108
112
  'notSupportedMessage': 'No compatible source was found for this video.'
109
113
  };
110
114
 
111
115
  // Set CDN Version of swf
112
- // The added (+) blocks the replace from changing this 4.6 string
116
+ // The added (+) blocks the replace from changing this 4.7 string
113
117
  if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
114
118
  videojs.options['flash']['swf'] = "<%= asset_path('video-js.swf') %>";
115
119
  }
@@ -276,11 +280,15 @@ vjs.CoreObject.create = function(){
276
280
  * and adds a generic handler to the element's event,
277
281
  * along with a unique id (guid) to the element.
278
282
  * @param {Element|Object} elem Element or object to bind listeners to
279
- * @param {String} type Type of event to bind to.
283
+ * @param {String|Array} type Type of event to bind to.
280
284
  * @param {Function} fn Event listener.
281
285
  * @private
282
286
  */
283
287
  vjs.on = function(elem, type, fn){
288
+ if (vjs.obj.isArray(type)) {
289
+ return _handleMultipleEvents(vjs.on, elem, type, fn);
290
+ }
291
+
284
292
  var data = vjs.getData(elem);
285
293
 
286
294
  // We need a place to store all our handler data
@@ -318,9 +326,9 @@ vjs.on = function(elem, type, fn){
318
326
  }
319
327
 
320
328
  if (data.handlers[type].length == 1) {
321
- if (document.addEventListener) {
329
+ if (elem.addEventListener) {
322
330
  elem.addEventListener(type, data.dispatcher, false);
323
- } else if (document.attachEvent) {
331
+ } else if (elem.attachEvent) {
324
332
  elem.attachEvent('on' + type, data.dispatcher);
325
333
  }
326
334
  }
@@ -329,7 +337,7 @@ vjs.on = function(elem, type, fn){
329
337
  /**
330
338
  * Removes event listeners from an element
331
339
  * @param {Element|Object} elem Object to remove listeners from
332
- * @param {String=} type Type of listener to remove. Don't include to remove all events from element.
340
+ * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
333
341
  * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
334
342
  * @private
335
343
  */
@@ -342,6 +350,10 @@ vjs.off = function(elem, type, fn) {
342
350
  // If no events exist, nothing to unbind
343
351
  if (!data.handlers) { return; }
344
352
 
353
+ if (vjs.obj.isArray(type)) {
354
+ return _handleMultipleEvents(vjs.off, elem, type, fn);
355
+ }
356
+
345
357
  // Utility function
346
358
  var removeType = function(t){
347
359
  data.handlers[t] = [];
@@ -393,9 +405,9 @@ vjs.cleanUpEvents = function(elem, type) {
393
405
  // Setting to null was causing an error with data.handlers
394
406
 
395
407
  // Remove the meta-handler from the element
396
- if (document.removeEventListener) {
408
+ if (elem.removeEventListener) {
397
409
  elem.removeEventListener(type, data.dispatcher, false);
398
- } else if (document.detachEvent) {
410
+ } else if (elem.detachEvent) {
399
411
  elem.detachEvent('on' + type, data.dispatcher);
400
412
  }
401
413
  }
@@ -529,8 +541,8 @@ vjs.fixEvent = function(event) {
529
541
 
530
542
  /**
531
543
  * Trigger an event for an element
532
- * @param {Element|Object} elem Element to trigger an event on
533
- * @param {String} event Type of event to trigger
544
+ * @param {Element|Object} elem Element to trigger an event on
545
+ * @param {Event|Object|String} event A string (the type) or an event object with a type attribute
534
546
  * @private
535
547
  */
536
548
  vjs.trigger = function(elem, event) {
@@ -602,18 +614,36 @@ vjs.trigger = function(elem, event) {
602
614
  /**
603
615
  * Trigger a listener only once for an event
604
616
  * @param {Element|Object} elem Element or object to
605
- * @param {String} type
617
+ * @param {String|Array} type
606
618
  * @param {Function} fn
607
619
  * @private
608
620
  */
609
621
  vjs.one = function(elem, type, fn) {
622
+ if (vjs.obj.isArray(type)) {
623
+ return _handleMultipleEvents(vjs.one, elem, type, fn);
624
+ }
610
625
  var func = function(){
611
626
  vjs.off(elem, type, func);
612
627
  fn.apply(this, arguments);
613
628
  };
629
+ // copy the guid to the new function so it can removed using the original function's ID
614
630
  func.guid = fn.guid = fn.guid || vjs.guid++;
615
631
  vjs.on(elem, type, func);
616
632
  };
633
+
634
+ /**
635
+ * Loops through an array of event types and calls the requested method for each type.
636
+ * @param {Function} fn The event method we want to use.
637
+ * @param {Element|Object} elem Element or object to bind listeners to
638
+ * @param {String} type Type of event to bind to.
639
+ * @param {Function} callback Event listener.
640
+ * @private
641
+ */
642
+ function _handleMultipleEvents(fn, elem, type, callback) {
643
+ vjs.arr.forEach(type, function(type) {
644
+ fn(elem, type, callback); //Call the event method for each one of the types
645
+ });
646
+ }
617
647
  var hasOwnProp = Object.prototype.hasOwnProperty;
618
648
 
619
649
  /**
@@ -624,29 +654,29 @@ var hasOwnProp = Object.prototype.hasOwnProperty;
624
654
  * @private
625
655
  */
626
656
  vjs.createEl = function(tagName, properties){
627
- var el, propName;
628
-
629
- el = document.createElement(tagName || 'div');
630
-
631
- for (propName in properties){
632
- if (hasOwnProp.call(properties, propName)) {
633
- //el[propName] = properties[propName];
634
- // Not remembering why we were checking for dash
635
- // but using setAttribute means you have to use getAttribute
636
-
637
- // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
638
- // The additional check for "role" is because the default method for adding attributes does not
639
- // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
640
- // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
641
- // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
642
-
643
- if (propName.indexOf('aria-') !== -1 || propName=='role') {
644
- el.setAttribute(propName, properties[propName]);
645
- } else {
646
- el[propName] = properties[propName];
647
- }
657
+ var el;
658
+
659
+ tagName = tagName || 'div';
660
+ properties = properties || {};
661
+
662
+ el = document.createElement(tagName);
663
+
664
+ vjs.obj.each(properties, function(propName, val){
665
+ // Not remembering why we were checking for dash
666
+ // but using setAttribute means you have to use getAttribute
667
+
668
+ // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
669
+ // The additional check for "role" is because the default method for adding attributes does not
670
+ // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
671
+ // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
672
+ // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
673
+ if (propName.indexOf('aria-') !== -1 || propName == 'role') {
674
+ el.setAttribute(propName, val);
675
+ } else {
676
+ el[propName] = val;
648
677
  }
649
- }
678
+ });
679
+
650
680
  return el;
651
681
  };
652
682
 
@@ -776,6 +806,17 @@ vjs.obj.isPlain = function(obj){
776
806
  && obj.constructor === Object;
777
807
  };
778
808
 
809
+ /**
810
+ * Check if an object is Array
811
+ * Since instanceof Array will not work on arrays created in another frame we need to use Array.isArray, but since IE8 does not support Array.isArray we need this shim
812
+ * @param {Object} obj Object to check
813
+ * @return {Boolean} True if plain, false otherwise
814
+ * @private
815
+ */
816
+ vjs.obj.isArray = Array.isArray || function(arr) {
817
+ return Object.prototype.toString.call(arr) === '[object Array]';
818
+ };
819
+
779
820
  /**
780
821
  * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
781
822
  It also stores a unique id on the function so it can be easily removed from events
@@ -996,6 +1037,22 @@ vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
996
1037
 
997
1038
  vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
998
1039
 
1040
+ /**
1041
+ * Apply attributes to an HTML element.
1042
+ * @param {Element} el Target element.
1043
+ * @param {Object=} attributes Element attributes to be applied.
1044
+ * @private
1045
+ */
1046
+ vjs.setElementAttributes = function(el, attributes){
1047
+ vjs.obj.each(attributes, function(attrName, attrValue) {
1048
+ if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1049
+ el.removeAttribute(attrName);
1050
+ } else {
1051
+ el.setAttribute(attrName, (attrValue === true ? '' : attrValue));
1052
+ }
1053
+ });
1054
+ };
1055
+
999
1056
  /**
1000
1057
  * Get an element's attribute values, as defined on the HTML tag
1001
1058
  * Attributs are not the same as properties. They're defined on the tag
@@ -1005,7 +1062,7 @@ vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && doc
1005
1062
  * @return {Object}
1006
1063
  * @private
1007
1064
  */
1008
- vjs.getAttributeValues = function(tag){
1065
+ vjs.getElementAttributes = function(tag){
1009
1066
  var obj, knownBooleans, attrs, attrName, attrVal;
1010
1067
 
1011
1068
  obj = {};
@@ -1448,6 +1505,31 @@ vjs.findPosition = function(el) {
1448
1505
  top: vjs.round(top)
1449
1506
  };
1450
1507
  };
1508
+
1509
+ /**
1510
+ * Array functions container
1511
+ * @type {Object}
1512
+ * @private
1513
+ */
1514
+ vjs.arr = {};
1515
+
1516
+ /*
1517
+ * Loops through an array and runs a function for each item inside it.
1518
+ * @param {Array} array The array
1519
+ * @param {Function} callback The function to be run for each item
1520
+ * @param {*} thisArg The `this` binding of callback
1521
+ * @returns {Array} The array
1522
+ * @private
1523
+ */
1524
+ vjs.arr.forEach = function(array, callback, thisArg) {
1525
+ if (vjs.obj.isArray(array) && callback instanceof Function) {
1526
+ for (var i = 0, len = array.length; i < len; ++i) {
1527
+ callback.call(thisArg || vjs, array[i], i, array);
1528
+ }
1529
+ }
1530
+
1531
+ return array;
1532
+ };
1451
1533
  /**
1452
1534
  * Utility functions namespace
1453
1535
  * @namespace
@@ -1456,10 +1538,9 @@ vjs.findPosition = function(el) {
1456
1538
  vjs.util = {};
1457
1539
 
1458
1540
  /**
1459
- * Merge two options objects,
1460
- * recursively merging any plain object properties as well.
1461
- * Previously `deepMerge`
1462
- *
1541
+ * Merge two options objects, recursively merging any plain object properties as
1542
+ * well. Previously `deepMerge`
1543
+ *
1463
1544
  * @param {Object} obj1 Object to override values in
1464
1545
  * @param {Object} obj2 Overriding object
1465
1546
  * @return {Object} New object -- obj1 and obj2 will be untouched
@@ -1685,6 +1766,15 @@ vjs.Component.prototype.createEl = function(tagName, attributes){
1685
1766
  return vjs.createEl(tagName, attributes);
1686
1767
  };
1687
1768
 
1769
+ vjs.Component.prototype.localize = function(string){
1770
+ var lang = this.player_.language(),
1771
+ languages = this.player_.languages();
1772
+ if (languages && languages[lang] && languages[lang][string]) {
1773
+ return languages[lang][string];
1774
+ }
1775
+ return string;
1776
+ };
1777
+
1688
1778
  /**
1689
1779
  * Get the component's DOM element
1690
1780
  *
@@ -1961,7 +2051,7 @@ vjs.Component.prototype.initChildren = function(){
1961
2051
 
1962
2052
  if (children) {
1963
2053
  // Allow for an array of children details to passed in the options
1964
- if (children instanceof Array) {
2054
+ if (vjs.obj.isArray(children)) {
1965
2055
  for (var i = 0; i < children.length; i++) {
1966
2056
  child = children[i];
1967
2057
 
@@ -2053,13 +2143,13 @@ vjs.Component.prototype.one = function(type, fn) {
2053
2143
  * Trigger an event on an element
2054
2144
  *
2055
2145
  * myComponent.trigger('eventName');
2146
+ * myComponent.trigger({'type':'eventName'});
2056
2147
  *
2057
- * @param {String} type The event type to trigger, e.g. 'click'
2058
- * @param {Event|Object} event The event object to be passed to the listener
2059
- * @return {vjs.Component} self
2148
+ * @param {Event|Object|String} event A string (the type) or an event object with a type attribute
2149
+ * @return {vjs.Component} self
2060
2150
  */
2061
- vjs.Component.prototype.trigger = function(type, event){
2062
- vjs.trigger(this.el_, type, event);
2151
+ vjs.Component.prototype.trigger = function(event){
2152
+ vjs.trigger(this.el_, event);
2063
2153
  return this;
2064
2154
  };
2065
2155
 
@@ -2515,7 +2605,7 @@ vjs.Button.prototype.createEl = function(type, props){
2515
2605
 
2516
2606
  this.controlText_ = vjs.createEl('span', {
2517
2607
  className: 'vjs-control-text',
2518
- innerHTML: this.buttonText || 'Need Text'
2608
+ innerHTML: this.localize(this.buttonText) || 'Need Text'
2519
2609
  });
2520
2610
 
2521
2611
  this.contentEl_.appendChild(this.controlText_);
@@ -2580,6 +2670,10 @@ vjs.Slider = vjs.Component.extend({
2580
2670
  player.on(this.playerEvent, vjs.bind(this, this.update));
2581
2671
 
2582
2672
  this.boundEvents = {};
2673
+
2674
+
2675
+ this.boundEvents.move = vjs.bind(this, this.onMouseMove);
2676
+ this.boundEvents.end = vjs.bind(this, this.onMouseUp);
2583
2677
  }
2584
2678
  });
2585
2679
 
@@ -2601,9 +2695,7 @@ vjs.Slider.prototype.createEl = function(type, props) {
2601
2695
  vjs.Slider.prototype.onMouseDown = function(event){
2602
2696
  event.preventDefault();
2603
2697
  vjs.blockTextSelection();
2604
-
2605
- this.boundEvents.move = vjs.bind(this, this.onMouseMove);
2606
- this.boundEvents.end = vjs.bind(this, this.onMouseUp);
2698
+ this.addClass('vjs-sliding');
2607
2699
 
2608
2700
  vjs.on(document, 'mousemove', this.boundEvents.move);
2609
2701
  vjs.on(document, 'mouseup', this.boundEvents.end);
@@ -2613,8 +2705,13 @@ vjs.Slider.prototype.onMouseDown = function(event){
2613
2705
  this.onMouseMove(event);
2614
2706
  };
2615
2707
 
2708
+ // To be overridden by a subclass
2709
+ vjs.Slider.prototype.onMouseMove = function(){};
2710
+
2616
2711
  vjs.Slider.prototype.onMouseUp = function() {
2617
2712
  vjs.unblockTextSelection();
2713
+ this.removeClass('vjs-sliding');
2714
+
2618
2715
  vjs.off(document, 'mousemove', this.boundEvents.move, false);
2619
2716
  vjs.off(document, 'mouseup', this.boundEvents.end, false);
2620
2717
  vjs.off(document, 'touchmove', this.boundEvents.move, false);
@@ -2670,7 +2767,9 @@ vjs.Slider.prototype.update = function(){
2670
2767
  }
2671
2768
 
2672
2769
  // Set the new bar width
2673
- bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
2770
+ if (bar) {
2771
+ bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
2772
+ }
2674
2773
  };
2675
2774
 
2676
2775
  vjs.Slider.prototype.calculateDistance = function(event){
@@ -2681,7 +2780,7 @@ vjs.Slider.prototype.calculateDistance = function(event){
2681
2780
  boxW = boxH = el.offsetWidth;
2682
2781
  handle = this.handle;
2683
2782
 
2684
- if (this.options_.vertical) {
2783
+ if (this.options()['vertical']) {
2685
2784
  boxY = box.top;
2686
2785
 
2687
2786
  if (event.changedTouches) {
@@ -2727,10 +2826,10 @@ vjs.Slider.prototype.onFocus = function(){
2727
2826
  };
2728
2827
 
2729
2828
  vjs.Slider.prototype.onKeyPress = function(event){
2730
- if (event.which == 37) { // Left Arrow
2829
+ if (event.which == 37 || event.which == 40) { // Left and Down Arrows
2731
2830
  event.preventDefault();
2732
2831
  this.stepBack();
2733
- } else if (event.which == 39) { // Right Arrow
2832
+ } else if (event.which == 38 || event.which == 39) { // Up and Right Arrows
2734
2833
  event.preventDefault();
2735
2834
  this.stepForward();
2736
2835
  }
@@ -3152,7 +3251,7 @@ for (var errNum = 0; errNum < vjs.MediaError.errorTypes.length; errNum++) {
3152
3251
  * var myPlayer = videojs('example_video_1');
3153
3252
  * ```
3154
3253
  *
3155
- * In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
3254
+ * In the following example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
3156
3255
  *
3157
3256
  * ```html
3158
3257
  * <video id="example_video_1" data-setup='{}' controls>
@@ -3182,6 +3281,9 @@ vjs.Player = vjs.Component.extend({
3182
3281
  // Make sure tag ID exists
3183
3282
  tag.id = tag.id || 'vjs_video_' + vjs.guid++;
3184
3283
 
3284
+ // Store the tag attributes used to restore html5 element
3285
+ this.tagAttributes = tag && vjs.getElementAttributes(tag);
3286
+
3185
3287
  // Set Options
3186
3288
  // The options argument overrides options set in the video tag
3187
3289
  // which overrides globally set options.
@@ -3189,6 +3291,12 @@ vjs.Player = vjs.Component.extend({
3189
3291
  // (tag must exist before Player)
3190
3292
  options = vjs.obj.merge(this.getTagSettings(tag), options);
3191
3293
 
3294
+ // Update Current Language
3295
+ this.language_ = options['language'] || vjs.options['language'];
3296
+
3297
+ // Update Supported Languages
3298
+ this.languages_ = options['languages'] || vjs.options['languages'];
3299
+
3192
3300
  // Cache for video property values.
3193
3301
  this.cache_ = {};
3194
3302
 
@@ -3237,6 +3345,41 @@ vjs.Player = vjs.Component.extend({
3237
3345
  }
3238
3346
  });
3239
3347
 
3348
+ /**
3349
+ * The players's stored language code
3350
+ *
3351
+ * @type {String}
3352
+ * @private
3353
+ */
3354
+ vjs.Player.prototype.language_;
3355
+
3356
+ /**
3357
+ * The player's language code
3358
+ * @param {String} languageCode The locale string
3359
+ * @return {String} The locale string when getting
3360
+ * @return {vjs.Player} self, when setting
3361
+ */
3362
+ vjs.Player.prototype.language = function (languageCode) {
3363
+ if (languageCode === undefined) {
3364
+ return this.language_;
3365
+ }
3366
+
3367
+ this.language_ = languageCode;
3368
+ return this;
3369
+ };
3370
+
3371
+ /**
3372
+ * The players's stored language dictionary
3373
+ *
3374
+ * @type {Object}
3375
+ * @private
3376
+ */
3377
+ vjs.Player.prototype.languages_;
3378
+
3379
+ vjs.Player.prototype.languages = function(){
3380
+ return this.languages_;
3381
+ };
3382
+
3240
3383
  /**
3241
3384
  * Player instance options, surfaced using vjs.options
3242
3385
  * vjs.options = vjs.Player.prototype.options_
@@ -3282,7 +3425,7 @@ vjs.Player.prototype.getTagSettings = function(tag){
3282
3425
  'tracks': []
3283
3426
  };
3284
3427
 
3285
- vjs.obj.merge(options, vjs.getAttributeValues(tag));
3428
+ vjs.obj.merge(options, vjs.getElementAttributes(tag));
3286
3429
 
3287
3430
  // Get tag children settings
3288
3431
  if (tag.hasChildNodes()) {
@@ -3295,9 +3438,9 @@ vjs.Player.prototype.getTagSettings = function(tag){
3295
3438
  // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
3296
3439
  childName = child.nodeName.toLowerCase();
3297
3440
  if (childName === 'source') {
3298
- options['sources'].push(vjs.getAttributeValues(child));
3441
+ options['sources'].push(vjs.getElementAttributes(child));
3299
3442
  } else if (childName === 'track') {
3300
- options['tracks'].push(vjs.getAttributeValues(child));
3443
+ options['tracks'].push(vjs.getElementAttributes(child));
3301
3444
  }
3302
3445
  }
3303
3446
  }
@@ -3306,8 +3449,10 @@ vjs.Player.prototype.getTagSettings = function(tag){
3306
3449
  };
3307
3450
 
3308
3451
  vjs.Player.prototype.createEl = function(){
3309
- var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
3310
- var tag = this.tag;
3452
+ var
3453
+ el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div'),
3454
+ tag = this.tag,
3455
+ attrs;
3311
3456
 
3312
3457
  // Remove width/height attrs from tag so CSS can make it 100% width/height
3313
3458
  tag.removeAttribute('width');
@@ -3336,10 +3481,12 @@ vjs.Player.prototype.createEl = function(){
3336
3481
  }
3337
3482
  }
3338
3483
 
3339
- // Give video tag ID and class to player div
3484
+ // Copy over all the attributes from the tag, including ID and class
3340
3485
  // ID will now reference player box, not the video tag
3341
- el.id = tag.id;
3342
- el.className = tag.className;
3486
+ attrs = vjs.getElementAttributes(tag);
3487
+ vjs.obj.each(attrs, function(attr) {
3488
+ el.setAttribute(attr, attrs[attr]);
3489
+ });
3343
3490
 
3344
3491
  // Update tag id/class for use as HTML5 playback tech
3345
3492
  // Might think we should do this after embedding in container so .vjs-tech class
@@ -3371,6 +3518,10 @@ vjs.Player.prototype.createEl = function(){
3371
3518
  // adding children
3372
3519
  this.el_ = el;
3373
3520
  this.on('loadstart', this.onLoadStart);
3521
+ this.on('waiting', this.onWaiting);
3522
+ this.on(['canplay', 'canplaythrough', 'playing', 'ended'], this.onWaitEnd);
3523
+ this.on('seeking', this.onSeeking);
3524
+ this.on('seeked', this.onSeeked);
3374
3525
  this.on('ended', this.onEnded);
3375
3526
  this.on('play', this.onPlay);
3376
3527
  this.on('firstplay', this.onFirstPlay);
@@ -3422,6 +3573,7 @@ vjs.Player.prototype.loadTech = function(techName, source){
3422
3573
  var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
3423
3574
 
3424
3575
  if (source) {
3576
+ this.currentType_ = source.type;
3425
3577
  if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
3426
3578
  techOptions['startTime'] = this.cache_.currentTime;
3427
3579
  }
@@ -3437,13 +3589,14 @@ vjs.Player.prototype.loadTech = function(techName, source){
3437
3589
 
3438
3590
  vjs.Player.prototype.unloadTech = function(){
3439
3591
  this.isReady_ = false;
3440
- this.tech.dispose();
3441
3592
 
3442
3593
  // Turn off any manual progress or timeupdate tracking
3443
3594
  if (this.manualProgress) { this.manualProgressOff(); }
3444
3595
 
3445
3596
  if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
3446
3597
 
3598
+ this.tech.dispose();
3599
+
3447
3600
  this.tech = false;
3448
3601
  };
3449
3602
 
@@ -3496,13 +3649,17 @@ vjs.Player.prototype.trackProgress = function(){
3496
3649
 
3497
3650
  this.progressInterval = setInterval(vjs.bind(this, function(){
3498
3651
  // Don't trigger unless buffered amount is greater than last time
3499
- // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
3500
- /* TODO: update for multiple buffered regions */
3501
- if (this.cache_.bufferEnd < this.buffered().end(0)) {
3652
+
3653
+ var bufferedPercent = this.bufferedPercent();
3654
+
3655
+ if (this.cache_.bufferedPercent != bufferedPercent) {
3502
3656
  this.trigger('progress');
3503
- } else if (this.bufferedPercent() == 1) {
3657
+ }
3658
+
3659
+ this.cache_.bufferedPercent = bufferedPercent;
3660
+
3661
+ if (bufferedPercent == 1) {
3504
3662
  this.stopTrackingProgress();
3505
- this.trigger('progress'); // Last update
3506
3663
  }
3507
3664
  }), 500);
3508
3665
  };
@@ -3619,8 +3776,40 @@ vjs.Player.prototype.onLoadedAllData;
3619
3776
  * @event play
3620
3777
  */
3621
3778
  vjs.Player.prototype.onPlay = function(){
3622
- vjs.removeClass(this.el_, 'vjs-paused');
3623
- vjs.addClass(this.el_, 'vjs-playing');
3779
+ this.removeClass('vjs-paused');
3780
+ this.addClass('vjs-playing');
3781
+ };
3782
+
3783
+ /**
3784
+ * Fired whenever the media begins wating
3785
+ * @event waiting
3786
+ */
3787
+ vjs.Player.prototype.onWaiting = function(){
3788
+ this.addClass('vjs-waiting');
3789
+ };
3790
+
3791
+ /**
3792
+ * A handler for events that signal that waiting has eneded
3793
+ * which is not consistent between browsers. See #1351
3794
+ */
3795
+ vjs.Player.prototype.onWaitEnd = function(){
3796
+ this.removeClass('vjs-waiting');
3797
+ };
3798
+
3799
+ /**
3800
+ * Fired whenever the player is jumping to a new time
3801
+ * @event seeking
3802
+ */
3803
+ vjs.Player.prototype.onSeeking = function(){
3804
+ this.addClass('vjs-seeking');
3805
+ };
3806
+
3807
+ /**
3808
+ * Fired when the player has finished jumping to a new time
3809
+ * @event seeked
3810
+ */
3811
+ vjs.Player.prototype.onSeeked = function(){
3812
+ this.removeClass('vjs-seeking');
3624
3813
  };
3625
3814
 
3626
3815
  /**
@@ -3647,8 +3836,8 @@ vjs.Player.prototype.onFirstPlay = function(){
3647
3836
  * @event pause
3648
3837
  */
3649
3838
  vjs.Player.prototype.onPause = function(){
3650
- vjs.removeClass(this.el_, 'vjs-playing');
3651
- vjs.addClass(this.el_, 'vjs-paused');
3839
+ this.removeClass('vjs-playing');
3840
+ this.addClass('vjs-paused');
3652
3841
  };
3653
3842
 
3654
3843
  /**
@@ -3889,7 +4078,6 @@ vjs.Player.prototype.remainingTime = function(){
3889
4078
  // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
3890
4079
  // Buffered returns a timerange object.
3891
4080
  // Kind of like an array of portions of the video that have been downloaded.
3892
- // So far no browsers return more than one range (portion)
3893
4081
 
3894
4082
  /**
3895
4083
  * Get a TimeRange object with the times of the video that have been downloaded
@@ -3912,19 +4100,13 @@ vjs.Player.prototype.remainingTime = function(){
3912
4100
  * @return {Object} A mock TimeRange object (following HTML spec)
3913
4101
  */
3914
4102
  vjs.Player.prototype.buffered = function(){
3915
- var buffered = this.techGet('buffered'),
3916
- start = 0,
3917
- buflast = buffered.length - 1,
3918
- // Default end to 0 and store in values
3919
- end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
4103
+ var buffered = this.techGet('buffered');
3920
4104
 
3921
- if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
3922
- end = buffered.end(buflast);
3923
- // Storing values allows them be overridden by setBufferedFromProgress
3924
- this.cache_.bufferEnd = end;
4105
+ if (!buffered || !buffered.length) {
4106
+ buffered = vjs.createTimeRange(0,0);
3925
4107
  }
3926
4108
 
3927
- return vjs.createTimeRange(start, end);
4109
+ return buffered;
3928
4110
  };
3929
4111
 
3930
4112
  /**
@@ -3938,7 +4120,46 @@ vjs.Player.prototype.buffered = function(){
3938
4120
  * @return {Number} A decimal between 0 and 1 representing the percent
3939
4121
  */
3940
4122
  vjs.Player.prototype.bufferedPercent = function(){
3941
- return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
4123
+ var duration = this.duration(),
4124
+ buffered = this.buffered(),
4125
+ bufferedDuration = 0,
4126
+ start, end;
4127
+
4128
+ if (!duration) {
4129
+ return 0;
4130
+ }
4131
+
4132
+ for (var i=0; i<buffered.length; i++){
4133
+ start = buffered.start(i);
4134
+ end = buffered.end(i);
4135
+
4136
+ // buffered end can be bigger than duration by a very small fraction
4137
+ if (end > duration) {
4138
+ end = duration;
4139
+ }
4140
+
4141
+ bufferedDuration += end - start;
4142
+ }
4143
+
4144
+ return bufferedDuration / duration;
4145
+ };
4146
+
4147
+ /**
4148
+ * Get the ending time of the last buffered time range
4149
+ *
4150
+ * This is used in the progress bar to encapsulate all time ranges.
4151
+ * @return {Number} The end of the last buffered time range
4152
+ */
4153
+ vjs.Player.prototype.bufferedEnd = function(){
4154
+ var buffered = this.buffered(),
4155
+ duration = this.duration(),
4156
+ end = buffered.end(buffered.length-1);
4157
+
4158
+ if (end > duration) {
4159
+ end = duration;
4160
+ }
4161
+
4162
+ return end;
3942
4163
  };
3943
4164
 
3944
4165
  /**
@@ -4249,64 +4470,69 @@ vjs.Player.prototype.src = function(source){
4249
4470
  return this.techGet('src');
4250
4471
  }
4251
4472
 
4252
- // Case: Array of source objects to choose from and pick the best to play
4253
- if (source instanceof Array) {
4254
-
4255
- var sourceTech = this.selectSource(source),
4256
- techName;
4473
+ // case: Array of source objects to choose from and pick the best to play
4474
+ if (vjs.obj.isArray(source)) {
4475
+ this.sourceList_(source);
4257
4476
 
4258
- if (sourceTech) {
4259
- source = sourceTech.source;
4260
- techName = sourceTech.tech;
4477
+ // case: URL String (http://myvideo...)
4478
+ } else if (typeof source === 'string') {
4479
+ // create a source object from the string
4480
+ this.src({ src: source });
4261
4481
 
4262
- // If this technology is already loaded, set source
4263
- if (techName == this.techName) {
4264
- this.src(source); // Passing the source object
4265
- // Otherwise load this technology with chosen source
4266
- } else {
4267
- this.loadTech(techName, source);
4268
- }
4269
- } else {
4270
- // this.el_.appendChild(vjs.createEl('p', {
4271
- // innerHTML: this.options()['notSupportedMessage']
4272
- // }));
4273
- this.error({ code: 4, message: this.options()['notSupportedMessage'] });
4274
- this.triggerReady(); // we could not find an appropriate tech, but let's still notify the delegate that this is it
4275
- }
4276
-
4277
- // Case: Source object { src: '', type: '' ... }
4482
+ // case: Source object { src: '', type: '' ... }
4278
4483
  } else if (source instanceof Object) {
4279
-
4280
- if (window['videojs'][this.techName]['canPlaySource'](source)) {
4281
- this.src(source.src);
4484
+ // check if the source has a type and the loaded tech cannot play the source
4485
+ // if there's no type we'll just try the current tech
4486
+ if (source.type && !window['videojs'][this.techName]['canPlaySource'](source)) {
4487
+ // create a source list with the current source and send through
4488
+ // the tech loop to check for a compatible technology
4489
+ this.sourceList_([source]);
4282
4490
  } else {
4283
- // Send through tech loop to check for a compatible technology.
4284
- this.src([source]);
4285
- }
4491
+ this.cache_.src = source.src;
4492
+ this.currentType_ = source.type || '';
4286
4493
 
4287
- // Case: URL String (http://myvideo...)
4288
- } else {
4289
- // Cache for getting last set source
4290
- this.cache_.src = source;
4291
-
4292
- if (!this.isReady_) {
4494
+ // wait until the tech is ready to set the source
4293
4495
  this.ready(function(){
4294
- this.src(source);
4496
+ this.techCall('src', source.src);
4497
+
4498
+ if (this.options_['preload'] == 'auto') {
4499
+ this.load();
4500
+ }
4501
+
4502
+ if (this.options_['autoplay']) {
4503
+ this.play();
4504
+ }
4295
4505
  });
4296
- } else {
4297
- this.techCall('src', source);
4298
- if (this.options_['preload'] == 'auto') {
4299
- this.load();
4300
- }
4301
- if (this.options_['autoplay']) {
4302
- this.play();
4303
- }
4304
4506
  }
4305
4507
  }
4306
4508
 
4307
4509
  return this;
4308
4510
  };
4309
4511
 
4512
+ /**
4513
+ * Handle an array of source objects
4514
+ * @param {[type]} sources Array of source objects
4515
+ * @private
4516
+ */
4517
+ vjs.Player.prototype.sourceList_ = function(sources){
4518
+ var sourceTech = this.selectSource(sources);
4519
+
4520
+ if (sourceTech) {
4521
+ if (sourceTech.tech === this.techName) {
4522
+ // if this technology is already loaded, set the source
4523
+ this.src(sourceTech.source);
4524
+ } else {
4525
+ // load this technology with the chosen source
4526
+ this.loadTech(sourceTech.tech, sourceTech.source);
4527
+ }
4528
+ } else {
4529
+ this.error({ code: 4, message: this.options()['notSupportedMessage'] });
4530
+ // we could not find an appropriate tech, but let's still notify the delegate that this is it
4531
+ // this needs a better comment about why this is needed
4532
+ this.triggerReady();
4533
+ }
4534
+ };
4535
+
4310
4536
  // Begin loading the src data
4311
4537
  // http://dev.w3.org/html5/spec/video.html#dom-media-load
4312
4538
  vjs.Player.prototype.load = function(){
@@ -4319,6 +4545,16 @@ vjs.Player.prototype.currentSrc = function(){
4319
4545
  return this.techGet('currentSrc') || this.cache_.src || '';
4320
4546
  };
4321
4547
 
4548
+ /**
4549
+ * Get the current source type e.g. video/mp4
4550
+ * This can allow you rebuild the current source object so that you could load the same
4551
+ * source and tech later
4552
+ * @return {String} The source MIME type
4553
+ */
4554
+ vjs.Player.prototype.currentType = function(){
4555
+ return this.currentType_ || '';
4556
+ };
4557
+
4322
4558
  // Attributes/Options
4323
4559
  vjs.Player.prototype.preload = function(value){
4324
4560
  if (value !== undefined) {
@@ -4731,7 +4967,7 @@ vjs.LiveDisplay.prototype.createEl = function(){
4731
4967
 
4732
4968
  this.contentEl_ = vjs.createEl('div', {
4733
4969
  className: 'vjs-live-display',
4734
- innerHTML: '<span class="vjs-control-text">Stream Type </span>LIVE',
4970
+ innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '</span>' + this.localize('LIVE'),
4735
4971
  'aria-live': 'off'
4736
4972
  });
4737
4973
 
@@ -4775,14 +5011,14 @@ vjs.PlayToggle.prototype.onClick = function(){
4775
5011
  vjs.PlayToggle.prototype.onPlay = function(){
4776
5012
  vjs.removeClass(this.el_, 'vjs-paused');
4777
5013
  vjs.addClass(this.el_, 'vjs-playing');
4778
- this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
5014
+ this.el_.children[0].children[0].innerHTML = this.localize('Pause'); // change the button text to "Pause"
4779
5015
  };
4780
5016
 
4781
5017
  // OnPause - Add the vjs-paused class to the element so it can change appearance
4782
5018
  vjs.PlayToggle.prototype.onPause = function(){
4783
5019
  vjs.removeClass(this.el_, 'vjs-playing');
4784
5020
  vjs.addClass(this.el_, 'vjs-paused');
4785
- this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
5021
+ this.el_.children[0].children[0].innerHTML = this.localize('Play'); // change the button text to "Play"
4786
5022
  };
4787
5023
  /**
4788
5024
  * Displays the current time
@@ -4817,7 +5053,7 @@ vjs.CurrentTimeDisplay.prototype.createEl = function(){
4817
5053
  vjs.CurrentTimeDisplay.prototype.updateContent = function(){
4818
5054
  // Allows for smooth scrubbing, when player can't keep up.
4819
5055
  var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
4820
- this.contentEl_.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
5056
+ this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Current Time') + '</span> ' + vjs.formatTime(time, this.player_.duration());
4821
5057
  };
4822
5058
 
4823
5059
  /**
@@ -4847,7 +5083,7 @@ vjs.DurationDisplay.prototype.createEl = function(){
4847
5083
 
4848
5084
  this.contentEl_ = vjs.createEl('div', {
4849
5085
  className: 'vjs-duration-display',
4850
- innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
5086
+ innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + '0:00', // label the duration time for screen reader users
4851
5087
  'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
4852
5088
  });
4853
5089
 
@@ -4858,7 +5094,7 @@ vjs.DurationDisplay.prototype.createEl = function(){
4858
5094
  vjs.DurationDisplay.prototype.updateContent = function(){
4859
5095
  var duration = this.player_.duration();
4860
5096
  if (duration) {
4861
- this.contentEl_.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
5097
+ this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + vjs.formatTime(duration); // label the duration time for screen reader users
4862
5098
  }
4863
5099
  };
4864
5100
 
@@ -4907,7 +5143,7 @@ vjs.RemainingTimeDisplay.prototype.createEl = function(){
4907
5143
 
4908
5144
  this.contentEl_ = vjs.createEl('div', {
4909
5145
  className: 'vjs-remaining-time-display',
4910
- innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
5146
+ innerHTML: '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-0:00', // label the remaining time for screen reader users
4911
5147
  'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
4912
5148
  });
4913
5149
 
@@ -4917,7 +5153,7 @@ vjs.RemainingTimeDisplay.prototype.createEl = function(){
4917
5153
 
4918
5154
  vjs.RemainingTimeDisplay.prototype.updateContent = function(){
4919
5155
  if (this.player_.duration()) {
4920
- this.contentEl_.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
5156
+ this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-'+ vjs.formatTime(this.player_.remainingTime());
4921
5157
  }
4922
5158
 
4923
5159
  // Allows for smooth scrubbing, when player can't keep up.
@@ -4951,10 +5187,10 @@ vjs.FullscreenToggle.prototype.buildCSSClass = function(){
4951
5187
  vjs.FullscreenToggle.prototype.onClick = function(){
4952
5188
  if (!this.player_.isFullscreen()) {
4953
5189
  this.player_.requestFullscreen();
4954
- this.controlText_.innerHTML = 'Non-Fullscreen';
5190
+ this.controlText_.innerHTML = this.localize('Non-Fullscreen');
4955
5191
  } else {
4956
5192
  this.player_.exitFullscreen();
4957
- this.controlText_.innerHTML = 'Fullscreen';
5193
+ this.controlText_.innerHTML = this.localize('Fullscreen');
4958
5194
  }
4959
5195
  };
4960
5196
  /**
@@ -5066,7 +5302,6 @@ vjs.SeekBar.prototype.stepBack = function(){
5066
5302
  this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
5067
5303
  };
5068
5304
 
5069
-
5070
5305
  /**
5071
5306
  * Shows load progress
5072
5307
  *
@@ -5085,14 +5320,45 @@ vjs.LoadProgressBar = vjs.Component.extend({
5085
5320
  vjs.LoadProgressBar.prototype.createEl = function(){
5086
5321
  return vjs.Component.prototype.createEl.call(this, 'div', {
5087
5322
  className: 'vjs-load-progress',
5088
- innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
5323
+ innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
5089
5324
  });
5090
5325
  };
5091
5326
 
5092
5327
  vjs.LoadProgressBar.prototype.update = function(){
5093
- if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
5094
- };
5328
+ var i, start, end, part,
5329
+ buffered = this.player_.buffered(),
5330
+ duration = this.player_.duration(),
5331
+ bufferedEnd = this.player_.bufferedEnd(),
5332
+ children = this.el_.children,
5333
+ // get the percent width of a time compared to the total end
5334
+ percentify = function (time, end){
5335
+ var percent = (time / end) || 0; // no NaN
5336
+ return (percent * 100) + '%';
5337
+ };
5338
+
5339
+ // update the width of the progress bar
5340
+ this.el_.style.width = percentify(bufferedEnd, duration);
5095
5341
 
5342
+ // add child elements to represent the individual buffered time ranges
5343
+ for (i = 0; i < buffered.length; i++) {
5344
+ start = buffered.start(i),
5345
+ end = buffered.end(i),
5346
+ part = children[i];
5347
+
5348
+ if (!part) {
5349
+ part = this.el_.appendChild(vjs.createEl())
5350
+ };
5351
+
5352
+ // set the percent based on the width of the progress bar (bufferedEnd)
5353
+ part.style.left = percentify(start, bufferedEnd);
5354
+ part.style.width = percentify(end - start, bufferedEnd);
5355
+ };
5356
+
5357
+ // remove unused buffered range elements
5358
+ for (i = children.length; i > buffered.length; i--) {
5359
+ this.el_.removeChild(children[i-1]);
5360
+ }
5361
+ };
5096
5362
 
5097
5363
  /**
5098
5364
  * Shows play progress
@@ -5111,7 +5377,7 @@ vjs.PlayProgressBar = vjs.Component.extend({
5111
5377
  vjs.PlayProgressBar.prototype.createEl = function(){
5112
5378
  return vjs.Component.prototype.createEl.call(this, 'div', {
5113
5379
  className: 'vjs-play-progress',
5114
- innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
5380
+ innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
5115
5381
  });
5116
5382
  };
5117
5383
 
@@ -5321,7 +5587,7 @@ vjs.MuteToggle = vjs.Button.extend({
5321
5587
  vjs.MuteToggle.prototype.createEl = function(){
5322
5588
  return vjs.Button.prototype.createEl.call(this, 'div', {
5323
5589
  className: 'vjs-mute-control vjs-control',
5324
- innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
5590
+ innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
5325
5591
  });
5326
5592
  };
5327
5593
 
@@ -5345,12 +5611,12 @@ vjs.MuteToggle.prototype.update = function(){
5345
5611
  // This causes unnecessary and confusing information for screen reader users.
5346
5612
  // This check is needed because this function gets called every time the volume level is changed.
5347
5613
  if(this.player_.muted()){
5348
- if(this.el_.children[0].children[0].innerHTML!='Unmute'){
5349
- this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
5614
+ if(this.el_.children[0].children[0].innerHTML!=this.localize('Unmute')){
5615
+ this.el_.children[0].children[0].innerHTML = this.localize('Unmute'); // change the button text to "Unmute"
5350
5616
  }
5351
5617
  } else {
5352
- if(this.el_.children[0].children[0].innerHTML!='Mute'){
5353
- this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
5618
+ if(this.el_.children[0].children[0].innerHTML!=this.localize('Mute')){
5619
+ this.el_.children[0].children[0].innerHTML = this.localize('Mute'); // change the button text to "Mute"
5354
5620
  }
5355
5621
  }
5356
5622
 
@@ -5391,7 +5657,7 @@ vjs.VolumeMenuButton.prototype.createMenu = function(){
5391
5657
  var menu = new vjs.Menu(this.player_, {
5392
5658
  contentElType: 'div'
5393
5659
  });
5394
- var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));
5660
+ var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({'vertical': true}, this.options_.volumeBar));
5395
5661
  menu.addChild(vc);
5396
5662
  return menu;
5397
5663
  };
@@ -5404,7 +5670,7 @@ vjs.VolumeMenuButton.prototype.onClick = function(){
5404
5670
  vjs.VolumeMenuButton.prototype.createEl = function(){
5405
5671
  return vjs.Button.prototype.createEl.call(this, 'div', {
5406
5672
  className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
5407
- innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
5673
+ innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
5408
5674
  });
5409
5675
  };
5410
5676
  vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
@@ -5431,7 +5697,7 @@ vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
5431
5697
  vjs.PlaybackRateMenuButton.prototype.createEl = function(){
5432
5698
  var el = vjs.Component.prototype.createEl.call(this, 'div', {
5433
5699
  className: 'vjs-playback-rate vjs-menu-button vjs-control',
5434
- innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">Playback Rate</span></div>'
5700
+ innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + this.localize('Playback Rate') + '</span></div>'
5435
5701
  });
5436
5702
 
5437
5703
  this.labelEl_ = vjs.createEl('div', {
@@ -5629,23 +5895,25 @@ vjs.LoadingSpinner = vjs.Component.extend({
5629
5895
  init: function(player, options){
5630
5896
  vjs.Component.call(this, player, options);
5631
5897
 
5632
- player.on('canplay', vjs.bind(this, this.hide));
5633
- player.on('canplaythrough', vjs.bind(this, this.hide));
5634
- player.on('playing', vjs.bind(this, this.hide));
5635
- player.on('seeking', vjs.bind(this, this.show));
5898
+ // MOVING DISPLAY HANDLING TO CSS
5899
+
5900
+ // player.on('canplay', vjs.bind(this, this.hide));
5901
+ // player.on('canplaythrough', vjs.bind(this, this.hide));
5902
+ // player.on('playing', vjs.bind(this, this.hide));
5903
+ // player.on('seeking', vjs.bind(this, this.show));
5636
5904
 
5637
5905
  // in some browsers seeking does not trigger the 'playing' event,
5638
5906
  // so we also need to trap 'seeked' if we are going to set a
5639
5907
  // 'seeking' event
5640
- player.on('seeked', vjs.bind(this, this.hide));
5908
+ // player.on('seeked', vjs.bind(this, this.hide));
5641
5909
 
5642
- player.on('ended', vjs.bind(this, this.hide));
5910
+ // player.on('ended', vjs.bind(this, this.hide));
5643
5911
 
5644
5912
  // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
5645
5913
  // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
5646
5914
  // player.on('stalled', vjs.bind(this, this.show));
5647
5915
 
5648
- player.on('waiting', vjs.bind(this, this.show));
5916
+ // player.on('waiting', vjs.bind(this, this.show));
5649
5917
  }
5650
5918
  });
5651
5919
 
@@ -5705,7 +5973,7 @@ vjs.ErrorDisplay.prototype.createEl = function(){
5705
5973
 
5706
5974
  vjs.ErrorDisplay.prototype.update = function(){
5707
5975
  if (this.player().error()) {
5708
- this.contentEl_.innerHTML = this.player().error().message;
5976
+ this.contentEl_.innerHTML = this.localize(this.player().error().message);
5709
5977
  }
5710
5978
  };
5711
5979
  /**
@@ -5797,8 +6065,6 @@ vjs.MediaTechController.prototype.addControlsListeners = function(){
5797
6065
  // so we'll check if the controls were already showing before reporting user
5798
6066
  // activity
5799
6067
  this.on('touchstart', function(event) {
5800
- // Stop the mouse events from also happening
5801
- event.preventDefault();
5802
6068
  userWasActive = this.player_.userActive();
5803
6069
  });
5804
6070
 
@@ -5808,6 +6074,11 @@ vjs.MediaTechController.prototype.addControlsListeners = function(){
5808
6074
  }
5809
6075
  });
5810
6076
 
6077
+ this.on('touchend', function(event) {
6078
+ // Stop the mouse events from also happening
6079
+ event.preventDefault();
6080
+ });
6081
+
5811
6082
  // Turn on component tap events
5812
6083
  this.emitTapEvents();
5813
6084
 
@@ -5942,6 +6213,7 @@ vjs.Html5 = vjs.MediaTechController.extend({
5942
6213
  });
5943
6214
 
5944
6215
  vjs.Html5.prototype.dispose = function(){
6216
+ vjs.Html5.disposeMediaElement(this.el_);
5945
6217
  vjs.MediaTechController.prototype.dispose.call(this);
5946
6218
  };
5947
6219
 
@@ -5964,10 +6236,13 @@ vjs.Html5.prototype.createEl = function(){
5964
6236
  el = clone;
5965
6237
  player.tag = null;
5966
6238
  } else {
5967
- el = vjs.createEl('video', {
5968
- id:player.id() + '_html5_api',
5969
- className:'vjs-tech'
5970
- });
6239
+ el = vjs.createEl('video');
6240
+ vjs.setElementAttributes(el,
6241
+ vjs.obj.merge(player.tagAttributes || {}, {
6242
+ id:player.id() + '_html5_api',
6243
+ 'class':'vjs-tech'
6244
+ })
6245
+ );
5971
6246
  }
5972
6247
  // associate the player with the new tag
5973
6248
  el['player'] = player;
@@ -5976,12 +6251,14 @@ vjs.Html5.prototype.createEl = function(){
5976
6251
  }
5977
6252
 
5978
6253
  // Update specific tag settings, in case they were overridden
5979
- var attrs = ['autoplay','preload','loop','muted'];
5980
- for (var i = attrs.length - 1; i >= 0; i--) {
5981
- var attr = attrs[i];
5982
- if (player.options_[attr] !== null) {
5983
- el[attr] = player.options_[attr];
6254
+ var settingsAttrs = ['autoplay','preload','loop','muted'];
6255
+ for (var i = settingsAttrs.length - 1; i >= 0; i--) {
6256
+ var attr = settingsAttrs[i];
6257
+ var overwriteAttrs = {};
6258
+ if (typeof player.options_[attr] !== 'undefined') {
6259
+ overwriteAttrs[attr] = player.options_[attr];
5984
6260
  }
6261
+ vjs.setElementAttributes(el, overwriteAttrs);
5985
6262
  }
5986
6263
 
5987
6264
  return el;
@@ -6310,9 +6587,7 @@ vjs.Flash = vjs.MediaTechController.extend({
6310
6587
  'id': objId,
6311
6588
  'name': objId, // Both ID and Name needed or swf to identifty itself
6312
6589
  'class': 'vjs-tech'
6313
- }, options['attributes']),
6314
-
6315
- lastSeekTarget
6590
+ }, options['attributes'])
6316
6591
  ;
6317
6592
 
6318
6593
  // If source was supplied pass as a flash var.
@@ -6327,19 +6602,6 @@ vjs.Flash = vjs.MediaTechController.extend({
6327
6602
  }
6328
6603
  }
6329
6604
 
6330
- this['setCurrentTime'] = function(time){
6331
- lastSeekTarget = time;
6332
- this.el_.vjs_setProperty('currentTime', time);
6333
- };
6334
- this['currentTime'] = function(time){
6335
- // when seeking make the reported time keep up with the requested time
6336
- // by reading the time we're seeking to
6337
- if (this.seeking()) {
6338
- return lastSeekTarget;
6339
- }
6340
- return this.el_.vjs_getProperty('currentTime');
6341
- };
6342
-
6343
6605
  // Add placeholder to player div
6344
6606
  vjs.insertFirst(placeHolder, parentEl);
6345
6607
 
@@ -6349,7 +6611,7 @@ vjs.Flash = vjs.MediaTechController.extend({
6349
6611
  this.ready(function(){
6350
6612
  this.load();
6351
6613
  this.play();
6352
- this.currentTime(options['startTime']);
6614
+ this['currentTime'](options['startTime']);
6353
6615
  });
6354
6616
  }
6355
6617
 
@@ -6364,134 +6626,11 @@ vjs.Flash = vjs.MediaTechController.extend({
6364
6626
  });
6365
6627
  }
6366
6628
 
6367
- // Flash iFrame Mode
6368
- // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
6369
- // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
6370
- // - Webkit when hiding the plugin
6371
- // - Webkit and Firefox when using requestFullScreen on a parent element
6372
- // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
6373
- // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
6374
-
6375
- // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
6376
- // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
6377
- // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
6378
- // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
6379
- // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
6380
- // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
6381
-
6382
- // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
6383
- // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
6384
- // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
6385
- // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
6386
-
6387
- if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
6388
-
6389
- // Create iFrame with vjs-tech class so it's 100% width/height
6390
- var iFrm = vjs.createEl('iframe', {
6391
- 'id': objId + '_iframe',
6392
- 'name': objId + '_iframe',
6393
- 'className': 'vjs-tech',
6394
- 'scrolling': 'no',
6395
- 'marginWidth': 0,
6396
- 'marginHeight': 0,
6397
- 'frameBorder': 0
6398
- });
6399
-
6400
- // Update ready function names in flash vars for iframe window
6401
- flashVars['readyFunction'] = 'ready';
6402
- flashVars['eventProxyFunction'] = 'events';
6403
- flashVars['errorEventProxyFunction'] = 'errors';
6404
-
6405
- // Tried multiple methods to get this to work in all browsers
6406
-
6407
- // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
6408
- // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
6409
- // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
6410
- // (in onload)
6411
- // var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
6412
- // iDoc.body.appendChild(temp);
6413
-
6414
- // Tried embedding the flash object through javascript in the iframe source.
6415
- // This works in webkit but still triggers the firefox security error
6416
- // iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
6417
-
6418
- // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
6419
- // We should add an option to host the iframe locally though, because it could help a lot of issues.
6420
- // iFrm.src = "iframe.html";
6421
-
6422
- // Wait until iFrame has loaded to write into it.
6423
- vjs.on(iFrm, 'load', vjs.bind(this, function(){
6424
-
6425
- var iDoc,
6426
- iWin = iFrm.contentWindow;
6427
-
6428
- // The one working method I found was to use the iframe's document.write() to create the swf object
6429
- // This got around the security issue in all browsers except firefox.
6430
- // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
6431
- // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
6432
- // Plus Firefox 3.6 didn't work no matter what I tried.
6433
- // if (vjs.USER_AGENT.match('Firefox')) {
6434
- // iWin.location.href = '';
6435
- // }
6436
-
6437
- // Get the iFrame's document depending on what the browser supports
6438
- iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
6439
-
6440
- // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
6441
- // Even tried adding /. that was mentioned in a browser security writeup
6442
- // document.domain = document.domain+'/.';
6443
- // iDoc.domain = document.domain+'/.';
6444
-
6445
- // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
6446
- // iDoc.body.innerHTML = swfObjectHTML;
6447
-
6448
- // Tried appending the object to the iframe doc's body. Security error in all browsers.
6449
- // iDoc.body.appendChild(swfObject);
6450
-
6451
- // Using document.write actually got around the security error that browsers were throwing.
6452
- // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
6453
- // Not sure why that's a security issue, but apparently it is.
6454
- iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
6455
-
6456
- // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
6457
- // So far no issues with swf ready event being called before it's set on the window.
6458
- iWin['player'] = this.player_;
6459
-
6460
- // Create swf ready function for iFrame window
6461
- iWin['ready'] = vjs.bind(this.player_, function(currSwf){
6462
- var el = iDoc.getElementById(currSwf),
6463
- player = this,
6464
- tech = player.tech;
6465
-
6466
- // Update reference to playback technology element
6467
- tech.el_ = el;
6468
-
6469
- // Make sure swf is actually ready. Sometimes the API isn't actually yet.
6470
- vjs.Flash.checkReady(tech);
6471
- });
6472
-
6473
- // Create event listener for all swf events
6474
- iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
6475
- var player = this;
6476
- if (player && player.techName === 'flash') {
6477
- player.trigger(eventName);
6478
- }
6479
- });
6480
-
6481
- // Create error listener for all swf errors
6482
- iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
6483
- vjs.log('Flash Error', eventName);
6484
- });
6485
-
6486
- }));
6487
-
6488
- // Replace placeholder with iFrame (it will load now)
6489
- placeHolder.parentNode.replaceChild(iFrm, placeHolder);
6629
+ // native click events on the SWF aren't triggered on IE11, Win8.1RT
6630
+ // use stageclick events triggered from inside the SWF instead
6631
+ player.on('stageclick', player.reportUserActivity);
6490
6632
 
6491
- // If not using iFrame mode, embed as normal object
6492
- } else {
6493
- vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
6494
- }
6633
+ this.el_ = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
6495
6634
  }
6496
6635
  });
6497
6636
 
@@ -6509,7 +6648,7 @@ vjs.Flash.prototype.pause = function(){
6509
6648
 
6510
6649
  vjs.Flash.prototype.src = function(src){
6511
6650
  if (src === undefined) {
6512
- return this.currentSrc();
6651
+ return this['currentSrc']();
6513
6652
  }
6514
6653
 
6515
6654
  if (vjs.Flash.isStreamingSrc(src)) {
@@ -6530,7 +6669,21 @@ vjs.Flash.prototype.src = function(src){
6530
6669
  }
6531
6670
  };
6532
6671
 
6533
- vjs.Flash.prototype.currentSrc = function(){
6672
+ vjs.Flash.prototype['setCurrentTime'] = function(time){
6673
+ this.lastSeekTarget_ = time;
6674
+ this.el_.vjs_setProperty('currentTime', time);
6675
+ };
6676
+
6677
+ vjs.Flash.prototype['currentTime'] = function(time){
6678
+ // when seeking make the reported time keep up with the requested time
6679
+ // by reading the time we're seeking to
6680
+ if (this.seeking()) {
6681
+ return this.lastSeekTarget_ || 0;
6682
+ }
6683
+ return this.el_.vjs_getProperty('currentTime');
6684
+ };
6685
+
6686
+ vjs.Flash.prototype['currentSrc'] = function(){
6534
6687
  var src = this.el_.vjs_getProperty('currentSrc');
6535
6688
  // no src, check and see if RTMP
6536
6689
  if (src == null) {
@@ -6551,7 +6704,7 @@ vjs.Flash.prototype.load = function(){
6551
6704
  vjs.Flash.prototype.poster = function(){
6552
6705
  this.el_.vjs_getProperty('poster');
6553
6706
  };
6554
- vjs.Flash.prototype.setPoster = function(){
6707
+ vjs.Flash.prototype['setPoster'] = function(){
6555
6708
  // poster images are not handled by the Flash tech so make this a no-op
6556
6709
  };
6557
6710
 
@@ -6567,31 +6720,22 @@ vjs.Flash.prototype.enterFullScreen = function(){
6567
6720
  return false;
6568
6721
  };
6569
6722
 
6570
- // Create setters and getters for attributes
6571
- var api = vjs.Flash.prototype,
6723
+ (function(){
6724
+ // Create setters and getters for attributes
6725
+ var api = vjs.Flash.prototype,
6572
6726
  readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
6573
- readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
6727
+ readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(','),
6574
6728
  // Overridden: buffered, currentTime, currentSrc
6729
+ i;
6575
6730
 
6576
- /**
6577
- * @this {*}
6578
- * @private
6579
- */
6580
- var createSetter = function(attr){
6581
- var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
6582
- api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
6583
- };
6584
-
6585
- /**
6586
- * @this {*}
6587
- * @private
6588
- */
6589
- var createGetter = function(attr){
6590
- api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
6591
- };
6731
+ function createSetter(attr){
6732
+ var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
6733
+ api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
6734
+ };
6735
+ function createGetter(attr) {
6736
+ api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
6737
+ };
6592
6738
 
6593
- (function(){
6594
- var i;
6595
6739
  // Create getter and setters for all read/write attributes
6596
6740
  for (i = 0; i < readWrite.length; i++) {
6597
6741
  createGetter(readWrite[i]);
@@ -6637,39 +6781,40 @@ vjs.Flash.streamingFormats = {
6637
6781
  };
6638
6782
 
6639
6783
  vjs.Flash['onReady'] = function(currSwf){
6640
- var el = vjs.el(currSwf);
6641
-
6642
- // Get player from box
6643
- // On firefox reloads, el might already have a player
6644
- var player = el['player'] || el.parentNode['player'],
6645
- tech = player.tech;
6784
+ var el, player;
6646
6785
 
6647
- // Reference player on tech element
6648
- el['player'] = player;
6786
+ el = vjs.el(currSwf);
6649
6787
 
6650
- // Update reference to playback technology element
6651
- tech.el_ = el;
6788
+ // get player from the player div property
6789
+ player = el && el.parentNode && el.parentNode['player'];
6652
6790
 
6653
- vjs.Flash.checkReady(tech);
6791
+ // if there is no el or player then the tech has been disposed
6792
+ // and the tech element was removed from the player div
6793
+ if (player) {
6794
+ // reference player on tech element
6795
+ el['player'] = player;
6796
+ // check that the flash object is really ready
6797
+ vjs.Flash['checkReady'](player.tech);
6798
+ }
6654
6799
  };
6655
6800
 
6656
- // The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
6801
+ // The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object.
6657
6802
  // If it's not ready, we set a timeout to check again shortly.
6658
- vjs.Flash.checkReady = function(tech){
6803
+ vjs.Flash['checkReady'] = function(tech){
6804
+ // stop worrying if the tech has been disposed
6805
+ if (!tech.el()) {
6806
+ return;
6807
+ }
6659
6808
 
6660
- // Check if API property exists
6809
+ // check if API property exists
6661
6810
  if (tech.el().vjs_getProperty) {
6662
-
6663
- // If so, tell tech it's ready
6811
+ // tell tech it's ready
6664
6812
  tech.triggerReady();
6665
-
6666
- // Otherwise wait longer.
6667
6813
  } else {
6668
-
6814
+ // wait longer
6669
6815
  setTimeout(function(){
6670
- vjs.Flash.checkReady(tech);
6816
+ vjs.Flash['checkReady'](tech);
6671
6817
  }, 50);
6672
-
6673
6818
  }
6674
6819
  };
6675
6820
 
@@ -6925,7 +7070,7 @@ vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
6925
7070
  if (track.dflt()) {
6926
7071
  this.ready(function(){
6927
7072
  setTimeout(function(){
6928
- track.show();
7073
+ track.player().showTextTrack(track.id());
6929
7074
  }, 0);
6930
7075
  });
6931
7076
  }
@@ -7344,9 +7489,9 @@ vjs.TextTrack.prototype.parseCues = function(srcContent) {
7344
7489
  };
7345
7490
 
7346
7491
  // Timing line
7347
- time = line.split(' --> ');
7492
+ time = line.split(/[\t ]+/);
7348
7493
  cue.startTime = this.parseCueTime(time[0]);
7349
- cue.endTime = this.parseCueTime(time[1]);
7494
+ cue.endTime = this.parseCueTime(time[2]);
7350
7495
 
7351
7496
  // Additional lines - Cue Text
7352
7497
  text = [];
@@ -7803,11 +7948,10 @@ vjs.ChaptersButton.prototype.createMenu = function(){
7803
7948
 
7804
7949
  for (;i<j;i++) {
7805
7950
  track = tracks[i];
7806
- if (track.kind() == this.kind_ && track.dflt()) {
7807
- if (track.readyState() < 2) {
7808
- this.chaptersTrack = track;
7951
+ if (track.kind() == this.kind_) {
7952
+ if (track.readyState() === 0) {
7953
+ track.load();
7809
7954
  track.on('loaded', vjs.bind(this, this.createMenu));
7810
- return;
7811
7955
  } else {
7812
7956
  chaptersTrack = track;
7813
7957
  break;
@@ -7815,13 +7959,15 @@ vjs.ChaptersButton.prototype.createMenu = function(){
7815
7959
  }
7816
7960
  }
7817
7961
 
7818
- var menu = this.menu = new vjs.Menu(this.player_);
7819
-
7820
- menu.contentEl().appendChild(vjs.createEl('li', {
7821
- className: 'vjs-menu-title',
7822
- innerHTML: vjs.capitalize(this.kind_),
7823
- tabindex: -1
7824
- }));
7962
+ var menu = this.menu;
7963
+ if (menu === undefined) {
7964
+ menu = new vjs.Menu(this.player_);
7965
+ menu.contentEl().appendChild(vjs.createEl('li', {
7966
+ className: 'vjs-menu-title',
7967
+ innerHTML: vjs.capitalize(this.kind_),
7968
+ tabindex: -1
7969
+ }));
7970
+ }
7825
7971
 
7826
7972
  if (chaptersTrack) {
7827
7973
  var cues = chaptersTrack.cues_, cue, mi;
@@ -7840,6 +7986,7 @@ vjs.ChaptersButton.prototype.createMenu = function(){
7840
7986
 
7841
7987
  menu.addChild(mi);
7842
7988
  }
7989
+ this.addChild(menu);
7843
7990
  }
7844
7991
 
7845
7992
  if (this.items.length > 0) {
@@ -8015,7 +8162,7 @@ vjs.autoSetup = function(){
8015
8162
  }
8016
8163
  }
8017
8164
 
8018
- // No videos were found, so keep looping unless page is finisehd loading.
8165
+ // No videos were found, so keep looping unless page is finished loading.
8019
8166
  } else if (!vjs.windowLoaded) {
8020
8167
  vjs.autoSetupTimeout(1);
8021
8168
  }