wai-website-theme 1.2 → 1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/_includes/backtotop.html +1 -1
  3. data/_includes/excol.html +1 -1
  4. data/_includes/feedback-box.html +13 -10
  5. data/_includes/footer.html +20 -6
  6. data/_includes/header.html +73 -35
  7. data/_includes/inpl.html +6 -0
  8. data/_includes/lang.html +7 -0
  9. data/_includes/link.html +92 -0
  10. data/_includes/menuitem.html +5 -3
  11. data/_includes/multilang-list-policy-links.html +1 -1
  12. data/_includes/navlist.html +1 -1
  13. data/_includes/prevnext.html +5 -4
  14. data/_includes/secondarynav.html +34 -6
  15. data/_includes/t.html +33 -0
  16. data/_includes/translation-note-msg.html +45 -0
  17. data/_includes/video-player.html +50 -9
  18. data/_layouts/default.html +32 -9
  19. data/_layouts/home.html +29 -5
  20. data/_layouts/news.html +27 -6
  21. data/_layouts/policy.html +27 -6
  22. data/_layouts/sidenav.html +27 -6
  23. data/_layouts/sidenavsidebar.html +27 -6
  24. data/assets/ableplayer/.gitattributes +0 -0
  25. data/assets/ableplayer/.gitignore +3 -1
  26. data/assets/ableplayer/Gruntfile.js +3 -1
  27. data/assets/ableplayer/LICENSE +0 -0
  28. data/assets/ableplayer/README.md +214 -170
  29. data/assets/ableplayer/_config.yml +1 -0
  30. data/assets/ableplayer/build/ableplayer.dist.js +2637 -744
  31. data/assets/ableplayer/build/ableplayer.js +2637 -744
  32. data/assets/ableplayer/build/ableplayer.min.css +2 -2
  33. data/assets/ableplayer/build/ableplayer.min.js +9 -7
  34. data/assets/ableplayer/button-icons/able-icons.svg +0 -0
  35. data/assets/ableplayer/button-icons/black/rabbit.png +0 -0
  36. data/assets/ableplayer/button-icons/black/turtle.png +0 -0
  37. data/assets/ableplayer/button-icons/white/rabbit.png +0 -0
  38. data/assets/ableplayer/button-icons/white/turtle.png +0 -0
  39. data/assets/ableplayer/images/wingrip.png +0 -0
  40. data/assets/ableplayer/package-lock.json +705 -0
  41. data/assets/ableplayer/package.json +11 -2
  42. data/assets/ableplayer/scripts/JQuery.doWhen.js +0 -0
  43. data/assets/ableplayer/scripts/ableplayer-base.js +129 -29
  44. data/assets/ableplayer/scripts/browser.js +0 -0
  45. data/assets/ableplayer/scripts/buildplayer.js +342 -262
  46. data/assets/ableplayer/scripts/caption.js +19 -0
  47. data/assets/ableplayer/scripts/chapters.js +21 -0
  48. data/assets/ableplayer/scripts/control.js +139 -56
  49. data/assets/ableplayer/scripts/description.js +0 -0
  50. data/assets/ableplayer/scripts/dialog.js +13 -13
  51. data/assets/ableplayer/scripts/dragdrop.js +102 -109
  52. data/assets/ableplayer/scripts/event.js +186 -83
  53. data/assets/ableplayer/scripts/initialize.js +261 -71
  54. data/assets/ableplayer/scripts/langs.js +4 -0
  55. data/assets/ableplayer/scripts/metadata.js +0 -0
  56. data/assets/ableplayer/scripts/misc.js +76 -7
  57. data/assets/ableplayer/scripts/preference.js +2 -2
  58. data/assets/ableplayer/scripts/search.js +10 -7
  59. data/assets/ableplayer/scripts/sign.js +0 -0
  60. data/assets/ableplayer/scripts/slider.js +35 -34
  61. data/assets/ableplayer/scripts/track.js +38 -22
  62. data/assets/ableplayer/scripts/transcript.js +15 -6
  63. data/assets/ableplayer/scripts/translation.js +29 -20
  64. data/assets/ableplayer/scripts/ttml2webvtt.js +87 -0
  65. data/assets/ableplayer/scripts/volume.js +16 -15
  66. data/assets/ableplayer/scripts/vts.js +1093 -0
  67. data/assets/ableplayer/scripts/webvtt.js +0 -0
  68. data/assets/ableplayer/scripts/youtube.js +16 -5
  69. data/assets/ableplayer/styles/ableplayer.css +125 -22
  70. data/assets/ableplayer/thirdparty/js.cookie.js +0 -0
  71. data/assets/ableplayer/thirdparty/modernizr.custom.js +0 -0
  72. data/assets/ableplayer/translations/ca.js +311 -1
  73. data/assets/ableplayer/translations/de.js +1 -1
  74. data/assets/ableplayer/translations/en.js +6 -0
  75. data/assets/ableplayer/translations/es.js +6 -0
  76. data/assets/ableplayer/translations/fr.js +6 -0
  77. data/assets/ableplayer/translations/he.js +311 -0
  78. data/assets/ableplayer/translations/it.js +7 -1
  79. data/assets/ableplayer/translations/ja.js +6 -0
  80. data/assets/ableplayer/translations/nb.js +311 -0
  81. data/assets/ableplayer/translations/nl.js +6 -0
  82. data/assets/ableplayer/translations/zh-tw.js +311 -0
  83. data/assets/css/style.css +1 -1
  84. data/assets/css/style.css.map +1 -1
  85. data/assets/fonts/{anonymouspro-bold.woff → anonymouspro/anonymouspro-bold.woff} +0 -0
  86. data/assets/fonts/{anonymouspro-bold.woff2 → anonymouspro/anonymouspro-bold.woff2} +0 -0
  87. data/assets/fonts/{anonymouspro-bolditalic.woff → anonymouspro/anonymouspro-bolditalic.woff} +0 -0
  88. data/assets/fonts/{anonymouspro-bolditalic.woff2 → anonymouspro/anonymouspro-bolditalic.woff2} +0 -0
  89. data/assets/fonts/{anonymouspro-italic.woff → anonymouspro/anonymouspro-italic.woff} +0 -0
  90. data/assets/fonts/{anonymouspro-italic.woff2 → anonymouspro/anonymouspro-italic.woff2} +0 -0
  91. data/assets/fonts/{anonymouspro-regular.woff → anonymouspro/anonymouspro-regular.woff} +0 -0
  92. data/assets/fonts/{anonymouspro-regular.woff2 → anonymouspro/anonymouspro-regular.woff2} +0 -0
  93. data/assets/fonts/notonaskh/bold-minimal.woff +0 -0
  94. data/assets/fonts/notonaskh/bold-minimal.woff2 +0 -0
  95. data/assets/fonts/notonaskh/bold.woff +0 -0
  96. data/assets/fonts/notonaskh/bold.woff2 +0 -0
  97. data/assets/fonts/notonaskh/regular-minimal.woff +0 -0
  98. data/assets/fonts/notonaskh/regular-minimal.woff2 +0 -0
  99. data/assets/fonts/notonaskh/regular.woff +0 -0
  100. data/assets/fonts/notonaskh/regular.woff2 +0 -0
  101. data/assets/fonts/{notosans-bold-subset.woff → notosans/notosans-bold-subset.woff} +0 -0
  102. data/assets/fonts/{notosans-bold-subset.woff2 → notosans/notosans-bold-subset.woff2} +0 -0
  103. data/assets/fonts/{notosans-bold.woff → notosans/notosans-bold.woff} +0 -0
  104. data/assets/fonts/{notosans-bold.woff2 → notosans/notosans-bold.woff2} +0 -0
  105. data/assets/fonts/{notosans-bolditalic-subset.woff → notosans/notosans-bolditalic-subset.woff} +0 -0
  106. data/assets/fonts/{notosans-bolditalic-subset.woff2 → notosans/notosans-bolditalic-subset.woff2} +0 -0
  107. data/assets/fonts/{notosans-bolditalic.woff → notosans/notosans-bolditalic.woff} +0 -0
  108. data/assets/fonts/{notosans-bolditalic.woff2 → notosans/notosans-bolditalic.woff2} +0 -0
  109. data/assets/fonts/{notosans-italic-subset.woff → notosans/notosans-italic-subset.woff} +0 -0
  110. data/assets/fonts/{notosans-italic-subset.woff2 → notosans/notosans-italic-subset.woff2} +0 -0
  111. data/assets/fonts/{notosans-italic.woff → notosans/notosans-italic.woff} +0 -0
  112. data/assets/fonts/{notosans-italic.woff2 → notosans/notosans-italic.woff2} +0 -0
  113. data/assets/fonts/{notosans-regular-subset.woff → notosans/notosans-regular-subset.woff} +0 -0
  114. data/assets/fonts/{notosans-regular-subset.woff2 → notosans/notosans-regular-subset.woff2} +0 -0
  115. data/assets/fonts/{notosans-regular.woff → notosans/notosans-regular.woff} +0 -0
  116. data/assets/fonts/{notosans-regular.woff2 → notosans/notosans-regular.woff2} +0 -0
  117. data/assets/fonts/notosansmono/notosansmono-semicondensed.woff +0 -0
  118. data/assets/fonts/notosansmono/notosansmono-semicondensed.woff2 +0 -0
  119. data/assets/fonts/notosansmono/notosansmono-semicondensedbold.woff +0 -0
  120. data/assets/fonts/notosansmono/notosansmono-semicondensedbold.woff2 +0 -0
  121. data/assets/images/icons.svg +24 -0
  122. data/assets/scripts/main.js +10 -3
  123. metadata +66 -33
  124. data/_data/lang.json +0 -730
  125. data/_data/techniques.yml +0 -180
  126. data/_data/wcag.yml +0 -125
  127. data/_includes/.DS_Store +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ableplayer",
