wai-website-theme 1.6 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -168,6 +168,20 @@
168
168
  this.useDescriptionsButton = true;
169
169
  }
170
170
 
171
+ // Silence audio description
172
+ // set to "false" if the sole purposes of the WebVTT descriptions file
173
+ // is to display description text visibly and to integrate it into the transcript
174
+ if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) {
175
+ this.exposeTextDescriptions = false;
176
+ }
177
+ else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) {
178
+ // support both singular and plural spelling of attribute
179
+ this.exposeTextDescriptions = false;
180
+ }
181
+ else {
182
+ this.exposeTextDescriptions = true;
183
+ }
184
+
171
185
  // Headings
172
186
  // By default, an off-screen heading is automatically added to the top of the media player
173
187
  // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel()
@@ -189,6 +203,19 @@
189
203
  // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button
190
204
  // If data-include-transcript="false", there is no "popup" transcript
191
205
 
206
+ if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") {
207
+ this.transcriptDivLocation = $(media).data('transcript-div');
208
+ }
209
+ else {
210
+ this.transcriptDivLocation = null;
211
+ }
212
+ if ($(media).data('include-transcript') !== undefined && $(media).data('include-transcript') === false) {
213
+ this.hideTranscriptButton = true;
214
+ }
215
+ else {
216
+ this.hideTranscriptButton = null;
217
+ }
218
+
192
219
  this.transcriptType = null;
193
220
  if ($(media).data('transcript-src') !== undefined) {
194
221
  this.transcriptSrc = $(media).data('transcript-src');
@@ -198,15 +225,9 @@
198
225
  }
199
226
  else if ($(media).find('track[kind="captions"], track[kind="subtitles"]').length > 0) {
200
227
  // required tracks are present. COULD automatically generate a transcript
201
- if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") {
202
- this.transcriptDivLocation = $(media).data('transcript-div');
228
+ if (this.transcriptDivLocation) {
203
229
  this.transcriptType = 'external';
204
230
  }
205
- else if ($(media).data('include-transcript') !== undefined) {
206
- if ($(media).data('include-transcript') !== false) {
207
- this.transcriptType = 'popup';
208
- }
209
- }
210
231
  else {
211
232
  this.transcriptType = 'popup';
212
233
  }
@@ -258,18 +279,6 @@
258
279
  this.defaultChapter = null;
259
280
  }
260
281
 
261
- // Previous/Next buttons
262
- // valid values of data-prevnext-unit are 'playlist' and 'chapter'; will also accept 'chapters'
263
- if ($(media).data('prevnext-unit') === 'chapter' || $(media).data('prevnext-unit') === 'chapters') {
264
- this.prevNextUnit = 'chapter';
265
- }
266
- else if ($(media).data('prevnext-unit') === 'playlist') {
267
- this.prevNextUnit = 'playlist';
268
- }
269
- else {
270
- this.prevNextUnit = false;
271
- }
272
-
273
282
  // Slower/Faster buttons
274
283
  // valid values of data-speed-icons are 'animals' (default) and 'arrows'
275
284
  // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows
@@ -757,7 +766,7 @@
757
766
  svg[1] = 'M7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714zM18.75 12.093v1.657h-1.657l-2.093-2.093-2.093 2.093h-1.657v-1.657l2.093-2.093-2.093-2.093v-1.657h1.657l2.093 2.093 2.093-2.093h1.657v1.657l-2.093 2.093z';
758
767
  break;
759
768
 
760
- case 'volume-mute':
769
+ case 'volume-soft':
761
770
  svg[0] = '0 0 20 20';
762
771
  svg[1] = 'M10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z';
763
772
  break;
@@ -1144,11 +1153,10 @@
1144
1153
  thisObj.showSearchResults();
1145
1154
 
1146
1155
  // Go ahead and load media, without user requesting it
1147
- // Normally, we wait until user clicks play, rather than unnecessarily consume their bandwidth
1148
- // Exceptions are if the video is intended to autostart or if running on iOS (a workaround for iOS issues)
1149
- // TODO: Confirm that this is still necessary with iOS (this would added early, & I don't remember what the issues were)
1150
- if (thisObj.player === 'html5' &&
1151
- (thisObj.isIOS() || thisObj.startTime > 0 || thisObj.autoplay || thisObj.okToPlay)) {
1156
+ // Ideally, we would wait until user clicks play, rather than unnecessarily consume their bandwidth
1157
+ // However, the media needs to load before the 'loadedmetadata' event is fired
1158
+ // and until that happens we can't get the media's duration
1159
+ if (thisObj.player === 'html5') {
1152
1160
  thisObj.$media[0].load();
1153
1161
  }
1154
1162
  // refreshControls is called twice building/initializing the player
@@ -1571,7 +1579,7 @@
1571
1579
  });
