videojs_rails 4.11.4 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,6 +37,16 @@ var vjs = function(id, options, ready){
37
37
 
38
38
  // If a player instance has already been created for this ID return it.
39
39
  if (vjs.players[id]) {
40
+
41
+ // If options or ready funtion are passed, warn
42
+ if (options) {
43
+ vjs.log.warn ('Player "' + id + '" is already initialised. Options will not be applied.');
44
+ }
45
+
46
+ if (ready) {
47
+ vjs.players[id].ready(ready);
48
+ }
49
+
40
50
  return vjs.players[id];
41
51
 
42
52
  // Otherwise get element for ID
@@ -63,9 +73,15 @@ var vjs = function(id, options, ready){
63
73
  var videojs = window['videojs'] = vjs;
64
74
 
65
75
  // CDN Version. Used to target right flash swf.
66
- vjs.CDN_VERSION = '4.11';
76
+ vjs.CDN_VERSION = '4.12';
67
77
  vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
68
78
 
79
+ /**
80
+ * Full player version
81
+ * @type {string}
82
+ */
83
+ vjs['VERSION'] = '4.12.0';
84
+
69
85
  /**
70
86
  * Global Player instance options, surfaced from vjs.Player.prototype.options_
71
87
  * vjs.options = vjs.Player.prototype.options_
@@ -99,11 +115,12 @@ vjs.options = {
99
115
  'children': {
100
116
  'mediaLoader': {},
101
117
  'posterImage': {},
102
- 'textTrackDisplay': {},
103
118
  'loadingSpinner': {},
119
+ 'textTrackDisplay': {},
104
120
  'bigPlayButton': {},
105
121
  'controlBar': {},
106
- 'errorDisplay': {}
122
+ 'errorDisplay': {},
123
+ 'textTrackSettings': {}
107
124
  },
108
125
 
109
126
  'language': document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
@@ -116,7 +133,7 @@ vjs.options = {
116
133
  };
117
134
 
118
135
  // Set CDN Version of swf
119
- // The added (+) blocks the replace from changing this 4.11 string
136
+ // The added (+) blocks the replace from changing this 4.12 string
120
137
  if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
121
138
  videojs.options['flash']['swf'] = "<%= asset_path('video-js.swf') %>";
122
139
  }
@@ -154,7 +171,7 @@ vjs.players = {};
154
171
  * compiler compatible, so string keys are used.
155
172
  */
156
173
  if (typeof define === 'function' && define['amd']) {
157
- define([], function(){ return videojs; });
174
+ define('videojs', [], function(){ return videojs; });
158
175
 
159
176
  // checking that module is an object too because of umdjs/umd#35
160
177
  } else if (typeof exports === 'object' && typeof module === 'object') {
@@ -913,6 +930,8 @@ vjs.getData = function(el){
913
930
  var id = el[vjs.expando];
914
931
  if (!id) {
915
932
  id = el[vjs.expando] = vjs.guid++;
933
+ }
934
+ if (!vjs.cache[id]) {
916
935
  vjs.cache[id] = {};
917
936
  }
918
937
  return vjs.cache[id];
@@ -1025,6 +1044,13 @@ vjs.removeClass = function(element, classToRemove){
1025
1044
  * @private
1026
1045
  */
1027
1046
  vjs.TEST_VID = vjs.createEl('video');
1047
+ (function() {
1048
+ var track = document.createElement('track');
1049
+ track.kind = 'captions';
1050
+ track.srclang = 'en';
1051
+ track.label = 'English';
1052
+ vjs.TEST_VID.appendChild(track);
1053
+ })();
1028
1054
 
1029
1055
  /**
1030
1056
  * Useragent for browser testing.
@@ -1078,6 +1104,7 @@ vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.A
1078
1104
 
1079
1105
  vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
1080
1106
  vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
1107
+ vjs.IS_IE8 = (/MSIE\s8\.0/).test(vjs.USER_AGENT);
1081
1108
 
1082
1109
  vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
1083
1110
  vjs.BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in vjs.TEST_VID.style;
@@ -1360,6 +1387,15 @@ vjs.parseUrl = function(url) {
1360
1387
  details[props[i]] = a[props[i]];
1361
1388
  }
1362
1389
 
1390
+ // IE9 adds the port to the host property unlike everyone else. If
1391
+ // a port identifier is added for standard ports, strip it.
1392
+ if (details.protocol === 'http:') {
1393
+ details.host = details.host.replace(/:80$/, '');
1394
+ }
1395
+ if (details.protocol === 'https:') {
1396
+ details.host = details.host.replace(/:443$/, '');
1397
+ }
1398
+
1363
1399
  if (addToBody) {
1364
1400
  document.body.removeChild(div);
1365
1401
  }
@@ -1685,7 +1721,50 @@ vjs.util.mergeOptions = function(obj1, obj2){
1685
1721
  }
1686
1722
  }
1687
1723
  return obj1;
1688
- };/**
1724
+ };vjs.EventEmitter = function() {
1725
+ };
1726
+
1727
+ vjs.EventEmitter.prototype.allowedEvents_ = {
1728
+ };
1729
+
1730
+ vjs.EventEmitter.prototype.on = function(type, fn) {
1731
+ // Remove the addEventListener alias before calling vjs.on
1732
+ // so we don't get into an infinite type loop
1733
+ var ael = this.addEventListener;
1734
+ this.addEventListener = Function.prototype;
1735
+ vjs.on(this, type, fn);
1736
+ this.addEventListener = ael;
1737
+ };
1738
+ vjs.EventEmitter.prototype.addEventListener = vjs.EventEmitter.prototype.on;
1739
+
1740
+ vjs.EventEmitter.prototype.off = function(type, fn) {
1741
+ vjs.off(this, type, fn);
1742
+ };
1743
+ vjs.EventEmitter.prototype.removeEventListener = vjs.EventEmitter.prototype.off;
1744
+
1745
+ vjs.EventEmitter.prototype.one = function(type, fn) {
1746
+ vjs.one(this, type, fn);
1747
+ };
1748
+
1749
+ vjs.EventEmitter.prototype.trigger = function(event) {
1750
+ var type = event.type || event;
1751
+
1752
+ if (typeof event === 'string') {
1753
+ event = {
1754
+ type: type
1755
+ };
1756
+ }
1757
+ event = vjs.fixEvent(event);
1758
+
1759
+ if (this.allowedEvents_[type] && this['on' + type]) {
1760
+ this['on' + type](event);
1761
+ }
1762
+
1763
+ vjs.trigger(this, event);
1764
+ };
1765
+ // The standard DOM EventTarget.dispatchEvent() is aliased to trigger()
1766
+ vjs.EventEmitter.prototype.dispatchEvent = vjs.EventEmitter.prototype.trigger;
1767
+ /**
1689
1768
  * @fileoverview Player Component - Base class for all UI objects
1690
1769
  *
1691
1770
  */
@@ -2532,7 +2611,7 @@ vjs.Component.prototype.removeClass = function(classToRemove){
2532
2611
  * @return {vjs.Component}
2533
2612
  */
2534
2613
  vjs.Component.prototype.show = function(){
2535
- this.el_.style.display = 'block';
2614
+ this.removeClass('vjs-hidden');
2536
2615
  return this;
2537
2616
  };
2538
2617
 
@@ -2542,7 +2621,7 @@ vjs.Component.prototype.show = function(){
2542
2621
  * @return {vjs.Component}
2543
2622
  */
2544
2623
  vjs.Component.prototype.hide = function(){
2545
- this.el_.style.display = 'none';
2624
+ this.addClass('vjs-hidden');
2546
2625
  return this;
2547
2626
  };
2548
2627
 
@@ -2718,19 +2797,23 @@ vjs.Component.prototype.onResize;
2718
2797
  */
2719
2798
  vjs.Component.prototype.emitTapEvents = function(){
2720
2799
  var touchStart, firstTouch, touchTime, couldBeTap, noTap,
2721
- xdiff, ydiff, touchDistance, tapMovementThreshold;
2800
+ xdiff, ydiff, touchDistance, tapMovementThreshold, touchTimeThreshold;
2722
2801
 
2723
2802
  // Track the start time so we can determine how long the touch lasted
2724
2803
  touchStart = 0;
2725
2804
  firstTouch = null;
2726
2805
 
2727
2806
  // Maximum movement allowed during a touch event to still be considered a tap
2728
- tapMovementThreshold = 22;
2807
+ // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number.
2808
+ tapMovementThreshold = 10;
2809
+
2810
+ // The maximum length a touch can be while still being considered a tap
2811
+ touchTimeThreshold = 200;
2729
2812
 
2730
2813
  this.on('touchstart', function(event) {
2731
2814
  // If more than one finger, don't consider treating this as a click
2732
2815
  if (event.touches.length === 1) {
2733
- firstTouch = event.touches[0];
2816
+ firstTouch = vjs.obj.copy(event.touches[0]);
2734
2817
  // Record start time so we can detect a tap vs. "touch and hold"
2735
2818
  touchStart = new Date().getTime();
2736
2819
  // Reset couldBeTap tracking
@@ -2769,8 +2852,8 @@ vjs.Component.prototype.emitTapEvents = function(){
2769
2852
  if (couldBeTap === true) {
2770
2853
  // Measure how long the touch lasted
2771
2854
  touchTime = new Date().getTime() - touchStart;
2772
- // The touch needs to be quick in order to consider it a tap
2773
- if (touchTime < 250) {
2855
+ // Make sure the touch was less than the threshold to be considered a tap
2856
+ if (touchTime < touchTimeThreshold) {
2774
2857
  event.preventDefault(); // Don't let browser turn this into a click
2775
2858
  this.trigger('tap');
2776
2859
  // It may be good to copy the touchend event object and change the
@@ -3083,7 +3166,12 @@ vjs.Slider.prototype.update = function(){
3083
3166
  bar = this.bar;
3084
3167
 
3085
3168
  // Protect against no duration and other division issues
3086
- if (isNaN(progress)) { progress = 0; }
3169
+ if (typeof progress !== 'number' ||
3170
+ progress !== progress ||
3171
+ progress < 0 ||
3172
+ progress === Infinity) {
3173
+ progress = 0;
3174
+ }
3087
3175
 
3088
3176
  barProgress = progress;
3089
3177
 
@@ -3328,15 +3416,7 @@ vjs.MenuButton = vjs.Button.extend({
3328
3416
  init: function(player, options){
3329
3417
  vjs.Button.call(this, player, options);
3330
3418
 
3331
- this.menu = this.createMenu();
3332
-
3333
- // Add list to element
3334
- this.addChild(this.menu);
3335
-
3336
- // Automatically hide empty menu buttons
3337
- if (this.items && this.items.length === 0) {
3338
- this.hide();
3339
- }
3419
+ this.update();
3340
3420
 
3341
3421
  this.on('keydown', this.onKeyPress);
3342
3422
  this.el_.setAttribute('aria-haspopup', true);
@@ -3344,6 +3424,23 @@ vjs.MenuButton = vjs.Button.extend({
3344
3424
  }
3345
3425
  });
3346
3426
 
3427
+ vjs.MenuButton.prototype.update = function() {
3428
+ var menu = this.createMenu();
3429
+
3430
+ if (this.menu) {
3431
+ this.removeChild(this.menu);
3432
+ }
3433
+
3434
+ this.menu = menu;
3435
+ this.addChild(menu);
3436
+
3437
+ if (this.items && this.items.length === 0) {
3438
+ this.hide();
3439
+ } else if (this.items && this.items.length > 1) {
3440
+ this.show();
3441
+ }
3442
+ };
3443
+
3347
3444
  /**
3348
3445
  * Track the state of the menu button
3349
3446
  * @type {Boolean}
@@ -3821,29 +3918,6 @@ vjs.Player.prototype.createEl = function(){
3821
3918
  // Remove width/height attrs from tag so CSS can make it 100% width/height
3822
3919
  tag.removeAttribute('width');
3823
3920
  tag.removeAttribute('height');
3824
- // Empty video tag tracks so the built-in player doesn't use them also.
3825
- // This may not be fast enough to stop HTML5 browsers from reading the tags
3826
- // so we'll need to turn off any default tracks if we're manually doing
3827
- // captions and subtitles. videoElement.textTracks
3828
- if (tag.hasChildNodes()) {
3829
- var nodes, nodesLength, i, node, nodeName, removeNodes;
3830
-
3831
- nodes = tag.childNodes;
3832
- nodesLength = nodes.length;
3833
- removeNodes = [];
3834
-
3835
- while (nodesLength--) {
3836
- node = nodes[nodesLength];
3837
- nodeName = node.nodeName.toLowerCase();
3838
- if (nodeName === 'track') {
3839
- removeNodes.push(node);
3840
- }
3841
- }
3842
-
3843
- for (i=0; i<removeNodes.length; i++) {
3844
- tag.removeChild(removeNodes[i]);
3845
- }
3846
- }
3847
3921
 
3848
3922
  // Copy over all the attributes from the tag, including ID and class
3849
3923
  // ID will now reference player box, not the video tag
@@ -3983,6 +4057,8 @@ vjs.Player.prototype.unloadTech = function(){
3983
4057
  vjs.Player.prototype.onLoadStart = function() {
3984
4058
  // TODO: Update to use `emptied` event instead. See #1277.
3985
4059
 
4060
+ this.removeClass('vjs-ended');
4061
+
3986
4062
  // reset the error state
3987
4063
  this.error(null);
3988
4064
 
@@ -3994,9 +4070,6 @@ vjs.Player.prototype.onLoadStart = function() {
3994
4070
  } else {
3995
4071
  // reset the hasStarted state
3996
4072
  this.hasStarted(false);
3997
- this.one('play', function(){
3998
- this.hasStarted(true);
3999
- });
4000
4073
  }
4001
4074
  };
4002
4075
 
@@ -4043,8 +4116,13 @@ vjs.Player.prototype.onLoadedAllData;
4043
4116
  * @event play
4044
4117
  */
4045
4118
  vjs.Player.prototype.onPlay = function(){
4119
+ this.removeClass('vjs-ended');
4046
4120
  this.removeClass('vjs-paused');
4047
4121
  this.addClass('vjs-playing');
4122
+
4123
+ // hide the poster when the user hits play
4124
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play
4125
+ this.hasStarted(true);
4048
4126
  };
4049
4127
 
4050
4128
  /**
@@ -4133,6 +4211,7 @@ vjs.Player.prototype.onProgress = function(){
4133
4211
  * @event ended
4134
4212
  */
4135
4213
  vjs.Player.prototype.onEnded = function(){
4214
+ this.addClass('vjs-ended');
4136
4215
  if (this.options_['loop']) {
4137
4216
  this.currentTime(0);
4138
4217
  this.play();
@@ -4182,6 +4261,12 @@ vjs.Player.prototype.onFullscreenChange = function() {
4182
4261
  }
4183
4262
  };
4184
4263
 
4264
+ /**
4265
+ * Fired when an error occurs
4266
+ * @event error
4267
+ */
4268
+ vjs.Player.prototype.onError;
4269
+
4185
4270
  // /* Player API
4186
4271
  // ================================================================================ */
4187
4272
 
@@ -5247,9 +5332,97 @@ vjs.Player.prototype.isAudio = function(bool) {
5247
5332
  return this.isAudio_;
5248
5333
  };
5249
5334
 
5335
+ /**
5336
+ * Returns the current state of network activity for the element, from
5337
+ * the codes in the list below.
5338
+ * - NETWORK_EMPTY (numeric value 0)
5339
+ * The element has not yet been initialised. All attributes are in
5340
+ * their initial states.
5341
+ * - NETWORK_IDLE (numeric value 1)
5342
+ * The element's resource selection algorithm is active and has
5343
+ * selected a resource, but it is not actually using the network at
5344
+ * this time.
5345
+ * - NETWORK_LOADING (numeric value 2)
5346
+ * The user agent is actively trying to download data.
5347
+ * - NETWORK_NO_SOURCE (numeric value 3)
5348
+ * The element's resource selection algorithm is active, but it has
5349
+ * not yet found a resource to use.
5350
+ * @return {Number} the current network activity state
5351
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
5352
+ */
5353
+ vjs.Player.prototype.networkState = function(){
5354
+ return this.techGet('networkState');
5355
+ };
5356
+
5357
+ /**
5358
+ * Returns a value that expresses the current state of the element
5359
+ * with respect to rendering the current playback position, from the
5360
+ * codes in the list below.
5361
+ * - HAVE_NOTHING (numeric value 0)
5362
+ * No information regarding the media resource is available.
5363
+ * - HAVE_METADATA (numeric value 1)
5364
+ * Enough of the resource has been obtained that the duration of the
5365
+ * resource is available.
5366
+ * - HAVE_CURRENT_DATA (numeric value 2)
5367
+ * Data for the immediate current playback position is available.
5368
+ * - HAVE_FUTURE_DATA (numeric value 3)
5369
+ * Data for the immediate current playback position is available, as
5370
+ * well as enough data for the user agent to advance the current
5371
+ * playback position in the direction of playback.
5372
+ * - HAVE_ENOUGH_DATA (numeric value 4)
5373
+ * The user agent estimates that enough data is available for
5374
+ * playback to proceed uninterrupted.
5375
+ * @return {Number} the current playback rendering state
5376
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
5377
+ */
5378
+ vjs.Player.prototype.readyState = function(){
5379
+ return this.techGet('readyState');
5380
+ };
5381
+
5382
+ /**
5383
+ * Text tracks are tracks of timed text events.
5384
+ * Captions - text displayed over the video for the hearing impaired
5385
+ * Subtitles - text displayed over the video for those who don't understand language in the video
5386
+ * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
5387
+ * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
5388
+ */
5389
+
5390
+ /**
5391
+ * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
5392
+ * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
5393
+ * @return {Array} Array of track objects
5394
+ */
5395
+ vjs.Player.prototype.textTracks = function(){
5396
+ // cannot use techGet directly because it checks to see whether the tech is ready.
5397
+ // Flash is unlikely to be ready in time but textTracks should still work.
5398
+ return this.tech && this.tech['textTracks']();
5399
+ };
5400
+
5401
+ vjs.Player.prototype.remoteTextTracks = function() {
5402
+ return this.tech && this.tech['remoteTextTracks']();
5403
+ };
5404
+
5405
+ /**
5406
+ * Add a text track
5407
+ * In addition to the W3C settings we allow adding additional info through options.
5408
+ * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
5409
+ * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
5410
+ * @param {String=} label Optional label
5411
+ * @param {String=} language Optional language
5412
+ */
5413
+ vjs.Player.prototype.addTextTrack = function(kind, label, language) {
5414
+ return this.tech && this.tech['addTextTrack'](kind, label, language);
5415
+ };
5416
+
5417
+ vjs.Player.prototype.addRemoteTextTrack = function(options) {
5418
+ return this.tech && this.tech['addRemoteTextTrack'](options);
5419
+ };
5420
+
5421
+ vjs.Player.prototype.removeRemoteTextTrack = function(track) {
5422
+ this.tech && this.tech['removeRemoteTextTrack'](track);
5423
+ };
5424
+
5250
5425
  // Methods to add support for
5251
- // networkState: function(){ return this.techCall('networkState'); },
5252
- // readyState: function(){ return this.techCall('readyState'); },
5253
5426
  // initialTime: function(){ return this.techCall('initialTime'); },
5254
5427
  // startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
5255
5428
  // played: function(){ return this.techCall('played'); },
@@ -5290,7 +5463,10 @@ vjs.ControlBar.prototype.options_ = {
5290
5463
  'volumeControl': {},
5291
5464
  'muteToggle': {},
5292
5465
  // 'volumeMenuButton': {},
5293
- 'playbackRateMenuButton': {}
5466
+ 'playbackRateMenuButton': {},
5467
+ 'subtitlesButton': {},
5468
+ 'captionsButton': {},
5469
+ 'chaptersButton': {}
5294
5470
  }
5295
5471
  };
5296
5472
 
@@ -5622,6 +5798,7 @@ vjs.SeekBar.prototype.onMouseDown = function(event){
5622
5798
  vjs.Slider.prototype.onMouseDown.call(this, event);
5623
5799
 
5624
5800
  this.player_.scrubbing = true;
5801
+ this.player_.addClass('vjs-scrubbing');
5625
5802
 
5626
5803
  this.videoWasPlaying = !this.player_.paused();
5627
5804
  this.player_.pause();
@@ -5641,6 +5818,7 @@ vjs.SeekBar.prototype.onMouseUp = function(event){
5641
5818
  vjs.Slider.prototype.onMouseUp.call(this, event);
5642
5819
 
5643
5820
  this.player_.scrubbing = false;
5821
+ this.player_.removeClass('vjs-scrubbing');
5644
5822
  if (this.videoWasPlaying) {
5645
5823
  this.player_.play();
5646
5824
  }
@@ -5989,7 +6167,7 @@ vjs.VolumeMenuButton = vjs.MenuButton.extend({
5989
6167
  vjs.MenuButton.call(this, player, options);
5990
6168
 
5991
6169
  // Same listeners as MuteToggle
5992
- this.on(player, 'volumechange', this.update);
6170
+ this.on(player, 'volumechange', this.volumeUpdate);
5993
6171
 
5994
6172
  // hide mute toggle if the current tech doesn't support volume control
5995
6173
  if (player.tech && player.tech['featuresVolumeControl'] === false) {
@@ -6032,7 +6210,7 @@ vjs.VolumeMenuButton.prototype.createEl = function(){
6032
6210
  innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
6033
6211
  });
6034
6212
  };
6035
- vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
6213
+ vjs.VolumeMenuButton.prototype.volumeUpdate = vjs.MuteToggle.prototype.update;
6036
6214
  /**
6037
6215
  * The component for controlling the playback rate
6038
6216
  *
@@ -6224,10 +6402,7 @@ vjs.PosterImage.prototype.update = function(){
6224
6402
  // If there's no poster source we should display:none on this component
6225
6403
  // so it's not still clickable or right-clickable
6226
6404
  if (url) {
6227
- // Remove the display style property that hide() adds
6228
- // as opposed to show() which sets display to block
6229
- // In the future it might be worth creating an `unhide` component method
6230
- this.el_.style.display = '';
6405
+ this.show();
6231
6406
  } else {
6232
6407
  this.hide();
6233
6408
  }
@@ -6356,6 +6531,8 @@ vjs.ErrorDisplay.prototype.update = function(){
6356
6531
  this.contentEl_.innerHTML = this.localize(this.player().error().message);
6357
6532
  }
6358
6533
  };
6534
+ (function() {
6535
+ var createTrackHelper;
6359
6536
  /**
6360
6537
  * @fileoverview Media Technology Controller - Base class for media playback
6361
6538
  * technology controllers like Flash and HTML5
@@ -6387,6 +6564,12 @@ vjs.MediaTechController = vjs.Component.extend({
6387
6564
  }
6388
6565
 
6389
6566
  this.initControlsListeners();
6567
+
6568
+ if (!this['featuresNativeTextTracks']) {
6569
+ this.emulateTextTracks();
6570
+ }
6571
+
6572
+ this.initTextTrackListeners();
6390
6573
  }
6391
6574
  });
6392
6575
 
@@ -6573,10 +6756,12 @@ vjs.MediaTechController.prototype.manualTimeUpdatesOn = function(){
6573
6756
  };
6574
6757
 
6575
6758
  vjs.MediaTechController.prototype.manualTimeUpdatesOff = function(){
6759
+ var player = this.player_;
6760
+
6576
6761
  this.manualTimeUpdates = false;
6577
6762
  this.stopTrackingCurrentTime();
6578
- this.off('play', this.trackCurrentTime);
6579
- this.off('pause', this.stopTrackingCurrentTime);
6763
+ this.off(player, 'play', this.trackCurrentTime);
6764
+ this.off(player, 'pause', this.stopTrackingCurrentTime);
6580
6765
  };
6581
6766
 
6582
6767
  vjs.MediaTechController.prototype.trackCurrentTime = function(){
@@ -6609,6 +6794,141 @@ vjs.MediaTechController.prototype.setCurrentTime = function() {
6609
6794
  if (this.manualTimeUpdates) { this.player().trigger('timeupdate'); }
6610
6795
  };
6611
6796
 
6797
+ // TODO: Consider looking at moving this into the text track display directly
6798
+ // https://github.com/videojs/video.js/issues/1863
6799
+ vjs.MediaTechController.prototype.initTextTrackListeners = function() {
6800
+ var player = this.player_,
6801
+ tracks,
6802
+ textTrackListChanges = function() {
6803
+ var textTrackDisplay = player.getChild('textTrackDisplay'),
6804
+ controlBar;
6805
+
6806
+ if (textTrackDisplay) {
6807
+ textTrackDisplay.updateDisplay();
6808
+ }
6809
+ };
6810
+
6811
+ tracks = this.textTracks();
6812
+
6813
+ if (!tracks) {
6814
+ return;
6815
+ }
6816
+
6817
+ tracks.addEventListener('removetrack', textTrackListChanges);
6818
+ tracks.addEventListener('addtrack', textTrackListChanges);
6819
+
6820
+ this.on('dispose', vjs.bind(this, function() {
6821
+ tracks.removeEventListener('removetrack', textTrackListChanges);
6822
+ tracks.removeEventListener('addtrack', textTrackListChanges);
6823
+ }));
6824
+ };
6825
+
6826
+ vjs.MediaTechController.prototype.emulateTextTracks = function() {
6827
+ var player = this.player_,
6828
+ textTracksChanges,
6829
+ tracks,
6830
+ script;
6831
+
6832
+ if (!window['WebVTT']) {
6833
+ script = document.createElement('script');
6834
+ script.src = player.options()['vtt.js'] || '../node_modules/vtt.js/dist/vtt.js';
6835
+ player.el().appendChild(script);
6836
+ window['WebVTT'] = true;
6837
+ }
6838
+
6839
+ tracks = this.textTracks();
6840
+ if (!tracks) {
6841
+ return;
6842
+ }
6843
+
6844
+ textTracksChanges = function() {
6845
+ var i, track, textTrackDisplay;
6846
+
6847
+ textTrackDisplay = player.getChild('textTrackDisplay'),
6848
+
6849
+ textTrackDisplay.updateDisplay();
6850
+
6851
+ for (i = 0; i < this.length; i++) {
6852
+ track = this[i];
6853
+ track.removeEventListener('cuechange', vjs.bind(textTrackDisplay, textTrackDisplay.updateDisplay));
6854
+ if (track.mode === 'showing') {
6855
+ track.addEventListener('cuechange', vjs.bind(textTrackDisplay, textTrackDisplay.updateDisplay));
6856
+ }
6857
+ }
6858
+ };
6859
+
6860
+ tracks.addEventListener('change', textTracksChanges);
6861
+
6862
+ this.on('dispose', vjs.bind(this, function() {
6863
+ tracks.removeEventListener('change', textTracksChanges);
6864
+ }));
6865
+ };
6866
+
6867
+ /**
6868
+ * Provide default methods for text tracks.
6869
+ *
6870
+ * Html5 tech overrides these.
6871
+ */
6872
+
6873
+ /**
6874
+ * List of associated text tracks
6875
+ * @type {Array}
6876
+ * @private
6877
+ */
6878
+ vjs.MediaTechController.prototype.textTracks_;
6879
+
6880
+ vjs.MediaTechController.prototype.textTracks = function() {
6881
+ this.textTracks_ = this.textTracks_ || new vjs.TextTrackList();
6882
+ return this.textTracks_;
6883
+ };
6884
+
6885
+ vjs.MediaTechController.prototype.remoteTextTracks = function() {
6886
+ this.remoteTextTracks_ = this.remoteTextTracks_ || new vjs.TextTrackList();
6887
+ return this.remoteTextTracks_;
6888
+ };
6889
+
6890
+ createTrackHelper = function(self, kind, label, language, options) {
6891
+ var tracks = self.textTracks(),
6892
+ track;
6893
+
6894
+ options = options || {};
6895
+
6896
+ options['kind'] = kind;
6897
+ if (label) {
6898
+ options['label'] = label;
6899
+ }
6900
+ if (language) {
6901
+ options['language'] = language;
6902
+ }
6903
+ options['player'] = self.player_;
6904
+
6905
+ track = new vjs.TextTrack(options);
6906
+ tracks.addTrack_(track);
6907
+
6908
+ return track;
6909
+ };
6910
+
6911
+ vjs.MediaTechController.prototype.addTextTrack = function(kind, label, language) {
6912
+ if (!kind) {
6913
+ throw new Error('TextTrack kind is required but was not provided');
6914
+ }
6915
+
6916
+ return createTrackHelper(this, kind, label, language);
6917
+ };
6918
+
6919
+ vjs.MediaTechController.prototype.addRemoteTextTrack = function(options) {
6920
+ var track = createTrackHelper(this, options['kind'], options['label'], options['language'], options);
6921
+ this.remoteTextTracks().addTrack_(track);
6922
+ return {
6923
+ track: track
6924
+ };
6925
+ };
6926
+
6927
+ vjs.MediaTechController.prototype.removeRemoteTextTrack = function(track) {
6928
+ this.textTracks().removeTrack_(track);
6929
+ this.remoteTextTracks().removeTrack_(track);
6930
+ };
6931
+
6612
6932
  /**
6613
6933
  * Provide a default setPoster method for techs
6614
6934
  *
@@ -6628,6 +6948,8 @@ vjs.MediaTechController.prototype['featuresPlaybackRate'] = false;
6628
6948
  vjs.MediaTechController.prototype['featuresProgressEvents'] = false;
6629
6949
  vjs.MediaTechController.prototype['featuresTimeupdateEvents'] = false;
6630
6950
 
6951
+ vjs.MediaTechController.prototype['featuresNativeTextTracks'] = false;
6952
+
6631
6953
  /**
6632
6954
  * A functional mixin for techs that want to use the Source Handler pattern.
6633
6955
  *
@@ -6728,6 +7050,10 @@ vjs.MediaTechController.withSourceHandlers = function(Tech){
6728
7050
  };
6729
7051
 
6730
7052
  };
7053
+
7054
+ vjs.media = {};
7055
+
7056
+ })();
6731
7057
  /**
6732
7058
  * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
6733
7059
  */
@@ -6742,6 +7068,12 @@ vjs.MediaTechController.withSourceHandlers = function(Tech){
6742
7068
  vjs.Html5 = vjs.MediaTechController.extend({
6743
7069
  /** @constructor */
6744
7070
  init: function(player, options, ready){
7071
+ var nodes, nodesLength, i, node, nodeName, removeNodes;
7072
+
7073
+ if (options['nativeCaptions'] === false || options['nativeTextTracks'] === false) {
7074
+ this['featuresNativeTextTracks'] = false;
7075
+ }
7076
+
6745
7077
  vjs.MediaTechController.call(this, player, options, ready);
6746
7078
 
6747
7079
  this.setupTriggers();
@@ -6756,6 +7088,37 @@ vjs.Html5 = vjs.MediaTechController.extend({
6756
7088
  this.setSource(source);
6757
7089
  }
6758
7090
 
7091
+ if (this.el_.hasChildNodes()) {
7092
+
7093
+ nodes = this.el_.childNodes;
7094
+ nodesLength = nodes.length;
7095
+ removeNodes = [];
7096
+
7097
+ while (nodesLength--) {
7098
+ node = nodes[nodesLength];
7099
+ nodeName = node.nodeName.toLowerCase();
7100
+ if (nodeName === 'track') {
7101
+ if (!this['featuresNativeTextTracks']) {
7102
+ // Empty video tag tracks so the built-in player doesn't use them also.
7103
+ // This may not be fast enough to stop HTML5 browsers from reading the tags
7104
+ // so we'll need to turn off any default tracks if we're manually doing
7105
+ // captions and subtitles. videoElement.textTracks
7106
+ removeNodes.push(node);
7107
+ } else {
7108
+ this.remoteTextTracks().addTrack_(node['track']);
7109
+ }
7110
+ }
7111
+ }
7112
+
7113
+ for (i=0; i<removeNodes.length; i++) {
7114
+ this.el_.removeChild(removeNodes[i]);
7115
+ }
7116
+ }
7117
+
7118
+ if (this['featuresNativeTextTracks']) {
7119
+ this.on('loadstart', vjs.bind(this, this.hideCaptions));
7120
+ }
7121
+
6759
7122
  // Determine if native controls should be used
6760
7123
  // Our goal should be to get the custom controls on mobile solid everywhere
6761
7124
  // so we can remove this all together. Right now this will block custom
@@ -6786,6 +7149,9 @@ vjs.Html5.prototype.dispose = function(){
6786
7149
 
6787
7150
  vjs.Html5.prototype.createEl = function(){
6788
7151
  var player = this.player_,
7152
+ track,
7153
+ trackEl,
7154
+ i,
6789
7155
  // If possible, reuse original tag for HTML5 playback technology element
6790
7156
  el = player.tag,
6791
7157
  attributes,
@@ -6822,12 +7188,27 @@ vjs.Html5.prototype.createEl = function(){
6822
7188
  // associate the player with the new tag
6823
7189
  el['player'] = player;
6824
7190
 
7191
+ if (player.options_.tracks) {
7192
+ for (i = 0; i < player.options_.tracks.length; i++) {
7193
+ track = player.options_.tracks[i];
7194
+ trackEl = document.createElement('track');
7195
+ trackEl.kind = track.kind;
7196
+ trackEl.label = track.label;
7197
+ trackEl.srclang = track.srclang;
7198
+ trackEl.src = track.src;
7199
+ if ('default' in track) {
7200
+ trackEl.setAttribute('default', 'default');
7201
+ }
7202
+ el.appendChild(trackEl);
7203
+ }
7204
+ }
7205
+
6825
7206
  vjs.insertFirst(el, player.el());
6826
7207
  }
6827
7208
 
6828
7209
  // Update specific tag settings, in case they were overridden
6829
7210
  var settingsAttrs = ['autoplay','preload','loop','muted'];
6830
- for (var i = settingsAttrs.length - 1; i >= 0; i--) {
7211
+ for (i = settingsAttrs.length - 1; i >= 0; i--) {
6831
7212
  var attr = settingsAttrs[i];
6832
7213
  var overwriteAttrs = {};
6833
7214
  if (typeof player.options_[attr] !== 'undefined') {
@@ -6840,6 +7221,24 @@ vjs.Html5.prototype.createEl = function(){
6840
7221
  // jenniisawesome = true;
6841
7222
  };
6842
7223
 
7224
+
7225
+ vjs.Html5.prototype.hideCaptions = function() {
7226
+ var tracks = this.el_.textTracks,
7227
+ track,
7228
+ i = tracks.length,
7229
+ kinds = {
7230
+ 'captions': 1,
7231
+ 'subtitles': 1
7232
+ };
7233
+
7234
+ while (i--) {
7235
+ track = tracks[i];
7236
+ if (track && track['kind'] in kinds) {
7237
+ track.mode = 'disabled';
7238
+ }
7239
+ }
7240
+ };
7241
+
6843
7242
  // Make video events trigger player events
6844
7243
  // May seem verbose here, but makes other APIs possible.
6845
7244
  // Triggers removed using this.off when disposed
@@ -7011,7 +7410,95 @@ vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
7011
7410
  vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
7012
7411
 
7013
7412
  vjs.Html5.prototype.networkState = function(){ return this.el_.networkState; };
7413
+ vjs.Html5.prototype.readyState = function(){ return this.el_.readyState; };
7414
+
7415
+ vjs.Html5.prototype.textTracks = function() {
7416
+ if (!this['featuresNativeTextTracks']) {
7417
+ return vjs.MediaTechController.prototype.textTracks.call(this);
7418
+ }
7419
+
7420
+ return this.el_.textTracks;
7421
+ };
7422
+ vjs.Html5.prototype.addTextTrack = function(kind, label, language) {
7423
+ if (!this['featuresNativeTextTracks']) {
7424
+ return vjs.MediaTechController.prototype.addTextTrack.call(this, kind, label, language);
7425
+ }
7426
+
7427
+ return this.el_.addTextTrack(kind, label, language);
7428
+ };
7429
+
7430
+ vjs.Html5.prototype.addRemoteTextTrack = function(options) {
7431
+ if (!this['featuresNativeTextTracks']) {
7432
+ return vjs.MediaTechController.prototype.addRemoteTextTrack.call(this, options);
7433
+ }
7434
+
7435
+ var track = document.createElement('track');
7436
+ options = options || {};
7437
+
7438
+ if (options['kind']) {
7439
+ track['kind'] = options['kind'];
7440
+ }
7441
+ if (options['label']) {
7442
+ track['label'] = options['label'];
7443
+ }
7444
+ if (options['language'] || options['srclang']) {
7445
+ track['srclang'] = options['language'] || options['srclang'];
7446
+ }
7447
+ if (options['default']) {
7448
+ track['default'] = options['default'];
7449
+ }
7450
+ if (options['id']) {
7451
+ track['id'] = options['id'];
7452
+ }
7453
+ if (options['src']) {
7454
+ track['src'] = options['src'];
7455
+ }
7456
+
7457
+ this.el().appendChild(track);
7014
7458
 
7459
+ if (track.track['kind'] === 'metadata') {
7460
+ track['track']['mode'] = 'hidden';
7461
+ } else {
7462
+ track['track']['mode'] = 'disabled';
7463
+ }
7464
+
7465
+ track['onload'] = function() {
7466
+ var tt = track['track'];
7467
+ if (track.readyState >= 2) {
7468
+ if (tt['kind'] === 'metadata' && tt['mode'] !== 'hidden') {
7469
+ tt['mode'] = 'hidden';
7470
+ } else if (tt['kind'] !== 'metadata' && tt['mode'] !== 'disabled') {
7471
+ tt['mode'] = 'disabled';
7472
+ }
7473
+ track['onload'] = null;
7474
+ }
7475
+ };
7476
+
7477
+ this.remoteTextTracks().addTrack_(track.track);
7478
+
7479
+ return track;
7480
+ };
7481
+
7482
+ vjs.Html5.prototype.removeRemoteTextTrack = function(track) {
7483
+ if (!this['featuresNativeTextTracks']) {
7484
+ return vjs.MediaTechController.prototype.removeRemoteTextTrack.call(this, track);
7485
+ }
7486
+
7487
+ var tracks, i;
7488
+
7489
+ this.remoteTextTracks().removeTrack_(track);
7490
+
7491
+ tracks = this.el()['querySelectorAll']('track');
7492
+
7493
+ for (i = 0; i < tracks.length; i++) {
7494
+ if (tracks[i] === track || tracks[i]['track'] === track) {
7495
+ tracks[i]['parentNode']['removeChild'](tracks[i]);
7496
+ break;
7497
+ }
7498
+ }
7499
+ };
7500
+
7501
+ /* HTML5 Support Testing ---------------------------------------------------- */
7015
7502
 
7016
7503
  /**
7017
7504
  * Check if HTML5 video is supported by this browser/device
@@ -7113,6 +7600,29 @@ vjs.Html5.canControlPlaybackRate = function(){
7113
7600
  return playbackRate !== vjs.TEST_VID.playbackRate;
7114
7601
  };
7115
7602
 
7603
+ /**
7604
+ * Check to see if native text tracks are supported by this browser/device
7605
+ * @return {Boolean}
7606
+ */
7607
+ vjs.Html5.supportsNativeTextTracks = function() {
7608
+ var supportsTextTracks;
7609
+
7610
+ // Figure out native text track support
7611
+ // If mode is a number, we cannot change it because it'll disappear from view.
7612
+ // Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
7613
+ // Firefox isn't playing nice either with modifying the mode
7614
+ // TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
7615
+ supportsTextTracks = !!vjs.TEST_VID.textTracks;
7616
+ if (supportsTextTracks && vjs.TEST_VID.textTracks.length > 0) {
7617
+ supportsTextTracks = typeof vjs.TEST_VID.textTracks[0]['mode'] !== 'number';
7618
+ }
7619
+ if (supportsTextTracks && vjs.IS_FIREFOX) {
7620
+ supportsTextTracks = false;
7621
+ }
7622
+
7623
+ return supportsTextTracks;
7624
+ };
7625
+
7116
7626
  /**
7117
7627
  * Set the tech's volume control support status
7118
7628
  * @type {Boolean}
@@ -7145,8 +7655,14 @@ vjs.Html5.prototype['featuresFullscreenResize'] = true;
7145
7655
  */
7146
7656
  vjs.Html5.prototype['featuresProgressEvents'] = true;
7147
7657
 
7148
- // HTML5 Feature detection and Device Fixes --------------------------------- //
7149
- (function() {
7658
+ /**
7659
+ * Sets the tech's status on native text track support
7660
+ * @type {Boolean}
7661
+ */
7662
+ vjs.Html5.prototype['featuresNativeTextTracks'] = vjs.Html5.supportsNativeTextTracks();
7663
+
7664
+ // HTML5 Feature detection and Device Fixes --------------------------------- //
7665
+ (function() {
7150
7666
  var canPlayType,
7151
7667
  mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i,
7152
7668
  mp4RE = /^video\/mp4/i;
@@ -7414,7 +7930,7 @@ vjs.Flash.prototype.enterFullScreen = function(){
7414
7930
  // Create setters and getters for attributes
7415
7931
  var api = vjs.Flash.prototype,
7416
7932
  readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
7417
- readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(','),
7933
+ readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(','),
7418
7934
  // Overridden: buffered, currentTime, currentSrc
7419
7935
  i;
7420
7936
 
@@ -7770,702 +8286,538 @@ vjs.MediaLoader = vjs.Component.extend({
7770
8286
  }
7771
8287
  }
7772
8288
  });
7773
- /**
7774
- * @fileoverview Text Tracks
7775
- * Text tracks are tracks of timed text events.
7776
- * Captions - text displayed over the video for the hearing impaired
7777
- * Subtitles - text displayed over the video for those who don't understand language in the video
7778
- * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
7779
- * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
7780
- */
7781
-
7782
- // Player Additions - Functions add to the player object for easier access to tracks
7783
-
7784
- /**
7785
- * List of associated text tracks
7786
- * @type {Array}
7787
- * @private
8289
+ /*
8290
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
8291
+ *
8292
+ * enum TextTrackMode { "disabled", "hidden", "showing" };
7788
8293
  */
7789
- vjs.Player.prototype.textTracks_;
8294
+ vjs.TextTrackMode = {
8295
+ 'disabled': 'disabled',
8296
+ 'hidden': 'hidden',
8297
+ 'showing': 'showing'
8298
+ };
7790
8299
 
7791
- /**
7792
- * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
7793
- * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
7794
- * @return {Array} Array of track objects
7795
- * @private
8300
+ /*
8301
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
8302
+ *
8303
+ * enum TextTrackKind { "subtitles", "captions", "descriptions", "chapters", "metadata" };
7796
8304
  */
7797
- vjs.Player.prototype.textTracks = function(){
7798
- this.textTracks_ = this.textTracks_ || [];
7799
- return this.textTracks_;
8305
+ vjs.TextTrackKind = {
8306
+ 'subtitles': 'subtitles',
8307
+ 'captions': 'captions',
8308
+ 'descriptions': 'descriptions',
8309
+ 'chapters': 'chapters',
8310
+ 'metadata': 'metadata'
7800
8311
  };
7801
-
7802
- /**
7803
- * Add a text track
7804
- * In addition to the W3C settings we allow adding additional info through options.
7805
- * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
7806
- * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
7807
- * @param {String=} label Optional label
7808
- * @param {String=} language Optional language
7809
- * @param {Object=} options Additional track options, like src
7810
- * @private
8312
+ (function() {
8313
+ /*
8314
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
8315
+ *
8316
+ * interface TextTrack : EventTarget {
8317
+ * readonly attribute TextTrackKind kind;
8318
+ * readonly attribute DOMString label;
8319
+ * readonly attribute DOMString language;
8320
+ *
8321
+ * readonly attribute DOMString id;
8322
+ * readonly attribute DOMString inBandMetadataTrackDispatchType;
8323
+ *
8324
+ * attribute TextTrackMode mode;
8325
+ *
8326
+ * readonly attribute TextTrackCueList? cues;
8327
+ * readonly attribute TextTrackCueList? activeCues;
8328
+ *
8329
+ * void addCue(TextTrackCue cue);
8330
+ * void removeCue(TextTrackCue cue);
8331
+ *
8332
+ * attribute EventHandler oncuechange;
8333
+ * };
7811
8334
  */
7812
- vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
7813
- var tracks = this.textTracks_ = this.textTracks_ || [];
8335
+
8336
+ vjs.TextTrack = function(options) {
8337
+ var tt, id, mode, kind, label, language, cues, activeCues, timeupdateHandler, changed, prop;
8338
+
7814
8339
  options = options || {};
7815
8340
 
7816
- options['kind'] = kind;
7817
- options['label'] = label;
7818
- options['language'] = language;
7819
-
7820
- // HTML5 Spec says default to subtitles.
7821
- // Uppercase first letter to match class names
7822
- var Kind = vjs.capitalize(kind || 'subtitles');
7823
-
7824
- // Create correct texttrack class. CaptionsTrack, etc.
7825
- var track = new window['videojs'][Kind + 'Track'](this, options);
7826
-
7827
- tracks.push(track);
7828
-
7829
- // If track.dflt() is set, start showing immediately
7830
- // TODO: Add a process to determine the best track to show for the specific kind
7831
- // In case there are multiple defaulted tracks of the same kind
7832
- // Or the user has a set preference of a specific language that should override the default
7833
- // Note: The setTimeout is a workaround because with the html5 tech, the player is 'ready'
7834
- // before it's child components (including the textTrackDisplay) have finished loading.
7835
- if (track.dflt()) {
7836
- this.ready(function(){
7837
- this.setTimeout(function(){
7838
- track.player().showTextTrack(track.id());
7839
- }, 0);
7840
- });
8341
+ if (!options['player']) {
8342
+ throw new Error('A player was not provided.');
7841
8343
  }
7842
8344
 
7843
- return track;
7844
- };
7845
-
7846
- /**
7847
- * Add an array of text tracks. captions, subtitles, chapters, descriptions
7848
- * Track objects will be stored in the player.textTracks() array
7849
- * @param {Array} trackList Array of track elements or objects (fake track elements)
7850
- * @private
7851
- */
7852
- vjs.Player.prototype.addTextTracks = function(trackList){
7853
- var trackObj;
8345
+ tt = this;
8346
+ if (vjs.IS_IE8) {
8347
+ tt = document.createElement('custom');
7854
8348
 
7855
- for (var i = 0; i < trackList.length; i++) {
7856
- trackObj = trackList[i];
7857
- this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
8349
+ for (prop in vjs.TextTrack.prototype) {
8350
+ tt[prop] = vjs.TextTrack.prototype[prop];
8351
+ }
7858
8352
  }
7859
8353
 
7860
- return this;
7861
- };
8354
+ tt.player_ = options['player'];
7862
8355
 
7863
- // Show a text track
7864
- // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
7865
- vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
7866
- var tracks = this.textTracks_,
7867
- i = 0,
7868
- j = tracks.length,
7869
- track, showTrack, kind;
8356
+ mode = vjs.TextTrackMode[options['mode']] || 'disabled';
8357
+ kind = vjs.TextTrackKind[options['kind']] || 'subtitles';
8358
+ label = options['label'] || '';
8359
+ language = options['language'] || options['srclang'] || '';
8360
+ id = options['id'] || 'vjs_text_track_' + vjs.guid++;
7870
8361
 
7871
- // Find Track with same ID
7872
- for (;i<j;i++) {
7873
- track = tracks[i];
7874
- if (track.id() === id) {
7875
- track.show();
7876
- showTrack = track;
8362
+ if (kind === 'metadata' || kind === 'chapters') {
8363
+ mode = 'hidden';
8364
+ }
7877
8365
 
7878
- // Disable tracks of the same kind
7879
- } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
7880
- track.disable();
8366
+ tt.cues_ = [];
8367
+ tt.activeCues_ = [];
8368
+
8369
+ cues = new vjs.TextTrackCueList(tt.cues_);
8370
+ activeCues = new vjs.TextTrackCueList(tt.activeCues_);
8371
+
8372
+ changed = false;
8373
+ timeupdateHandler = vjs.bind(tt, function() {
8374
+ this['activeCues'];
8375
+ if (changed) {
8376
+ this['trigger']('cuechange');
8377
+ changed = false;
7881
8378
  }
8379
+ });
8380
+ if (mode !== 'disabled') {
8381
+ tt.player_.on('timeupdate', timeupdateHandler);
7882
8382
  }
7883
8383
 
7884
- // Get track kind from shown track or disableSameKind
7885
- kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
8384
+ Object.defineProperty(tt, 'kind', {
8385
+ get: function() {
8386
+ return kind;
8387
+ },
8388
+ set: Function.prototype
8389
+ });
7886
8390
 
7887
- // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
7888
- if (kind) {
7889
- this.trigger(kind+'trackchange');
7890
- }
8391
+ Object.defineProperty(tt, 'label', {
8392
+ get: function() {
8393
+ return label;
8394
+ },
8395
+ set: Function.prototype
8396
+ });
7891
8397
 
7892
- return this;
7893
- };
8398
+ Object.defineProperty(tt, 'language', {
8399
+ get: function() {
8400
+ return language;
8401
+ },
8402
+ set: Function.prototype
8403
+ });
7894
8404
 
7895
- /**
7896
- * The base class for all text tracks
7897
- *
7898
- * Handles the parsing, hiding, and showing of text track cues
7899
- *
7900
- * @param {vjs.Player|Object} player
7901
- * @param {Object=} options
7902
- * @constructor
7903
- */
7904
- vjs.TextTrack = vjs.Component.extend({
7905
- /** @constructor */
7906
- init: function(player, options){
7907
- vjs.Component.call(this, player, options);
8405
+ Object.defineProperty(tt, 'id', {
8406
+ get: function() {
8407
+ return id;
8408
+ },
8409
+ set: Function.prototype
8410
+ });
7908
8411
 
7909
- // Apply track info to track object
7910
- // Options will often be a track element
8412
+ Object.defineProperty(tt, 'mode', {
8413
+ get: function() {
8414
+ return mode;
8415
+ },
8416
+ set: function(newMode) {
8417
+ if (!vjs.TextTrackMode[newMode]) {
8418
+ return;
8419
+ }
8420
+ mode = newMode;
8421
+ if (mode === 'showing') {
8422
+ this.player_.on('timeupdate', timeupdateHandler);
8423
+ }
8424
+ this.trigger('modechange');
8425
+ }
8426
+ });
7911
8427
 
7912
- // Build ID if one doesn't exist
7913
- this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
7914
- this.src_ = options['src'];
7915
- // 'default' is a reserved keyword in js so we use an abbreviated version
7916
- this.dflt_ = options['default'] || options['dflt'];
7917
- this.title_ = options['title'];
7918
- this.language_ = options['srclang'];
7919
- this.label_ = options['label'];
7920
- this.cues_ = [];
7921
- this.activeCues_ = [];
7922
- this.readyState_ = 0;
7923
- this.mode_ = 0;
8428
+ Object.defineProperty(tt, 'cues', {
8429
+ get: function() {
8430
+ if (!this.loaded_) {
8431
+ return null;
8432
+ }
7924
8433
 
7925
- player.on('dispose', vjs.bind(this, this.deactivate, this.id_));
7926
- }
7927
- });
8434
+ return cues;
8435
+ },
8436
+ set: Function.prototype
8437
+ });
7928
8438
 
7929
- /**
7930
- * Track kind value. Captions, subtitles, etc.
7931
- * @private
7932
- */
7933
- vjs.TextTrack.prototype.kind_;
8439
+ Object.defineProperty(tt, 'activeCues', {
8440
+ get: function() {
8441
+ var i, l, active, ct, cue;
7934
8442
 
7935
- /**
7936
- * Get the track kind value
7937
- * @return {String}
7938
- */
7939
- vjs.TextTrack.prototype.kind = function(){
7940
- return this.kind_;
7941
- };
8443
+ if (!this.loaded_) {
8444
+ return null;
8445
+ }
7942
8446
 
7943
- /**
7944
- * Track src value
7945
- * @private
7946
- */
7947
- vjs.TextTrack.prototype.src_;
8447
+ if (this['cues'].length === 0) {
8448
+ return activeCues; // nothing to do
8449
+ }
7948
8450
 
7949
- /**
7950
- * Get the track src value
7951
- * @return {String}
7952
- */
7953
- vjs.TextTrack.prototype.src = function(){
7954
- return this.src_;
7955
- };
8451
+ ct = this.player_.currentTime();
8452
+ i = 0;
8453
+ l = this['cues'].length;
8454
+ active = [];
8455
+
8456
+ for (; i < l; i++) {
8457
+ cue = this['cues'][i];
8458
+ if (cue['startTime'] <= ct && cue['endTime'] >= ct) {
8459
+ active.push(cue);
8460
+ } else if (cue['startTime'] === cue['endTime'] && cue['startTime'] <= ct && cue['startTime'] + 0.5 >= ct) {
8461
+ active.push(cue);
8462
+ }
8463
+ }
7956
8464
 
7957
- /**
7958
- * Track default value
7959
- * If default is used, subtitles/captions to start showing
7960
- * @private
7961
- */
7962
- vjs.TextTrack.prototype.dflt_;
8465
+ changed = false;
7963
8466
 
7964
- /**
7965
- * Get the track default value. ('default' is a reserved keyword)
7966
- * @return {Boolean}
7967
- */
7968
- vjs.TextTrack.prototype.dflt = function(){
7969
- return this.dflt_;
7970
- };
8467
+ if (active.length !== this.activeCues_.length) {
8468
+ changed = true;
8469
+ } else {
8470
+ for (i = 0; i < active.length; i++) {
8471
+ if (indexOf.call(this.activeCues_, active[i]) === -1) {
8472
+ changed = true;
8473
+ }
8474
+ }
8475
+ }
7971
8476
 
7972
- /**
7973
- * Track title value
7974
- * @private
7975
- */
7976
- vjs.TextTrack.prototype.title_;
8477
+ this.activeCues_ = active;
8478
+ activeCues.setCues_(this.activeCues_);
7977
8479
 
7978
- /**
7979
- * Get the track title value
7980
- * @return {String}
7981
- */
7982
- vjs.TextTrack.prototype.title = function(){
7983
- return this.title_;
7984
- };
8480
+ return activeCues;
8481
+ },
8482
+ set: Function.prototype
8483
+ });
7985
8484
 
7986
- /**
7987
- * Language - two letter string to represent track language, e.g. 'en' for English
7988
- * Spec def: readonly attribute DOMString language;
7989
- * @private
7990
- */
7991
- vjs.TextTrack.prototype.language_;
8485
+ if (options.src) {
8486
+ loadTrack(options.src, tt);
8487
+ } else {
8488
+ tt.loaded_ = true;
8489
+ }
7992
8490
 
7993
- /**
7994
- * Get the track language value
7995
- * @return {String}
7996
- */
7997
- vjs.TextTrack.prototype.language = function(){
7998
- return this.language_;
8491
+ if (vjs.IS_IE8) {
8492
+ return tt;
8493
+ }
7999
8494
  };
8000
8495
 
8001
- /**
8002
- * Track label e.g. 'English'
8003
- * Spec def: readonly attribute DOMString label;
8004
- * @private
8005
- */
8006
- vjs.TextTrack.prototype.label_;
8496
+ vjs.TextTrack.prototype = vjs.obj.create(vjs.EventEmitter.prototype);
8497
+ vjs.TextTrack.prototype.constructor = vjs.TextTrack;
8007
8498
 
8008
- /**
8009
- * Get the track label value
8010
- * @return {String}
8499
+ /*
8500
+ * cuechange - One or more cues in the track have become active or stopped being active.
8011
8501
  */
8012
- vjs.TextTrack.prototype.label = function(){
8013
- return this.label_;
8502
+ vjs.TextTrack.prototype.allowedEvents_ = {
8503
+ 'cuechange': 'cuechange'
8014
8504
  };
8015
8505
 
8016
- /**
8017
- * All cues of the track. Cues have a startTime, endTime, text, and other properties.
8018
- * Spec def: readonly attribute TextTrackCueList cues;
8019
- * @private
8020
- */
8021
- vjs.TextTrack.prototype.cues_;
8506
+ vjs.TextTrack.prototype.addCue = function(cue) {
8507
+ var tracks = this.player_.textTracks(),
8508
+ i = 0;
8022
8509
 
8023
- /**
8024
- * Get the track cues
8025
- * @return {Array}
8026
- */
8027
- vjs.TextTrack.prototype.cues = function(){
8028
- return this.cues_;
8510
+ if (tracks) {
8511
+ for (; i < tracks.length; i++) {
8512
+ if (tracks[i] !== this) {
8513
+ tracks[i].removeCue(cue);
8514
+ }
8515
+ }
8516
+ }
8517
+
8518
+ this.cues_.push(cue);
8519
+ this['cues'].setCues_(this.cues_);
8029
8520
  };
8030
8521
 
8031
- /**
8032
- * ActiveCues is all cues that are currently showing
8033
- * Spec def: readonly attribute TextTrackCueList activeCues;
8034
- * @private
8035
- */
8036
- vjs.TextTrack.prototype.activeCues_;
8522
+ vjs.TextTrack.prototype.removeCue = function(removeCue) {
8523
+ var i = 0,
8524
+ l = this.cues_.length,
8525
+ cue,
8526
+ removed = false;
8037
8527
 
8038
- /**
8039
- * Get the track active cues
8040
- * @return {Array}
8041
- */
8042
- vjs.TextTrack.prototype.activeCues = function(){
8043
- return this.activeCues_;
8528
+ for (; i < l; i++) {
8529
+ cue = this.cues_[i];
8530
+ if (cue === removeCue) {
8531
+ this.cues_.splice(i, 1);
8532
+ removed = true;
8533
+ }
8534
+ }
8535
+
8536
+ if (removed) {
8537
+ this.cues.setCues_(this.cues_);
8538
+ }
8044
8539
  };
8045
8540
 
8046
- /**
8047
- * ReadyState describes if the text file has been loaded
8048
- * const unsigned short NONE = 0;
8049
- * const unsigned short LOADING = 1;
8050
- * const unsigned short LOADED = 2;
8051
- * const unsigned short ERROR = 3;
8052
- * readonly attribute unsigned short readyState;
8053
- * @private
8541
+ /*
8542
+ * Downloading stuff happens below this point
8054
8543
  */
8055
- vjs.TextTrack.prototype.readyState_;
8544
+ var loadTrack, parseCues, indexOf;
8056
8545
 
8057
- /**
8058
- * Get the track readyState
8059
- * @return {Number}
8060
- */
8061
- vjs.TextTrack.prototype.readyState = function(){
8062
- return this.readyState_;
8063
- };
8546
+ loadTrack = function(src, track) {
8547
+ vjs.xhr(src, vjs.bind(this, function(err, response, responseBody){
8548
+ if (err) {
8549
+ return vjs.log.error(err);
8550
+ }
8064
8551
 
8065
- /**
8066
- * Mode describes if the track is showing, hidden, or disabled
8067
- * const unsigned short OFF = 0;
8068
- * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
8069
- * const unsigned short SHOWING = 2;
8070
- * attribute unsigned short mode;
8071
- * @private
8072
- */
8073
- vjs.TextTrack.prototype.mode_;
8074
8552
 
8075
- /**
8076
- * Get the track mode
8077
- * @return {Number}
8078
- */
8079
- vjs.TextTrack.prototype.mode = function(){
8080
- return this.mode_;
8553
+ track.loaded_ = true;
8554
+ parseCues(responseBody, track);
8555
+ }));
8081
8556
  };
8082
8557
 
8083
- /**
8084
- * Create basic div to hold cue text
8085
- * @return {Element}
8086
- */
8087
- vjs.TextTrack.prototype.createEl = function(){
8088
- return vjs.Component.prototype.createEl.call(this, 'div', {
8089
- className: 'vjs-' + this.kind_ + ' vjs-text-track'
8090
- });
8091
- };
8558
+ parseCues = function(srcContent, track) {
8559
+ if (typeof window['WebVTT'] !== 'function') {
8560
+ //try again a bit later
8561
+ return window.setTimeout(function() {
8562
+ parseCues(srcContent, track);
8563
+ }, 25);
8564
+ }
8092
8565
 
8093
- /**
8094
- * Show: Mode Showing (2)
8095
- * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
8096
- * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
8097
- * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
8098
- * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
8099
- * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
8100
- * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
8101
- * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
8102
- */
8103
- vjs.TextTrack.prototype.show = function(){
8104
- this.activate();
8566
+ var parser = new window['WebVTT']['Parser'](window, window['vttjs'], window['WebVTT']['StringDecoder']());
8105
8567
 
8106
- this.mode_ = 2;
8568
+ parser['oncue'] = function(cue) {
8569
+ track.addCue(cue);
8570
+ };
8571
+ parser['onparsingerror'] = function(error) {
8572
+ vjs.log.error(error);
8573
+ };
8107
8574
 
8108
- // Show element.
8109
- vjs.Component.prototype.show.call(this);
8575
+ parser['parse'](srcContent);
8576
+ parser['flush']();
8110
8577
  };
8111
8578
 
8112
- /**
8113
- * Hide: Mode Hidden (1)
8114
- * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
8115
- * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
8116
- * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
8117
- */
8118
- vjs.TextTrack.prototype.hide = function(){
8119
- // When hidden, cues are still triggered. Disable to stop triggering.
8120
- this.activate();
8579
+ indexOf = function(searchElement, fromIndex) {
8121
8580
 
8122
- this.mode_ = 1;
8581
+ var k;
8123
8582
 
8124
- // Hide element.
8125
- vjs.Component.prototype.hide.call(this);
8126
- };
8583
+ if (this == null) {
8584
+ throw new TypeError('"this" is null or not defined');
8585
+ }
8127
8586
 
8128
- /**
8129
- * Disable: Mode Off/Disable (0)
8130
- * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
8131
- * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
8132
- */
8133
- vjs.TextTrack.prototype.disable = function(){
8134
- // If showing, hide.
8135
- if (this.mode_ == 2) { this.hide(); }
8587
+ var O = Object(this);
8136
8588
 
8137
- // Stop triggering cues
8138
- this.deactivate();
8589
+ var len = O.length >>> 0;
8139
8590
 
8140
- // Switch Mode to Off
8141
- this.mode_ = 0;
8142
- };
8591
+ if (len === 0) {
8592
+ return -1;
8593
+ }
8143
8594
 
8144
- /**
8145
- * Turn on cue tracking. Tracks that are showing OR hidden are active.
8146
- */
8147
- vjs.TextTrack.prototype.activate = function(){
8148
- // Load text file if it hasn't been yet.
8149
- if (this.readyState_ === 0) { this.load(); }
8595
+ var n = +fromIndex || 0;
8596
+
8597
+ if (Math.abs(n) === Infinity) {
8598
+ n = 0;
8599
+ }
8150
8600
 
8151
- // Only activate if not already active.
8152
- if (this.mode_ === 0) {
8153
- // Update current cue on timeupdate
8154
- // Using unique ID for bind function so other tracks don't remove listener
8155
- this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
8601
+ if (n >= len) {
8602
+ return -1;
8603
+ }
8156
8604
 
8157
- // Reset cue time on media end
8158
- this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
8605
+ k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
8159
8606
 
8160
- // Add to display
8161
- if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
8162
- this.player_.getChild('textTrackDisplay').addChild(this);
8607
+ while (k < len) {
8608
+ if (k in O && O[k] === searchElement) {
8609
+ return k;
8163
8610
  }
8611
+ k++;
8164
8612
  }
8613
+ return -1;
8165
8614
  };
8166
8615
 
8167
- /**
8168
- * Turn off cue tracking.
8169
- */
8170
- vjs.TextTrack.prototype.deactivate = function(){
8171
- // Using unique ID for bind function so other tracks don't remove listener
8172
- this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
8173
- this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
8174
- this.reset(); // Reset
8175
-
8176
- // Remove from display
8177
- this.player_.getChild('textTrackDisplay').removeChild(this);
8178
- };
8616
+ })();
8617
+ /*
8618
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
8619
+ *
8620
+ * interface TextTrackList : EventTarget {
8621
+ * readonly attribute unsigned long length;
8622
+ * getter TextTrack (unsigned long index);
8623
+ * TextTrack? getTrackById(DOMString id);
8624
+ *
8625
+ * attribute EventHandler onchange;
8626
+ * attribute EventHandler onaddtrack;
8627
+ * attribute EventHandler onremovetrack;
8628
+ * };
8629
+ */
8630
+ vjs.TextTrackList = function(tracks) {
8631
+ var list = this,
8632
+ prop,
8633
+ i = 0;
8634
+
8635
+ if (vjs.IS_IE8) {
8636
+ list = document.createElement('custom');
8637
+
8638
+ for (prop in vjs.TextTrackList.prototype) {
8639
+ list[prop] = vjs.TextTrackList.prototype[prop];
8640
+ }
8641
+ }
8179
8642
 
8180
- // A readiness state
8181
- // One of the following:
8182
- //
8183
- // Not loaded
8184
- // Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
8185
- //
8186
- // Loading
8187
- // Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
8188
- //
8189
- // Loaded
8190
- // Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
8191
- //
8192
- // Failed to load
8193
- // Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
8194
- vjs.TextTrack.prototype.load = function(){
8643
+ tracks = tracks || [];
8644
+ list.tracks_ = [];
8195
8645
 
8196
- // Only load if not loaded yet.
8197
- if (this.readyState_ === 0) {
8198
- this.readyState_ = 1;
8199
- vjs.xhr(this.src_, vjs.bind(this, function(err, response, responseBody){
8200
- if (err) {
8201
- return this.onError(err);
8202
- }
8646
+ Object.defineProperty(list, 'length', {
8647
+ get: function() {
8648
+ return this.tracks_.length;
8649
+ }
8650
+ });
8203
8651
 
8204
- this.parseCues(responseBody);
8205
- }));
8652
+ for (; i < tracks.length; i++) {
8653
+ list.addTrack_(tracks[i]);
8206
8654
  }
8207
8655
 
8656
+ if (vjs.IS_IE8) {
8657
+ return list;
8658
+ }
8208
8659
  };
8209
8660
 
8210
- vjs.TextTrack.prototype.onError = function(err){
8211
- this.error = err;
8212
- this.readyState_ = 3;
8213
- this.trigger('error');
8214
- };
8215
-
8216
- // Parse the WebVTT text format for cue times.
8217
- // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
8218
- vjs.TextTrack.prototype.parseCues = function(srcContent) {
8219
- var cue, time, text,
8220
- lines = srcContent.split('\n'),
8221
- line = '', id;
8661
+ vjs.TextTrackList.prototype = vjs.obj.create(vjs.EventEmitter.prototype);
8662
+ vjs.TextTrackList.prototype.constructor = vjs.TextTrackList;
8222
8663
 
8223
- for (var i=1, j=lines.length; i<j; i++) {
8224
- // Line 0 should be 'WEBVTT', so skipping i=0
8664
+ /*
8665
+ * change - One or more tracks in the track list have been enabled or disabled.
8666
+ * addtrack - A track has been added to the track list.
8667
+ * removetrack - A track has been removed from the track list.
8668
+ */
8669
+ vjs.TextTrackList.prototype.allowedEvents_ = {
8670
+ 'change': 'change',
8671
+ 'addtrack': 'addtrack',
8672
+ 'removetrack': 'removetrack'
8673
+ };
8225
8674
 
8226
- line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
8675
+ // emulate attribute EventHandler support to allow for feature detection
8676
+ (function() {
8677
+ var event;
8227
8678
 
8228
- if (line) { // Loop until a line with content
8679
+ for (event in vjs.TextTrackList.prototype.allowedEvents_) {
8680
+ vjs.TextTrackList.prototype['on' + event] = null;
8681
+ }
8682
+ })();
8229
8683
 
8230
- // First line could be an optional cue ID
8231
- // Check if line has the time separator
8232
- if (line.indexOf('-->') == -1) {
8233
- id = line;
8234
- // Advance to next line for timing.
8235
- line = vjs.trim(lines[++i]);
8236
- } else {
8237
- id = this.cues_.length;
8684
+ vjs.TextTrackList.prototype.addTrack_ = function(track) {
8685
+ var index = this.tracks_.length;
8686
+ if (!(''+index in this)) {
8687
+ Object.defineProperty(this, index, {
8688
+ get: function() {
8689
+ return this.tracks_[index];
8238
8690
  }
8691
+ });
8692
+ }
8239
8693
 
8240
- // First line - Number
8241
- cue = {
8242
- id: id, // Cue Number
8243
- index: this.cues_.length // Position in Array
8244
- };
8245
-
8246
- // Timing line
8247
- time = line.split(/[\t ]+/);
8248
- cue.startTime = this.parseCueTime(time[0]);
8249
- cue.endTime = this.parseCueTime(time[2]);
8250
-
8251
- // Additional lines - Cue Text
8252
- text = [];
8694
+ track.addEventListener('modechange', vjs.bind(this, function() {
8695
+ this.trigger('change');
8696
+ }));
8697
+ this.tracks_.push(track);
8253
8698
 
8254
- // Loop until a blank line or end of lines
8255
- // Assuming trim('') returns false for blank lines
8256
- while (lines[++i] && (line = vjs.trim(lines[i]))) {
8257
- text.push(line);
8258
- }
8699
+ this.trigger({
8700
+ type: 'addtrack',
8701
+ track: track
8702
+ });
8703
+ };
8259
8704
 
8260
- cue.text = text.join('<br/>');
8705
+ vjs.TextTrackList.prototype.removeTrack_ = function(rtrack) {
8706
+ var i = 0,
8707
+ l = this.length,
8708
+ result = null,
8709
+ track;
8261
8710
 
8262
- // Add this cue
8263
- this.cues_.push(cue);
8711
+ for (; i < l; i++) {
8712
+ track = this[i];
8713
+ if (track === rtrack) {
8714
+ this.tracks_.splice(i, 1);
8715
+ break;
8264
8716
  }
8265
8717
  }
8266
8718
 
8267
- this.readyState_ = 2;
8268
- this.trigger('loaded');
8719
+ this.trigger({
8720
+ type: 'removetrack',
8721
+ track: rtrack
8722
+ });
8269
8723
  };
8270
8724
 
8725
+ vjs.TextTrackList.prototype.getTrackById = function(id) {
8726
+ var i = 0,
8727
+ l = this.length,
8728
+ result = null,
8729
+ track;
8271
8730
 
8272
- vjs.TextTrack.prototype.parseCueTime = function(timeText) {
8273
- var parts = timeText.split(':'),
8274
- time = 0,
8275
- hours, minutes, other, seconds, ms;
8276
-
8277
- // Check if optional hours place is included
8278
- // 00:00:00.000 vs. 00:00.000
8279
- if (parts.length == 3) {
8280
- hours = parts[0];
8281
- minutes = parts[1];
8282
- other = parts[2];
8283
- } else {
8284
- hours = 0;
8285
- minutes = parts[0];
8286
- other = parts[1];
8287
- }
8288
-
8289
- // Break other (seconds, milliseconds, and flags) by spaces
8290
- // TODO: Make additional cue layout settings work with flags
8291
- other = other.split(/\s+/);
8292
- // Remove seconds. Seconds is the first part before any spaces.
8293
- seconds = other.splice(0,1)[0];
8294
- // Could use either . or , for decimal
8295
- seconds = seconds.split(/\.|,/);
8296
- // Get milliseconds
8297
- ms = parseFloat(seconds[1]);
8298
- seconds = seconds[0];
8299
-
8300
- // hours => seconds
8301
- time += parseFloat(hours) * 3600;
8302
- // minutes => seconds
8303
- time += parseFloat(minutes) * 60;
8304
- // Add seconds
8305
- time += parseFloat(seconds);
8306
- // Add milliseconds
8307
- if (ms) { time += ms/1000; }
8308
-
8309
- return time;
8310
- };
8311
-
8312
- // Update active cues whenever timeupdate events are triggered on the player.
8313
- vjs.TextTrack.prototype.update = function(){
8314
- if (this.cues_.length > 0) {
8315
-
8316
- // Get current player time, adjust for track offset
8317
- var offset = this.player_.options()['trackTimeOffset'] || 0;
8318
- var time = this.player_.currentTime() + offset;
8319
-
8320
- // Check if the new time is outside the time box created by the the last update.
8321
- if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
8322
- var cues = this.cues_,
8323
-
8324
- // Create a new time box for this state.
8325
- newNextChange = this.player_.duration(), // Start at beginning of the timeline
8326
- newPrevChange = 0, // Start at end
8327
-
8328
- reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
8329
- newCues = [], // Store new active cues.
8330
-
8331
- // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
8332
- firstActiveIndex, lastActiveIndex,
8333
- cue, i; // Loop vars
8334
-
8335
- // Check if time is going forwards or backwards (scrubbing/rewinding)
8336
- // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
8337
- if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
8338
- // Forwards, so start at the index of the first active cue and loop forward
8339
- i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
8340
- } else {
8341
- // Backwards, so start at the index of the last active cue and loop backward
8342
- reverse = true;
8343
- i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
8344
- }
8731
+ for (; i < l; i++) {
8732
+ track = this[i];
8733
+ if (track.id === id) {
8734
+ result = track;
8735
+ break;
8736
+ }
8737
+ }
8345
8738
 
8346
- while (true) { // Loop until broken
8347
- cue = cues[i];
8739
+ return result;
8740
+ };
8741
+ /*
8742
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist
8743
+ *
8744
+ * interface TextTrackCueList {
8745
+ * readonly attribute unsigned long length;
8746
+ * getter TextTrackCue (unsigned long index);
8747
+ * TextTrackCue? getCueById(DOMString id);
8748
+ * };
8749
+ */
8348
8750
 
8349
- // Cue ended at this point
8350
- if (cue.endTime <= time) {
8351
- newPrevChange = Math.max(newPrevChange, cue.endTime);
8751
+ vjs.TextTrackCueList = function(cues) {
8752
+ var list = this,
8753
+ prop;
8352
8754
 
8353
- if (cue.active) {
8354
- cue.active = false;
8355
- }
8755
+ if (vjs.IS_IE8) {
8756
+ list = document.createElement('custom');
8356
8757
 
8357
- // No earlier cues should have an active start time.
8358
- // Nevermind. Assume first cue could have a duration the same as the video.
8359
- // In that case we need to loop all the way back to the beginning.
8360
- // if (reverse && cue.startTime) { break; }
8758
+ for (prop in vjs.TextTrackCueList.prototype) {
8759
+ list[prop] = vjs.TextTrackCueList.prototype[prop];
8760
+ }
8761
+ }
8361
8762
 
8362
- // Cue hasn't started
8363
- } else if (time < cue.startTime) {
8364
- newNextChange = Math.min(newNextChange, cue.startTime);
8763
+ vjs.TextTrackCueList.prototype.setCues_.call(list, cues);
8365
8764
 
8366
- if (cue.active) {
8367
- cue.active = false;
8368
- }
8765
+ Object.defineProperty(list, 'length', {
8766
+ get: function() {
8767
+ return this.length_;
8768
+ }
8769
+ });
8369
8770
 
8370
- // No later cues should have an active start time.
8371
- if (!reverse) { break; }
8771
+ if (vjs.IS_IE8) {
8772
+ return list;
8773
+ }
8774
+ };
8372
8775
 
8373
- // Cue is current
8374
- } else {
8776
+ vjs.TextTrackCueList.prototype.setCues_ = function(cues) {
8777
+ var oldLength = this.length || 0,
8778
+ i = 0,
8779
+ l = cues.length,
8780
+ defineProp;
8375
8781
 
8376
- if (reverse) {
8377
- // Add cue to front of array to keep in time order
8378
- newCues.splice(0,0,cue);
8782
+ this.cues_ = cues;
8783
+ this.length_ = cues.length;
8379
8784
 
8380
- // If in reverse, the first current cue is our lastActiveCue
8381
- if (lastActiveIndex === undefined) { lastActiveIndex = i; }
8382
- firstActiveIndex = i;
8383
- } else {
8384
- // Add cue to end of array
8385
- newCues.push(cue);
8386
-
8387
- // If forward, the first current cue is our firstActiveIndex
8388
- if (firstActiveIndex === undefined) { firstActiveIndex = i; }
8389
- lastActiveIndex = i;
8390
- }
8391
-
8392
- newNextChange = Math.min(newNextChange, cue.endTime);
8393
- newPrevChange = Math.max(newPrevChange, cue.startTime);
8394
-
8395
- cue.active = true;
8785
+ defineProp = function(i) {
8786
+ if (!(''+i in this)) {
8787
+ Object.defineProperty(this, '' + i, {
8788
+ get: function() {
8789
+ return this.cues_[i];
8396
8790
  }
8791
+ });
8792
+ }
8793
+ };
8397
8794
 
8398
- if (reverse) {
8399
- // Reverse down the array of cues, break if at first
8400
- if (i === 0) { break; } else { i--; }
8401
- } else {
8402
- // Walk up the array fo cues, break if at last
8403
- if (i === cues.length - 1) { break; } else { i++; }
8404
- }
8405
-
8406
- }
8407
-
8408
- this.activeCues_ = newCues;
8409
- this.nextChange = newNextChange;
8410
- this.prevChange = newPrevChange;
8411
- this.firstActiveIndex = firstActiveIndex;
8412
- this.lastActiveIndex = lastActiveIndex;
8413
-
8414
- this.updateDisplay();
8415
-
8416
- this.trigger('cuechange');
8795
+ if (oldLength < l) {
8796
+ i = oldLength;
8797
+ for(; i < l; i++) {
8798
+ defineProp.call(this, i);
8417
8799
  }
8418
8800
  }
8419
8801
  };
8420
8802
 
8421
- // Add cue HTML to display
8422
- vjs.TextTrack.prototype.updateDisplay = function(){
8423
- var cues = this.activeCues_,
8424
- html = '',
8425
- i=0,j=cues.length;
8803
+ vjs.TextTrackCueList.prototype.getCueById = function(id) {
8804
+ var i = 0,
8805
+ l = this.length,
8806
+ result = null,
8807
+ cue;
8426
8808
 
8427
- for (;i<j;i++) {
8428
- html += '<span class="vjs-tt-cue">'+cues[i].text+'</span>';
8809
+ for (; i < l; i++) {
8810
+ cue = this[i];
8811
+ if (cue.id === id) {
8812
+ result = cue;
8813
+ break;
8814
+ }
8429
8815
  }
8430
8816
 
8431
- this.el_.innerHTML = html;
8432
- };
8433
-
8434
- // Set all loop helper values back
8435
- vjs.TextTrack.prototype.reset = function(){
8436
- this.nextChange = 0;
8437
- this.prevChange = this.player_.duration();
8438
- this.firstActiveIndex = 0;
8439
- this.lastActiveIndex = 0;
8817
+ return result;
8440
8818
  };
8441
-
8442
- // Create specific track types
8443
- /**
8444
- * The track component for managing the hiding and showing of captions
8445
- *
8446
- * @constructor
8447
- */
8448
- vjs.CaptionsTrack = vjs.TextTrack.extend();
8449
- vjs.CaptionsTrack.prototype.kind_ = 'captions';
8450
- // Exporting here because Track creation requires the track kind
8451
- // to be available on global object. e.g. new window['videojs'][Kind + 'Track']
8452
-
8453
- /**
8454
- * The track component for managing the hiding and showing of subtitles
8455
- *
8456
- * @constructor
8457
- */
8458
- vjs.SubtitlesTrack = vjs.TextTrack.extend();
8459
- vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
8460
-
8461
- /**
8462
- * The track component for managing the hiding and showing of chapters
8463
- *
8464
- * @constructor
8465
- */
8466
- vjs.ChaptersTrack = vjs.TextTrack.extend();
8467
- vjs.ChaptersTrack.prototype.kind_ = 'chapters';
8468
-
8819
+ (function() {
8820
+ 'use strict';
8469
8821
 
8470
8822
  /* Text Track Display
8471
8823
  ============================================================================= */
@@ -8481,22 +8833,176 @@ vjs.TextTrackDisplay = vjs.Component.extend({
8481
8833
  init: function(player, options, ready){
8482
8834
  vjs.Component.call(this, player, options, ready);
8483
8835
 
8836
+ player.on('loadstart', vjs.bind(this, this.toggleDisplay));
8837
+
8484
8838
  // This used to be called during player init, but was causing an error
8485
8839
  // if a track should show by default and the display hadn't loaded yet.
8486
8840
  // Should probably be moved to an external track loader when we support
8487
8841
  // tracks that don't need a display.
8488
- if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
8489
- this.player_.addTextTracks(player.options_['tracks']);
8490
- }
8842
+ player.ready(vjs.bind(this, function() {
8843
+ if (player.tech && player.tech['featuresNativeTextTracks']) {
8844
+ this.hide();
8845
+ return;
8846
+ }
8847
+
8848
+ var i, tracks, track;
8849
+
8850
+ player.on('fullscreenchange', vjs.bind(this, this.updateDisplay));
8851
+
8852
+ tracks = player.options_['tracks'] || [];
8853
+ for (i = 0; i < tracks.length; i++) {
8854
+ track = tracks[i];
8855
+ this.player_.addRemoteTextTrack(track);
8856
+ }
8857
+ }));
8491
8858
  }
8492
8859
  });
8493
8860
 
8861
+ vjs.TextTrackDisplay.prototype.toggleDisplay = function() {
8862
+ if (this.player_.tech && this.player_.tech['featuresNativeTextTracks']) {
8863
+ this.hide();
8864
+ } else {
8865
+ this.show();
8866
+ }
8867
+ };
8868
+
8494
8869
  vjs.TextTrackDisplay.prototype.createEl = function(){
8495
8870
  return vjs.Component.prototype.createEl.call(this, 'div', {
8496
8871
  className: 'vjs-text-track-display'
8497
8872
  });
8498
8873
  };
8499
8874
 
8875
+ vjs.TextTrackDisplay.prototype.clearDisplay = function() {
8876
+ if (typeof window['WebVTT'] === 'function') {
8877
+ window['WebVTT']['processCues'](window, [], this.el_);
8878
+ }
8879
+ };
8880
+
8881
+ // Add cue HTML to display
8882
+ var constructColor = function(color, opacity) {
8883
+ return 'rgba(' +
8884
+ // color looks like "#f0e"
8885
+ parseInt(color[1] + color[1], 16) + ',' +
8886
+ parseInt(color[2] + color[2], 16) + ',' +
8887
+ parseInt(color[3] + color[3], 16) + ',' +
8888
+ opacity + ')';
8889
+ };
8890
+ var darkGray = '#222';
8891
+ var lightGray = '#ccc';
8892
+ var fontMap = {
8893
+ monospace: 'monospace',
8894
+ sansSerif: 'sans-serif',
8895
+ serif: 'serif',
8896
+ monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
8897
+ monospaceSerif: '"Courier New", monospace',
8898
+ proportionalSansSerif: 'sans-serif',
8899
+ proportionalSerif: 'serif',
8900
+ casual: '"Comic Sans MS", Impact, fantasy',
8901
+ script: '"Monotype Corsiva", cursive',
8902
+ smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
8903
+ };
8904
+ var tryUpdateStyle = function(el, style, rule) {
8905
+ // some style changes will throw an error, particularly in IE8. Those should be noops.
8906
+ try {
8907
+ el.style[style] = rule;
8908
+ } catch (e) {}
8909
+ };
8910
+
8911
+ vjs.TextTrackDisplay.prototype.updateDisplay = function() {
8912
+ var tracks = this.player_.textTracks(),
8913
+ i = 0,
8914
+ track;
8915
+
8916
+ this.clearDisplay();
8917
+
8918
+ if (!tracks) {
8919
+ return;
8920
+ }
8921
+
8922
+ for (; i < tracks.length; i++) {
8923
+ track = tracks[i];
8924
+ if (track['mode'] === 'showing') {
8925
+ this.updateForTrack(track);
8926
+ }
8927
+ }
8928
+ };
8929
+
8930
+ vjs.TextTrackDisplay.prototype.updateForTrack = function(track) {
8931
+ if (typeof window['WebVTT'] !== 'function' || !track['activeCues']) {
8932
+ return;
8933
+ }
8934
+
8935
+ var i = 0,
8936
+ property,
8937
+ cueDiv,
8938
+ overrides = this.player_['textTrackSettings'].getValues(),
8939
+ fontSize,
8940
+ cues = [];
8941
+
8942
+ for (; i < track['activeCues'].length; i++) {
8943
+ cues.push(track['activeCues'][i]);
8944
+ }
8945
+
8946
+ window['WebVTT']['processCues'](window, track['activeCues'], this.el_);
8947
+
8948
+ i = cues.length;
8949
+ while (i--) {
8950
+ cueDiv = cues[i].displayState;
8951
+ if (overrides.color) {
8952
+ cueDiv.firstChild.style.color = overrides.color;
8953
+ }
8954
+ if (overrides.textOpacity) {
8955
+ tryUpdateStyle(cueDiv.firstChild,
8956
+ 'color',
8957
+ constructColor(overrides.color || '#fff',
8958
+ overrides.textOpacity));
8959
+ }
8960
+ if (overrides.backgroundColor) {
8961
+ cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
8962
+ }
8963
+ if (overrides.backgroundOpacity) {
8964
+ tryUpdateStyle(cueDiv.firstChild,
8965
+ 'backgroundColor',
8966
+ constructColor(overrides.backgroundColor || '#000',
8967
+ overrides.backgroundOpacity));
8968
+ }
8969
+ if (overrides.windowColor) {
8970
+ if (overrides.windowOpacity) {
8971
+ tryUpdateStyle(cueDiv,
8972
+ 'backgroundColor',
8973
+ constructColor(overrides.windowColor, overrides.windowOpacity));
8974
+ } else {
8975
+ cueDiv.style.backgroundColor = overrides.windowColor;
8976
+ }
8977
+ }
8978
+ if (overrides.edgeStyle) {
8979
+ if (overrides.edgeStyle === 'dropshadow') {
8980
+ cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
8981
+ } else if (overrides.edgeStyle === 'raised') {
8982
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
8983
+ } else if (overrides.edgeStyle === 'depressed') {
8984
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
8985
+ } else if (overrides.edgeStyle === 'uniform') {
8986
+ cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
8987
+ }
8988
+ }
8989
+ if (overrides.fontPercent && overrides.fontPercent !== 1) {
8990
+ fontSize = window.parseFloat(cueDiv.style.fontSize);
8991
+ cueDiv.style.fontSize = (fontSize * overrides.fontPercent) + 'px';
8992
+ cueDiv.style.height = 'auto';
8993
+ cueDiv.style.top = 'auto';
8994
+ cueDiv.style.bottom = '2px';
8995
+ }
8996
+ if (overrides.fontFamily && overrides.fontFamily !== 'default') {
8997
+ if (overrides.fontFamily === 'small-caps') {
8998
+ cueDiv.firstChild.style.fontVariant = 'small-caps';
8999
+ } else {
9000
+ cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
9001
+ }
9002
+ }
9003
+ }
9004
+ };
9005
+
8500
9006
 
8501
9007
  /**
8502
9008
  * The specific menu item type for selecting a language within a text track kind
@@ -8506,24 +9012,98 @@ vjs.TextTrackDisplay.prototype.createEl = function(){
8506
9012
  vjs.TextTrackMenuItem = vjs.MenuItem.extend({
8507
9013
  /** @constructor */
8508
9014
  init: function(player, options){
8509
- var track = this.track = options['track'];
9015
+ var track = this.track = options['track'],
9016
+ tracks = player.textTracks(),
9017
+ changeHandler,
9018
+ event;
9019
+
9020
+ if (tracks) {
9021
+ changeHandler = vjs.bind(this, function() {
9022
+ var selected = this.track['mode'] === 'showing',
9023
+ track,
9024
+ i,
9025
+ l;
9026
+
9027
+ if (this instanceof vjs.OffTextTrackMenuItem) {
9028
+ selected = true;
9029
+
9030
+ i = 0,
9031
+ l = tracks.length;
9032
+
9033
+ for (; i < l; i++) {
9034
+ track = tracks[i];
9035
+ if (track['kind'] === this.track['kind'] && track['mode'] === 'showing') {
9036
+ selected = false;
9037
+ break;
9038
+ }
9039
+ }
9040
+ }
9041
+
9042
+ this.selected(selected);
9043
+ });
9044
+ tracks.addEventListener('change', changeHandler);
9045
+ player.on('dispose', function() {
9046
+ tracks.removeEventListener('change', changeHandler);
9047
+ });
9048
+ }
8510
9049
 
8511
9050
  // Modify options for parent MenuItem class's init.
8512
- options['label'] = track.label();
8513
- options['selected'] = track.dflt();
9051
+ options['label'] = track['label'] || track['language'] || 'Unknown';
9052
+ options['selected'] = track['default'] || track['mode'] === 'showing';
8514
9053
  vjs.MenuItem.call(this, player, options);
8515
9054
 
8516
- this.on(player, track.kind() + 'trackchange', this.update);
9055
+ // iOS7 doesn't dispatch change events to TextTrackLists when an
9056
+ // associated track's mode changes. Without something like
9057
+ // Object.observe() (also not present on iOS7), it's not
9058
+ // possible to detect changes to the mode attribute and polyfill
9059
+ // the change event. As a poor substitute, we manually dispatch
9060
+ // change events whenever the controls modify the mode.
9061
+ if (tracks && tracks.onchange === undefined) {
9062
+ this.on(['tap', 'click'], function() {
9063
+ if (typeof window.Event !== 'object') {
9064
+ // Android 2.3 throws an Illegal Constructor error for window.Event
9065
+ try {
9066
+ event = new window.Event('change');
9067
+ } catch(err){}
9068
+ }
9069
+
9070
+ if (!event) {
9071
+ event = document.createEvent('Event');
9072
+ event.initEvent('change', true, true);
9073
+ }
9074
+
9075
+ tracks.dispatchEvent(event);
9076
+ });
9077
+ }
8517
9078
  }
8518
9079
  });
8519
9080
 
8520
9081
  vjs.TextTrackMenuItem.prototype.onClick = function(){
9082
+ var kind = this.track['kind'],
9083
+ tracks = this.player_.textTracks(),
9084
+ mode,
9085
+ track,
9086
+ i = 0;
9087
+
8521
9088
  vjs.MenuItem.prototype.onClick.call(this);
8522
- this.player_.showTextTrack(this.track.id_, this.track.kind());
8523
- };
8524
9089
 
8525
- vjs.TextTrackMenuItem.prototype.update = function(){
8526
- this.selected(this.track.mode() == 2);
9090
+ if (!tracks) {
9091
+ return;
9092
+ }
9093
+
9094
+ for (; i < tracks.length; i++) {
9095
+ track = tracks[i];
9096
+
9097
+ if (track['kind'] !== kind) {
9098
+ continue;
9099
+ }
9100
+
9101
+ if (track === this.track) {
9102
+ track['mode'] = 'showing';
9103
+ } else {
9104
+ track['mode'] = 'disabled';
9105
+ }
9106
+ }
8527
9107
  };
8528
9108
 
8529
9109
  /**
@@ -8537,35 +9117,34 @@ vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
8537
9117
  // Create pseudo track info
8538
9118
  // Requires options['kind']
8539
9119
  options['track'] = {
8540
- kind: function() { return options['kind']; },
8541
- player: player,
8542
- label: function(){ return options['kind'] + ' off'; },
8543
- dflt: function(){ return false; },
8544
- mode: function(){ return false; }
9120
+ 'kind': options['kind'],
9121
+ 'player': player,
9122
+ 'label': options['kind'] + ' off',
9123
+ 'default': false,
9124
+ 'mode': 'disabled'
8545
9125
  };
8546
9126
  vjs.TextTrackMenuItem.call(this, player, options);
8547
9127
  this.selected(true);
8548
9128
  }
8549
9129
  });
8550
9130
 
8551
- vjs.OffTextTrackMenuItem.prototype.onClick = function(){
8552
- vjs.TextTrackMenuItem.prototype.onClick.call(this);
8553
- this.player_.showTextTrack(this.track.id_, this.track.kind());
8554
- };
8555
-
8556
- vjs.OffTextTrackMenuItem.prototype.update = function(){
8557
- var tracks = this.player_.textTracks(),
8558
- i=0, j=tracks.length, track,
8559
- off = true;
9131
+ vjs.CaptionSettingsMenuItem = vjs.TextTrackMenuItem.extend({
9132
+ init: function(player, options) {
9133
+ options['track'] = {
9134
+ 'kind': options['kind'],
9135
+ 'player': player,
9136
+ 'label': options['kind'] + ' settings',
9137
+ 'default': false,
9138
+ mode: 'disabled'
9139
+ };
8560
9140
 
8561
- for (;i<j;i++) {
8562
- track = tracks[i];
8563
- if (track.kind() == this.track.kind() && track.mode() == 2) {
8564
- off = false;
8565
- }
9141
+ vjs.TextTrackMenuItem.call(this, player, options);
9142
+ this.addClass('vjs-texttrack-settings');
8566
9143
  }
9144
+ });
8567
9145
 
8568
- this.selected(off);
9146
+ vjs.CaptionSettingsMenuItem.prototype.onClick = function() {
9147
+ this.player().getChild('textTrackSettings').show();
8569
9148
  };
8570
9149
 
8571
9150
  /**
@@ -8576,49 +9155,53 @@ vjs.OffTextTrackMenuItem.prototype.update = function(){
8576
9155
  vjs.TextTrackButton = vjs.MenuButton.extend({
8577
9156
  /** @constructor */
8578
9157
  init: function(player, options){
9158
+ var tracks, updateHandler;
9159
+
8579
9160
  vjs.MenuButton.call(this, player, options);
8580
9161
 
9162
+ tracks = this.player_.textTracks();
9163
+
8581
9164
  if (this.items.length <= 1) {
8582
9165
  this.hide();
8583
9166
  }
8584
- }
8585
- });
8586
-
8587
- // vjs.TextTrackButton.prototype.buttonPressed = false;
8588
-
8589
- // vjs.TextTrackButton.prototype.createMenu = function(){
8590
- // var menu = new vjs.Menu(this.player_);
8591
-
8592
- // // Add a title list item to the top
8593
- // // menu.el().appendChild(vjs.createEl('li', {
8594
- // // className: 'vjs-menu-title',
8595
- // // innerHTML: vjs.capitalize(this.kind_),
8596
- // // tabindex: -1
8597
- // // }));
8598
-
8599
- // this.items = this.createItems();
8600
9167
 
8601
- // // Add menu items to the menu
8602
- // for (var i = 0; i < this.items.length; i++) {
8603
- // menu.addItem(this.items[i]);
8604
- // }
9168
+ if (!tracks) {
9169
+ return;
9170
+ }
8605
9171
 
8606
- // // Add list to element
8607
- // this.addChild(menu);
9172
+ updateHandler = vjs.bind(this, this.update);
9173
+ tracks.addEventListener('removetrack', updateHandler);
9174
+ tracks.addEventListener('addtrack', updateHandler);
8608
9175
 
8609
- // return menu;
8610
- // };
9176
+ this.player_.on('dispose', function() {
9177
+ tracks.removeEventListener('removetrack', updateHandler);
9178
+ tracks.removeEventListener('addtrack', updateHandler);
9179
+ });
9180
+ }
9181
+ });
8611
9182
 
8612
9183
  // Create a menu item for each text track
8613
9184
  vjs.TextTrackButton.prototype.createItems = function(){
8614
- var items = [], track;
9185
+ var items = [], track, tracks;
9186
+
9187
+ if (this instanceof vjs.CaptionsButton && !(this.player().tech && this.player().tech['featuresNativeTextTracks'])) {
9188
+ items.push(new vjs.CaptionSettingsMenuItem(this.player_, { 'kind': this.kind_ }));
9189
+ }
8615
9190
 
8616
9191
  // Add an OFF menu item to turn all tracks off
8617
9192
  items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
8618
9193
 
8619
- for (var i = 0; i < this.player_.textTracks().length; i++) {
8620
- track = this.player_.textTracks()[i];
8621
- if (track.kind() === this.kind_) {
9194
+ tracks = this.player_.textTracks();
9195
+
9196
+ if (!tracks) {
9197
+ return items;
9198
+ }
9199
+
9200
+ for (var i = 0; i < tracks.length; i++) {
9201
+ track = tracks[i];
9202
+
9203
+ // only add tracks that are of the appropriate kind and have a label
9204
+ if (track['kind'] === this.kind_) {
8622
9205
  items.push(new vjs.TextTrackMenuItem(this.player_, {
8623
9206
  'track': track
8624
9207
  }));
@@ -8644,6 +9227,22 @@ vjs.CaptionsButton.prototype.kind_ = 'captions';
8644
9227
  vjs.CaptionsButton.prototype.buttonText = 'Captions';
8645
9228
  vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
8646
9229
 
9230
+ vjs.CaptionsButton.prototype.update = function() {
9231
+ var threshold = 2;
9232
+ vjs.TextTrackButton.prototype.update.call(this);
9233
+
9234
+ // if native, then threshold is 1 because no settings button
9235
+ if (this.player().tech && this.player().tech['featuresNativeTextTracks']) {
9236
+ threshold = 1;
9237
+ }
9238
+
9239
+ if (this.items && this.items.length > threshold) {
9240
+ this.show();
9241
+ } else {
9242
+ this.hide();
9243
+ }
9244
+ };
9245
+
8647
9246
  /**
8648
9247
  * The button component for toggling and selecting subtitles
8649
9248
  *
@@ -8680,11 +9279,17 @@ vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
8680
9279
 
8681
9280
  // Create a menu item for each text track
8682
9281
  vjs.ChaptersButton.prototype.createItems = function(){
8683
- var items = [], track;
9282
+ var items = [], track, tracks;
8684
9283
 
8685
- for (var i = 0; i < this.player_.textTracks().length; i++) {
8686
- track = this.player_.textTracks()[i];
8687
- if (track.kind() === this.kind_) {
9284
+ tracks = this.player_.textTracks();
9285
+
9286
+ if (!tracks) {
9287
+ return items;
9288
+ }
9289
+
9290
+ for (var i = 0; i < tracks.length; i++) {
9291
+ track = tracks[i];
9292
+ if (track['kind'] === this.kind_) {
8688
9293
  items.push(new vjs.TextTrackMenuItem(this.player_, {
8689
9294
  'track': track
8690
9295
  }));
@@ -8695,18 +9300,23 @@ vjs.ChaptersButton.prototype.createItems = function(){
8695
9300
  };
8696
9301
 
8697
9302
  vjs.ChaptersButton.prototype.createMenu = function(){
8698
- var tracks = this.player_.textTracks(),
9303
+ var tracks = this.player_.textTracks() || [],
8699
9304
  i = 0,
8700
- j = tracks.length,
9305
+ l = tracks.length,
8701
9306
  track, chaptersTrack,
8702
9307
  items = this.items = [];
8703
9308
 
8704
- for (;i<j;i++) {
9309
+ for (; i < l; i++) {
8705
9310
  track = tracks[i];
8706
- if (track.kind() == this.kind_) {
8707
- if (track.readyState() === 0) {
8708
- track.load();
8709
- track.on('loaded', vjs.bind(this, this.createMenu));
9311
+ if (track['kind'] == this.kind_) {
9312
+ if (!track.cues) {
9313
+ track['mode'] = 'hidden';
9314
+ /* jshint loopfunc:true */
9315
+ // TODO see if we can figure out a better way of doing this https://github.com/videojs/video.js/issues/1864
9316
+ window.setTimeout(vjs.bind(this, function() {
9317
+ this.createMenu();
9318
+ }), 100);
9319
+ /* jshint loopfunc:false */
8710
9320
  } else {
8711
9321
  chaptersTrack = track;
8712
9322
  break;
@@ -8725,11 +9335,11 @@ vjs.ChaptersButton.prototype.createMenu = function(){
8725
9335
  }
8726
9336
 
8727
9337
  if (chaptersTrack) {
8728
- var cues = chaptersTrack.cues_, cue, mi;
9338
+ var cues = chaptersTrack['cues'], cue, mi;
8729
9339
  i = 0;
8730
- j = cues.length;
9340
+ l = cues.length;
8731
9341
 
8732
- for (;i<j;i++) {
9342
+ for (; i < l; i++) {
8733
9343
  cue = cues[i];
8734
9344
 
8735
9345
  mi = new vjs.ChaptersTrackMenuItem(this.player_, {
@@ -8764,10 +9374,10 @@ vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
8764
9374
 
8765
9375
  // Modify options for parent MenuItem class's init.
8766
9376
  options['label'] = cue.text;
8767
- options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
9377
+ options['selected'] = (cue['startTime'] <= currentTime && currentTime < cue['endTime']);
8768
9378
  vjs.MenuItem.call(this, player, options);
8769
9379
 
8770
- track.on('cuechange', vjs.bind(this, this.update));
9380
+ track.addEventListener('cuechange', vjs.bind(this, this.update));
8771
9381
  }
8772
9382
  });
8773
9383
 
@@ -8782,22 +9392,297 @@ vjs.ChaptersTrackMenuItem.prototype.update = function(){
8782
9392
  currentTime = this.player_.currentTime();
8783
9393
 
8784
9394
  // vjs.log(currentTime, cue.startTime);
8785
- this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
9395
+ this.selected(cue['startTime'] <= currentTime && currentTime < cue['endTime']);
8786
9396
  };
9397
+ })();
9398
+ (function() {
9399
+ 'use strict';
8787
9400
 
8788
- // Add Buttons to controlBar
8789
- vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
8790
- 'subtitlesButton': {},
8791
- 'captionsButton': {},
8792
- 'chaptersButton': {}
8793
- });
9401
+ vjs.TextTrackSettings = vjs.Component.extend({
9402
+ init: function(player, options) {
9403
+ vjs.Component.call(this, player, options);
9404
+ this.hide();
9405
+
9406
+ vjs.on(this.el().querySelector('.vjs-done-button'), 'click', vjs.bind(this, function() {
9407
+ this.saveSettings();
9408
+ this.hide();
9409
+ }));
9410
+
9411
+ vjs.on(this.el().querySelector('.vjs-default-button'), 'click', vjs.bind(this, function() {
9412
+ this.el().querySelector('.vjs-fg-color > select').selectedIndex = 0;
9413
+ this.el().querySelector('.vjs-bg-color > select').selectedIndex = 0;
9414
+ this.el().querySelector('.window-color > select').selectedIndex = 0;
9415
+ this.el().querySelector('.vjs-text-opacity > select').selectedIndex = 0;
9416
+ this.el().querySelector('.vjs-bg-opacity > select').selectedIndex = 0;
9417
+ this.el().querySelector('.vjs-window-opacity > select').selectedIndex = 0;
9418
+ this.el().querySelector('.vjs-edge-style select').selectedIndex = 0;
9419
+ this.el().querySelector('.vjs-font-family select').selectedIndex = 0;
9420
+ this.el().querySelector('.vjs-font-percent select').selectedIndex = 2;
9421
+ this.updateDisplay();
9422
+ }));
9423
+
9424
+ vjs.on(this.el().querySelector('.vjs-fg-color > select'), 'change', vjs.bind(this, this.updateDisplay));
9425
+ vjs.on(this.el().querySelector('.vjs-bg-color > select'), 'change', vjs.bind(this, this.updateDisplay));
9426
+ vjs.on(this.el().querySelector('.window-color > select'), 'change', vjs.bind(this, this.updateDisplay));
9427
+ vjs.on(this.el().querySelector('.vjs-text-opacity > select'), 'change', vjs.bind(this, this.updateDisplay));
9428
+ vjs.on(this.el().querySelector('.vjs-bg-opacity > select'), 'change', vjs.bind(this, this.updateDisplay));
9429
+ vjs.on(this.el().querySelector('.vjs-window-opacity > select'), 'change', vjs.bind(this, this.updateDisplay));
9430
+ vjs.on(this.el().querySelector('.vjs-font-percent select'), 'change', vjs.bind(this, this.updateDisplay));
9431
+ vjs.on(this.el().querySelector('.vjs-edge-style select'), 'change', vjs.bind(this, this.updateDisplay));
9432
+ vjs.on(this.el().querySelector('.vjs-font-family select'), 'change', vjs.bind(this, this.updateDisplay));
9433
+
9434
+ if (player.options()['persistTextTrackSettings']) {
9435
+ this.restoreSettings();
9436
+ }
9437
+ }
9438
+ });
9439
+
9440
+ vjs.TextTrackSettings.prototype.createEl = function() {
9441
+ return vjs.Component.prototype.createEl.call(this, 'div', {
9442
+ className: 'vjs-caption-settings vjs-modal-overlay',
9443
+ innerHTML: captionOptionsMenuTemplate()
9444
+ });
9445
+ };
9446
+
9447
+ vjs.TextTrackSettings.prototype.getValues = function() {
9448
+ var el, bgOpacity, textOpacity, windowOpacity, textEdge, fontFamily, fgColor, bgColor, windowColor, result, name, fontPercent;
9449
+
9450
+ el = this.el();
9451
+
9452
+ textEdge = getSelectedOptionValue(el.querySelector('.vjs-edge-style select'));
9453
+ fontFamily = getSelectedOptionValue(el.querySelector('.vjs-font-family select'));
9454
+ fgColor = getSelectedOptionValue(el.querySelector('.vjs-fg-color > select'));
9455
+ textOpacity = getSelectedOptionValue(el.querySelector('.vjs-text-opacity > select'));
9456
+ bgColor = getSelectedOptionValue(el.querySelector('.vjs-bg-color > select'));
9457
+ bgOpacity = getSelectedOptionValue(el.querySelector('.vjs-bg-opacity > select'));
9458
+ windowColor = getSelectedOptionValue(el.querySelector('.window-color > select'));
9459
+ windowOpacity = getSelectedOptionValue(el.querySelector('.vjs-window-opacity > select'));
9460
+ fontPercent = window['parseFloat'](getSelectedOptionValue(el.querySelector('.vjs-font-percent > select')));
9461
+
9462
+ result = {
9463
+ 'backgroundOpacity': bgOpacity,
9464
+ 'textOpacity': textOpacity,
9465
+ 'windowOpacity': windowOpacity,
9466
+ 'edgeStyle': textEdge,
9467
+ 'fontFamily': fontFamily,
9468
+ 'color': fgColor,
9469
+ 'backgroundColor': bgColor,
9470
+ 'windowColor': windowColor,
9471
+ 'fontPercent': fontPercent
9472
+ };
9473
+ for (name in result) {
9474
+ if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) {
9475
+ delete result[name];
9476
+ }
9477
+ }
9478
+ return result;
9479
+ };
9480
+
9481
+ vjs.TextTrackSettings.prototype.setValues = function(values) {
9482
+ var el = this.el(), fontPercent;
9483
+
9484
+ setSelectedOption(el.querySelector('.vjs-edge-style select'), values.edgeStyle);
9485
+ setSelectedOption(el.querySelector('.vjs-font-family select'), values.fontFamily);
9486
+ setSelectedOption(el.querySelector('.vjs-fg-color > select'), values.color);
9487
+ setSelectedOption(el.querySelector('.vjs-text-opacity > select'), values.textOpacity);
9488
+ setSelectedOption(el.querySelector('.vjs-bg-color > select'), values.backgroundColor);
9489
+ setSelectedOption(el.querySelector('.vjs-bg-opacity > select'), values.backgroundOpacity);
9490
+ setSelectedOption(el.querySelector('.window-color > select'), values.windowColor);
9491
+ setSelectedOption(el.querySelector('.vjs-window-opacity > select'), values.windowOpacity);
9492
+
9493
+ fontPercent = values.fontPercent;
9494
+
9495
+ if (fontPercent) {
9496
+ fontPercent = fontPercent.toFixed(2);
9497
+ }
9498
+
9499
+ setSelectedOption(el.querySelector('.vjs-font-percent > select'), fontPercent);
9500
+ };
9501
+
9502
+ vjs.TextTrackSettings.prototype.restoreSettings = function() {
9503
+ var values;
9504
+ try {
9505
+ values = JSON.parse(window.localStorage.getItem('vjs-text-track-settings'));
9506
+ } catch (e) {}
9507
+
9508
+ if (values) {
9509
+ this.setValues(values);
9510
+ }
9511
+ };
9512
+
9513
+ vjs.TextTrackSettings.prototype.saveSettings = function() {
9514
+ var values;
9515
+
9516
+ if (!this.player_.options()['persistTextTrackSettings']) {
9517
+ return;
9518
+ }
9519
+
9520
+ values = this.getValues();
9521
+ try {
9522
+ if (!vjs.isEmpty(values)) {
9523
+ window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
9524
+ } else {
9525
+ window.localStorage.removeItem('vjs-text-track-settings');
9526
+ }
9527
+ } catch (e) {}
9528
+ };
9529
+
9530
+ vjs.TextTrackSettings.prototype.updateDisplay = function() {
9531
+ var ttDisplay = this.player_.getChild('textTrackDisplay');
9532
+ if (ttDisplay) {
9533
+ ttDisplay.updateDisplay();
9534
+ }
9535
+ };
9536
+
9537
+ function getSelectedOptionValue(target) {
9538
+ var selectedOption;
9539
+ // not all browsers support selectedOptions, so, fallback to options
9540
+ if (target.selectedOptions) {
9541
+ selectedOption = target.selectedOptions[0];
9542
+ } else if (target.options) {
9543
+ selectedOption = target.options[target.options.selectedIndex];
9544
+ }
9545
+
9546
+ return selectedOption.value;
9547
+ }
8794
9548
 
8795
- // vjs.Cue = vjs.Component.extend({
8796
- // /** @constructor */
8797
- // init: function(player, options){
8798
- // vjs.Component.call(this, player, options);
8799
- // }
8800
- // });
9549
+ function setSelectedOption(target, value) {
9550
+ var i, option;
9551
+
9552
+ if (!value) {
9553
+ return;
9554
+ }
9555
+
9556
+ for (i = 0; i < target.options.length; i++) {
9557
+ option = target.options[i];
9558
+ if (option.value === value) {
9559
+ break;
9560
+ }
9561
+ }
9562
+
9563
+ if (target.selectedOptions) {
9564
+ target.selectedOptions[0] = option;
9565
+ }
9566
+
9567
+ target.selectedIndex = i;
9568
+ }
9569
+
9570
+ function captionOptionsMenuTemplate() {
9571
+ return '<div class="vjs-tracksettings">' +
9572
+ '<div class="vjs-tracksettings-colors">' +
9573
+ '<div class="vjs-fg-color vjs-tracksetting">' +
9574
+ '<label class="vjs-label">Foreground</label>' +
9575
+ '<select>' +
9576
+ '<option value="">---</option>' +
9577
+ '<option value="#FFF">White</option>' +
9578
+ '<option value="#000">Black</option>' +
9579
+ '<option value="#F00">Red</option>' +
9580
+ '<option value="#0F0">Green</option>' +
9581
+ '<option value="#00F">Blue</option>' +
9582
+ '<option value="#FF0">Yellow</option>' +
9583
+ '<option value="#F0F">Magenta</option>' +
9584
+ '<option value="#0FF">Cyan</option>' +
9585
+ '</select>' +
9586
+ '<span class="vjs-text-opacity vjs-opacity">' +
9587
+ '<select>' +
9588
+ '<option value="">---</option>' +
9589
+ '<option value="1">Opaque</option>' +
9590
+ '<option value="0.5">Semi-Opaque</option>' +
9591
+ '</select>' +
9592
+ '</span>' +
9593
+ '</div>' + // vjs-fg-color
9594
+ '<div class="vjs-bg-color vjs-tracksetting">' +
9595
+ '<label class="vjs-label">Background</label>' +
9596
+ '<select>' +
9597
+ '<option value="">---</option>' +
9598
+ '<option value="#FFF">White</option>' +
9599
+ '<option value="#000">Black</option>' +
9600
+ '<option value="#F00">Red</option>' +
9601
+ '<option value="#0F0">Green</option>' +
9602
+ '<option value="#00F">Blue</option>' +
9603
+ '<option value="#FF0">Yellow</option>' +
9604
+ '<option value="#F0F">Magenta</option>' +
9605
+ '<option value="#0FF">Cyan</option>' +
9606
+ '</select>' +
9607
+ '<span class="vjs-bg-opacity vjs-opacity">' +
9608
+ '<select>' +
9609
+ '<option value="">---</option>' +
9610
+ '<option value="1">Opaque</option>' +
9611
+ '<option value="0.5">Semi-Transparent</option>' +
9612
+ '<option value="0">Transparent</option>' +
9613
+ '</select>' +
9614
+ '</span>' +
9615
+ '</div>' + // vjs-bg-color
9616
+ '<div class="window-color vjs-tracksetting">' +
9617
+ '<label class="vjs-label">Window</label>' +
9618
+ '<select>' +
9619
+ '<option value="">---</option>' +
9620
+ '<option value="#FFF">White</option>' +
9621
+ '<option value="#000">Black</option>' +
9622
+ '<option value="#F00">Red</option>' +
9623
+ '<option value="#0F0">Green</option>' +
9624
+ '<option value="#00F">Blue</option>' +
9625
+ '<option value="#FF0">Yellow</option>' +
9626
+ '<option value="#F0F">Magenta</option>' +
9627
+ '<option value="#0FF">Cyan</option>' +
9628
+ '</select>' +
9629
+ '<span class="vjs-window-opacity vjs-opacity">' +
9630
+ '<select>' +
9631
+ '<option value="">---</option>' +
9632
+ '<option value="1">Opaque</option>' +
9633
+ '<option value="0.5">Semi-Transparent</option>' +
9634
+ '<option value="0">Transparent</option>' +
9635
+ '</select>' +
9636
+ '</span>' +
9637
+ '</div>' + // vjs-window-color
9638
+ '</div>' + // vjs-tracksettings
9639
+ '<div class="vjs-tracksettings-font">' +
9640
+ '<div class="vjs-font-percent vjs-tracksetting">' +
9641
+ '<label class="vjs-label">Font Size</label>' +
9642
+ '<select>' +
9643
+ '<option value="0.50">50%</option>' +
9644
+ '<option value="0.75">75%</option>' +
9645
+ '<option value="1.00" selected>100%</option>' +
9646
+ '<option value="1.25">125%</option>' +
9647
+ '<option value="1.50">150%</option>' +
9648
+ '<option value="1.75">175%</option>' +
9649
+ '<option value="2.00">200%</option>' +
9650
+ '<option value="3.00">300%</option>' +
9651
+ '<option value="4.00">400%</option>' +
9652
+ '</select>' +
9653
+ '</div>' + // vjs-font-percent
9654
+ '<div class="vjs-edge-style vjs-tracksetting">' +
9655
+ '<label class="vjs-label">Text Edge Style</label>' +
9656
+ '<select>' +
9657
+ '<option value="none">None</option>' +
9658
+ '<option value="raised">Raised</option>' +
9659
+ '<option value="depressed">Depressed</option>' +
9660
+ '<option value="uniform">Uniform</option>' +
9661
+ '<option value="dropshadow">Dropshadow</option>' +
9662
+ '</select>' +
9663
+ '</div>' + // vjs-edge-style
9664
+ '<div class="vjs-font-family vjs-tracksetting">' +
9665
+ '<label class="vjs-label">Font Family</label>' +
9666
+ '<select>' +
9667
+ '<option value="">Default</option>' +
9668
+ '<option value="monospaceSerif">Monospace Serif</option>' +
9669
+ '<option value="proportionalSerif">Proportional Serif</option>' +
9670
+ '<option value="monospaceSansSerif">Monospace Sans-Serif</option>' +
9671
+ '<option value="proportionalSansSerif">Proportional Sans-Serif</option>' +
9672
+ '<option value="casual">Casual</option>' +
9673
+ '<option value="script">Script</option>' +
9674
+ '<option value="small-caps">Small Caps</option>' +
9675
+ '</select>' +
9676
+ '</div>' + // vjs-font-family
9677
+ '</div>' +
9678
+ '</div>' +
9679
+ '<div class="vjs-tracksettings-controls">' +
9680
+ '<button class="vjs-default-button">Defaults</button>' +
9681
+ '<button class="vjs-done-button">Done</button>' +
9682
+ '</div>';
9683
+ }
9684
+
9685
+ })();
8801
9686
  /**
8802
9687
  * @fileoverview Add JSON support
8803
9688
  * @suppress {undefinedVars}
@@ -8963,3 +9848,1954 @@ vjs.autoSetupTimeout(1);
8963
9848
  vjs.plugin = function(name, init){
8964
9849
  vjs.Player.prototype[name] = init;
8965
9850
  };
9851
+
9852
+ /* vtt.js - v0.11.11 (https://github.com/mozilla/vtt.js) built on 22-01-2015 */
9853
+
9854
+ (function(root) {
9855
+ var vttjs = root.vttjs = {};
9856
+ var cueShim = vttjs.VTTCue;
9857
+ var regionShim = vttjs.VTTRegion;
9858
+ var oldVTTCue = root.VTTCue;
9859
+ var oldVTTRegion = root.VTTRegion;
9860
+
9861
+ vttjs.shim = function() {
9862
+ vttjs.VTTCue = cueShim;
9863
+ vttjs.VTTRegion = regionShim;
9864
+ };
9865
+
9866
+ vttjs.restore = function() {
9867
+ vttjs.VTTCue = oldVTTCue;
9868
+ vttjs.VTTRegion = oldVTTRegion;
9869
+ };
9870
+ }(this));
9871
+
9872
+ /**
9873
+ * Copyright 2013 vtt.js Contributors
9874
+ *
9875
+ * Licensed under the Apache License, Version 2.0 (the "License");
9876
+ * you may not use this file except in compliance with the License.
9877
+ * You may obtain a copy of the License at
9878
+ *
9879
+ * http://www.apache.org/licenses/LICENSE-2.0
9880
+ *
9881
+ * Unless required by applicable law or agreed to in writing, software
9882
+ * distributed under the License is distributed on an "AS IS" BASIS,
9883
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9884
+ * See the License for the specific language governing permissions and
9885
+ * limitations under the License.
9886
+ */
9887
+
9888
+ (function(root, vttjs) {
9889
+
9890
+ var autoKeyword = "auto";
9891
+ var directionSetting = {
9892
+ "": true,
9893
+ "lr": true,
9894
+ "rl": true
9895
+ };
9896
+ var alignSetting = {
9897
+ "start": true,
9898
+ "middle": true,
9899
+ "end": true,
9900
+ "left": true,
9901
+ "right": true
9902
+ };
9903
+
9904
+ function findDirectionSetting(value) {
9905
+ if (typeof value !== "string") {
9906
+ return false;
9907
+ }
9908
+ var dir = directionSetting[value.toLowerCase()];
9909
+ return dir ? value.toLowerCase() : false;
9910
+ }
9911
+
9912
+ function findAlignSetting(value) {
9913
+ if (typeof value !== "string") {
9914
+ return false;
9915
+ }
9916
+ var align = alignSetting[value.toLowerCase()];
9917
+ return align ? value.toLowerCase() : false;
9918
+ }
9919
+
9920
+ function extend(obj) {
9921
+ var i = 1;
9922
+ for (; i < arguments.length; i++) {
9923
+ var cobj = arguments[i];
9924
+ for (var p in cobj) {
9925
+ obj[p] = cobj[p];
9926
+ }
9927
+ }
9928
+
9929
+ return obj;
9930
+ }
9931
+
9932
+ function VTTCue(startTime, endTime, text) {
9933
+ var cue = this;
9934
+ var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
9935
+ var baseObj = {};
9936
+
9937
+ if (isIE8) {
9938
+ cue = document.createElement('custom');
9939
+ } else {
9940
+ baseObj.enumerable = true;
9941
+ }
9942
+
9943
+ /**
9944
+ * Shim implementation specific properties. These properties are not in
9945
+ * the spec.
9946
+ */
9947
+
9948
+ // Lets us know when the VTTCue's data has changed in such a way that we need
9949
+ // to recompute its display state. This lets us compute its display state
9950
+ // lazily.
9951
+ cue.hasBeenReset = false;
9952
+
9953
+ /**
9954
+ * VTTCue and TextTrackCue properties
9955
+ * http://dev.w3.org/html5/webvtt/#vttcue-interface
9956
+ */
9957
+
9958
+ var _id = "";
9959
+ var _pauseOnExit = false;
9960
+ var _startTime = startTime;
9961
+ var _endTime = endTime;
9962
+ var _text = text;
9963
+ var _region = null;
9964
+ var _vertical = "";
9965
+ var _snapToLines = true;
9966
+ var _line = "auto";
9967
+ var _lineAlign = "start";
9968
+ var _position = 50;
9969
+ var _positionAlign = "middle";
9970
+ var _size = 50;
9971
+ var _align = "middle";
9972
+
9973
+ Object.defineProperty(cue,
9974
+ "id", extend({}, baseObj, {
9975
+ get: function() {
9976
+ return _id;
9977
+ },
9978
+ set: function(value) {
9979
+ _id = "" + value;
9980
+ }
9981
+ }));
9982
+
9983
+ Object.defineProperty(cue,
9984
+ "pauseOnExit", extend({}, baseObj, {
9985
+ get: function() {
9986
+ return _pauseOnExit;
9987
+ },
9988
+ set: function(value) {
9989
+ _pauseOnExit = !!value;
9990
+ }
9991
+ }));
9992
+
9993
+ Object.defineProperty(cue,
9994
+ "startTime", extend({}, baseObj, {
9995
+ get: function() {
9996
+ return _startTime;
9997
+ },
9998
+ set: function(value) {
9999
+ if (typeof value !== "number") {
10000
+ throw new TypeError("Start time must be set to a number.");
10001
+ }
10002
+ _startTime = value;
10003
+ this.hasBeenReset = true;
10004
+ }
10005
+ }));
10006
+
10007
+ Object.defineProperty(cue,
10008
+ "endTime", extend({}, baseObj, {
10009
+ get: function() {
10010
+ return _endTime;
10011
+ },
10012
+ set: function(value) {
10013
+ if (typeof value !== "number") {
10014
+ throw new TypeError("End time must be set to a number.");
10015
+ }
10016
+ _endTime = value;
10017
+ this.hasBeenReset = true;
10018
+ }
10019
+ }));
10020
+
10021
+ Object.defineProperty(cue,
10022
+ "text", extend({}, baseObj, {
10023
+ get: function() {
10024
+ return _text;
10025
+ },
10026
+ set: function(value) {
10027
+ _text = "" + value;
10028
+ this.hasBeenReset = true;
10029
+ }
10030
+ }));
10031
+
10032
+ Object.defineProperty(cue,
10033
+ "region", extend({}, baseObj, {
10034
+ get: function() {
10035
+ return _region;
10036
+ },
10037
+ set: function(value) {
10038
+ _region = value;
10039
+ this.hasBeenReset = true;
10040
+ }
10041
+ }));
10042
+
10043
+ Object.defineProperty(cue,
10044
+ "vertical", extend({}, baseObj, {
10045
+ get: function() {
10046
+ return _vertical;
10047
+ },
10048
+ set: function(value) {
10049
+ var setting = findDirectionSetting(value);
10050
+ // Have to check for false because the setting an be an empty string.
10051
+ if (setting === false) {
10052
+ throw new SyntaxError("An invalid or illegal string was specified.");
10053
+ }
10054
+ _vertical = setting;
10055
+ this.hasBeenReset = true;
10056
+ }
10057
+ }));
10058
+
10059
+ Object.defineProperty(cue,
10060
+ "snapToLines", extend({}, baseObj, {
10061
+ get: function() {
10062
+ return _snapToLines;
10063
+ },
10064
+ set: function(value) {
10065
+ _snapToLines = !!value;
10066
+ this.hasBeenReset = true;
10067
+ }
10068
+ }));
10069
+
10070
+ Object.defineProperty(cue,
10071
+ "line", extend({}, baseObj, {
10072
+ get: function() {
10073
+ return _line;
10074
+ },
10075
+ set: function(value) {
10076
+ if (typeof value !== "number" && value !== autoKeyword) {
10077
+ throw new SyntaxError("An invalid number or illegal string was specified.");
10078
+ }
10079
+ _line = value;
10080
+ this.hasBeenReset = true;
10081
+ }
10082
+ }));
10083
+
10084
+ Object.defineProperty(cue,
10085
+ "lineAlign", extend({}, baseObj, {
10086
+ get: function() {
10087
+ return _lineAlign;
10088
+ },
10089
+ set: function(value) {
10090
+ var setting = findAlignSetting(value);
10091
+ if (!setting) {
10092
+ throw new SyntaxError("An invalid or illegal string was specified.");
10093
+ }
10094
+ _lineAlign = setting;
10095
+ this.hasBeenReset = true;
10096
+ }
10097
+ }));
10098
+
10099
+ Object.defineProperty(cue,
10100
+ "position", extend({}, baseObj, {
10101
+ get: function() {
10102
+ return _position;
10103
+ },
10104
+ set: function(value) {
10105
+ if (value < 0 || value > 100) {
10106
+ throw new Error("Position must be between 0 and 100.");
10107
+ }
10108
+ _position = value;
10109
+ this.hasBeenReset = true;
10110
+ }
10111
+ }));
10112
+
10113
+ Object.defineProperty(cue,
10114
+ "positionAlign", extend({}, baseObj, {
10115
+ get: function() {
10116
+ return _positionAlign;
10117
+ },
10118
+ set: function(value) {
10119
+ var setting = findAlignSetting(value);
10120
+ if (!setting) {
10121
+ throw new SyntaxError("An invalid or illegal string was specified.");
10122
+ }
10123
+ _positionAlign = setting;
10124
+ this.hasBeenReset = true;
10125
+ }
10126
+ }));
10127
+
10128
+ Object.defineProperty(cue,
10129
+ "size", extend({}, baseObj, {
10130
+ get: function() {
10131
+ return _size;
10132
+ },
10133
+ set: function(value) {
10134
+ if (value < 0 || value > 100) {
10135
+ throw new Error("Size must be between 0 and 100.");
10136
+ }
10137
+ _size = value;
10138
+ this.hasBeenReset = true;
10139
+ }
10140
+ }));
10141
+
10142
+ Object.defineProperty(cue,
10143
+ "align", extend({}, baseObj, {
10144
+ get: function() {
10145
+ return _align;
10146
+ },
10147
+ set: function(value) {
10148
+ var setting = findAlignSetting(value);
10149
+ if (!setting) {
10150
+ throw new SyntaxError("An invalid or illegal string was specified.");
10151
+ }
10152
+ _align = setting;
10153
+ this.hasBeenReset = true;
10154
+ }
10155
+ }));
10156
+
10157
+ /**
10158
+ * Other <track> spec defined properties
10159
+ */
10160
+
10161
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
10162
+ cue.displayState = undefined;
10163
+
10164
+ if (isIE8) {
10165
+ return cue;
10166
+ }
10167
+ }
10168
+
10169
+ /**
10170
+ * VTTCue methods
10171
+ */
10172
+
10173
+ VTTCue.prototype.getCueAsHTML = function() {
10174
+ // Assume WebVTT.convertCueToDOMTree is on the global.
10175
+ return WebVTT.convertCueToDOMTree(window, this.text);
10176
+ };
10177
+
10178
+ root.VTTCue = root.VTTCue || VTTCue;
10179
+ vttjs.VTTCue = VTTCue;
10180
+ }(this, (this.vttjs || {})));
10181
+
10182
+ /**
10183
+ * Copyright 2013 vtt.js Contributors
10184
+ *
10185
+ * Licensed under the Apache License, Version 2.0 (the "License");
10186
+ * you may not use this file except in compliance with the License.
10187
+ * You may obtain a copy of the License at
10188
+ *
10189
+ * http://www.apache.org/licenses/LICENSE-2.0
10190
+ *
10191
+ * Unless required by applicable law or agreed to in writing, software
10192
+ * distributed under the License is distributed on an "AS IS" BASIS,
10193
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10194
+ * See the License for the specific language governing permissions and
10195
+ * limitations under the License.
10196
+ */
10197
+
10198
+ (function(root, vttjs) {
10199
+
10200
+ var scrollSetting = {
10201
+ "": true,
10202
+ "up": true,
10203
+ };
10204
+
10205
+ function findScrollSetting(value) {
10206
+ if (typeof value !== "string") {
10207
+ return false;
10208
+ }
10209
+ var scroll = scrollSetting[value.toLowerCase()];
10210
+ return scroll ? value.toLowerCase() : false;
10211
+ }
10212
+
10213
+ function isValidPercentValue(value) {
10214
+ return typeof value === "number" && (value >= 0 && value <= 100);
10215
+ }
10216
+
10217
+ // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
10218
+ function VTTRegion() {
10219
+ var _width = 100;
10220
+ var _lines = 3;
10221
+ var _regionAnchorX = 0;
10222
+ var _regionAnchorY = 100;
10223
+ var _viewportAnchorX = 0;
10224
+ var _viewportAnchorY = 100;
10225
+ var _scroll = "";
10226
+
10227
+ Object.defineProperties(this, {
10228
+ "width": {
10229
+ enumerable: true,
10230
+ get: function() {
10231
+ return _width;
10232
+ },
10233
+ set: function(value) {
10234
+ if (!isValidPercentValue(value)) {
10235
+ throw new Error("Width must be between 0 and 100.");
10236
+ }
10237
+ _width = value;
10238
+ }
10239
+ },
10240
+ "lines": {
10241
+ enumerable: true,
10242
+ get: function() {
10243
+ return _lines;
10244
+ },
10245
+ set: function(value) {
10246
+ if (typeof value !== "number") {
10247
+ throw new TypeError("Lines must be set to a number.");
10248
+ }
10249
+ _lines = value;
10250
+ }
10251
+ },
10252
+ "regionAnchorY": {
10253
+ enumerable: true,
10254
+ get: function() {
10255
+ return _regionAnchorY;
10256
+ },
10257
+ set: function(value) {
10258
+ if (!isValidPercentValue(value)) {
10259
+ throw new Error("RegionAnchorX must be between 0 and 100.");
10260
+ }
10261
+ _regionAnchorY = value;
10262
+ }
10263
+ },
10264
+ "regionAnchorX": {
10265
+ enumerable: true,
10266
+ get: function() {
10267
+ return _regionAnchorX;
10268
+ },
10269
+ set: function(value) {
10270
+ if(!isValidPercentValue(value)) {
10271
+ throw new Error("RegionAnchorY must be between 0 and 100.");
10272
+ }
10273
+ _regionAnchorX = value;
10274
+ }
10275
+ },
10276
+ "viewportAnchorY": {
10277
+ enumerable: true,
10278
+ get: function() {
10279
+ return _viewportAnchorY;
10280
+ },
10281
+ set: function(value) {
10282
+ if (!isValidPercentValue(value)) {
10283
+ throw new Error("ViewportAnchorY must be between 0 and 100.");
10284
+ }
10285
+ _viewportAnchorY = value;
10286
+ }
10287
+ },
10288
+ "viewportAnchorX": {
10289
+ enumerable: true,
10290
+ get: function() {
10291
+ return _viewportAnchorX;
10292
+ },
10293
+ set: function(value) {
10294
+ if (!isValidPercentValue(value)) {
10295
+ throw new Error("ViewportAnchorX must be between 0 and 100.");
10296
+ }
10297
+ _viewportAnchorX = value;
10298
+ }
10299
+ },
10300
+ "scroll": {
10301
+ enumerable: true,
10302
+ get: function() {
10303
+ return _scroll;
10304
+ },
10305
+ set: function(value) {
10306
+ var setting = findScrollSetting(value);
10307
+ // Have to check for false as an empty string is a legal value.
10308
+ if (setting === false) {
10309
+ throw new SyntaxError("An invalid or illegal string was specified.");
10310
+ }
10311
+ _scroll = setting;
10312
+ }
10313
+ }
10314
+ });
10315
+ }
10316
+
10317
+ root.VTTRegion = root.VTTRegion || VTTRegion;
10318
+ vttjs.VTTRegion = VTTRegion;
10319
+ }(this, (this.vttjs || {})));
10320
+
10321
+ /**
10322
+ * Copyright 2013 vtt.js Contributors
10323
+ *
10324
+ * Licensed under the Apache License, Version 2.0 (the "License");
10325
+ * you may not use this file except in compliance with the License.
10326
+ * You may obtain a copy of the License at
10327
+ *
10328
+ * http://www.apache.org/licenses/LICENSE-2.0
10329
+ *
10330
+ * Unless required by applicable law or agreed to in writing, software
10331
+ * distributed under the License is distributed on an "AS IS" BASIS,
10332
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10333
+ * See the License for the specific language governing permissions and
10334
+ * limitations under the License.
10335
+ */
10336
+
10337
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
10338
+ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
10339
+
10340
+ (function(global) {
10341
+
10342
+ var _objCreate = Object.create || (function() {
10343
+ function F() {}
10344
+ return function(o) {
10345
+ if (arguments.length !== 1) {
10346
+ throw new Error('Object.create shim only accepts one parameter.');
10347
+ }
10348
+ F.prototype = o;
10349
+ return new F();
10350
+ };
10351
+ })();
10352
+
10353
+ // Creates a new ParserError object from an errorData object. The errorData
10354
+ // object should have default code and message properties. The default message
10355
+ // property can be overriden by passing in a message parameter.
10356
+ // See ParsingError.Errors below for acceptable errors.
10357
+ function ParsingError(errorData, message) {
10358
+ this.name = "ParsingError";
10359
+ this.code = errorData.code;
10360
+ this.message = message || errorData.message;
10361
+ }
10362
+ ParsingError.prototype = _objCreate(Error.prototype);
10363
+ ParsingError.prototype.constructor = ParsingError;
10364
+
10365
+ // ParsingError metadata for acceptable ParsingErrors.
10366
+ ParsingError.Errors = {
10367
+ BadSignature: {
10368
+ code: 0,
10369
+ message: "Malformed WebVTT signature."
10370
+ },
10371
+ BadTimeStamp: {
10372
+ code: 1,
10373
+ message: "Malformed time stamp."
10374
+ }
10375
+ };
10376
+
10377
+ // Try to parse input as a time stamp.
10378
+ function parseTimeStamp(input) {
10379
+
10380
+ function computeSeconds(h, m, s, f) {
10381
+ return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
10382
+ }
10383
+
10384
+ var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
10385
+ if (!m) {
10386
+ return null;
10387
+ }
10388
+
10389
+ if (m[3]) {
10390
+ // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
10391
+ return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
10392
+ } else if (m[1] > 59) {
10393
+ // Timestamp takes the form of [hours]:[minutes].[milliseconds]
10394
+ // First position is hours as it's over 59.
10395
+ return computeSeconds(m[1], m[2], 0, m[4]);
10396
+ } else {
10397
+ // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
10398
+ return computeSeconds(0, m[1], m[2], m[4]);
10399
+ }
10400
+ }
10401
+
10402
+ // A settings object holds key/value pairs and will ignore anything but the first
10403
+ // assignment to a specific key.
10404
+ function Settings() {
10405
+ this.values = _objCreate(null);
10406
+ }
10407
+
10408
+ Settings.prototype = {
10409
+ // Only accept the first assignment to any key.
10410
+ set: function(k, v) {
10411
+ if (!this.get(k) && v !== "") {
10412
+ this.values[k] = v;
10413
+ }
10414
+ },
10415
+ // Return the value for a key, or a default value.
10416
+ // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
10417
+ // a number of possible default values as properties where 'defaultKey' is
10418
+ // the key of the property that will be chosen; otherwise it's assumed to be
10419
+ // a single value.
10420
+ get: function(k, dflt, defaultKey) {
10421
+ if (defaultKey) {
10422
+ return this.has(k) ? this.values[k] : dflt[defaultKey];
10423
+ }
10424
+ return this.has(k) ? this.values[k] : dflt;
10425
+ },
10426
+ // Check whether we have a value for a key.
10427
+ has: function(k) {
10428
+ return k in this.values;
10429
+ },
10430
+ // Accept a setting if its one of the given alternatives.
10431
+ alt: function(k, v, a) {
10432
+ for (var n = 0; n < a.length; ++n) {
10433
+ if (v === a[n]) {
10434
+ this.set(k, v);
10435
+ break;
10436
+ }
10437
+ }
10438
+ },
10439
+ // Accept a setting if its a valid (signed) integer.
10440
+ integer: function(k, v) {
10441
+ if (/^-?\d+$/.test(v)) { // integer
10442
+ this.set(k, parseInt(v, 10));
10443
+ }
10444
+ },
10445
+ // Accept a setting if its a valid percentage.
10446
+ percent: function(k, v) {
10447
+ var m;
10448
+ if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
10449
+ v = parseFloat(v);
10450
+ if (v >= 0 && v <= 100) {
10451
+ this.set(k, v);
10452
+ return true;
10453
+ }
10454
+ }
10455
+ return false;
10456
+ }
10457
+ };
10458
+
10459
+ // Helper function to parse input into groups separated by 'groupDelim', and
10460
+ // interprete each group as a key/value pair separated by 'keyValueDelim'.
10461
+ function parseOptions(input, callback, keyValueDelim, groupDelim) {
10462
+ var groups = groupDelim ? input.split(groupDelim) : [input];
10463
+ for (var i in groups) {
10464
+ if (typeof groups[i] !== "string") {
10465
+ continue;
10466
+ }
10467
+ var kv = groups[i].split(keyValueDelim);
10468
+ if (kv.length !== 2) {
10469
+ continue;
10470
+ }
10471
+ var k = kv[0];
10472
+ var v = kv[1];
10473
+ callback(k, v);
10474
+ }
10475
+ }
10476
+
10477
+ function parseCue(input, cue, regionList) {
10478
+ // Remember the original input if we need to throw an error.
10479
+ var oInput = input;
10480
+ // 4.1 WebVTT timestamp
10481
+ function consumeTimeStamp() {
10482
+ var ts = parseTimeStamp(input);
10483
+ if (ts === null) {
10484
+ throw new ParsingError(ParsingError.Errors.BadTimeStamp,
10485
+ "Malformed timestamp: " + oInput);
10486
+ }
10487
+ // Remove time stamp from input.
10488
+ input = input.replace(/^[^\sa-zA-Z-]+/, "");
10489
+ return ts;
10490
+ }
10491
+
10492
+ // 4.4.2 WebVTT cue settings
10493
+ function consumeCueSettings(input, cue) {
10494
+ var settings = new Settings();
10495
+
10496
+ parseOptions(input, function (k, v) {
10497
+ switch (k) {
10498
+ case "region":
10499
+ // Find the last region we parsed with the same region id.
10500
+ for (var i = regionList.length - 1; i >= 0; i--) {
10501
+ if (regionList[i].id === v) {
10502
+ settings.set(k, regionList[i].region);
10503
+ break;
10504
+ }
10505
+ }
10506
+ break;
10507
+ case "vertical":
10508
+ settings.alt(k, v, ["rl", "lr"]);
10509
+ break;
10510
+ case "line":
10511
+ var vals = v.split(","),
10512
+ vals0 = vals[0];
10513
+ settings.integer(k, vals0);
10514
+ settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
10515
+ settings.alt(k, vals0, ["auto"]);
10516
+ if (vals.length === 2) {
10517
+ settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
10518
+ }
10519
+ break;
10520
+ case "position":
10521
+ vals = v.split(",");
10522
+ settings.percent(k, vals[0]);
10523
+ if (vals.length === 2) {
10524
+ settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
10525
+ }
10526
+ break;
10527
+ case "size":
10528
+ settings.percent(k, v);
10529
+ break;
10530
+ case "align":
10531
+ settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
10532
+ break;
10533
+ }
10534
+ }, /:/, /\s/);
10535
+
10536
+ // Apply default values for any missing fields.
10537
+ cue.region = settings.get("region", null);
10538
+ cue.vertical = settings.get("vertical", "");
10539
+ cue.line = settings.get("line", "auto");
10540
+ cue.lineAlign = settings.get("lineAlign", "start");
10541
+ cue.snapToLines = settings.get("snapToLines", true);
10542
+ cue.size = settings.get("size", 100);
10543
+ cue.align = settings.get("align", "middle");
10544
+ cue.position = settings.get("position", {
10545
+ start: 0,
10546
+ left: 0,
10547
+ middle: 50,
10548
+ end: 100,
10549
+ right: 100
10550
+ }, cue.align);
10551
+ cue.positionAlign = settings.get("positionAlign", {
10552
+ start: "start",
10553
+ left: "start",
10554
+ middle: "middle",
10555
+ end: "end",
10556
+ right: "end"
10557
+ }, cue.align);
10558
+ }
10559
+
10560
+ function skipWhitespace() {
10561
+ input = input.replace(/^\s+/, "");
10562
+ }
10563
+
10564
+ // 4.1 WebVTT cue timings.
10565
+ skipWhitespace();
10566
+ cue.startTime = consumeTimeStamp(); // (1) collect cue start time
10567
+ skipWhitespace();
10568
+ if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->"
10569
+ throw new ParsingError(ParsingError.Errors.BadTimeStamp,
10570
+ "Malformed time stamp (time stamps must be separated by '-->'): " +
10571
+ oInput);
10572
+ }
10573
+ input = input.substr(3);
10574
+ skipWhitespace();
10575
+ cue.endTime = consumeTimeStamp(); // (5) collect cue end time
10576
+
10577
+ // 4.1 WebVTT cue settings list.
10578
+ skipWhitespace();
10579
+ consumeCueSettings(input, cue);
10580
+ }
10581
+
10582
+ var ESCAPE = {
10583
+ "&amp;": "&",
10584
+ "&lt;": "<",
10585
+ "&gt;": ">",
10586
+ "&lrm;": "\u200e",
10587
+ "&rlm;": "\u200f",
10588
+ "&nbsp;": "\u00a0"
10589
+ };
10590
+
10591
+ var TAG_NAME = {
10592
+ c: "span",
10593
+ i: "i",
10594
+ b: "b",
10595
+ u: "u",
10596
+ ruby: "ruby",
10597
+ rt: "rt",
10598
+ v: "span",
10599
+ lang: "span"
10600
+ };
10601
+
10602
+ var TAG_ANNOTATION = {
10603
+ v: "title",
10604
+ lang: "lang"
10605
+ };
10606
+
10607
+ var NEEDS_PARENT = {
10608
+ rt: "ruby"
10609
+ };
10610
+
10611
+ // Parse content into a document fragment.
10612
+ function parseContent(window, input) {
10613
+ function nextToken() {
10614
+ // Check for end-of-string.
10615
+ if (!input) {
10616
+ return null;
10617
+ }
10618
+
10619
+ // Consume 'n' characters from the input.
10620
+ function consume(result) {
10621
+ input = input.substr(result.length);
10622
+ return result;
10623
+ }
10624
+
10625
+ var m = input.match(/^([^<]*)(<[^>]+>?)?/);
10626
+ // If there is some text before the next tag, return it, otherwise return
10627
+ // the tag.
10628
+ return consume(m[1] ? m[1] : m[2]);
10629
+ }
10630
+
10631
+ // Unescape a string 's'.
10632
+ function unescape1(e) {
10633
+ return ESCAPE[e];
10634
+ }
10635
+ function unescape(s) {
10636
+ while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
10637
+ s = s.replace(m[0], unescape1);
10638
+ }
10639
+ return s;
10640
+ }
10641
+
10642
+ function shouldAdd(current, element) {
10643
+ return !NEEDS_PARENT[element.localName] ||
10644
+ NEEDS_PARENT[element.localName] === current.localName;
10645
+ }
10646
+
10647
+ // Create an element for this tag.
10648
+ function createElement(type, annotation) {
10649
+ var tagName = TAG_NAME[type];
10650
+ if (!tagName) {
10651
+ return null;
10652
+ }
10653
+ var element = window.document.createElement(tagName);
10654
+ element.localName = tagName;
10655
+ var name = TAG_ANNOTATION[type];
10656
+ if (name && annotation) {
10657
+ element[name] = annotation.trim();
10658
+ }
10659
+ return element;
10660
+ }
10661
+
10662
+ var rootDiv = window.document.createElement("div"),
10663
+ current = rootDiv,
10664
+ t,
10665
+ tagStack = [];
10666
+
10667
+ while ((t = nextToken()) !== null) {
10668
+ if (t[0] === '<') {
10669
+ if (t[1] === "/") {
10670
+ // If the closing tag matches, move back up to the parent node.
10671
+ if (tagStack.length &&
10672
+ tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
10673
+ tagStack.pop();
10674
+ current = current.parentNode;
10675
+ }
10676
+ // Otherwise just ignore the end tag.
10677
+ continue;
10678
+ }
10679
+ var ts = parseTimeStamp(t.substr(1, t.length - 2));
10680
+ var node;
10681
+ if (ts) {
10682
+ // Timestamps are lead nodes as well.
10683
+ node = window.document.createProcessingInstruction("timestamp", ts);
10684
+ current.appendChild(node);
10685
+ continue;
10686
+ }
10687
+ var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
10688
+ // If we can't parse the tag, skip to the next tag.
10689
+ if (!m) {
10690
+ continue;
10691
+ }
10692
+ // Try to construct an element, and ignore the tag if we couldn't.
10693
+ node = createElement(m[1], m[3]);
10694
+ if (!node) {
10695
+ continue;
10696
+ }
10697
+ // Determine if the tag should be added based on the context of where it
10698
+ // is placed in the cuetext.
10699
+ if (!shouldAdd(current, node)) {
10700
+ continue;
10701
+ }
10702
+ // Set the class list (as a list of classes, separated by space).
10703
+ if (m[2]) {
10704
+ node.className = m[2].substr(1).replace('.', ' ');
10705
+ }
10706
+ // Append the node to the current node, and enter the scope of the new
10707
+ // node.
10708
+ tagStack.push(m[1]);
10709
+ current.appendChild(node);
10710
+ current = node;
10711
+ continue;
10712
+ }
10713
+
10714
+ // Text nodes are leaf nodes.
10715
+ current.appendChild(window.document.createTextNode(unescape(t)));
10716
+ }
10717
+
10718
+ return rootDiv;
10719
+ }
10720
+
10721
+ // This is a list of all the Unicode characters that have a strong
10722
+ // right-to-left category. What this means is that these characters are
10723
+ // written right-to-left for sure. It was generated by pulling all the strong
10724
+ // right-to-left characters out of the Unicode data table. That table can
10725
+ // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
10726
+ var strongRTLChars = [0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1,
10727
+ 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA,
10728
+ 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3,
10729
+ 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1,
10730
+ 0x05F2, 0x05F3, 0x05F4, 0x0608, 0x060B, 0x060D, 0x061B, 0x061E, 0x061F,
10731
+ 0x0620, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628,
10732
+ 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631,
10733
+ 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A,
10734
+ 0x063B, 0x063C, 0x063D, 0x063E, 0x063F, 0x0640, 0x0641, 0x0642, 0x0643,
10735
+ 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x066D, 0x066E,
10736
+ 0x066F, 0x0671, 0x0672, 0x0673, 0x0674, 0x0675, 0x0676, 0x0677, 0x0678,
10737
+ 0x0679, 0x067A, 0x067B, 0x067C, 0x067D, 0x067E, 0x067F, 0x0680, 0x0681,
10738
+ 0x0682, 0x0683, 0x0684, 0x0685, 0x0686, 0x0687, 0x0688, 0x0689, 0x068A,
10739
+ 0x068B, 0x068C, 0x068D, 0x068E, 0x068F, 0x0690, 0x0691, 0x0692, 0x0693,
10740
+ 0x0694, 0x0695, 0x0696, 0x0697, 0x0698, 0x0699, 0x069A, 0x069B, 0x069C,
10741
+ 0x069D, 0x069E, 0x069F, 0x06A0, 0x06A1, 0x06A2, 0x06A3, 0x06A4, 0x06A5,
10742
+ 0x06A6, 0x06A7, 0x06A8, 0x06A9, 0x06AA, 0x06AB, 0x06AC, 0x06AD, 0x06AE,
10743
+ 0x06AF, 0x06B0, 0x06B1, 0x06B2, 0x06B3, 0x06B4, 0x06B5, 0x06B6, 0x06B7,
10744
+ 0x06B8, 0x06B9, 0x06BA, 0x06BB, 0x06BC, 0x06BD, 0x06BE, 0x06BF, 0x06C0,
10745
+ 0x06C1, 0x06C2, 0x06C3, 0x06C4, 0x06C5, 0x06C6, 0x06C7, 0x06C8, 0x06C9,
10746
+ 0x06CA, 0x06CB, 0x06CC, 0x06CD, 0x06CE, 0x06CF, 0x06D0, 0x06D1, 0x06D2,
10747
+ 0x06D3, 0x06D4, 0x06D5, 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FB,
10748
+ 0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704,
10749
+ 0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D,
10750
+ 0x070F, 0x0710, 0x0712, 0x0713, 0x0714, 0x0715, 0x0716, 0x0717, 0x0718,
10751
+ 0x0719, 0x071A, 0x071B, 0x071C, 0x071D, 0x071E, 0x071F, 0x0720, 0x0721,
10752
+ 0x0722, 0x0723, 0x0724, 0x0725, 0x0726, 0x0727, 0x0728, 0x0729, 0x072A,
10753
+ 0x072B, 0x072C, 0x072D, 0x072E, 0x072F, 0x074D, 0x074E, 0x074F, 0x0750,
10754
+ 0x0751, 0x0752, 0x0753, 0x0754, 0x0755, 0x0756, 0x0757, 0x0758, 0x0759,
10755
+ 0x075A, 0x075B, 0x075C, 0x075D, 0x075E, 0x075F, 0x0760, 0x0761, 0x0762,
10756
+ 0x0763, 0x0764, 0x0765, 0x0766, 0x0767, 0x0768, 0x0769, 0x076A, 0x076B,
10757
+ 0x076C, 0x076D, 0x076E, 0x076F, 0x0770, 0x0771, 0x0772, 0x0773, 0x0774,
10758
+ 0x0775, 0x0776, 0x0777, 0x0778, 0x0779, 0x077A, 0x077B, 0x077C, 0x077D,
10759
+ 0x077E, 0x077F, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0786,
10760
+ 0x0787, 0x0788, 0x0789, 0x078A, 0x078B, 0x078C, 0x078D, 0x078E, 0x078F,
10761
+ 0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0796, 0x0797, 0x0798,
10762
+ 0x0799, 0x079A, 0x079B, 0x079C, 0x079D, 0x079E, 0x079F, 0x07A0, 0x07A1,
10763
+ 0x07A2, 0x07A3, 0x07A4, 0x07A5, 0x07B1, 0x07C0, 0x07C1, 0x07C2, 0x07C3,
10764
+ 0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8, 0x07C9, 0x07CA, 0x07CB, 0x07CC,
10765
+ 0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1, 0x07D2, 0x07D3, 0x07D4, 0x07D5,
10766
+ 0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA, 0x07DB, 0x07DC, 0x07DD, 0x07DE,
10767
+ 0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3, 0x07E4, 0x07E5, 0x07E6, 0x07E7,
10768
+ 0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x0800, 0x0801, 0x0802,
10769
+ 0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B,
10770
+ 0x080C, 0x080D, 0x080E, 0x080F, 0x0810, 0x0811, 0x0812, 0x0813, 0x0814,
10771
+ 0x0815, 0x081A, 0x0824, 0x0828, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834,
10772
+ 0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083A, 0x083B, 0x083C, 0x083D,
10773
+ 0x083E, 0x0840, 0x0841, 0x0842, 0x0843, 0x0844, 0x0845, 0x0846, 0x0847,
10774
+ 0x0848, 0x0849, 0x084A, 0x084B, 0x084C, 0x084D, 0x084E, 0x084F, 0x0850,
10775
+ 0x0851, 0x0852, 0x0853, 0x0854, 0x0855, 0x0856, 0x0857, 0x0858, 0x085E,
10776
+ 0x08A0, 0x08A2, 0x08A3, 0x08A4, 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9,
10777
+ 0x08AA, 0x08AB, 0x08AC, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22,
10778
+ 0xFB23, 0xFB24, 0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C,
10779
+ 0xFB2D, 0xFB2E, 0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35,
10780
+ 0xFB36, 0xFB38, 0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41,
10781
+ 0xFB43, 0xFB44, 0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C,
10782
+ 0xFB4D, 0xFB4E, 0xFB4F, 0xFB50, 0xFB51, 0xFB52, 0xFB53, 0xFB54, 0xFB55,
10783
+ 0xFB56, 0xFB57, 0xFB58, 0xFB59, 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D, 0xFB5E,
10784
+ 0xFB5F, 0xFB60, 0xFB61, 0xFB62, 0xFB63, 0xFB64, 0xFB65, 0xFB66, 0xFB67,
10785
+ 0xFB68, 0xFB69, 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D, 0xFB6E, 0xFB6F, 0xFB70,
10786
+ 0xFB71, 0xFB72, 0xFB73, 0xFB74, 0xFB75, 0xFB76, 0xFB77, 0xFB78, 0xFB79,
10787
+ 0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D, 0xFB7E, 0xFB7F, 0xFB80, 0xFB81, 0xFB82,
10788
+ 0xFB83, 0xFB84, 0xFB85, 0xFB86, 0xFB87, 0xFB88, 0xFB89, 0xFB8A, 0xFB8B,
10789
+ 0xFB8C, 0xFB8D, 0xFB8E, 0xFB8F, 0xFB90, 0xFB91, 0xFB92, 0xFB93, 0xFB94,
10790
+ 0xFB95, 0xFB96, 0xFB97, 0xFB98, 0xFB99, 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D,
10791
+ 0xFB9E, 0xFB9F, 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3, 0xFBA4, 0xFBA5, 0xFBA6,
10792
+ 0xFBA7, 0xFBA8, 0xFBA9, 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD, 0xFBAE, 0xFBAF,
10793
+ 0xFBB0, 0xFBB1, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8,
10794
+ 0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1,
10795
+ 0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6, 0xFBD7, 0xFBD8, 0xFBD9, 0xFBDA, 0xFBDB,
10796
+ 0xFBDC, 0xFBDD, 0xFBDE, 0xFBDF, 0xFBE0, 0xFBE1, 0xFBE2, 0xFBE3, 0xFBE4,
10797
+ 0xFBE5, 0xFBE6, 0xFBE7, 0xFBE8, 0xFBE9, 0xFBEA, 0xFBEB, 0xFBEC, 0xFBED,
10798
+ 0xFBEE, 0xFBEF, 0xFBF0, 0xFBF1, 0xFBF2, 0xFBF3, 0xFBF4, 0xFBF5, 0xFBF6,
10799
+ 0xFBF7, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF,
10800
+ 0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFC08,
10801
+ 0xFC09, 0xFC0A, 0xFC0B, 0xFC0C, 0xFC0D, 0xFC0E, 0xFC0F, 0xFC10, 0xFC11,
10802
+ 0xFC12, 0xFC13, 0xFC14, 0xFC15, 0xFC16, 0xFC17, 0xFC18, 0xFC19, 0xFC1A,
10803
+ 0xFC1B, 0xFC1C, 0xFC1D, 0xFC1E, 0xFC1F, 0xFC20, 0xFC21, 0xFC22, 0xFC23,
10804
+ 0xFC24, 0xFC25, 0xFC26, 0xFC27, 0xFC28, 0xFC29, 0xFC2A, 0xFC2B, 0xFC2C,
10805
+ 0xFC2D, 0xFC2E, 0xFC2F, 0xFC30, 0xFC31, 0xFC32, 0xFC33, 0xFC34, 0xFC35,
10806
+ 0xFC36, 0xFC37, 0xFC38, 0xFC39, 0xFC3A, 0xFC3B, 0xFC3C, 0xFC3D, 0xFC3E,
10807
+ 0xFC3F, 0xFC40, 0xFC41, 0xFC42, 0xFC43, 0xFC44, 0xFC45, 0xFC46, 0xFC47,
10808
+ 0xFC48, 0xFC49, 0xFC4A, 0xFC4B, 0xFC4C, 0xFC4D, 0xFC4E, 0xFC4F, 0xFC50,
10809
+ 0xFC51, 0xFC52, 0xFC53, 0xFC54, 0xFC55, 0xFC56, 0xFC57, 0xFC58, 0xFC59,
10810
+ 0xFC5A, 0xFC5B, 0xFC5C, 0xFC5D, 0xFC5E, 0xFC5F, 0xFC60, 0xFC61, 0xFC62,
10811
+ 0xFC63, 0xFC64, 0xFC65, 0xFC66, 0xFC67, 0xFC68, 0xFC69, 0xFC6A, 0xFC6B,
10812
+ 0xFC6C, 0xFC6D, 0xFC6E, 0xFC6F, 0xFC70, 0xFC71, 0xFC72, 0xFC73, 0xFC74,
10813
+ 0xFC75, 0xFC76, 0xFC77, 0xFC78, 0xFC79, 0xFC7A, 0xFC7B, 0xFC7C, 0xFC7D,
10814
+ 0xFC7E, 0xFC7F, 0xFC80, 0xFC81, 0xFC82, 0xFC83, 0xFC84, 0xFC85, 0xFC86,
10815
+ 0xFC87, 0xFC88, 0xFC89, 0xFC8A, 0xFC8B, 0xFC8C, 0xFC8D, 0xFC8E, 0xFC8F,
10816
+ 0xFC90, 0xFC91, 0xFC92, 0xFC93, 0xFC94, 0xFC95, 0xFC96, 0xFC97, 0xFC98,
10817
+ 0xFC99, 0xFC9A, 0xFC9B, 0xFC9C, 0xFC9D, 0xFC9E, 0xFC9F, 0xFCA0, 0xFCA1,
10818
+ 0xFCA2, 0xFCA3, 0xFCA4, 0xFCA5, 0xFCA6, 0xFCA7, 0xFCA8, 0xFCA9, 0xFCAA,
10819
+ 0xFCAB, 0xFCAC, 0xFCAD, 0xFCAE, 0xFCAF, 0xFCB0, 0xFCB1, 0xFCB2, 0xFCB3,
10820
+ 0xFCB4, 0xFCB5, 0xFCB6, 0xFCB7, 0xFCB8, 0xFCB9, 0xFCBA, 0xFCBB, 0xFCBC,
10821
+ 0xFCBD, 0xFCBE, 0xFCBF, 0xFCC0, 0xFCC1, 0xFCC2, 0xFCC3, 0xFCC4, 0xFCC5,
10822
+ 0xFCC6, 0xFCC7, 0xFCC8, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE,
10823
+ 0xFCCF, 0xFCD0, 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD4, 0xFCD5, 0xFCD6, 0xFCD7,
10824
+ 0xFCD8, 0xFCD9, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFCDE, 0xFCDF, 0xFCE0,
10825
+ 0xFCE1, 0xFCE2, 0xFCE3, 0xFCE4, 0xFCE5, 0xFCE6, 0xFCE7, 0xFCE8, 0xFCE9,
10826
+ 0xFCEA, 0xFCEB, 0xFCEC, 0xFCED, 0xFCEE, 0xFCEF, 0xFCF0, 0xFCF1, 0xFCF2,
10827
+ 0xFCF3, 0xFCF4, 0xFCF5, 0xFCF6, 0xFCF7, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB,
10828
+ 0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04,
10829
+ 0xFD05, 0xFD06, 0xFD07, 0xFD08, 0xFD09, 0xFD0A, 0xFD0B, 0xFD0C, 0xFD0D,
10830
+ 0xFD0E, 0xFD0F, 0xFD10, 0xFD11, 0xFD12, 0xFD13, 0xFD14, 0xFD15, 0xFD16,
10831
+ 0xFD17, 0xFD18, 0xFD19, 0xFD1A, 0xFD1B, 0xFD1C, 0xFD1D, 0xFD1E, 0xFD1F,
10832
+ 0xFD20, 0xFD21, 0xFD22, 0xFD23, 0xFD24, 0xFD25, 0xFD26, 0xFD27, 0xFD28,
10833
+ 0xFD29, 0xFD2A, 0xFD2B, 0xFD2C, 0xFD2D, 0xFD2E, 0xFD2F, 0xFD30, 0xFD31,
10834
+ 0xFD32, 0xFD33, 0xFD34, 0xFD35, 0xFD36, 0xFD37, 0xFD38, 0xFD39, 0xFD3A,
10835
+ 0xFD3B, 0xFD3C, 0xFD3D, 0xFD50, 0xFD51, 0xFD52, 0xFD53, 0xFD54, 0xFD55,
10836
+ 0xFD56, 0xFD57, 0xFD58, 0xFD59, 0xFD5A, 0xFD5B, 0xFD5C, 0xFD5D, 0xFD5E,
10837
+ 0xFD5F, 0xFD60, 0xFD61, 0xFD62, 0xFD63, 0xFD64, 0xFD65, 0xFD66, 0xFD67,
10838
+ 0xFD68, 0xFD69, 0xFD6A, 0xFD6B, 0xFD6C, 0xFD6D, 0xFD6E, 0xFD6F, 0xFD70,
10839
+ 0xFD71, 0xFD72, 0xFD73, 0xFD74, 0xFD75, 0xFD76, 0xFD77, 0xFD78, 0xFD79,
10840
+ 0xFD7A, 0xFD7B, 0xFD7C, 0xFD7D, 0xFD7E, 0xFD7F, 0xFD80, 0xFD81, 0xFD82,
10841
+ 0xFD83, 0xFD84, 0xFD85, 0xFD86, 0xFD87, 0xFD88, 0xFD89, 0xFD8A, 0xFD8B,
10842
+ 0xFD8C, 0xFD8D, 0xFD8E, 0xFD8F, 0xFD92, 0xFD93, 0xFD94, 0xFD95, 0xFD96,
10843
+ 0xFD97, 0xFD98, 0xFD99, 0xFD9A, 0xFD9B, 0xFD9C, 0xFD9D, 0xFD9E, 0xFD9F,
10844
+ 0xFDA0, 0xFDA1, 0xFDA2, 0xFDA3, 0xFDA4, 0xFDA5, 0xFDA6, 0xFDA7, 0xFDA8,
10845
+ 0xFDA9, 0xFDAA, 0xFDAB, 0xFDAC, 0xFDAD, 0xFDAE, 0xFDAF, 0xFDB0, 0xFDB1,
10846
+ 0xFDB2, 0xFDB3, 0xFDB4, 0xFDB5, 0xFDB6, 0xFDB7, 0xFDB8, 0xFDB9, 0xFDBA,
10847
+ 0xFDBB, 0xFDBC, 0xFDBD, 0xFDBE, 0xFDBF, 0xFDC0, 0xFDC1, 0xFDC2, 0xFDC3,
10848
+ 0xFDC4, 0xFDC5, 0xFDC6, 0xFDC7, 0xFDF0, 0xFDF1, 0xFDF2, 0xFDF3, 0xFDF4,
10849
+ 0xFDF5, 0xFDF6, 0xFDF7, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFE70,
10850
+ 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE76, 0xFE77, 0xFE78, 0xFE79, 0xFE7A,
10851
+ 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, 0xFE80, 0xFE81, 0xFE82, 0xFE83,
10852
+ 0xFE84, 0xFE85, 0xFE86, 0xFE87, 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C,
10853
+ 0xFE8D, 0xFE8E, 0xFE8F, 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95,
10854
+ 0xFE96, 0xFE97, 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E,
10855
+ 0xFE9F, 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7,
10856
+ 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, 0xFEB0,
10857
+ 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8, 0xFEB9,
10858
+ 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0, 0xFEC1, 0xFEC2,
10859
+ 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8, 0xFEC9, 0xFECA, 0xFECB,
10860
+ 0xFECC, 0xFECD, 0xFECE, 0xFECF, 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4,
10861
+ 0xFED5, 0xFED6, 0xFED7, 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD,
10862
+ 0xFEDE, 0xFEDF, 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6,
10863
+ 0xFEE7, 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF,
10864
+ 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8,
10865
+ 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0x10800, 0x10801, 0x10802, 0x10803,
10866
+ 0x10804, 0x10805, 0x10808, 0x1080A, 0x1080B, 0x1080C, 0x1080D, 0x1080E,
10867
+ 0x1080F, 0x10810, 0x10811, 0x10812, 0x10813, 0x10814, 0x10815, 0x10816,
10868
+ 0x10817, 0x10818, 0x10819, 0x1081A, 0x1081B, 0x1081C, 0x1081D, 0x1081E,
10869
+ 0x1081F, 0x10820, 0x10821, 0x10822, 0x10823, 0x10824, 0x10825, 0x10826,
10870
+ 0x10827, 0x10828, 0x10829, 0x1082A, 0x1082B, 0x1082C, 0x1082D, 0x1082E,
10871
+ 0x1082F, 0x10830, 0x10831, 0x10832, 0x10833, 0x10834, 0x10835, 0x10837,
10872
+ 0x10838, 0x1083C, 0x1083F, 0x10840, 0x10841, 0x10842, 0x10843, 0x10844,
10873
+ 0x10845, 0x10846, 0x10847, 0x10848, 0x10849, 0x1084A, 0x1084B, 0x1084C,
10874
+ 0x1084D, 0x1084E, 0x1084F, 0x10850, 0x10851, 0x10852, 0x10853, 0x10854,
10875
+ 0x10855, 0x10857, 0x10858, 0x10859, 0x1085A, 0x1085B, 0x1085C, 0x1085D,
10876
+ 0x1085E, 0x1085F, 0x10900, 0x10901, 0x10902, 0x10903, 0x10904, 0x10905,
10877
+ 0x10906, 0x10907, 0x10908, 0x10909, 0x1090A, 0x1090B, 0x1090C, 0x1090D,
10878
+ 0x1090E, 0x1090F, 0x10910, 0x10911, 0x10912, 0x10913, 0x10914, 0x10915,
10879
+ 0x10916, 0x10917, 0x10918, 0x10919, 0x1091A, 0x1091B, 0x10920, 0x10921,
10880
+ 0x10922, 0x10923, 0x10924, 0x10925, 0x10926, 0x10927, 0x10928, 0x10929,
10881
+ 0x1092A, 0x1092B, 0x1092C, 0x1092D, 0x1092E, 0x1092F, 0x10930, 0x10931,
10882
+ 0x10932, 0x10933, 0x10934, 0x10935, 0x10936, 0x10937, 0x10938, 0x10939,
10883
+ 0x1093F, 0x10980, 0x10981, 0x10982, 0x10983, 0x10984, 0x10985, 0x10986,
10884
+ 0x10987, 0x10988, 0x10989, 0x1098A, 0x1098B, 0x1098C, 0x1098D, 0x1098E,
10885
+ 0x1098F, 0x10990, 0x10991, 0x10992, 0x10993, 0x10994, 0x10995, 0x10996,
10886
+ 0x10997, 0x10998, 0x10999, 0x1099A, 0x1099B, 0x1099C, 0x1099D, 0x1099E,
10887
+ 0x1099F, 0x109A0, 0x109A1, 0x109A2, 0x109A3, 0x109A4, 0x109A5, 0x109A6,
10888
+ 0x109A7, 0x109A8, 0x109A9, 0x109AA, 0x109AB, 0x109AC, 0x109AD, 0x109AE,
10889
+ 0x109AF, 0x109B0, 0x109B1, 0x109B2, 0x109B3, 0x109B4, 0x109B5, 0x109B6,
10890
+ 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A10, 0x10A11, 0x10A12, 0x10A13,
10891
+ 0x10A15, 0x10A16, 0x10A17, 0x10A19, 0x10A1A, 0x10A1B, 0x10A1C, 0x10A1D,
10892
+ 0x10A1E, 0x10A1F, 0x10A20, 0x10A21, 0x10A22, 0x10A23, 0x10A24, 0x10A25,
10893
+ 0x10A26, 0x10A27, 0x10A28, 0x10A29, 0x10A2A, 0x10A2B, 0x10A2C, 0x10A2D,
10894
+ 0x10A2E, 0x10A2F, 0x10A30, 0x10A31, 0x10A32, 0x10A33, 0x10A40, 0x10A41,
10895
+ 0x10A42, 0x10A43, 0x10A44, 0x10A45, 0x10A46, 0x10A47, 0x10A50, 0x10A51,
10896
+ 0x10A52, 0x10A53, 0x10A54, 0x10A55, 0x10A56, 0x10A57, 0x10A58, 0x10A60,
10897
+ 0x10A61, 0x10A62, 0x10A63, 0x10A64, 0x10A65, 0x10A66, 0x10A67, 0x10A68,
10898
+ 0x10A69, 0x10A6A, 0x10A6B, 0x10A6C, 0x10A6D, 0x10A6E, 0x10A6F, 0x10A70,
10899
+ 0x10A71, 0x10A72, 0x10A73, 0x10A74, 0x10A75, 0x10A76, 0x10A77, 0x10A78,
10900
+ 0x10A79, 0x10A7A, 0x10A7B, 0x10A7C, 0x10A7D, 0x10A7E, 0x10A7F, 0x10B00,
10901
+ 0x10B01, 0x10B02, 0x10B03, 0x10B04, 0x10B05, 0x10B06, 0x10B07, 0x10B08,
10902
+ 0x10B09, 0x10B0A, 0x10B0B, 0x10B0C, 0x10B0D, 0x10B0E, 0x10B0F, 0x10B10,
10903
+ 0x10B11, 0x10B12, 0x10B13, 0x10B14, 0x10B15, 0x10B16, 0x10B17, 0x10B18,
10904
+ 0x10B19, 0x10B1A, 0x10B1B, 0x10B1C, 0x10B1D, 0x10B1E, 0x10B1F, 0x10B20,
10905
+ 0x10B21, 0x10B22, 0x10B23, 0x10B24, 0x10B25, 0x10B26, 0x10B27, 0x10B28,
10906
+ 0x10B29, 0x10B2A, 0x10B2B, 0x10B2C, 0x10B2D, 0x10B2E, 0x10B2F, 0x10B30,
10907
+ 0x10B31, 0x10B32, 0x10B33, 0x10B34, 0x10B35, 0x10B40, 0x10B41, 0x10B42,
10908
+ 0x10B43, 0x10B44, 0x10B45, 0x10B46, 0x10B47, 0x10B48, 0x10B49, 0x10B4A,
10909
+ 0x10B4B, 0x10B4C, 0x10B4D, 0x10B4E, 0x10B4F, 0x10B50, 0x10B51, 0x10B52,
10910
+ 0x10B53, 0x10B54, 0x10B55, 0x10B58, 0x10B59, 0x10B5A, 0x10B5B, 0x10B5C,
10911
+ 0x10B5D, 0x10B5E, 0x10B5F, 0x10B60, 0x10B61, 0x10B62, 0x10B63, 0x10B64,
10912
+ 0x10B65, 0x10B66, 0x10B67, 0x10B68, 0x10B69, 0x10B6A, 0x10B6B, 0x10B6C,
10913
+ 0x10B6D, 0x10B6E, 0x10B6F, 0x10B70, 0x10B71, 0x10B72, 0x10B78, 0x10B79,
10914
+ 0x10B7A, 0x10B7B, 0x10B7C, 0x10B7D, 0x10B7E, 0x10B7F, 0x10C00, 0x10C01,
10915
+ 0x10C02, 0x10C03, 0x10C04, 0x10C05, 0x10C06, 0x10C07, 0x10C08, 0x10C09,
10916
+ 0x10C0A, 0x10C0B, 0x10C0C, 0x10C0D, 0x10C0E, 0x10C0F, 0x10C10, 0x10C11,
10917
+ 0x10C12, 0x10C13, 0x10C14, 0x10C15, 0x10C16, 0x10C17, 0x10C18, 0x10C19,
10918
+ 0x10C1A, 0x10C1B, 0x10C1C, 0x10C1D, 0x10C1E, 0x10C1F, 0x10C20, 0x10C21,
10919
+ 0x10C22, 0x10C23, 0x10C24, 0x10C25, 0x10C26, 0x10C27, 0x10C28, 0x10C29,
10920
+ 0x10C2A, 0x10C2B, 0x10C2C, 0x10C2D, 0x10C2E, 0x10C2F, 0x10C30, 0x10C31,
10921
+ 0x10C32, 0x10C33, 0x10C34, 0x10C35, 0x10C36, 0x10C37, 0x10C38, 0x10C39,
10922
+ 0x10C3A, 0x10C3B, 0x10C3C, 0x10C3D, 0x10C3E, 0x10C3F, 0x10C40, 0x10C41,
10923
+ 0x10C42, 0x10C43, 0x10C44, 0x10C45, 0x10C46, 0x10C47, 0x10C48, 0x1EE00,
10924
+ 0x1EE01, 0x1EE02, 0x1EE03, 0x1EE05, 0x1EE06, 0x1EE07, 0x1EE08, 0x1EE09,
10925
+ 0x1EE0A, 0x1EE0B, 0x1EE0C, 0x1EE0D, 0x1EE0E, 0x1EE0F, 0x1EE10, 0x1EE11,
10926
+ 0x1EE12, 0x1EE13, 0x1EE14, 0x1EE15, 0x1EE16, 0x1EE17, 0x1EE18, 0x1EE19,
10927
+ 0x1EE1A, 0x1EE1B, 0x1EE1C, 0x1EE1D, 0x1EE1E, 0x1EE1F, 0x1EE21, 0x1EE22,
10928
+ 0x1EE24, 0x1EE27, 0x1EE29, 0x1EE2A, 0x1EE2B, 0x1EE2C, 0x1EE2D, 0x1EE2E,
10929
+ 0x1EE2F, 0x1EE30, 0x1EE31, 0x1EE32, 0x1EE34, 0x1EE35, 0x1EE36, 0x1EE37,
10930
+ 0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE4D, 0x1EE4E,
10931
+ 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D,
10932
+ 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE67, 0x1EE68, 0x1EE69, 0x1EE6A,
10933
+ 0x1EE6C, 0x1EE6D, 0x1EE6E, 0x1EE6F, 0x1EE70, 0x1EE71, 0x1EE72, 0x1EE74,
10934
+ 0x1EE75, 0x1EE76, 0x1EE77, 0x1EE79, 0x1EE7A, 0x1EE7B, 0x1EE7C, 0x1EE7E,
10935
+ 0x1EE80, 0x1EE81, 0x1EE82, 0x1EE83, 0x1EE84, 0x1EE85, 0x1EE86, 0x1EE87,
10936
+ 0x1EE88, 0x1EE89, 0x1EE8B, 0x1EE8C, 0x1EE8D, 0x1EE8E, 0x1EE8F, 0x1EE90,
10937
+ 0x1EE91, 0x1EE92, 0x1EE93, 0x1EE94, 0x1EE95, 0x1EE96, 0x1EE97, 0x1EE98,
10938
+ 0x1EE99, 0x1EE9A, 0x1EE9B, 0x1EEA1, 0x1EEA2, 0x1EEA3, 0x1EEA5, 0x1EEA6,
10939
+ 0x1EEA7, 0x1EEA8, 0x1EEA9, 0x1EEAB, 0x1EEAC, 0x1EEAD, 0x1EEAE, 0x1EEAF,
10940
+ 0x1EEB0, 0x1EEB1, 0x1EEB2, 0x1EEB3, 0x1EEB4, 0x1EEB5, 0x1EEB6, 0x1EEB7,
10941
+ 0x1EEB8, 0x1EEB9, 0x1EEBA, 0x1EEBB, 0x10FFFD];
10942
+
10943
+ function determineBidi(cueDiv) {
10944
+ var nodeStack = [],
10945
+ text = "",
10946
+ charCode;
10947
+
10948
+ if (!cueDiv || !cueDiv.childNodes) {
10949
+ return "ltr";
10950
+ }
10951
+
10952
+ function pushNodes(nodeStack, node) {
10953
+ for (var i = node.childNodes.length - 1; i >= 0; i--) {
10954
+ nodeStack.push(node.childNodes[i]);
10955
+ }
10956
+ }
10957
+
10958
+ function nextTextNode(nodeStack) {
10959
+ if (!nodeStack || !nodeStack.length) {
10960
+ return null;
10961
+ }
10962
+
10963
+ var node = nodeStack.pop(),
10964
+ text = node.textContent || node.innerText;
10965
+ if (text) {
10966
+ // TODO: This should match all unicode type B characters (paragraph
10967
+ // separator characters). See issue #115.
10968
+ var m = text.match(/^.*(\n|\r)/);
10969
+ if (m) {
10970
+ nodeStack.length = 0;
10971
+ return m[0];
10972
+ }
10973
+ return text;
10974
+ }
10975
+ if (node.tagName === "ruby") {
10976
+ return nextTextNode(nodeStack);
10977
+ }
10978
+ if (node.childNodes) {
10979
+ pushNodes(nodeStack, node);
10980
+ return nextTextNode(nodeStack);
10981
+ }
10982
+ }
10983
+
10984
+ pushNodes(nodeStack, cueDiv);
10985
+ while ((text = nextTextNode(nodeStack))) {
10986
+ for (var i = 0; i < text.length; i++) {
10987
+ charCode = text.charCodeAt(i);
10988
+ for (var j = 0; j < strongRTLChars.length; j++) {
10989
+ if (strongRTLChars[j] === charCode) {
10990
+ return "rtl";
10991
+ }
10992
+ }
10993
+ }
10994
+ }
10995
+ return "ltr";
10996
+ }
10997
+
10998
+ function computeLinePos(cue) {
10999
+ if (typeof cue.line === "number" &&
11000
+ (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
11001
+ return cue.line;
11002
+ }
11003
+ if (!cue.track || !cue.track.textTrackList ||
11004
+ !cue.track.textTrackList.mediaElement) {
11005
+ return -1;
11006
+ }
11007
+ var track = cue.track,
11008
+ trackList = track.textTrackList,
11009
+ count = 0;
11010
+ for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
11011
+ if (trackList[i].mode === "showing") {
11012
+ count++;
11013
+ }
11014
+ }
11015
+ return ++count * -1;
11016
+ }
11017
+
11018
+ function StyleBox() {
11019
+ }
11020
+
11021
+ // Apply styles to a div. If there is no div passed then it defaults to the
11022
+ // div on 'this'.
11023
+ StyleBox.prototype.applyStyles = function(styles, div) {
11024
+ div = div || this.div;
11025
+ for (var prop in styles) {
11026
+ if (styles.hasOwnProperty(prop)) {
11027
+ div.style[prop] = styles[prop];
11028
+ }
11029
+ }
11030
+ };
11031
+
11032
+ StyleBox.prototype.formatStyle = function(val, unit) {
11033
+ return val === 0 ? 0 : val + unit;
11034
+ };
11035
+
11036
+ // Constructs the computed display state of the cue (a div). Places the div
11037
+ // into the overlay which should be a block level element (usually a div).
11038
+ function CueStyleBox(window, cue, styleOptions) {
11039
+ var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
11040
+ var color = "rgba(255, 255, 255, 1)";
11041
+ var backgroundColor = "rgba(0, 0, 0, 0.8)";
11042
+
11043
+ if (isIE8) {
11044
+ color = "rgb(255, 255, 255)";
11045
+ backgroundColor = "rgb(0, 0, 0)";
11046
+ }
11047
+
11048
+ StyleBox.call(this);
11049
+ this.cue = cue;
11050
+
11051
+ // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
11052
+ // have inline positioning and will function as the cue background box.
11053
+ this.cueDiv = parseContent(window, cue.text);
11054
+ var styles = {
11055
+ color: color,
11056
+ backgroundColor: backgroundColor,
11057
+ position: "relative",
11058
+ left: 0,
11059
+ right: 0,
11060
+ top: 0,
11061
+ bottom: 0,
11062
+ display: "inline"
11063
+ };
11064
+
11065
+ if (!isIE8) {
11066
+ styles.writingMode = cue.vertical === "" ? "horizontal-tb"
11067
+ : cue.vertical === "lr" ? "vertical-lr"
11068
+ : "vertical-rl";
11069
+ styles.unicodeBidi = "plaintext";
11070
+ }
11071
+ this.applyStyles(styles, this.cueDiv);
11072
+
11073
+ // Create an absolutely positioned div that will be used to position the cue
11074
+ // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
11075
+ // mirrors of them except "middle" which is "center" in CSS.
11076
+ this.div = window.document.createElement("div");
11077
+ styles = {
11078
+ textAlign: cue.align === "middle" ? "center" : cue.align,
11079
+ font: styleOptions.font,
11080
+ whiteSpace: "pre-line",
11081
+ position: "absolute"
11082
+ };
11083
+
11084
+ if (!isIE8) {
11085
+ styles.direction = determineBidi(this.cueDiv);
11086
+ styles.writingMode = cue.vertical === "" ? "horizontal-tb"
11087
+ : cue.vertical === "lr" ? "vertical-lr"
11088
+ : "vertical-rl".
11089
+ stylesunicodeBidi = "plaintext";
11090
+ }
11091
+
11092
+ this.applyStyles(styles);
11093
+
11094
+ this.div.appendChild(this.cueDiv);
11095
+
11096
+ // Calculate the distance from the reference edge of the viewport to the text
11097
+ // position of the cue box. The reference edge will be resolved later when
11098
+ // the box orientation styles are applied.
11099
+ var textPos = 0;
11100
+ switch (cue.positionAlign) {
11101
+ case "start":
11102
+ textPos = cue.position;
11103
+ break;
11104
+ case "middle":
11105
+ textPos = cue.position - (cue.size / 2);
11106
+ break;
11107
+ case "end":
11108
+ textPos = cue.position - cue.size;
11109
+ break;
11110
+ }
11111
+
11112
+ // Horizontal box orientation; textPos is the distance from the left edge of the
11113
+ // area to the left edge of the box and cue.size is the distance extending to
11114
+ // the right from there.
11115
+ if (cue.vertical === "") {
11116
+ this.applyStyles({
11117
+ left: this.formatStyle(textPos, "%"),
11118
+ width: this.formatStyle(cue.size, "%"),
11119
+ });
11120
+ // Vertical box orientation; textPos is the distance from the top edge of the
11121
+ // area to the top edge of the box and cue.size is the height extending
11122
+ // downwards from there.
11123
+ } else {
11124
+ this.applyStyles({
11125
+ top: this.formatStyle(textPos, "%"),
11126
+ height: this.formatStyle(cue.size, "%")
11127
+ });
11128
+ }
11129
+
11130
+ this.move = function(box) {
11131
+ this.applyStyles({
11132
+ top: this.formatStyle(box.top, "px"),
11133
+ bottom: this.formatStyle(box.bottom, "px"),
11134
+ left: this.formatStyle(box.left, "px"),
11135
+ right: this.formatStyle(box.right, "px"),
11136
+ height: this.formatStyle(box.height, "px"),
11137
+ width: this.formatStyle(box.width, "px"),
11138
+ });
11139
+ };
11140
+ }
11141
+ CueStyleBox.prototype = _objCreate(StyleBox.prototype);
11142
+ CueStyleBox.prototype.constructor = CueStyleBox;
11143
+
11144
+ // Represents the co-ordinates of an Element in a way that we can easily
11145
+ // compute things with such as if it overlaps or intersects with another Element.
11146
+ // Can initialize it with either a StyleBox or another BoxPosition.
11147
+ function BoxPosition(obj) {
11148
+ var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
11149
+
11150
+ // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
11151
+ // was passed in and we need to copy the results of 'getBoundingClientRect'
11152
+ // as the object returned is readonly. All co-ordinate values are in reference
11153
+ // to the viewport origin (top left).
11154
+ var lh, height, width, top;
11155
+ if (obj.div) {
11156
+ height = obj.div.offsetHeight;
11157
+ width = obj.div.offsetWidth;
11158
+ top = obj.div.offsetTop;
11159
+
11160
+ var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
11161
+ rects.getClientRects && rects.getClientRects();
11162
+ obj = obj.div.getBoundingClientRect();
11163
+ // In certain cases the outter div will be slightly larger then the sum of
11164
+ // the inner div's lines. This could be due to bold text, etc, on some platforms.
11165
+ // In this case we should get the average line height and use that. This will
11166
+ // result in the desired behaviour.
11167
+ lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
11168
+ : 0;
11169
+
11170
+ }
11171
+ this.left = obj.left;
11172
+ this.right = obj.right;
11173
+ this.top = obj.top || top;
11174
+ this.height = obj.height || height;
11175
+ this.bottom = obj.bottom || (top + (obj.height || height));
11176
+ this.width = obj.width || width;
11177
+ this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
11178
+
11179
+ if (isIE8 && !this.lineHeight) {
11180
+ this.lineHeight = 13;
11181
+ }
11182
+ }
11183
+
11184
+ // Move the box along a particular axis. Optionally pass in an amount to move
11185
+ // the box. If no amount is passed then the default is the line height of the
11186
+ // box.
11187
+ BoxPosition.prototype.move = function(axis, toMove) {
11188
+ toMove = toMove !== undefined ? toMove : this.lineHeight;
11189
+ switch (axis) {
11190
+ case "+x":
11191
+ this.left += toMove;
11192
+ this.right += toMove;
11193
+ break;
11194
+ case "-x":
11195
+ this.left -= toMove;
11196
+ this.right -= toMove;
11197
+ break;
11198
+ case "+y":
11199
+ this.top += toMove;
11200
+ this.bottom += toMove;
11201
+ break;
11202
+ case "-y":
11203
+ this.top -= toMove;
11204
+ this.bottom -= toMove;
11205
+ break;
11206
+ }
11207
+ };
11208
+
11209
+ // Check if this box overlaps another box, b2.
11210
+ BoxPosition.prototype.overlaps = function(b2) {
11211
+ return this.left < b2.right &&
11212
+ this.right > b2.left &&
11213
+ this.top < b2.bottom &&
11214
+ this.bottom > b2.top;
11215
+ };
11216
+
11217
+ // Check if this box overlaps any other boxes in boxes.
11218
+ BoxPosition.prototype.overlapsAny = function(boxes) {
11219
+ for (var i = 0; i < boxes.length; i++) {
11220
+ if (this.overlaps(boxes[i])) {
11221
+ return true;
11222
+ }
11223
+ }
11224
+ return false;
11225
+ };
11226
+
11227
+ // Check if this box is within another box.
11228
+ BoxPosition.prototype.within = function(container) {
11229
+ return this.top >= container.top &&
11230
+ this.bottom <= container.bottom &&
11231
+ this.left >= container.left &&
11232
+ this.right <= container.right;
11233
+ };
11234
+
11235
+ // Check if this box is entirely within the container or it is overlapping
11236
+ // on the edge opposite of the axis direction passed. For example, if "+x" is
11237
+ // passed and the box is overlapping on the left edge of the container, then
11238
+ // return true.
11239
+ BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
11240
+ switch (axis) {
11241
+ case "+x":
11242
+ return this.left < container.left;
11243
+ case "-x":
11244
+ return this.right > container.right;
11245
+ case "+y":
11246
+ return this.top < container.top;
11247
+ case "-y":
11248
+ return this.bottom > container.bottom;
11249
+ }
11250
+ };
11251
+
11252
+ // Find the percentage of the area that this box is overlapping with another
11253
+ // box.
11254
+ BoxPosition.prototype.intersectPercentage = function(b2) {
11255
+ var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
11256
+ y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
11257
+ intersectArea = x * y;
11258
+ return intersectArea / (this.height * this.width);
11259
+ };
11260
+
11261
+ // Convert the positions from this box to CSS compatible positions using
11262
+ // the reference container's positions. This has to be done because this
11263
+ // box's positions are in reference to the viewport origin, whereas, CSS
11264
+ // values are in referecne to their respective edges.
11265
+ BoxPosition.prototype.toCSSCompatValues = function(reference) {
11266
+ return {
11267
+ top: this.top - reference.top,
11268
+ bottom: reference.bottom - this.bottom,
11269
+ left: this.left - reference.left,
11270
+ right: reference.right - this.right,
11271
+ height: this.height,
11272
+ width: this.width
11273
+ };
11274
+ };
11275
+
11276
+ // Get an object that represents the box's position without anything extra.
11277
+ // Can pass a StyleBox, HTMLElement, or another BoxPositon.
11278
+ BoxPosition.getSimpleBoxPosition = function(obj) {
11279
+ var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
11280
+ var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
11281
+ var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
11282
+
11283
+ obj = obj.div ? obj.div.getBoundingClientRect() :
11284
+ obj.tagName ? obj.getBoundingClientRect() : obj;
11285
+ var ret = {
11286
+ left: obj.left,
11287
+ right: obj.right,
11288
+ top: obj.top || top,
11289
+ height: obj.height || height,
11290
+ bottom: obj.bottom || (top + (obj.height || height)),
11291
+ width: obj.width || width
11292
+ };
11293
+ return ret;
11294
+ };
11295
+
11296
+ // Move a StyleBox to its specified, or next best, position. The containerBox
11297
+ // is the box that contains the StyleBox, such as a div. boxPositions are
11298
+ // a list of other boxes that the styleBox can't overlap with.
11299
+ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
11300
+
11301
+ // Find the best position for a cue box, b, on the video. The axis parameter
11302
+ // is a list of axis, the order of which, it will move the box along. For example:
11303
+ // Passing ["+x", "-x"] will move the box first along the x axis in the positive
11304
+ // direction. If it doesn't find a good position for it there it will then move
11305
+ // it along the x axis in the negative direction.
11306
+ function findBestPosition(b, axis) {
11307
+ var bestPosition,
11308
+ specifiedPosition = new BoxPosition(b),
11309
+ percentage = 1; // Highest possible so the first thing we get is better.
11310
+
11311
+ for (var i = 0; i < axis.length; i++) {
11312
+ while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
11313
+ (b.within(containerBox) && b.overlapsAny(boxPositions))) {
11314
+ b.move(axis[i]);
11315
+ }
11316
+ // We found a spot where we aren't overlapping anything. This is our
11317
+ // best position.
11318
+ if (b.within(containerBox)) {
11319
+ return b;
11320
+ }
11321
+ var p = b.intersectPercentage(containerBox);
11322
+ // If we're outside the container box less then we were on our last try
11323
+ // then remember this position as the best position.
11324
+ if (percentage > p) {
11325
+ bestPosition = new BoxPosition(b);
11326
+ percentage = p;
11327
+ }
11328
+ // Reset the box position to the specified position.
11329
+ b = new BoxPosition(specifiedPosition);
11330
+ }
11331
+ return bestPosition || specifiedPosition;
11332
+ }
11333
+
11334
+ var boxPosition = new BoxPosition(styleBox),
11335
+ cue = styleBox.cue,
11336
+ linePos = computeLinePos(cue),
11337
+ axis = [];
11338
+
11339
+ // If we have a line number to align the cue to.
11340
+ if (cue.snapToLines) {
11341
+ var size;
11342
+ switch (cue.vertical) {
11343
+ case "":
11344
+ axis = [ "+y", "-y" ];
11345
+ size = "height";
11346
+ break;
11347
+ case "rl":
11348
+ axis = [ "+x", "-x" ];
11349
+ size = "width";
11350
+ break;
11351
+ case "lr":
11352
+ axis = [ "-x", "+x" ];
11353
+ size = "width";
11354
+ break;
11355
+ }
11356
+
11357
+ var step = boxPosition.lineHeight,
11358
+ position = step * Math.round(linePos),
11359
+ maxPosition = containerBox[size] + step,
11360
+ initialAxis = axis[0];
11361
+
11362
+ // If the specified intial position is greater then the max position then
11363
+ // clamp the box to the amount of steps it would take for the box to
11364
+ // reach the max position.
11365
+ if (Math.abs(position) > maxPosition) {
11366
+ position = position < 0 ? -1 : 1;
11367
+ position *= Math.ceil(maxPosition / step) * step;
11368
+ }
11369
+
11370
+ // If computed line position returns negative then line numbers are
11371
+ // relative to the bottom of the video instead of the top. Therefore, we
11372
+ // need to increase our initial position by the length or width of the
11373
+ // video, depending on the writing direction, and reverse our axis directions.
11374
+ if (linePos < 0) {
11375
+ position += cue.vertical === "" ? containerBox.height : containerBox.width;
11376
+ axis = axis.reverse();
11377
+ }
11378
+
11379
+ // Move the box to the specified position. This may not be its best
11380
+ // position.
11381
+ boxPosition.move(initialAxis, position);
11382
+
11383
+ } else {
11384
+ // If we have a percentage line value for the cue.
11385
+ var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;
11386
+
11387
+ switch (cue.lineAlign) {
11388
+ case "middle":
11389
+ linePos -= (calculatedPercentage / 2);
11390
+ break;
11391
+ case "end":
11392
+ linePos -= calculatedPercentage;
11393
+ break;
11394
+ }
11395
+
11396
+ // Apply initial line position to the cue box.
11397
+ switch (cue.vertical) {
11398
+ case "":
11399
+ styleBox.applyStyles({
11400
+ top: styleBox.formatStyle(linePos, "%")
11401
+ });
11402
+ break;
11403
+ case "rl":
11404
+ styleBox.applyStyles({
11405
+ left: styleBox.formatStyle(linePos, "%")
11406
+ });
11407
+ break;
11408
+ case "lr":
11409
+ styleBox.applyStyles({
11410
+ right: styleBox.formatStyle(linePos, "%")
11411
+ });
11412
+ break;
11413
+ }
11414
+
11415
+ axis = [ "+y", "-x", "+x", "-y" ];
11416
+
11417
+ // Get the box position again after we've applied the specified positioning
11418
+ // to it.
11419
+ boxPosition = new BoxPosition(styleBox);
11420
+ }
11421
+
11422
+ var bestPosition = findBestPosition(boxPosition, axis);
11423
+ styleBox.move(bestPosition.toCSSCompatValues(containerBox));
11424
+ }
11425
+
11426
+ function WebVTT() {
11427
+ // Nothing
11428
+ }
11429
+
11430
+ // Helper to allow strings to be decoded instead of the default binary utf8 data.
11431
+ WebVTT.StringDecoder = function() {
11432
+ return {
11433
+ decode: function(data) {
11434
+ if (!data) {
11435
+ return "";
11436
+ }
11437
+ if (typeof data !== "string") {
11438
+ throw new Error("Error - expected string data.");
11439
+ }
11440
+ return decodeURIComponent(encodeURIComponent(data));
11441
+ }
11442
+ };
11443
+ };
11444
+
11445
+ WebVTT.convertCueToDOMTree = function(window, cuetext) {
11446
+ if (!window || !cuetext) {
11447
+ return null;
11448
+ }
11449
+ return parseContent(window, cuetext);
11450
+ };
11451
+
11452
+ var FONT_SIZE_PERCENT = 0.05;
11453
+ var FONT_STYLE = "sans-serif";
11454
+ var CUE_BACKGROUND_PADDING = "1.5%";
11455
+
11456
+ // Runs the processing model over the cues and regions passed to it.
11457
+ // @param overlay A block level element (usually a div) that the computed cues
11458
+ // and regions will be placed into.
11459
+ WebVTT.processCues = function(window, cues, overlay) {
11460
+ if (!window || !cues || !overlay) {
11461
+ return null;
11462
+ }
11463
+
11464
+ // Remove all previous children.
11465
+ while (overlay.firstChild) {
11466
+ overlay.removeChild(overlay.firstChild);
11467
+ }
11468
+
11469
+ var paddedOverlay = window.document.createElement("div");
11470
+ paddedOverlay.style.position = "absolute";
11471
+ paddedOverlay.style.left = "0";
11472
+ paddedOverlay.style.right = "0";
11473
+ paddedOverlay.style.top = "0";
11474
+ paddedOverlay.style.bottom = "0";
11475
+ paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
11476
+ overlay.appendChild(paddedOverlay);
11477
+
11478
+ // Determine if we need to compute the display states of the cues. This could
11479
+ // be the case if a cue's state has been changed since the last computation or
11480
+ // if it has not been computed yet.
11481
+ function shouldCompute(cues) {
11482
+ for (var i = 0; i < cues.length; i++) {
11483
+ if (cues[i].hasBeenReset || !cues[i].displayState) {
11484
+ return true;
11485
+ }
11486
+ }
11487
+ return false;
11488
+ }
11489
+
11490
+ // We don't need to recompute the cues' display states. Just reuse them.
11491
+ if (!shouldCompute(cues)) {
11492
+ for (var i = 0; i < cues.length; i++) {
11493
+ paddedOverlay.appendChild(cues[i].displayState);
11494
+ }
11495
+ return;
11496
+ }
11497
+
11498
+ var boxPositions = [],
11499
+ containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
11500
+ fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
11501
+ var styleOptions = {
11502
+ font: fontSize + "px " + FONT_STYLE
11503
+ };
11504
+
11505
+ (function() {
11506
+ var styleBox, cue;
11507
+
11508
+ for (var i = 0; i < cues.length; i++) {
11509
+ cue = cues[i];
11510
+
11511
+ // Compute the intial position and styles of the cue div.
11512
+ styleBox = new CueStyleBox(window, cue, styleOptions);
11513
+ paddedOverlay.appendChild(styleBox.div);
11514
+
11515
+ // Move the cue div to it's correct line position.
11516
+ moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
11517
+
11518
+ // Remember the computed div so that we don't have to recompute it later
11519
+ // if we don't have too.
11520
+ cue.displayState = styleBox.div;
11521
+
11522
+ boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
11523
+ }
11524
+ })();
11525
+ };
11526
+
11527
+ WebVTT.Parser = function(window, vttjs, decoder) {
11528
+ if (!decoder) {
11529
+ decoder = vttjs;
11530
+ vttjs = {};
11531
+ }
11532
+ if (!vttjs) {
11533
+ vttjs = {};
11534
+ }
11535
+
11536
+ this.window = window;
11537
+ this.vttjs = vttjs;
11538
+ this.state = "INITIAL";
11539
+ this.buffer = "";
11540
+ this.decoder = decoder || new TextDecoder("utf8");
11541
+ this.regionList = [];
11542
+ };
11543
+
11544
+ WebVTT.Parser.prototype = {
11545
+ // If the error is a ParsingError then report it to the consumer if
11546
+ // possible. If it's not a ParsingError then throw it like normal.
11547
+ reportOrThrowError: function(e) {
11548
+ if (e instanceof ParsingError) {
11549
+ this.onparsingerror && this.onparsingerror(e);
11550
+ } else {
11551
+ throw e;
11552
+ }
11553
+ },
11554
+ parse: function (data) {
11555
+ var self = this;
11556
+
11557
+ // If there is no data then we won't decode it, but will just try to parse
11558
+ // whatever is in buffer already. This may occur in circumstances, for
11559
+ // example when flush() is called.
11560
+ if (data) {
11561
+ // Try to decode the data that we received.
11562
+ self.buffer += self.decoder.decode(data, {stream: true});
11563
+ }
11564
+
11565
+ function collectNextLine() {
11566
+ var buffer = self.buffer;
11567
+ var pos = 0;
11568
+ while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
11569
+ ++pos;
11570
+ }
11571
+ var line = buffer.substr(0, pos);
11572
+ // Advance the buffer early in case we fail below.
11573
+ if (buffer[pos] === '\r') {
11574
+ ++pos;
11575
+ }
11576
+ if (buffer[pos] === '\n') {
11577
+ ++pos;
11578
+ }
11579
+ self.buffer = buffer.substr(pos);
11580
+ return line;
11581
+ }
11582
+
11583
+ // 3.4 WebVTT region and WebVTT region settings syntax
11584
+ function parseRegion(input) {
11585
+ var settings = new Settings();
11586
+
11587
+ parseOptions(input, function (k, v) {
11588
+ switch (k) {
11589
+ case "id":
11590
+ settings.set(k, v);
11591
+ break;
11592
+ case "width":
11593
+ settings.percent(k, v);
11594
+ break;
11595
+ case "lines":
11596
+ settings.integer(k, v);
11597
+ break;
11598
+ case "regionanchor":
11599
+ case "viewportanchor":
11600
+ var xy = v.split(',');
11601
+ if (xy.length !== 2) {
11602
+ break;
11603
+ }
11604
+ // We have to make sure both x and y parse, so use a temporary
11605
+ // settings object here.
11606
+ var anchor = new Settings();
11607
+ anchor.percent("x", xy[0]);
11608
+ anchor.percent("y", xy[1]);
11609
+ if (!anchor.has("x") || !anchor.has("y")) {
11610
+ break;
11611
+ }
11612
+ settings.set(k + "X", anchor.get("x"));
11613
+ settings.set(k + "Y", anchor.get("y"));
11614
+ break;
11615
+ case "scroll":
11616
+ settings.alt(k, v, ["up"]);
11617
+ break;
11618
+ }
11619
+ }, /=/, /\s/);
11620
+
11621
+ // Create the region, using default values for any values that were not
11622
+ // specified.
11623
+ if (settings.has("id")) {
11624
+ var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
11625
+ region.width = settings.get("width", 100);
11626
+ region.lines = settings.get("lines", 3);
11627
+ region.regionAnchorX = settings.get("regionanchorX", 0);
11628
+ region.regionAnchorY = settings.get("regionanchorY", 100);
11629
+ region.viewportAnchorX = settings.get("viewportanchorX", 0);
11630
+ region.viewportAnchorY = settings.get("viewportanchorY", 100);
11631
+ region.scroll = settings.get("scroll", "");
11632
+ // Register the region.
11633
+ self.onregion && self.onregion(region);
11634
+ // Remember the VTTRegion for later in case we parse any VTTCues that
11635
+ // reference it.
11636
+ self.regionList.push({
11637
+ id: settings.get("id"),
11638
+ region: region
11639
+ });
11640
+ }
11641
+ }
11642
+
11643
+ // 3.2 WebVTT metadata header syntax
11644
+ function parseHeader(input) {
11645
+ parseOptions(input, function (k, v) {
11646
+ switch (k) {
11647
+ case "Region":
11648
+ // 3.3 WebVTT region metadata header syntax
11649
+ parseRegion(v);
11650
+ break;
11651
+ }
11652
+ }, /:/);
11653
+ }
11654
+
11655
+ // 5.1 WebVTT file parsing.
11656
+ try {
11657
+ var line;
11658
+ if (self.state === "INITIAL") {
11659
+ // We can't start parsing until we have the first line.
11660
+ if (!/\r\n|\n/.test(self.buffer)) {
11661
+ return this;
11662
+ }
11663
+
11664
+ line = collectNextLine();
11665
+
11666
+ var m = line.match(/^WEBVTT([ \t].*)?$/);
11667
+ if (!m || !m[0]) {
11668
+ throw new ParsingError(ParsingError.Errors.BadSignature);
11669
+ }
11670
+
11671
+ self.state = "HEADER";
11672
+ }
11673
+
11674
+ var alreadyCollectedLine = false;
11675
+ while (self.buffer) {
11676
+ // We can't parse a line until we have the full line.
11677
+ if (!/\r\n|\n/.test(self.buffer)) {
11678
+ return this;
11679
+ }
11680
+
11681
+ if (!alreadyCollectedLine) {
11682
+ line = collectNextLine();
11683
+ } else {
11684
+ alreadyCollectedLine = false;
11685
+ }
11686
+
11687
+ switch (self.state) {
11688
+ case "HEADER":
11689
+ // 13-18 - Allow a header (metadata) under the WEBVTT line.
11690
+ if (/:/.test(line)) {
11691
+ parseHeader(line);
11692
+ } else if (!line) {
11693
+ // An empty line terminates the header and starts the body (cues).
11694
+ self.state = "ID";
11695
+ }
11696
+ continue;
11697
+ case "NOTE":
11698
+ // Ignore NOTE blocks.
11699
+ if (!line) {
11700
+ self.state = "ID";
11701
+ }
11702
+ continue;
11703
+ case "ID":
11704
+ // Check for the start of NOTE blocks.
11705
+ if (/^NOTE($|[ \t])/.test(line)) {
11706
+ self.state = "NOTE";
11707
+ break;
11708
+ }
11709
+ // 19-29 - Allow any number of line terminators, then initialize new cue values.
11710
+ if (!line) {
11711
+ continue;
11712
+ }
11713
+ self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
11714
+ self.state = "CUE";
11715
+ // 30-39 - Check if self line contains an optional identifier or timing data.
11716
+ if (line.indexOf("-->") === -1) {
11717
+ self.cue.id = line;
11718
+ continue;
11719
+ }
11720
+ // Process line as start of a cue.
11721
+ /*falls through*/
11722
+ case "CUE":
11723
+ // 40 - Collect cue timings and settings.
11724
+ try {
11725
+ parseCue(line, self.cue, self.regionList);
11726
+ } catch (e) {
11727
+ self.reportOrThrowError(e);
11728
+ // In case of an error ignore rest of the cue.
11729
+ self.cue = null;
11730
+ self.state = "BADCUE";
11731
+ continue;
11732
+ }
11733
+ self.state = "CUETEXT";
11734
+ continue;
11735
+ case "CUETEXT":
11736
+ var hasSubstring = line.indexOf("-->") !== -1;
11737
+ // 34 - If we have an empty line then report the cue.
11738
+ // 35 - If we have the special substring '-->' then report the cue,
11739
+ // but do not collect the line as we need to process the current
11740
+ // one as a new cue.
11741
+ if (!line || hasSubstring && (alreadyCollectedLine = true)) {
11742
+ // We are done parsing self cue.
11743
+ self.oncue && self.oncue(self.cue);
11744
+ self.cue = null;
11745
+ self.state = "ID";
11746
+ continue;
11747
+ }
11748
+ if (self.cue.text) {
11749
+ self.cue.text += "\n";
11750
+ }
11751
+ self.cue.text += line;
11752
+ continue;
11753
+ case "BADCUE": // BADCUE
11754
+ // 54-62 - Collect and discard the remaining cue.
11755
+ if (!line) {
11756
+ self.state = "ID";
11757
+ }
11758
+ continue;
11759
+ }
11760
+ }
11761
+ } catch (e) {
11762
+ self.reportOrThrowError(e);
11763
+
11764
+ // If we are currently parsing a cue, report what we have.
11765
+ if (self.state === "CUETEXT" && self.cue && self.oncue) {
11766
+ self.oncue(self.cue);
11767
+ }
11768
+ self.cue = null;
11769
+ // Enter BADWEBVTT state if header was not parsed correctly otherwise
11770
+ // another exception occurred so enter BADCUE state.
11771
+ self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
11772
+ }
11773
+ return this;
11774
+ },
11775
+ flush: function () {
11776
+ var self = this;
11777
+ try {
11778
+ // Finish decoding the stream.
11779
+ self.buffer += self.decoder.decode();
11780
+ // Synthesize the end of the current cue or region.
11781
+ if (self.cue || self.state === "HEADER") {
11782
+ self.buffer += "\n\n";
11783
+ self.parse();
11784
+ }
11785
+ // If we've flushed, parsed, and we're still on the INITIAL state then
11786
+ // that means we don't have enough of the stream to parse the first
11787
+ // line.
11788
+ if (self.state === "INITIAL") {
11789
+ throw new ParsingError(ParsingError.Errors.BadSignature);
11790
+ }
11791
+ } catch(e) {
11792
+ self.reportOrThrowError(e);
11793
+ }
11794
+ self.onflush && self.onflush();
11795
+ return this;
11796
+ }
11797
+ };
11798
+
11799
+ global.WebVTT = WebVTT;
11800
+
11801
+ }(this, (this.vttjs || {})));