3
- "version": "3.0.26",
3
+ "version": "3.2.0",
4
4
  "description": "fully accessible HTML5 media player",
5
5
  "homepage": "http://ableplayer.github.io/ableplayer",
6
6
  "bugs": "https://github.com/ableplayer/ableplayer/issues",
@@ -9,6 +9,15 @@
9
9
  "type": "git",
10
10
  "url": "https://github.com/ableplayer/ableplayer.git"
11
11
  },
12
+ "main": "build/ableplayer.min.js",
13
+ "scripts": {
14
+ "build": "grunt"
15
+ },
16
+ "dependencies": {
17
+ "jquery": "3.3.1",
18
+ "js-cookie": "2.2.0",
19
+ "xml-js": "1.6.2"
20
+ },
12
21
  "devDependencies": {
13
22
  "grunt": ">=0.4.1",
14
23
  "grunt-contrib-clean": ">=0.6.0",
@@ -19,4 +28,4 @@
19
28
  "grunt-contrib-uglify": ">=0.2.2",
20
29
  "grunt-remove-logging": ">=0.2.0"
21
30
  }
22
- }
31
+ }
@@ -67,7 +67,7 @@
67
67
 
68
68
  this.media = media;
69
69
  if ($(media).length === 0) {
70
- this.provideFallback('ERROR: No media specified.');
70
+ this.provideFallback();
71
71
  return;
72
72
  }
73
73
 
@@ -79,24 +79,32 @@
79
79
 
80
80
  // The following variables CAN be overridden with HTML attributes
81
81
 
82
- // autoplay
83
- if ($(media).attr('autoplay') !== undefined && $(media).attr('autoplay') !== "false") {
82
+ // autoplay (Boolean; if present always resolves to true, regardless of value)
83
+ if ($(media).attr('autoplay') !== undefined) {
84
84
  this.autoplay = true;
85
85
  }
86
86
  else {
87
87
  this.autoplay = false;
88
88
  }
89
89
 
90
- // loop (NOT FULLY SUPPORTED)
91
- if ($(media).attr('loop') !== undefined && $(media).attr('loop') !== "false") {
90
+ // loop (Boolean; if present always resolves to true, regardless of value)
91
+ if ($(media).attr('loop') !== undefined) {
92
92
  this.loop = true;
93
93
  }
94
94
  else {
95
95
  this.loop = false;
96
96
  }
97
97
 
98
+ // playsinline (Boolean; if present always resolves to true, regardless of value)
99
+ if ($(media).attr('playsinline') !== undefined) {
100
+ this.playsInline = '1'; // this value gets passed to YT.Player contructor in youtube.js
101
+ }
102
+ else {
103
+ this.playsInline = '0';
104
+ }
105
+
98
106
  // start-time
99
- if ($(media).data('start-time') !== undefined && $(media).data('start-time') !== "") {
107
+ if ($(media).data('start-time') !== undefined && $.isNumeric($(media).data('start-time'))) {
100
108
  this.startTime = $(media).data('start-time');
101
109
  }
102
110
  else {
@@ -104,7 +112,7 @@
104
112
  }
105
113
 
106
114
  // debug
107
- if ($(media).data('debug') !== undefined && $(media).data('debug') !== "false") {
115
+ if ($(media).data('debug') !== undefined && $(media).data('debug') !== false) {
108
116
  this.debug = true;
109
117
  }
110
118
  else {
@@ -131,7 +139,6 @@
131
139
  }
132
140
  this.volume = this.defaultVolume;
133
141
 
134
-
135
142
  // Optional Buttons
136
143
  // Buttons are added to the player controller if relevant media is present
137
144
  // However, in some applications it might be undesirable to show buttons
@@ -151,6 +158,19 @@
151
158
  this.useDescriptionsButton = true;
152
159
  }
153
160
 
161
+ // Headings
162
+ // By default, an off-screen heading is automatically added to the top of the media player
163
+ // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel()
164
+ // Authors can override this behavior by manually assigning a heading level using data-heading-level
165
+ // Accepted values are 1-6, or 0 which indicates "no heading"
166
+ // (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this)
167
+ if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") {
168
+ var headingLevel = $(media).data('heading-level');
169
+ if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0
170
+ this.playerHeadingLevel = headingLevel;
171
+ }
172
+ }
173
+
154
174
  // Transcripts
155
175
  // There are three types of interactive transcripts.
156
176
  // In descending of order of precedence (in case there are conflicting tags), they are:
@@ -169,7 +189,7 @@
169
189
  this.transcriptType = null;
170
190
  }
171
191
  }
172
- else if (media.find('track[kind="captions"], track[kind="subtitles"]').length > 0) {
192
+ else if ($(media).find('track[kind="captions"], track[kind="subtitles"]').length > 0) {
173
193
  // required tracks are present. COULD automatically generate a transcript
174
194
  if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") {
175
195
  this.transcriptDivLocation = $(media).data('transcript-div');
@@ -184,16 +204,17 @@
184
204
  this.transcriptType = 'popup';
185
205
  }
186
206
  }
207
+
187
208
  // In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript
188
209
  // If false (default), line breaks are are removed from transcripts in order to provide a more seamless reading experience
189
210
  // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem
190
-
191
- if ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== "false") {
211
+ if ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) {
192
212
  this.lyricsMode = true;
193
213
  }
194
214
  else {
195
215
  this.lyricsMode = false;
196
216
  }
217
+
197
218
  // Transcript Title
198
219
  if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") {
199
220
  this.transcriptTitle = $(media).data('transcript-title');
@@ -243,13 +264,13 @@
243
264
  }
244
265
 
245
266
  // Slower/Faster buttons
246
- // valid values of data-speed-icons are 'arrows' (default) and 'animals'
247
- // use 'animals' to use turtle and rabbit
248
- if ($(media).data('speed-icons') === 'animals') {
249
- this.speedIcons = 'animals';
267
+ // valid values of data-speed-icons are 'animals' (default) and 'arrows'
268
+ // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows
269
+ if ($(media).data('speed-icons') === 'arrows') {
270
+ this.speedIcons = 'arrows';
250
271
  }
251
272
  else {
252
- this.speedIcons = 'arrows';
273
+ this.speedIcons = 'animals';
253
274
  }
254
275
 
255
276
  // Seekbar
@@ -270,10 +291,18 @@
270
291
  this.youTubeDescId = $(media).data('youtube-desc-id');
271
292
  }