1572
1580
  prefs.push({
1573
1581
  'name': 'prefDescFormat', // audio description default state
1574
- 'label': this.tt.prefDescFormat,
1582
+ 'label': null,
1575
1583
  'group': 'descriptions',
1576
1584
  'default': 'video'
1577
1585
  });
@@ -1630,7 +1638,7 @@
1630
1638
  var available, thisObj, $prefsDiv, formTitle, introText,
1631
1639
  $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j,
1632
1640
  $fieldset, fieldsetClass, fieldsetId,
1633
- $descFieldset1, $descLegend1, $descFieldset2, $descLegend2, $legend,
1641
+ $descFieldset, $descLegend, $legend,
1634
1642
  thisPref, $thisDiv, thisClass, thisId, $thisLabel, $thisField,
1635
1643
  $div1,id1,$radio1,$label1,
1636
1644
  $div2,id2,$radio2,$label2,
@@ -1728,35 +1736,17 @@
1728
1736
  $prefsDiv.append($prefsIntro);
1729
1737
  }
1730
1738
 
1731
- if (form === 'descriptions') {
1732
- // descriptions form has two field sets
1733
-
1734
- // Fieldset 1
1735
- $descFieldset1 = $('<fieldset>');
1736
- fieldsetClass = 'able-prefs-' + form + '1';
1737
- fieldsetId = this.mediaId + '-prefs-' + form + '1';
1738
- $descFieldset1.addClass(fieldsetClass).attr('id',fieldsetId);
1739
- $descLegend1 = $('<legend>' + this.tt.prefDescFormat + '</legend>');
1740
- $descFieldset1.append($descLegend1);
1741
-
1742
- // Fieldset 2
1743
- $descFieldset2 = $('<fieldset>');
1744
- fieldsetClass = 'able-prefs-' + form + '2';
1745
- fieldsetId = this.mediaId + '-prefs-' + form + '2';
1746
- $descFieldset2.addClass(fieldsetClass).attr('id',fieldsetId);
1747
- $descLegend2 = $('<legend>' + this.tt.prefHeadingTextDescription + '</legend>');
1748
- $descFieldset2.append($descLegend2);
1739
+ $fieldset = $('<fieldset>');
1740
+ fieldsetClass = 'able-prefs-' + form;
1741
+ fieldsetId = this.mediaId + '-prefs-' + form;
1742
+ $fieldset.addClass(fieldsetClass).attr('id',fieldsetId);
1743
+ if (form === 'keyboard') {
1744
+ $legend = $('<legend>' + this.tt.prefHeadingKeyboard1 + '</legend>');
1745
+ $fieldset.append($legend);
1749
1746
  }
1750
- else {
1751
- // all other forms just have one fieldset
1752
- $fieldset = $('<fieldset>');
1753
- fieldsetClass = 'able-prefs-' + form;
1754
- fieldsetId = this.mediaId + '-prefs-' + form;
1755
- $fieldset.addClass(fieldsetClass).attr('id',fieldsetId);
1756
- if (form === 'keyboard') {
1757
- $legend = $('<legend>' + this.tt.prefHeadingKeyboard1 + '</legend>');
1758
- $fieldset.append($legend);
1759
- }
1747
+ else if (form === 'descriptions') {
1748
+ $legend = $('<legend>' + this.tt.prefHeadingTextDescription + '</legend>');
1749
+ $fieldset.append($legend);
1760
1750
  }
1761
1751
  for (i=0; i<available.length; i++) {
1762
1752
 
@@ -1766,48 +1756,9 @@
1766
1756
  thisPref = available[i]['name'];
1767
1757
  thisClass = 'able-' + thisPref;
1768
1758
  thisId = this.mediaId + '_' + thisPref;
1769
- if (thisPref !== 'prefDescFormat') {
1770
- $thisDiv = $('<div>').addClass(thisClass);
1771
- }
1772
-
1773
- // Audio Description preferred format radio buttons
1774
- if (thisPref == 'prefDescFormat') {
1759
+ $thisDiv = $('<div>').addClass(thisClass);
1775
1760
 
1776
- // option 1 radio button
1777
- $div1 = $('<div>');
1778
- id1 = thisId + '_1';
1779
- $label1 = $('<label>')
1780
- .attr('for',id1)
1781
- .text(this.capitalizeFirstLetter(this.tt.prefDescFormatOption1))
1782
- $radio1 = $('<input>',{
1783
- type: 'radio',
1784
- name: thisPref,
1785
- id: id1,
1786
- value: 'video'
1787
- });
1788
- if (this.prefDescFormat === 'video') {
1789
- $radio1.prop('checked',true);
1790
- };
1791
- $div1.append($radio1,$label1);
1792
-
1793
- // option 2 radio button
1794
- $div2 = $('<div>');
1795
- id2 = thisId + '_2';
1796
- $label2 = $('<label>')
1797
- .attr('for',id2)
1798
- .text(this.capitalizeFirstLetter(this.tt.prefDescFormatOption2));
1799
- $radio2 = $('<input>',{
1800
- type: 'radio',
1801
- name: thisPref,
1802
- id: id2,
1803
- value: 'text'
1804
- });
1805
- if (this.prefDescFormat === 'text') {
1806
- $radio2.prop('checked',true);
1807
- };
1808
- $div2.append($radio2,$label2);
1809
- }
1810
- else if (form === 'captions') {
1761
+ if (form === 'captions') {
1811
1762
  $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>');
1812
1763
  $thisField = $('<select>',{
1813
1764
  name: thisPref,
@@ -1899,25 +1850,11 @@
1899
1850
  }
1900
1851
  $thisDiv.append($thisField,$thisLabel);
1901
1852
  }
1902
- if (form === 'descriptions') {
1903
- if (thisPref === 'prefDescFormat') {
1904
- $descFieldset1.append($div1,$div2);
1905
- }
1906
- else {
1907
- $descFieldset2.append($thisDiv);
1908
- }
1909
- }
1910
- else {
1911
- $fieldset.append($thisDiv);
1912
- }
1853
+ $fieldset.append($thisDiv);
1913
1854
  }
1914
1855
  }
1915
- if (form === 'descriptions') {
1916
- $prefsDiv.append($descFieldset1,$descFieldset2);
1917
- }
1918
- else {
1919
- $prefsDiv.append($fieldset);
1920
- }
1856
+ $prefsDiv.append($fieldset);
1857
+
1921
1858
  if (form === 'captions') {
1922
1859
  // add a sample closed caption div to prefs dialog
1923
1860
  if (this.mediaType === 'video') {
@@ -1946,6 +1883,14 @@
1946
1883
  kbLabels.push(this.tt.restart);
1947
1884
  keys.push('s');
1948
1885
  }
1886
+ else if (this.controls[i] === 'previous') {
1887
+ kbLabels.push(this.tt.prevTrack);
1888
+ keys.push('b'); // b = back
1889
+ }
1890
+ else if (this.controls[i] === 'next') {
1891
+ kbLabels.push(this.tt.nextTrack);
1892
+ keys.push('n');
1893
+ }
1949
1894
  else if (this.controls[i] === 'rewind') {
1950
1895
  kbLabels.push(this.tt.rewind);
1951
1896
  keys.push('r');
@@ -2096,15 +2041,7 @@
2096
2041
  available = this.getAvailablePreferences();
2097
2042
  for (i=0; i<available.length; i++) {
2098
2043
  prefName = available[i]['name'];
2099
- if (prefName === 'prefDescFormat') {
2100
- if (this[prefName] === 'text') {
2101
- $('input[value="text"]').prop('checked',true);
2102
- }
2103
- else {
2104
- $('input[value="video"]').prop('checked',true);
2105
- }
2106
- }
2107
- else if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) {
2044
+ if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) {
2108
2045
  // this is a caption-related select box
2109
2046
  $('select[name="' + prefName + '"]').val(cookie.preferences[prefName]);
2110
2047
  }
@@ -2137,8 +2074,10 @@
2137
2074
  if (available[i]['label']) {
2138
2075
  var prefName = available[i]['name'];
2139
2076
  if (prefName == 'prefDescFormat') {
2140
- this.prefDescFormat = $('input[name="' + prefName + '"]:checked').val();
2141
- if (this.prefDescFormat !== cookie.preferences['prefDescFormat']) { // user changed setting
2077
+ // As of v4.0.10, prefDescFormat is no longer a choice
2078
+ // this.prefDescFormat = $('input[name="' + prefName + '"]:checked').val();
2079
+ this.prefDescFormat = 'video';
2080
+ if (this.prefDescFormat !== cookie.preferences['prefDescFormat']) { // user's preference has changed
2142
2081
  cookie.preferences['prefDescFormat'] = this.prefDescFormat;
2143
2082
  numChanges++;
2144
2083
  }
@@ -3187,10 +3126,14 @@
3187
3126
  // create a div for exposing description
3188
3127
  // description will be exposed via role="alert" & announced by screen readers
3189
3128
  this.$descDiv = $('<div>',{
3190
- 'class': 'able-descriptions',
3191
- 'aria-live': 'assertive',
3192
- 'aria-atomic': 'true'
3129
+ 'class': 'able-descriptions'
3193
3130
  });
3131
+ if (this.exposeTextDescriptions) {
3132
+ this.$descDiv.attr({
3133
+ 'aria-live': 'assertive',
3134
+ 'aria-atomic': 'true'
3135
+ });
3136
+ }
3194
3137
  // Start off with description hidden.
3195
3138
  // It will be exposed conditionally within description.js > initDescription()
3196
3139
  this.$descDiv.hide();
@@ -3843,6 +3786,11 @@
3843
3786
  'br': []
3844
3787
  }
3845
3788
 
3789
+ if (this.hasPlaylist) {
3790
+ controlLayout['ur'].push('previous');
3791
+ controlLayout['ur'].push('next');
3792
+ }
3793
+
3846
3794
  // test for browser support for volume before displaying volume button
3847
3795
  if (this.browserSupportsVolume()) {
3848
3796
  // volume buttons are: 'mute','volume-soft','volume-medium','volume-loud'
@@ -3874,7 +3822,7 @@
3874
3822
  bll.push('descriptions'); //audio description
3875
3823
  }
3876
3824
  }
3877
- if (this.transcriptType === 'popup') {
3825
+ if (this.transcriptType === 'popup' && !(this.hideTranscriptButton)) {
3878
3826
  bll.push('transcript');
3879
3827
  }
3880
3828
 
@@ -3955,7 +3903,7 @@
3955
3903
  $controllerSpan.append($sliderDiv);
3956
3904
  if (typeof this.duration === 'undefined' || this.duration === 0) {
3957
3905
  // set arbitrary starting duration, and change it when duration is known
3958
- this.duration = 100;
3906
+ this.duration = 60;
3959
3907
  // also set elapsed to 0
3960
3908
  this.elapsed = 0;
3961
3909
  }
@@ -4720,6 +4668,12 @@
4720
4668
  else if (control === 'restart') {
4721
4669
  return this.tt.restart;
4722
4670
  }
4671
+ else if (control === 'previous') {
4672
+ return this.tt.prevTrack;
4673
+ }
4674
+ else if (control === 'next') {
4675
+ return this.tt.nextTrack;
4676
+ }
4723
4677
  else if (control === 'rewind') {
4724
4678
  return this.tt.rewind;
4725
4679
  }
@@ -6505,7 +6459,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
6505
6459
 
6506
6460
  AblePlayer.prototype.refreshVolumeButton = function(volume) {
6507
6461
 
6508
- var volumeName, volumePct, volumeLabel, volumeIconClass, volumeImg;
6462
+ var volumeName, volumePct, volumeLabel, volumeIconClass, volumeImg, newSvgData;
6509
6463
 
6510
6464
  volumeName = this.getVolumeName(volume);
6511
6465
  volumePct = (volume/10) * 100;
@@ -6516,10 +6470,18 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
6516
6470
  this.$volumeButton.find('span').first().removeClass().addClass(volumeIconClass);
6517
6471
  this.$volumeButton.find('span.able-clipped').text(volumeLabel);
6518
6472
  }
6519
- else {
6473
+ else if (this.iconType === 'image') {
6520
6474
  volumeImg = this.imgPath + 'volume-' + volumeName + '.png';
6521
6475
  this.$volumeButton.find('img').attr('src',volumeImg);
6522
6476
  }
6477
+ else if (this.iconType === 'svg') {
6478
+ if (volumeName !== 'mute') {
6479
+ volumeName = 'volume-' + volumeName;
6480
+ }
6481
+ newSvgData = this.getSvgData(volumeName);
6482
+ this.$volumeButton.find('svg').attr('viewBox',newSvgData[0]);
6483
+ this.$volumeButton.find('path').attr('d',newSvgData[1]);
6484
+ }
6523
6485
  };
6524
6486
 
6525
6487
  AblePlayer.prototype.moveVolumeHead = function(y) {
@@ -6617,6 +6579,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
6617
6579
  };
6618
6580
 
6619
6581
  AblePlayer.prototype.handleMute = function() {
6582
+
6620
6583
  if (this.isMuted()) {
6621
6584
  this.setMute(false);
6622
6585
  }
@@ -7095,17 +7058,20 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7095
7058
 
7096
7059
  // The following variables are applicable to delivery of description:
7097
7060
  // prefDesc == 1 if user wants description (i.e., Description button is on); else 0
7098
- // prefDescFormat == either 'video' or 'text'
7061
+ // prefDescFormat == either 'video' or 'text' (as of v4.0.10, prefDescFormat is always 'video')
7099
7062
  // prefDescPause == 1 to pause video when description starts; else 0
7100
7063
  // prefVisibleDesc == 1 to visibly show text-based description area; else 0
7101
7064
  // hasOpenDesc == true if a described version of video is available via data-desc-src attribute
7102
7065
  // hasClosedDesc == true if a description text track is available
7103
7066
  // this.useDescFormat == either 'video' or 'text'; the format ultimately delivered
7104
7067
  // descOn == true if description of either type is on
7068
+ // exposeTextDescriptions == true if text description is to be announced audibly; otherwise false
7105
7069
 
7106
7070
  var thisObj = this;
7107
-
7108
- if (!this.refreshingDesc) {
7071
+ if (this.refreshingDesc) {
7072
+ this.prevDescFormat = this.useDescFormat;
7073
+ }
7074
+ else {
7109
7075
  // this is the initial build
7110
7076
  // first, check to see if there's an open-described version of this video
7111
7077
  // checks only the first source since if a described version is provided,
@@ -7125,12 +7091,15 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7125
7091
  }
7126
7092
  }
7127
7093
  }
7094
+
7128
7095
  // update this.useDescFormat based on media availability & user preferences
7129
7096
  if (this.prefDesc) {
7130
7097
  if (this.hasOpenDesc && this.hasClosedDesc) {
7131
- // both formats are available. Use whichever one user prefers
7098
+ // both formats are available. Always use 'video'
7132
7099
  this.useDescFormat = this.prefDescFormat;
7133
7100
  this.descOn = true;
7101
+ // Do not pause during descriptions when playing described video
7102
+ this.prefDescPause = false;
7134
7103
  }
7135
7104
  else if (this.hasOpenDesc) {
7136
7105
  this.useDescFormat = 'video';
@@ -7142,14 +7111,8 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7142
7111
  }
7143
7112
  }
7144
7113
  else { // description button is off
7145
- if (this.refreshingDesc) { // user just now toggled it off
7146
- this.prevDescFormat = this.useDescFormat;
7147
- this.useDescFormat = false;
7148
- this.descOn = false;
7149
- }
7150
- else { // desc has always been off
7151
- this.useDescFormat = false;
7152
- }
7114
+ this.useDescFormat = false;
7115
+ this.descOn = false;
7153
7116
  }
7154
7117
 
7155
7118
  if (this.useDescFormat === 'text') {
@@ -7181,22 +7144,21 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7181
7144
  }
7182
7145
  }
7183
7146
  if (this.descOn) {
7184
-
7185
7147
  if (this.useDescFormat === 'video') {
7186
7148
  if (!this.usingAudioDescription()) {
7187
7149
  // switched from non-described to described version
7188
7150
  this.swapDescription();
7189
7151
  }
7190
- // hide description div
7191
- this.$descDiv.hide();
7192
- this.$descDiv.removeClass('able-clipped');
7193
7152
  }
7194
- else if (this.useDescFormat === 'text') {
7195
- this.$descDiv.show();
7196
- if (this.prefVisibleDesc) { // make it visible to everyone
7153
+ if (this.hasClosedDesc) {
7154
+ if (this.prefVisibleDesc) {
7155
+ // make description text visible
7156
+ // New in v4.0.10: Do this regardless of useDescFormat
7157
+ this.$descDiv.show();
7197
7158
  this.$descDiv.removeClass('able-clipped');
7198
7159
  }
7199
- else { // keep it visible to screen readers, but hide from everyone else
7160
+ else {
7161
+ // keep it visible to screen readers, but hide it visibly
7200
7162
  this.$descDiv.addClass('able-clipped');
7201
7163
  }
7202
7164
  if (!this.swappingSrc) {
@@ -7205,7 +7167,6 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7205
7167
  }
7206
7168
  }
7207
7169
  else { // description is off.
7208
-
7209
7170
  if (this.prevDescFormat === 'video') { // user was previously using description via video
7210
7171
  if (this.usingAudioDescription()) {
7211
7172
  this.swapDescription();
@@ -7235,7 +7196,6 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7235
7196
  };
7236
7197
 
7237
7198
  AblePlayer.prototype.swapDescription = function() {
7238
-
7239
7199
  // swap described and non-described source media, depending on which is playing
7240
7200
  // this function is only called in two circumstances:
7241
7201
  // 1. Swapping to described version when initializing player (based on user prefs & availability)
@@ -7358,7 +7318,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7358
7318
  // there's a lot of redundancy between this function and showCaptions
7359
7319
  // Trying to combine them ended up in a mess though. Keeping as is for now.
7360
7320
 
7361
- if (this.swappingSrc) {
7321
+ if (this.swappingSrc || !this.descOn) {
7362
7322
  return;
7363
7323
  }
7364
7324
 
@@ -7398,7 +7358,10 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7398
7358
  // temporarily remove aria-live from $status in order to prevent description from being interrupted
7399
7359
  this.$status.removeAttr('aria-live');
7400
7360
  descText = flattenComponentForDescription(cues[thisDescription].components);
7401
- if (typeof this.synth !== 'undefined' && typeof this.descVoiceIndex !== 'undefined') {
7361
+ if (
7362
+ this.exposeTextDescriptions &&
7363
+ typeof this.synth !== 'undefined' &&
7364
+ typeof this.descVoiceIndex !== 'undefined') {
7402
7365
  // browser supports speech synthesis and a voice has been selected in initDescription()
7403
7366
  // use the web speech API
7404
7367
  msg = new SpeechSynthesisUtterance();
@@ -7414,7 +7377,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7414
7377
  if (thisObj.pausedForDescription) {
7415
7378
  thisObj.playMedia();
7416
7379
  }
7417
- };
7380
+ };
7418
7381
  this.synth.speak(msg);
7419
7382
  if (this.prefVisibleDesc) {
7420
7383
  // write description to the screen for sighted users
@@ -7427,7 +7390,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
7427
7390
  // load the new description into the container div for screen readers to read
7428
7391
  this.$descDiv.html(descText);
7429
7392
  }
7430
- if (this.prefDescPause) {
7393
+ if (this.prefDescPause && this.exposeTextDescriptions) {
7431
7394
  this.pauseMedia();
7432
7395
  this.pausedForDescription = true;
7433
7396
  }
@@ -8163,30 +8126,27 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
8163
8126
  if (typeof this.$elapsedTimeContainer !== 'undefined') {
8164
8127
  this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed));
8165
8128
  }
8166
-
8167
8129
  // Update seekbar width.
8168
8130
  // To do this, we need to calculate the width of all buttons surrounding it.
8169
8131
  if (this.seekBar) {
8170
8132
  widthUsed = 0;
8171
- seekbarSpacer = 40; // adjust for discrepancies in browsers' calculated button widths
8172
-
8173
8133
  leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls');
8174
8134
  rightControls = leftControls.next('div.able-right-controls');
8175
8135
  leftControls.children().each(function () {
8176
8136
  if ($(this).prop('tagName')=='BUTTON') {
8177
- widthUsed += $(this).width();
8137
+ widthUsed += $(this).outerWidth(true); // true = include margin
8178
8138
  }
8179
8139
  });
8180
8140
  rightControls.children().each(function () {
8181
8141
  if ($(this).prop('tagName')=='BUTTON') {
8182
- widthUsed += $(this).width();
8142
+ widthUsed += $(this).outerWidth(true);
8183
8143
  }
8184
8144
  });
8185
8145
  if (this.fullscreen) {
8186
- seekbarWidth = $(window).width() - widthUsed - seekbarSpacer;
8146
+ seekbarWidth = $(window).width() - widthUsed;
8187
8147
  }
8188
8148
  else {
8189
- seekbarWidth = this.$ableWrapper.width() - widthUsed - seekbarSpacer;
8149
+ seekbarWidth = this.$ableWrapper.width() - widthUsed;
8190
8150
  }
8191
8151
  // Sometimes some minor fluctuations based on browser weirdness, so set a threshold.
8192
8152
  if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) {
@@ -8437,7 +8397,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
8437
8397
 
8438
8398
  // Don't change play/pause button display while using the seek bar (or if YouTube stopped)
8439
8399
  if (!thisObj.seekBar.tracking && !thisObj.stoppingYouTube) {
8440
- if (currentState === 'paused' || currentState === 'stopped') {
8400
+ if (currentState === 'paused' || currentState === 'stopped' || currentState === 'ended') {
8441
8401
  thisObj.$playpauseButton.attr('aria-label',thisObj.tt.play);
8442
8402
 
8443
8403
  if (thisObj.iconType === 'font') {
@@ -8538,6 +8498,34 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
8538
8498
  this.seekTo(0);
8539
8499
  };
8540
8500
 
8501
+ AblePlayer.prototype.handlePrevTrack = function() {
8502
+
8503
+ if (this.playlistIndex === 0) {
8504
+ // currently on the first track
8505
+ // wrap to bottom and play the last track
8506
+ this.playlistIndex = this.$playlist.length - 1;
8507
+ }
8508
+ else {
8509
+ this.playlistIndex--;
8510
+ }
8511
+ this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
8512
+ this.cuePlaylistItem(this.playlistIndex);
8513
+ };
8514
+
8515
+ AblePlayer.prototype.handleNextTrack = function() {
8516
+
8517
+ if (this.playlistIndex === this.$playlist.length - 1) {
8518
+ // currently on the last track
8519
+ // wrap to top and play the forst track
8520
+ this.playlistIndex = 0;
8521
+ }
8522
+ else {
8523
+ this.playlistIndex++;
8524
+ }
8525
+ this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
8526
+ this.cuePlaylistItem(this.playlistIndex);
8527
+ };
8528
+
8541
8529
  AblePlayer.prototype.handleRewind = function() {
8542
8530
 
8543
8531
  var targetTime;
@@ -8763,6 +8751,9 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
8763
8751
  this.descOn = !this.descOn;
8764
8752
  this.prefDesc = + this.descOn; // convert boolean to integer
8765
8753
  this.updateCookie('prefDesc');
8754
+ if (!this.$descDiv.is(':hidden')) {
8755
+ this.$descDiv.hide();
8756
+ }
8766
8757
  this.refreshingDesc = true;
8767
8758
  this.initDescription();
8768
8759
  this.refreshControls('descriptions');
@@ -10279,6 +10270,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
10279
10270
  if (!this.transcriptType) {
10280
10271
  // previously set transcriptType to null since there are no <track> elements
10281
10272
  // check again to see if captions have been collected from other sources (e.g., YouTube)
10273
+
10282
10274
  if (this.captions.length && (!(this.usingYouTubeCaptions || this.usingVimeoCaptions))) {
10283
10275
  // captions are possible! Use the default type (popup)
10284
10276
  // if other types ('external' and 'manual') were desired, transcriptType would not be null here
@@ -11136,19 +11128,14 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11136
11128
  (function ($) {
11137
11129
  // Media events
11138
11130
  AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) {
11139
-
11140
11131
  // duration and elapsed are passed from callback functions of Vimeo API events
11141
11132
  // duration is expressed as sss.xxx
11142
11133
  // elapsed is expressed as sss.xxx
11143
11134
  var thisObj = this;
11144
11135
 
11145
11136
  this.getMediaTimes(duration,elapsed).then(function(mediaTimes) {
11146
- if (typeof duration === 'undefined') {
11147
- thisObj.duration = mediaTimes['duration'];
11148
- }
11149
- if (typeof elapsed === 'undefined') {
11150
- thisObj.elapsed = mediaTimes['elapsed'];
11151
- }
11137
+ thisObj.duration = mediaTimes['duration'];
11138
+ thisObj.elapsed = mediaTimes['elapsed'];
11152
11139
  if (thisObj.swappingSrc && (typeof thisObj.swapTime !== 'undefined')) {
11153
11140
  if (thisObj.swapTime === thisObj.elapsed) {
11154
11141
  // described version been swapped and media has scrubbed to time of previous version
@@ -11190,7 +11177,6 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11190
11177
  };
11191
11178
 
11192
11179
  AblePlayer.prototype.onMediaComplete = function () {
11193
-
11194
11180
  // if there's a playlist, advance to next item and start playing
11195
11181
  if (this.hasPlaylist && !this.cueingPlaylistItem) {
11196
11182
  if (this.playlistIndex === (this.$playlist.length - 1)) {
@@ -11200,6 +11186,10 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11200
11186
  this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
11201
11187
  this.cuePlaylistItem(0);
11202
11188
  }
11189
+ else {
11190
+ this.playing = false;
11191
+ this.paused = true;
11192
+ }
11203
11193
  }
11204
11194
  else {
11205
11195
  // this is not the last track. Play the next one.
@@ -11231,9 +11221,9 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11231
11221
  this.playMedia();
11232
11222
  }
11233
11223
  this.swappingSrc = false; // swapping is finished
11234
- this.refreshControls('init');
11235
11224
  }
11236
11225
  }
11226
+ this.refreshControls('init');
11237
11227
  };
11238
11228
 
11239
11229
  // End Media events
@@ -11325,12 +11315,21 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11325
11315
  // TODO: This is super-fragile since we need to know the length of the class name to split off; update this to other way of dispatching?
11326
11316
  var whichButton = $(el).attr('class').split(' ')[0].substr(20);
11327
11317
  if (whichButton === 'play') {
11318
+ this.clickedPlay = true;
11328
11319
  this.handlePlay();
11329
11320
  }
11330
11321
  else if (whichButton === 'restart') {
11331
11322
  this.seekTrigger = 'restart';
11332
11323
  this.handleRestart();
11333
11324
  }
11325
+ else if (whichButton === 'previous') {
11326
+ this.seekTrigger = 'previous';
11327
+ this.handlePrevTrack();
11328
+ }
11329
+ else if (whichButton === 'next') {
11330
+ this.seekTrigger = 'next';
11331
+ this.handleNextTrack();
11332
+ }
11334
11333
  else if (whichButton === 'rewind') {
11335
11334
  this.seekTrigger = 'rewind';
11336
11335
  this.handleRewind();
@@ -11427,6 +11426,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11427
11426
  e.target.tagName === 'TEXTAREA' ||
11428
11427
  e.target.tagName === 'SELECT'
11429
11428
  )){
11429
+
11430
11430
  if (which === 27) { // escape
11431
11431
  this.closePopups();
11432
11432
  }
@@ -11482,6 +11482,16 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11482
11482
  this.handleRewind();
11483
11483
  }
11484
11484
  }
11485
+ else if (which === 98) { // b = back (previous track)
11486
+ if (this.usingModifierKeys(e)) {
11487
+ this.handlePrevTrack();
11488
+ }
11489
+ }
11490
+ else if (which === 110) { // n = next track
11491
+ if (this.usingModifierKeys(e)) {
11492
+ this.handleNextTrack();
11493
+ }
11494
+ }
11485
11495
  else if (which === 101) { // e = preferences
11486
11496
  if (this.usingModifierKeys(e)) {
11487
11497
  this.handlePrefsClick();
@@ -11514,6 +11524,8 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
11514
11524
  // do something
11515
11525
  })
11516
11526
  .on('loadedmetadata',function() {
11527
+ // should be able to get duration now
11528
+ thisObj.duration = thisObj.media.duration;
11517
11529
  thisObj.onMediaNewSourceLoad();
11518
11530
  })
11519
11531
  .on('canplay',function() {
@@ -15669,4 +15681,4 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
15669
15681
  return false;
15670
15682
  };
15671
15683
 
15672
- })(jQuery);
15684
+ })(jQuery);