272
293
 
294
+ if ($(media).data('youtube-nocookie') !== undefined && $(media).data('youtube-nocookie')) {
295
+ this.youTubeNoCookie = true;
296
+ }
297
+ else {
298
+ this.youTubeNoCookie = false;
299
+ }
300
+
273
301
  // Icon type
274
- // By default, AblePlayer uses scalable icomoon fonts for the player controls
275
- // and falls back to images if the user has a custom style sheet that overrides font-family
276
- // use data-icon-type to force controls to use either 'font', 'images' or 'svg'
302
+ // By default, AblePlayer 3.0.33 and higher uses SVG icons for the player controls
303
+ // Fallback for browsers that don't support SVG is scalable icomoon fonts
304
+ // Ultimate fallback is images, if the user has a custom style sheet that overrides font-family
305
+ // Use data-icon-type to force controls to use either 'svg', 'font', or 'images'
277
306
  this.iconType = 'font';
278
307
  this.forceIconType = false;
279
308
  if ($(media).data('icon-type') !== undefined && $(media).data('icon-type') !== "") {
@@ -308,13 +337,23 @@
308
337
  // Now Playing
309
338
  // Shows "Now Playing:" plus the title of the current track above player
310
339
  // Only used if there is a playlist
311
- if ($(media).data('show-now-playing') !== undefined && $(media).data('show-now-playing') === "false") {
340
+ if ($(media).data('show-now-playing') !== undefined && $(media).data('show-now-playing') === false) {
312
341
  this.showNowPlaying = false;
313
342
  }
314
343
  else {
315
344
  this.showNowPlaying = true;
316
345
  }
317
346
 
347
+ // TTML support (experimental); enabled for testing with data-use-ttml (Boolean)
348
+ if ($(media).data('use-ttml') !== undefined) {
349
+ this.useTtml = true;
350
+ // The following may result in a console error.
351
+ this.convert = require('xml-js');
352
+ }
353
+ else {
354
+ this.useTtml = false;
355
+ }
356
+
318
357
  // Fallback Player
319
358
  // The only supported fallback is JW Player, licensed separately
320
359
  // JW Player files must be included in folder specified in this.fallbackPath
@@ -323,6 +362,7 @@
323
362
 
324
363
  this.fallback = null;
325
364
  this.fallbackPath = null;
365
+ this.fallbackJwKey = null;
326
366
  this.testFallback = false;
327
367
 
328
368
  if ($(media).data('fallback') !== undefined && $(media).data('fallback') !== "") {
@@ -334,14 +374,26 @@
334
374
 
335
375
  if (this.fallback === 'jw') {
336
376
 
337
- if ($(media).data('fallback-path') !== undefined && $(media).data('fallback-path') !== "false") {
377
+ if ($(media).data('fallback-path') !== undefined && $(media).data('fallback-path') !== false) {
338
378
  this.fallbackPath = $(media).data('fallback-path');
339
- }
340
- else {
379
+
380
+ var path = $(media).data('fallback-path');
381
+
382
+ // remove js file is specified.
383
+ var playerJs = 'jwplayer.js';
384
+ if (path.endsWith(playerJs)) {
385
+ path = path.slice(0, path.length - playerJs.length);
386
+ }
387
+ this.fallbackPath = path;
388
+ } else {
341
389
  this.fallbackPath = this.rootPath + 'thirdparty/';
342
390
  }
343
391
 
344
- if ($(media).data('test-fallback') !== undefined && $(media).data('test-fallback') !== "false") {
392
+ if ($(media).data('fallback-jwkey') !== undefined) {
393
+ this.fallbackJwKey = $(media).data('fallback-jwkey');
394
+ }
395
+
396
+ if ($(media).data('test-fallback') !== undefined && $(media).data('test-fallback') !== false) {
345
397
  this.testFallback = true;
346
398
  }
347
399
  }
@@ -354,12 +406,12 @@
354
406
  this.lang = lang;
355
407
  }
356
408
  }
357
- // Player language is determined as follows:
409
+ // Player language is determined as follows (in translation.js > getTranslationText() ):
358
410
  // 1. Lang attributes on <html> or <body>, if a matching translation file is available
359
411
  // 2. The value of this.lang, if a matching translation file is available
360
412
  // 3. English
361
413
  // To override this formula and force #2 to take precedence over #1, set data-force-lang="true"
362
- if ($(media).data('force-lang') !== undefined && $(media).data('force-lang') !== "false") {
414
+ if ($(media).data('force-lang') !== undefined && $(media).data('force-lang') !== false) {
363
415
  this.forceLang = true;
364
416
  }
365
417
  else {
@@ -382,6 +434,29 @@
382
434
  this.searchString = $(media).data('search');
383
435
  this.searchDiv = $(media).data('search-div');
384
436
  }
437
+
438
+ // Search Language
439
+ if ($(media).data('search-lang') !== undefined && $(media).data('search-lang') !== "") {
440
+ this.searchLang = $(media).data('search-lang');
441
+ }
442
+ else {
443
+ this.searchLang = null; // will change to final value of this.lang in translation.js > getTranslationText()
444
+ }
445
+
446
+ // conducting a search currently requires an external div in which to write the results
447
+ if ($(media).data('search-div') !== undefined && $(media).data('search-div') !== "") {
448
+ this.searchString = $(media).data('search');
449
+ this.searchDiv = $(media).data('search-div');
450
+ }
451
+ }
452
+
453
+ // Hide controls when video starts playing
454
+ // They will reappear again when user presses a key or moves the mouse
455
+ if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) {
456
+ this.hideControls = true;
457
+ }
458
+ else {
459
+ this.hideControls = false;
385
460
  }
386
461
 
387
462
  // Define built-in variables that CANNOT be overridden with HTML attributes
@@ -410,7 +485,7 @@
410
485
  }
411
486
  else {
412
487
  // can't continue loading player with no text
413
- thisObj.provideFallback('ERROR: Failed to load translation table');
488
+ thisObj.provideFallback();
414
489
  }
415
490
  }
416
491
  );
@@ -420,12 +495,11 @@
420
495
  AblePlayer.nextIndex = 0;
421
496
 
422
497
  AblePlayer.prototype.setup = function() {
423
-
424
498
  var thisObj = this;
425
499
  this.reinitialize().then(function () {
426
500
  if (!thisObj.player) {
427
501
  // No player for this media, show last-line fallback.
428
- thisObj.provideFallback('Unable to play media');
502
+ thisObj.provideFallback();
429
503
  }
430
504
  else {
431
505
  thisObj.setupInstance().then(function () {
@@ -435,6 +509,32 @@
435
509
  });
436
510
  };
437
511
 
512
+ AblePlayer.getActiveDOMElement = function () {
513
+ var activeElement = document.activeElement;
514
+
515
+ // For shadow DOMs we need to keep digging down through the DOMs
516
+ while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) {
517
+ activeElement = activeElement.shadowRoot.activeElement;
518
+ }
519
+
520
+ return activeElement;
521
+ };
522
+
523
+ AblePlayer.localGetElementById = function(element, id) {
524
+ if (element.getRootNode)
525
+ {
526
+ // Use getRootNode() and querySelector() where supported (for shadow DOM support)
527
+ return $(element.getRootNode().querySelector('#' + id));
528
+ }
529
+ else
530
+ {
531
+ // If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support)
532
+ return $(document.getElementById(id));
533
+ }
534
+ };
535
+
536
+
537
+
438
538
  AblePlayer.youtubeIframeAPIReady = false;
439
539
  AblePlayer.loadingYoutubeIframeAPI = false;
440
540
  })(jQuery);
File without changes
@@ -25,18 +25,22 @@
25
25
  this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent();
26
26
  this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent();
27
27
  this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent();
28
- if (this.player !== 'youtube') {
28
+
29
+ // NOTE: Excluding the following from youtube was resulting in a player
30
+ // that exceeds the width of the YouTube video
31
+ // Unclear why it was originally excluded; commented out in 3.1.20
32
+ // if (this.player !== 'youtube') {
29
33
  this.$ableWrapper.css({
30
34
  'max-width': this.playerMaxWidth + 'px'
31
35
  });
32
- }
36
+ // } // end if not youtube
33
37
 
34
38
  this.injectOffscreenHeading();
35
39
 
36
40
  // youtube adds its own big play button
37
41
  // if (this.mediaType === 'video' && this.player !== 'youtube') {
38
42
  if (this.mediaType === 'video') {
39
- if (this.iconType == 'font' && this.player !== 'youtube') {
43
+ if (this.iconType != 'image' && this.player !== 'youtube') {
40
44
  this.injectBigPlayButton();
41
45
  }
42
46
 
@@ -66,20 +70,27 @@
66
70
  };
67
71
 
68
72
  AblePlayer.prototype.injectOffscreenHeading = function () {
69
- // Add offscreen heading to the media container.
70
- // The heading injected in $ableDiv is one level deeper than the closest parent heading
73
+ // Inject an offscreen heading to the media container.
74
+ // If heading hasn't already been manually defined via data-heading-level,
75
+ // automatically assign a level that is one level deeper than the closest parent heading
71
76
  // as determined by getNextHeadingLevel()
72
77
  var headingType;
73
- this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6
74
- headingType = 'h' + this.playerHeadingLevel.toString();
75
- this.$headingDiv = $('<' + headingType + '>');
76
- this.$ableDiv.prepend(this.$headingDiv);
77
- this.$headingDiv.addClass('able-offscreen');
78
- this.$headingDiv.text(this.tt.playerHeading);
78
+ if (this.playerHeadingLevel == '0') {
79
+ // do NOT inject a heading (at author's request)
80
+ }
81
+ else {
82
+ if (typeof this.playerHeadingLevel === 'undefined') {
83
+ this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6
84
+ }
85
+ headingType = 'h' + this.playerHeadingLevel.toString();
86
+ this.$headingDiv = $('<' + headingType + '>');
87
+ this.$ableDiv.prepend(this.$headingDiv);
88
+ this.$headingDiv.addClass('able-offscreen');
89
+ this.$headingDiv.text(this.tt.playerHeading);
90
+ }
79
91
  };
80
92
 
81
93
  AblePlayer.prototype.injectBigPlayButton = function () {
82
-
83
94
  this.$bigPlayButton = $('<button>', {
84
95
  'class': 'able-big-play-button icon-play',
85
96
  'aria-hidden': true,
@@ -342,6 +353,7 @@
342
353
 
343
354
  this.$alertBox = $('<div role="alert"></div>');
344
355
  this.$alertBox.addClass('able-alert');
356
+ this.$alertBox.hide();
345
357
  this.$alertBox.appendTo(this.$ableDiv);
346
358
  if (this.mediaType == 'audio') {
347
359
  top = -10;
@@ -376,93 +388,248 @@
376
388
  }
377
389
  };
378
390
 
379
- // Create popup div and append to player
380
- // 'which' parameter is either 'captions', 'chapters', 'prefs', or 'X-window' (e.g., "sign-window")
381
- AblePlayer.prototype.createPopup = function (which) {
391
+ AblePlayer.prototype.createPopup = function (which, tracks) {
392
+
393
+ // Create popup menu and append to player
394
+ // 'which' parameter is either 'captions', 'chapters', 'prefs', 'transcript-window' or 'sign-window'
395
+ // TODO: Add 'ytcaptions' to parameter list??? Or do they get handled as 'captions'
396
+ // 'tracks', if provided, is a list of tracks to be used as menu items
397
+
398
+ var thisObj, $menu, prefCats, i, $menuItem, prefCat, whichPref,
399
+ hasDefault, track, windowOptions, whichPref, whichMenu,
400
+ $thisItem, $prevItem, $nextItem;
382
401
 
383
- var thisObj, $popup, $thisButton, $thisListItem, $prevButton, $nextButton,
384
- selectedTrackIndex, selectedTrack;
385
402
  thisObj = this;
386
- $popup = $('<div>',{
403
+
404
+ $menu = $('<ul>',{
387
405
  'id': this.mediaId + '-' + which + '-menu',
388
- 'class': 'able-popup'
389
- });
390
- if (which === 'chapters' || which === 'prefs' || which === 'sign-window' || which === 'transcript-window') {
391
- $popup.addClass('able-popup-no-radio');
392
- }
393
- $popup.on('keydown',function (e) {
394
- $thisButton = $(this).find('input:focus');
395
- $thisListItem = $thisButton.parent();
396
- if ($thisListItem.is(':first-child')) {
397
- // this is the first button
398
- $prevButton = $(this).find('input').last(); // wrap to bottom
399
- $nextButton = $thisListItem.next().find('input');
400
- }
401
- else if ($thisListItem.is(':last-child')) {
402
- // this is the last button
403
- $prevButton = $thisListItem.prev().find('input');
404
- $nextButton = $(this).find('input').first(); // wrap to top
406
+ 'class': 'able-popup',
407
+ 'role': 'menu'
408
+ }).hide();
409
+
410
+ if (which === 'captions') {
411
+ $menu.addClass('able-popup-captions');
412
+ }
413
+
414
+ // Populate menu with menu items
415
+ if (which === 'prefs') {
416
+ prefCats = this.getPreferencesGroups();
417
+ for (i = 0; i < prefCats.length; i++) {
418
+ $menuItem = $('<li></li>',{
419
+ 'role': 'menuitem',
420
+ 'tabindex': '-1'
421
+ });
422
+ prefCat = prefCats[i];
423
+ if (prefCat === 'captions') {
424
+ $menuItem.text(this.tt.prefMenuCaptions);
425
+ }
426
+ else if (prefCat === 'descriptions') {
427
+ $menuItem.text(this.tt.prefMenuDescriptions);
428
+ }
429
+ else if (prefCat === 'keyboard') {
430
+ $menuItem.text(this.tt.prefMenuKeyboard);
431
+ }
432
+ else if (prefCat === 'transcript') {
433
+ $menuItem.text(this.tt.prefMenuTranscript);
434
+ }
435
+ $menuItem.on('click',function() {
436
+ whichPref = $(this).text();
437
+ thisObj.setFullscreen(false);
438
+ if (whichPref === thisObj.tt.prefMenuCaptions) {
439
+ thisObj.captionPrefsDialog.show();
440
+ }
441
+ else if (whichPref === thisObj.tt.prefMenuDescriptions) {
442
+ thisObj.descPrefsDialog.show();
443
+ }
444
+ else if (whichPref === thisObj.tt.prefMenuKeyboard) {
445
+ thisObj.keyboardPrefsDialog.show();
446
+ }
447
+ else if (whichPref === thisObj.tt.prefMenuTranscript) {
448
+ thisObj.transcriptPrefsDialog.show();
449
+ }
450
+ thisObj.closePopups();
451
+ });
452
+ $menu.append($menuItem);
453
+ }
454
+ }
455
+ else if (which === 'captions' || which === 'chapters') {
456
+ hasDefault = false;
457
+ for (i = 0; i < tracks.length; i++) {
458
+ track = tracks[i];
459
+ $menuItem = $('<li></li>',{
460
+ 'role': 'menuitemradio',
461
+ 'tabindex': '-1',
462
+ 'lang': track.language
463
+ });
464
+ if (track.def) {
465
+ $menuItem.attr('aria-checked','true');
466
+ hasDefault = true;
467
+ }
468
+ else {
469
+ $menuItem.attr('aria-checked','false');
470
+ }
471
+ // Get a label using track data
472
+ if (which == 'captions' || which == 'ytCaptions') {
473
+ $menuItem.text(track.label);
474
+ $menuItem.on('click',this.getCaptionClickFunction(track));
475
+ }
476
+ else if (which == 'chapters') {
477
+ $menuItem.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start));
478
+ $menuItem.on('click',this.getChapterClickFunction(track.start));
479
+ }
480
+ $menu.append($menuItem);
481
+ }
482
+ if (which === 'captions' || which === 'ytcaptions') {
483
+ // add a 'captions off' menu item
484
+ $menuItem = $('<li></li>',{
485
+ 'role': 'menuitemradio',
486
+ 'tabindex': '-1',
487
+ }).text(this.tt.captionsOff);
488
+ if (this.prefCaptions === 0) {
489
+ $menuItem.attr('aria-checked','true');
490
+ hasDefault = true;
491
+ }
492
+ $menuItem.on('click',this.getCaptionOffFunction());
493
+ $menu.append($menuItem);
494
+ }
495
+ }
496
+ else if (which === 'transcript-window' || which === 'sign-window') {
497
+ windowOptions = [];
498
+ windowOptions.push({
499
+ 'name': 'move',
500
+ 'label': this.tt.windowMove
501
+ });
502
+ windowOptions.push({
503
+ 'name': 'resize',
504
+ 'label': this.tt.windowResize
505
+ });
506
+ windowOptions.push({
507
+ 'name': 'close',
508
+ 'label': this.tt.windowClose
509
+ });
510
+ for (i = 0; i < windowOptions.length; i++) {
511
+ $menuItem = $('<li></li>',{
512
+ 'role': 'menuitem',
513
+ 'tabindex': '-1',
514
+ 'data-choice': windowOptions[i].name
515
+ });
516
+ $menuItem.text(windowOptions[i].label);
517
+ $menuItem.on('click mousedown',function(e) {
518
+ e.stopPropagation();
519
+ if (e.button !== 0) { // not a left click
520
+ return false;
521
+ }
522
+ if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
523
+ thisObj.windowMenuClickRegistered = true;
524
+ thisObj.handleMenuChoice(which.substr(0, which.indexOf('-')), $(this).attr('data-choice'), e);
525
+ }
526
+ });
527
+ $menu.append($menuItem);
528
+ }
529
+ }
530
+ // assign default item, if there isn't one already
531
+ if ((which === 'captions' || which === 'ytcaptions') && !hasDefault) {
532
+ // check the menu item associated with the default language
533
+ // as determined in control.js > syncTrackLanguages()
534
+ if ($menu.find('li[lang=' + this.captionLang + ']')) {
535
+ // a track exists for the default language. Check that item in the menu
536
+ $menu.find('li[lang=' + this.captionLang + ']').attr('aria-checked','true');
537
+ }
538
+ else {
539
+ // check the last item (captions off)
540
+ $menu.find('li').last().attr('aria-checked','true');
541
+ }
542
+ }
543
+ else if (which === 'chapters') {
544
+ if ($menu.find('li:contains("' + this.defaultChapter + '")')) {
545
+ $menu.find('li:contains("' + this.defaultChapter + '")').attr('aria-checked','true').addClass('able-focus');
405
546
  }
406
547
  else {
407
- $prevButton = $thisListItem.prev().find('input');
408
- $nextButton = $thisListItem.next().find('input');
548
+ $menu.find('li').first().attr('aria-checked','true').addClass('able-focus');
549
+ }
550
+ }
551
+ // add keyboard handlers for navigating within popups
552
+ $menu.on('keydown',function (e) {
553
+ whichMenu = $(this).attr('id').split('-')[1];
554
+ $thisItem = $(this).find('li:focus');
555
+ if ($thisItem.is(':first-child')) {
556
+ // this is the first item in the menu
557
+ $prevItem = $(this).find('li').last(); // wrap to bottom
558
+ $nextItem = $thisItem.next();
559
+ }
560
+ else if ($thisItem.is(':last-child')) {
561
+ // this is the last Item
562
+ $prevItem = $thisItem.prev();
563
+ $nextItem = $(this).find('li').first(); // wrap to top
564
+ }
565
+ else {
566
+ $prevItem = $thisItem.prev();
567
+ $nextItem = $thisItem.next();
409
568
  }
410
569
  if (e.which === 9) { // Tab
411
570
  if (e.shiftKey) {
412
- $thisListItem.removeClass('able-focus');
413
- $prevButton.focus();
414
- $prevButton.parent().addClass('able-focus');
571
+ $thisItem.removeClass('able-focus');
572
+ $prevItem.focus().addClass('able-focus');
415
573
  }
416
574
  else {
417
- $thisListItem.removeClass('able-focus');
418
- $nextButton.focus();
419
- $nextButton.parent().addClass('able-focus');
575
+ $thisItem.removeClass('able-focus');
576
+ $nextItem.focus().addClass('able-focus');
420
577
  }
421
578
  }
422
579
  else if (e.which === 40 || e.which === 39) { // down or right arrow
423
- $thisListItem.removeClass('able-focus');
424
- $nextButton.focus();
425
- $nextButton.parent().addClass('able-focus');
580
+ $thisItem.removeClass('able-focus');
581
+ $nextItem.focus().addClass('able-focus');
426
582
  }
427
583
  else if (e.which == 38 || e.which === 37) { // up or left arrow
428
- $thisListItem.removeClass('able-focus');
429
- $prevButton.focus();
430
- $prevButton.parent().addClass('able-focus');
584
+ $thisItem.removeClass('able-focus');
585
+ $prevItem.focus().addClass('able-focus');
431
586
  }
432
587
  else if (e.which === 32 || e.which === 13) { // space or enter
433
- $('input:focus').click();
588
+ $thisItem.click();
434
589
  }
435
590
  else if (e.which === 27) { // Escape
436
- $thisListItem.removeClass('able-focus');
591
+ $thisItem.removeClass('able-focus');
437
592
  thisObj.closePopups();
438
593
  }
439
594
  e.preventDefault();
440
595
  });
441
- this.$controllerDiv.append($popup);
442
- return $popup;
596
+
597
+ this.$controllerDiv.append($menu);
598
+ return $menu;
443
599
  };
444
600
 
445
601
  AblePlayer.prototype.closePopups = function () {
602
+
446
603
  if (this.chaptersPopup && this.chaptersPopup.is(':visible')) {
447
604
  this.chaptersPopup.hide();
448
- this.$chaptersButton.focus();
605
+ this.$chaptersButton.attr('aria-expanded','false').focus();
449
606
  }
450
607
  if (this.captionsPopup && this.captionsPopup.is(':visible')) {
451
608
  this.captionsPopup.hide();
452
- this.$ccButton.focus();
609
+ this.$ccButton.attr('aria-expanded','false').focus();
453
610
  }
454
611
  if (this.prefsPopup && this.prefsPopup.is(':visible')) {
455
612
  this.prefsPopup.hide();
456
- this.$prefsButton.focus();
457
- }
458
- if (this.$windowPopup && this.$windowPopup.is(':visible')) {
459
- this.$windowPopup.hide();
460
- this.$windowButton.show().focus();
613
+ // restore menu items to their original state
614
+ this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
615
+ this.$prefsButton.attr('aria-expanded','false').focus();
461
616
  }
462
617
  if (this.$volumeSlider && this.$volumeSlider.is(':visible')) {
463
618
  this.$volumeSlider.hide().attr('aria-hidden','true');
464
619
  this.$volumeAlert.text(this.tt.volumeSliderClosed);
465
- this.$volumeButton.focus();
620
+ this.$volumeButton.attr('aria-expanded','false').focus();
621
+ }
622
+ if (this.$transcriptPopup && this.$transcriptPopup.is(':visible')) {
623
+ this.$transcriptPopup.hide();
624
+ // restore menu items to their original state
625
+ this.$transcriptPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
626
+ this.$transcriptPopupButton.attr('aria-expanded','false').focus();
627
+ }
628
+ if (this.$signPopup && this.$signPopup.is(':visible')) {
629
+ this.$signPopup.hide();
630
+ // restore menu items to their original state
631
+ this.$signPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
632
+ this.$signPopupButton.attr('aria-expanded','false').focus();
466
633
  }
467
634
  };
468
635
 
@@ -470,10 +637,11 @@
470
637
 
471
638
  // Create and fill in the popup menu forms for various controls.
472
639
  // parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters')
473
-
640
+ // If which is undefined, automatically setup 'captions', 'chapters', and 'prefs' popups
641
+ // However, only setup 'transcript-window' and 'sign-window' popups if passed as value of which
474
642
  var popups, thisObj, hasDefault, i, j,
475
- tracks, trackList, trackItem, track,
476
- radioName, radioId, trackButton, trackLabel,
643
+ tracks, track, $trackButton, $trackLabel,
644
+ radioName, radioId, $menu, $menuItem,
477
645
  prefCats, prefCat, prefLabel;
478
646
 
479
647
  popups = [];
@@ -498,6 +666,12 @@
498
666
  popups.push('chapters');
499
667
  }
500
668
  }
669
+ if (which === 'transcript-window' && this.transcriptType === 'popup') {
670
+ popups.push('transcript-window');
671
+ }
672
+ if (which === 'sign-window' && this.hasSignLanguage) {
673
+ popups.push('sign-window');
674
+ }
501
675
  if (popups.length > 0) {
502
676
  thisObj = this;
503
677
  for (var i=0; i<popups.length; i++) {
@@ -508,14 +682,10 @@
508
682
  }
509
683
  else if (popup == 'captions') {
510
684
  if (typeof this.captionsPopup === 'undefined') {
511
- this.captionsPopup = this.createPopup('captions');
685
+ this.captionsPopup = this.createPopup('captions',this.captions);
512
686
  }
513
- tracks = this.captions;
514
687
  }
515
688
  else if (popup == 'chapters') {
516
- if (typeof this.chaptersPopup === 'undefined') {
517
- this.chaptersPopup = this.createPopup('chapters');
518
- }
519
689
  if (this.selectedChapters) {
520
690
  tracks = this.selectedChapters.cues;
521
691
  }
@@ -525,159 +695,30 @@
525
695
  else {
526
696
  tracks = [];
527
697
  }
698
+ if (typeof this.chaptersPopup === 'undefined') {
699
+ this.chaptersPopup = this.createPopup('chapters',tracks);
700
+ }
528
701
  }
529
702
  else if (popup == 'ytCaptions') {
530
703
  if (typeof this.captionsPopup === 'undefined') {
531
- this.captionsPopup = this.createPopup('captions');
704
+ this.captionsPopup = this.createPopup('captions',this.ytCaptions);
532
705
  }
533
- tracks = this.ytCaptions;
534
706
  }
535
- var trackList = $('<ul></ul>');
536
- radioName = this.mediaId + '-' + popup + '-choice';
537
- if (popup === 'prefs') {
538
- prefCats = this.getPreferencesGroups();
539
- for (j = 0; j < prefCats.length; j++) {
540
- trackItem = $('<li></li>');
541
- prefCat = prefCats[j];
542
- if (prefCat === 'captions') {
543
- prefLabel = this.tt.prefMenuCaptions;
544
- }
545
- else if (prefCat === 'descriptions') {
546
- prefLabel = this.tt.prefMenuDescriptions;
547
- }
548
- else if (prefCat === 'keyboard') {
549
- prefLabel = this.tt.prefMenuKeyboard;
550
- }
551
- else if (prefCat === 'transcript') {
552
- prefLabel = this.tt.prefMenuTranscript;
553
- }
554
- radioId = this.mediaId + '-' + popup + '-' + j;
555
- trackButton = $('<input>',{
556
- 'type': 'radio',
557
- 'val': prefCat,
558
- 'name': radioName,
559
- 'id': radioId
560
- });
561
- trackLabel = $('<label>',{
562
- 'for': radioId
563
- });
564
- trackLabel.text(prefLabel);
565
- trackButton.click(function(event) {
566
- var whichPref = $(this).attr('value');
567
- thisObj.setFullscreen(false);
568
- if (whichPref === 'captions') {
569
- thisObj.captionPrefsDialog.show();
570
- }
571
- else if (whichPref === 'descriptions') {
572
- thisObj.descPrefsDialog.show();
573
- }
574
- else if (whichPref === 'keyboard') {
575
- thisObj.keyboardPrefsDialog.show();
576
- }
577
- else if (whichPref === 'transcript') {
578
- thisObj.transcriptPrefsDialog.show();
579
- }
580
- thisObj.closePopups();
581
- });
582
- trackItem.append(trackButton,trackLabel);
583
- trackList.append(trackItem);
584
- }
585
- this.prefsPopup.append(trackList);
707
+ else if (popup == 'transcript-window') {
708
+ return this.createPopup('transcript-window');
586
709
  }
587
- else {
588
- for (j = 0; j < tracks.length; j++) {
589
- trackItem = $('<li></li>');
590
- track = tracks[j];
591
- radioId = this.mediaId + '-' + popup + '-' + j;
592
- trackButton = $('<input>',{
593
- 'type': 'radio',
594
- 'val': j,
595
- 'name': radioName,
596
- 'id': radioId
597
- });
598
- if (track.def) {
599
- trackButton.prop('checked',true);
600
- hasDefault = true;
601
- }
602
- trackLabel = $('<label>',{
603
- 'for': radioId
604
- });
605
- if (track.language !== 'undefined') {
606
- trackButton.attr('lang',track.language);
607
- }
608
- if (popup == 'captions' || popup == 'ytCaptions') {
609
- trackLabel.text(track.label || track.language);
610
- trackButton.click(this.getCaptionClickFunction(track));
611
- }
612
- else if (popup == 'chapters') {
613
- trackLabel.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start));
614
- var getClickFunction = function (time) {
615
- return function () {
616
- thisObj.seekTo(time);
617
- // stopgap to prevent spacebar in Firefox from reopening popup
618
- // immediately after closing it (used in handleChapters())
619
- thisObj.hidingPopup = true;
620
- thisObj.chaptersPopup.hide();
621
- // Ensure stopgap gets cancelled if handleChapters() isn't called
622
- // e.g., if user triggered button with Enter or mouse click, not spacebar
623
- setTimeout(function() {
624
- thisObj.hidingPopup = false;
625
- }, 100);
626
- thisObj.$chaptersButton.focus();
627
- }
628
- }
629
- trackButton.on('click keypress',getClickFunction(track.start));
630
- }
631
- trackItem.append(trackButton,trackLabel);
632
- trackList.append(trackItem);
633
- }
634
- if (popup == 'captions' || popup == 'ytCaptions') {
635
- // add a captions off button
636
- radioId = this.mediaId + '-captions-off';
637
- trackItem = $('<li></li>');
638
- trackButton = $('<input>',{
639
- 'type': 'radio',
640
- 'name': radioName,
641
- 'id': radioId
642
- });
643
- trackLabel = $('<label>',{
644
- 'for': radioId
645
- });
646
- trackLabel.text(this.tt.captionsOff);
647
- if (this.prefCaptions === 0) {
648
- trackButton.prop('checked',true);
649
- }
650
- trackButton.click(this.getCaptionOffFunction());
651
- trackItem.append(trackButton,trackLabel);
652
- trackList.append(trackItem);
653
- }
654
- if (!hasDefault) { // no 'default' attribute was specified on any <track>
655
- if ((popup == 'captions' || popup == 'ytCaptions') && (trackList.find('input:radio[lang=' + this.captionLang + ']'))) {
656
- // check the button associated with the default caption language
657
- // (as determined in control.js > syncTrackLanguages())
658
- trackList.find('input:radio[lang=' + this.captionLang + ']').prop('checked',true);
659
- }
660
- else {
661
- // check the first button
662
- trackList.find('input').first().prop('checked',true);
663
- }
664
- }
665
- if (popup === 'captions' || popup === 'ytCaptions') {
666
- this.captionsPopup.html(trackList);
667
- }
668
- else if (popup === 'chapters') {
669
- this.chaptersPopup.html(trackList);
670
- }
710
+ else if (popup == 'sign-window') {
711
+ return this.createPopup('sign-window');
671
712
  }
672
713
  }
673
714
  }
674
715
  };
675
716
 
676
- AblePlayer.prototype.provideFallback = function(reason) {
717
+ AblePlayer.prototype.provideFallback = function() {
677
718
 
678
719
  // provide ultimate fallback for users who are unable to play the media
679
- // reason is a specific error message
680
- // if reason is 'NO SUPPORT', use standard text from translation file
720
+ // If there is HTML content nested within the media element, display that
721
+ // Otherwise, display standard localized error text
681
722
 
682
723
  var $fallbackDiv, width, mediaClone, fallback, fallbackText,
683
724
  showBrowserList, browsers, i, b, browserList;
@@ -709,18 +750,14 @@
709
750
  if (fallback.length) {
710
751
  $fallbackDiv.html(fallback);
711
752
  }
712
- else if (reason == 'NO SUPPORT') {
713
- // not using a supporting browser; use standard text from translation file
753
+ else {
754
+ // use standard localized error message
714
755
  fallbackText = this.tt.fallbackError1 + ' ' + this.tt[this.mediaType] + '. ';
715
756
  fallbackText += this.tt.fallbackError2 + ':';
716
757
  fallback = $('<p>').text(fallbackText);
717
758
  $fallbackDiv.html(fallback);
718
759
  showBrowserList = true;
719
760
  }
720
- else {
721
- // show the reason
722
- $fallbackDiv.text(reason);
723
- }
724
761
 
725
762
  if (showBrowserList) {
726
763
  browserList = $('<ul>');
@@ -861,34 +898,33 @@
861
898
  };
862
899
 
863
900
  AblePlayer.prototype.addControls = function() {
864
-
865
901
  // determine which controls to show based on several factors:
866
902
  // mediaType (audio vs video)
867
903
  // availability of tracks (e.g., for closed captions & audio description)
868
904
  // browser support (e.g., for sliders and speedButtons)
869
905
  // user preferences (???)
870
906
  // some controls are aligned on the left, and others on the right
871
- var useSpeedButtons, useFullScreen,
872
- i, j, k, controls, $controllerSpan, tooltipId, tooltipX, tooltipY, control,
873
- buttonImg, buttonImgSrc, buttonTitle, newButton, iconClass, buttonIcon, buttonUse,
907
+ var thisObj, baseSliderWidth, controlLayout, sectionByOrder, useSpeedButtons, useFullScreen,
908
+ i, j, k, controls, $controllerSpan, $sliderDiv, sliderLabel, duration, $pipe, $pipeImg, tooltipId, tooltipX, tooltipY, control,
909
+ buttonImg, buttonImgSrc, buttonTitle, $newButton, iconClass, buttonIcon, buttonUse, svgPath,
874
910
  leftWidth, rightWidth, totalWidth, leftWidthStyle, rightWidthStyle,
875
911
  controllerStyles, vidcapStyles, captionLabel, popupMenuId;
876
912
 
877
- var thisObj = this;
913
+ thisObj = this;
878
914
 
879
- var baseSliderWidth = 100;
915
+ baseSliderWidth = 100;
880
916
 
881
- // Initializes the layout into the this.controlLayout variable.
882
- var controlLayout = this.calculateControlLayout();
917
+ // Initialize the layout into the this.controlLayout variable.
918
+ controlLayout = this.calculateControlLayout();
883
919
 
884
- var sectionByOrder = {0: 'ul', 1:'ur', 2:'bl', 3:'br'};
920
+ sectionByOrder = {0: 'ul', 1:'ur', 2:'bl', 3:'br'};
885
921
 
886
922
  // add an empty div to serve as a tooltip
887
923
  tooltipId = this.mediaId + '-tooltip';
888
924
  this.$tooltipDiv = $('<div>',{
889
925
  'id': tooltipId,
890
926
  'class': 'able-tooltip'
891
- });
927
+ }).hide();
892
928
  this.$controllerDiv.append(this.$tooltipDiv);
893
929
 
894
930
  // step separately through left and right controls
@@ -908,34 +944,34 @@
908
944
  for (j=0; j<controls.length; j++) {
909
945
  control = controls[j];
910
946
  if (control === 'seek') {
911
- var sliderDiv = $('<div class="able-seekbar"></div>');
912
- var sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel;
913
- $controllerSpan.append(sliderDiv);
914
- var duration = this.getDuration();
947
+ $sliderDiv = $('<div class="able-seekbar"></div>');
948
+ sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel;
949
+ $controllerSpan.append($sliderDiv);
950
+ duration = this.getDuration();
915
951
  if (duration == 0) {
916
952
  // set arbitrary starting duration, and change it when duration is known
917
953
  duration = 100;
918
954
  }
919
- this.seekBar = new AccessibleSlider(this.mediaType, sliderDiv, 'horizontal', baseSliderWidth, 0, duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible');
955
+ this.seekBar = new AccessibleSlider(this.mediaType, $sliderDiv, 'horizontal', baseSliderWidth, 0, duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible');
920
956
  }
921
957
  else if (control === 'pipe') {
922
958
  // TODO: Unify this with buttons somehow to avoid code duplication
923
- var pipe = $('<span>', {
959
+ $pipe = $('<span>', {
924
960
  'tabindex': '-1',
925
961
  'aria-hidden': 'true'
926
962
  });
927
963
  if (this.iconType === 'font') {
928
- pipe.addClass('icon-pipe');
964
+ $pipe.addClass('icon-pipe');
929
965
  }
930
966
  else {
931
- var pipeImg = $('<img>', {
967
+ $pipeImg = $('<img>', {
932
968
  src: this.rootPath + 'button-icons/' + this.iconColor + '/pipe.png',
933
969
  alt: '',
934
970
  role: 'presentation'
935
971
  });
936
- pipe.append(pipeImg);
972
+ $pipe.append($pipeImg);
937
973
  }
938
- $controllerSpan.append(pipe);
974
+ $controllerSpan.append($pipe);
939
975
  }
940
976
  else {
941
977
  // this control is a button
@@ -974,22 +1010,23 @@
974
1010
  // And if iconType === 'image', we are replacing #2 with an image (with alt="" and role="presentation")
975
1011
  // This has been thoroughly tested and works well in all screen reader/browser combinations
976
1012
  // See https://github.com/ableplayer/ableplayer/issues/81
977
- newButton = $('<button>',{
1013
+ $newButton = $('<button>',{
978
1014
  'type': 'button',
979
1015
  'tabindex': '0',
980
1016
  'aria-label': buttonTitle,
981
1017
  'class': 'able-button-handler-' + control
982
1018
  });
983
1019
  if (control === 'volume' || control === 'preferences') {
984
- // This same ARIA for captions and chapters are added elsewhere
1020
+ // This same ARIA for captions and chapters are added elsewhere (FUCK where?)
985
1021
  if (control == 'preferences') {
986
1022
  popupMenuId = this.mediaId + '-prefs-menu';
987
1023
  }
988
1024
  else if (control === 'volume') {
989
1025
  popupMenuId = this.mediaId + '-volume-slider';
990
1026
  }
991
- newButton.attr({
992
- 'aria-controls': popupMenuId
1027
+ $newButton.attr({
1028
+ 'aria-controls': popupMenuId,
1029
+ 'aria-expanded': 'false'
993
1030
  });
994
1031
  }
995
1032
  if (this.iconType === 'font') {
@@ -1019,9 +1056,16 @@
1019
1056
  'class': iconClass,
1020
1057
  'aria-hidden': 'true'
1021
1058
  });
1022
- newButton.append(buttonIcon);
1059
+ $newButton.append(buttonIcon);
1023
1060
  }
1024
1061
  else if (this.iconType === 'svg') {
1062
+
1063
+ /*
1064
+ // Unused option for adding SVG:
1065
+ // Use <use> element to link to button-icons/able-icons.svg
1066
+ // Advantage: SVG file can be cached
1067
+ // Disadvantage: Not supported by Safari 6, IE 6-11, or Edge 12
1068
+ // Instead, adding <svg> element within each <button>
1025
1069
  if (control === 'volume') {
1026
1070
  iconClass = 'svg-' + this.volumeButton;
1027
1071
  }
@@ -1051,13 +1095,49 @@
1051
1095
  'class': iconClass
1052
1096
  });
1053
1097
  buttonUse = $('<use>',{
1054
- 'xlink:href': this.rootPath + 'icons/able-icons.svg#' + iconClass
1098
+ 'xlink:href': this.rootPath + 'button-icons/able-icons.svg#' + iconClass
1055
1099
  });
1056
1100
  buttonIcon.append(buttonUse);
1057
- newButton.html(buttonIcon);
1101
+ */
1102
+ var svgData;
1103
+ if (control === 'volume') {
1104
+ svgData = this.getSvgData(this.volumeButton);
1105
+ }
1106
+ else if (control === 'fullscreen') {
1107
+ svgData = this.getSvgData('fullscreen-expand');
1108
+ }
1109
+ else if (control === 'slower') {
1110
+ if (this.speedIcons === 'animals') {
1111
+ svgData = this.getSvgData('turtle');
1112
+ }
1113
+ else {
1114
+ svgData = this.getSvgData('slower');
1115
+ }
1116
+ }
1117
+ else if (control === 'faster') {
1118
+ if (this.speedIcons === 'animals') {
1119
+ svgData = this.getSvgData('rabbit');
1120
+ }
1121
+ else {
1122
+ svgData = this.getSvgData('faster');
1123
+ }
1124
+ }
1125
+ else {
1126
+ svgData = this.getSvgData(control);
1127
+ }
1128
+ buttonIcon = $('<svg>',{
1129
+ 'focusable': 'false',
1130
+ 'aria-hidden': 'true',
1131
+ 'viewBox': svgData[0]
1132
+ });
1133
+ svgPath = $('<path>',{
1134
+ 'd': svgData[1]
1135
+ });
1136
+ buttonIcon.append(svgPath);
1137
+ $newButton.html(buttonIcon);
1058
1138
 
1059
1139
  // Final step: Need to refresh the DOM in order for browser to process & display the SVG
1060
- newButton.html(newButton.html());
1140
+ $newButton.html($newButton.html());
1061
1141
  }
1062
1142
  else {
1063
1143
  // use images
@@ -1066,15 +1146,15 @@
1066
1146
  'alt': '',
1067
1147
  'role': 'presentation'
1068
1148
  });
1069
- newButton.append(buttonImg);
1149
+ $newButton.append(buttonImg);
1070
1150
  }
1071
1151
  // add the visibly-hidden label for screen readers that don't support aria-label on the button
1072
1152
  var buttonLabel = $('<span>',{
1073
1153
  'class': 'able-clipped'
1074
1154
  }).text(buttonTitle);
1075
- newButton.append(buttonLabel);
1155
+ $newButton.append(buttonLabel);
1076
1156
  // add an event listener that displays a tooltip on mouseenter or focus
1077
- newButton.on('mouseenter focus',function(event) {
1157
+ $newButton.on('mouseenter focus',function(e) {
1078
1158
  var label = $(this).attr('aria-label');
1079
1159
  // get position of this button
1080
1160
  var position = $(this).position();
@@ -1111,7 +1191,7 @@
1111
1191
  }
1112
1192
  if (centerTooltip) {
1113
1193
  // populate tooltip, then calculate its width before showing it
1114
- var tooltipWidth = $('#' + tooltipId).text(label).width();
1194
+ var tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label).width();
1115
1195
  // center the tooltip horizontally over the button
1116
1196
  var tooltipX = position.left - tooltipWidth/2;
1117
1197
  var tooltipStyle = {
@@ -1120,10 +1200,10 @@
1120
1200
  top: tooltipY + 'px'
1121
1201
  };
1122
1202
  }
1123
- var tooltip = $('#' + tooltipId).text(label).css(tooltipStyle);
1203
+ var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label).css(tooltipStyle);
1124
1204
  thisObj.showTooltip(tooltip);
1125
1205
  $(this).on('mouseleave blur',function() {
1126
- $('#' + tooltipId).text('').hide();
1206
+ AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
1127
1207
  })
1128
1208
  });
1129
1209
 
@@ -1136,7 +1216,7 @@
1136
1216
  else {
1137
1217
  captionLabel = this.tt.showCaptions;
1138
1218
  }
1139
- newButton.addClass('buttonOff').attr('title',captionLabel);
1219
+ $newButton.addClass('buttonOff').attr('title',captionLabel);
1140
1220
  }
1141
1221
  }
1142
1222
  else if (control === 'descriptions') {
@@ -1144,51 +1224,51 @@
1144
1224
  // user prefer non-audio described version
1145
1225
  // Therefore, load media without description
1146
1226
  // Description can be toggled on later with this button
1147
- newButton.addClass('buttonOff').attr('title',this.tt.turnOnDescriptions);
1227
+ $newButton.addClass('buttonOff').attr('title',this.tt.turnOnDescriptions);
1148
1228
  }
1149
1229
  }
1150
1230
 
1151
- $controllerSpan.append(newButton);
1231
+ $controllerSpan.append($newButton);
1152
1232
 
1153
1233
  // create variables of buttons that are referenced throughout the AblePlayer object
1154
1234
  if (control === 'play') {
1155
- this.$playpauseButton = newButton;
1235
+ this.$playpauseButton = $newButton;
1156
1236
  }
1157
1237
  else if (control === 'captions') {
1158
- this.$ccButton = newButton;
1238
+ this.$ccButton = $newButton;
1159
1239
  }
1160
1240
  else if (control === 'sign') {
1161
- this.$signButton = newButton;
1241
+ this.$signButton = $newButton;
1162
1242
  // gray out sign button if sign language window is not active
1163
1243
  if (!(this.$signWindow.is(':visible'))) {
1164
1244
  this.$signButton.addClass('buttonOff');
1165
1245
  }
1166
1246
  }
1167
1247
  else if (control === 'descriptions') {
1168
- this.$descButton = newButton;
1248
+ this.$descButton = $newButton;
1169
1249
  // button will be enabled or disabled in description.js > initDescription()
1170
1250
  }
1171
1251
  else if (control === 'mute') {
1172
- this.$muteButton = newButton;
1252
+ this.$muteButton = $newButton;
1173
1253
  }
1174
1254
  else if (control === 'transcript') {
1175
- this.$transcriptButton = newButton;
1255
+ this.$transcriptButton = $newButton;
1176
1256
  // gray out transcript button if transcript is not active
1177
1257
  if (!(this.$transcriptDiv.is(':visible'))) {
1178
1258
  this.$transcriptButton.addClass('buttonOff').attr('title',this.tt.showTranscript);
1179
1259
  }
1180
1260
  }
1181
1261
  else if (control === 'fullscreen') {
1182
- this.$fullscreenButton = newButton;
1262
+ this.$fullscreenButton = $newButton;
1183
1263
  }
1184
1264
  else if (control === 'chapters') {
1185
- this.$chaptersButton = newButton;
1265
+ this.$chaptersButton = $newButton;
1186
1266
  }
1187
1267
  else if (control === 'preferences') {
1188
- this.$prefsButton = newButton;
1268
+ this.$prefsButton = $newButton;
1189
1269
  }
1190
1270
  else if (control === 'volume') {
1191
- this.$volumeButton = newButton;
1271
+ this.$volumeButton = $newButton;
1192
1272
  }
1193
1273
  }
1194
1274
  if (control === 'volume') {