wai-website-theme 0.1.0

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 (173) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +52 -0
  4. data/_data/lang.json +730 -0
  5. data/_data/techniques.yml +180 -0
  6. data/_data/wcag.yml +125 -0
  7. data/_includes/.DS_Store +0 -0
  8. data/_includes/body-class.html +1 -0
  9. data/_includes/box.html +10 -0
  10. data/_includes/excol.html +13 -0
  11. data/_includes/footer.html +40 -0
  12. data/_includes/head.html +23 -0
  13. data/_includes/header.html +59 -0
  14. data/_includes/icon.html +6 -0
  15. data/_includes/img.html +17 -0
  16. data/_includes/multilang-list-policy-links.html +29 -0
  17. data/_includes/multilang-list.html +35 -0
  18. data/_includes/multilang-policy-title.html +5 -0
  19. data/_includes/multilang-title-full.html +1 -0
  20. data/_includes/multilang-title.html +1 -0
  21. data/_includes/navlist.html +22 -0
  22. data/_includes/notes.html +2 -0
  23. data/_includes/prevnext.html +34 -0
  24. data/_includes/resources.html +19 -0
  25. data/_includes/sidenav.html +65 -0
  26. data/_includes/sidenote.html +14 -0
  27. data/_includes/toc.html +10 -0
  28. data/_includes/video-player.html +99 -0
  29. data/_layouts/default.html +26 -0
  30. data/_layouts/home.html +14 -0
  31. data/_layouts/news.html +21 -0
  32. data/_layouts/none.html +1 -0
  33. data/_layouts/policy.html +72 -0
  34. data/_layouts/sidenav.html +27 -0
  35. data/_layouts/sidenavsidebar.html +22 -0
  36. data/assets/ableplayer/.gitattributes +14 -0
  37. data/assets/ableplayer/.gitignore +7 -0
  38. data/assets/ableplayer/Gruntfile.js +105 -0
  39. data/assets/ableplayer/LICENSE +26 -0
  40. data/assets/ableplayer/README.md +656 -0
  41. data/assets/ableplayer/build/ableplayer.dist.js +12157 -0
  42. data/assets/ableplayer/build/ableplayer.js +12157 -0
  43. data/assets/ableplayer/build/ableplayer.min.css +2 -0
  44. data/assets/ableplayer/build/ableplayer.min.js +8 -0
  45. data/assets/ableplayer/button-icons/able-icons.svg +116 -0
  46. data/assets/ableplayer/button-icons/black/captions.png +0 -0
  47. data/assets/ableplayer/button-icons/black/chapters.png +0 -0
  48. data/assets/ableplayer/button-icons/black/close.png +0 -0
  49. data/assets/ableplayer/button-icons/black/descriptions.png +0 -0
  50. data/assets/ableplayer/button-icons/black/ellipsis.png +0 -0
  51. data/assets/ableplayer/button-icons/black/faster.png +0 -0
  52. data/assets/ableplayer/button-icons/black/forward.png +0 -0
  53. data/assets/ableplayer/button-icons/black/fullscreen-collapse.png +0 -0
  54. data/assets/ableplayer/button-icons/black/fullscreen-expand.png +0 -0
  55. data/assets/ableplayer/button-icons/black/help.png +0 -0
  56. data/assets/ableplayer/button-icons/black/next.png +0 -0
  57. data/assets/ableplayer/button-icons/black/pause.png +0 -0
  58. data/assets/ableplayer/button-icons/black/pipe.png +0 -0
  59. data/assets/ableplayer/button-icons/black/play.png +0 -0
  60. data/assets/ableplayer/button-icons/black/preferences.png +0 -0
  61. data/assets/ableplayer/button-icons/black/previous.png +0 -0
  62. data/assets/ableplayer/button-icons/black/rabbit.png +0 -0
  63. data/assets/ableplayer/button-icons/black/restart.png +0 -0
  64. data/assets/ableplayer/button-icons/black/rewind.png +0 -0
  65. data/assets/ableplayer/button-icons/black/sign.png +0 -0
  66. data/assets/ableplayer/button-icons/black/slower.png +0 -0
  67. data/assets/ableplayer/button-icons/black/stop.png +0 -0
  68. data/assets/ableplayer/button-icons/black/transcript.png +0 -0
  69. data/assets/ableplayer/button-icons/black/turtle.png +0 -0
  70. data/assets/ableplayer/button-icons/black/volume-loud.png +0 -0
  71. data/assets/ableplayer/button-icons/black/volume-medium.png +0 -0
  72. data/assets/ableplayer/button-icons/black/volume-mute.png +0 -0
  73. data/assets/ableplayer/button-icons/black/volume-soft.png +0 -0
  74. data/assets/ableplayer/button-icons/fonts/able.eot +0 -0
  75. data/assets/ableplayer/button-icons/fonts/able.svg +40 -0
  76. data/assets/ableplayer/button-icons/fonts/able.ttf +0 -0
  77. data/assets/ableplayer/button-icons/fonts/able.woff +0 -0
  78. data/assets/ableplayer/button-icons/white/captions.png +0 -0
  79. data/assets/ableplayer/button-icons/white/chapters.png +0 -0
  80. data/assets/ableplayer/button-icons/white/close.png +0 -0
  81. data/assets/ableplayer/button-icons/white/descriptions.png +0 -0
  82. data/assets/ableplayer/button-icons/white/ellipsis.png +0 -0
  83. data/assets/ableplayer/button-icons/white/faster.png +0 -0
  84. data/assets/ableplayer/button-icons/white/forward.png +0 -0
  85. data/assets/ableplayer/button-icons/white/fullscreen-collapse.png +0 -0
  86. data/assets/ableplayer/button-icons/white/fullscreen-expand.png +0 -0
  87. data/assets/ableplayer/button-icons/white/help.png +0 -0
  88. data/assets/ableplayer/button-icons/white/next.png +0 -0
  89. data/assets/ableplayer/button-icons/white/pause.png +0 -0
  90. data/assets/ableplayer/button-icons/white/pipe.png +0 -0
  91. data/assets/ableplayer/button-icons/white/play.png +0 -0
  92. data/assets/ableplayer/button-icons/white/preferences.png +0 -0
  93. data/assets/ableplayer/button-icons/white/previous.png +0 -0
  94. data/assets/ableplayer/button-icons/white/rabbit.png +0 -0
  95. data/assets/ableplayer/button-icons/white/restart.png +0 -0
  96. data/assets/ableplayer/button-icons/white/rewind.png +0 -0
  97. data/assets/ableplayer/button-icons/white/sign.png +0 -0
  98. data/assets/ableplayer/button-icons/white/slower.png +0 -0
  99. data/assets/ableplayer/button-icons/white/stop.png +0 -0
  100. data/assets/ableplayer/button-icons/white/transcript.png +0 -0
  101. data/assets/ableplayer/button-icons/white/turtle.png +0 -0
  102. data/assets/ableplayer/button-icons/white/volume-loud.png +0 -0
  103. data/assets/ableplayer/button-icons/white/volume-medium.png +0 -0
  104. data/assets/ableplayer/button-icons/white/volume-mute.png +0 -0
  105. data/assets/ableplayer/button-icons/white/volume-soft.png +0 -0
  106. data/assets/ableplayer/images/wingrip.png +0 -0
  107. data/assets/ableplayer/package.json +22 -0
  108. data/assets/ableplayer/scripts/JQuery.doWhen.js +113 -0
  109. data/assets/ableplayer/scripts/ableplayer-base.js +440 -0
  110. data/assets/ableplayer/scripts/browser.js +162 -0
  111. data/assets/ableplayer/scripts/buildplayer.js +1609 -0
  112. data/assets/ableplayer/scripts/caption.js +385 -0
  113. data/assets/ableplayer/scripts/chapters.js +242 -0
  114. data/assets/ableplayer/scripts/control.js +1514 -0
  115. data/assets/ableplayer/scripts/description.js +283 -0
  116. data/assets/ableplayer/scripts/dialog.js +147 -0
  117. data/assets/ableplayer/scripts/dragdrop.js +766 -0
  118. data/assets/ableplayer/scripts/event.js +595 -0
  119. data/assets/ableplayer/scripts/initialize.js +725 -0
  120. data/assets/ableplayer/scripts/langs.js +750 -0
  121. data/assets/ableplayer/scripts/metadata.js +134 -0
  122. data/assets/ableplayer/scripts/misc.js +72 -0
  123. data/assets/ableplayer/scripts/preference.js +909 -0
  124. data/assets/ableplayer/scripts/search.js +171 -0
  125. data/assets/ableplayer/scripts/sign.js +92 -0
  126. data/assets/ableplayer/scripts/slider.js +454 -0
  127. data/assets/ableplayer/scripts/track.js +296 -0
  128. data/assets/ableplayer/scripts/transcript.js +590 -0
  129. data/assets/ableplayer/scripts/translation.js +66 -0
  130. data/assets/ableplayer/scripts/volume.js +383 -0
  131. data/assets/ableplayer/scripts/webvtt.js +765 -0
  132. data/assets/ableplayer/scripts/youtube.js +471 -0
  133. data/assets/ableplayer/styles/ableplayer.css +1241 -0
  134. data/assets/ableplayer/thirdparty/js.cookie.js +145 -0
  135. data/assets/ableplayer/thirdparty/modernizr.custom.js +4 -0
  136. data/assets/ableplayer/translations/ca.js +1 -0
  137. data/assets/ableplayer/translations/de.js +1 -0
  138. data/assets/ableplayer/translations/en.js +305 -0
  139. data/assets/ableplayer/translations/es.js +305 -0
  140. data/assets/ableplayer/translations/fr.js +305 -0
  141. data/assets/ableplayer/translations/it.js +303 -0
  142. data/assets/ableplayer/translations/ja.js +305 -0
  143. data/assets/ableplayer/translations/nl.js +305 -0
  144. data/assets/css/style.css +4360 -0
  145. data/assets/css/style.css.map +1 -0
  146. data/assets/fonts/anonymouspro-bold.woff +0 -0
  147. data/assets/fonts/anonymouspro-bold.woff2 +0 -0
  148. data/assets/fonts/anonymouspro-bolditalic.woff +0 -0
  149. data/assets/fonts/anonymouspro-bolditalic.woff2 +0 -0
  150. data/assets/fonts/anonymouspro-italic.woff +0 -0
  151. data/assets/fonts/anonymouspro-italic.woff2 +0 -0
  152. data/assets/fonts/anonymouspro-regular.woff +0 -0
  153. data/assets/fonts/anonymouspro-regular.woff2 +0 -0
  154. data/assets/fonts/notosans-bold.woff +0 -0
  155. data/assets/fonts/notosans-bold.woff2 +0 -0
  156. data/assets/fonts/notosans-bolditalic.woff +0 -0
  157. data/assets/fonts/notosans-bolditalic.woff2 +0 -0
  158. data/assets/fonts/notosans-italic.woff +0 -0
  159. data/assets/fonts/notosans-italic.woff2 +0 -0
  160. data/assets/fonts/notosans-regular.woff +0 -0
  161. data/assets/fonts/notosans-regular.woff2 +0 -0
  162. data/assets/images/.DS_Store +0 -0
  163. data/assets/images/Shape.svg +10 -0
  164. data/assets/images/icon-related-content.svg +14 -0
  165. data/assets/images/icons.svg +126 -0
  166. data/assets/images/teaser-image@1x.jpg +0 -0
  167. data/assets/images/teaser-image@2x.jpg +0 -0
  168. data/assets/images/w3c.sketch +0 -0
  169. data/assets/images/w3c.svg +10 -0
  170. data/assets/scripts/jquery.min.js +4 -0
  171. data/assets/scripts/main.js +208 -0
  172. data/assets/scripts/svg4everybody.js +1 -0
  173. metadata +257 -0
@@ -0,0 +1,1514 @@
1
+ (function ($) {
2
+ AblePlayer.prototype.seekTo = function (newTime) {
3
+
4
+ this.seeking = true;
5
+ this.liveUpdatePending = true;
6
+
7
+ if (this.player === 'html5') {
8
+ var seekable;
9
+
10
+ this.startTime = newTime;
11
+ // Check HTML5 media "seekable" property to be sure media is seekable to startTime
12
+ seekable = this.media.seekable;
13
+ if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) {
14
+ // ok to seek to startTime
15
+ // canplaythrough will be triggered when seeking is complete
16
+ // this.seeking will be set to false at that point
17
+ this.media.currentTime = this.startTime;
18
+ if (this.hasSignLanguage && this.signVideo) {
19
+ // keep sign languge video in sync
20
+ this.signVideo.currentTime = this.startTime;
21
+ }
22
+ }
23
+ }
24
+ else if (this.player === 'jw' && this.jwPlayer) {
25
+ // pause JW Player temporarily.
26
+ // When seek has successfully reached newTime,
27
+ // onSeek event will be called, and playback will be resumed
28
+ this.jwSeekPause = true;
29
+ this.jwPlayer.seek(newTime);
30
+ }
31
+ else if (this.player === 'youtube') {
32
+ this.youTubePlayer.seekTo(newTime,true);
33
+ if (newTime > 0) {
34
+ if (typeof this.$posterImg !== 'undefined') {
35
+ this.$posterImg.hide();
36
+ }
37
+ }
38
+ }
39
+ this.refreshControls();
40
+ };
41
+
42
+ AblePlayer.prototype.getDuration = function () {
43
+
44
+ var duration;
45
+ if (this.player === 'html5') {
46
+ duration = this.media.duration;
47
+ }
48
+ else if (this.player === 'jw' && this.jwPlayer) {
49
+ duration = this.jwPlayer.getDuration();
50
+ }
51
+ else if (this.player === 'youtube' && this.youTubePlayer) {
52
+ duration = this.youTubePlayer.getDuration();
53
+ }
54
+ if (duration === undefined || isNaN(duration) || duration === -1) {
55
+ return 0;
56
+ }
57
+ return duration;
58
+ };
59
+
60
+ AblePlayer.prototype.getElapsed = function () {
61
+ var position;
62
+ if (this.player === 'html5') {
63
+ position = this.media.currentTime;
64
+ }
65
+ else if (this.player === 'jw' && this.jwPlayer) {
66
+ if (this.jwPlayer.getState() === 'IDLE') {
67
+ return 0;
68
+ }
69
+ position = this.jwPlayer.getPosition();
70
+ }
71
+ else if (this.player === 'youtube') {
72
+ if (this.youTubePlayer) {
73
+ position = this.youTubePlayer.getCurrentTime();
74
+ }
75
+ }
76
+
77
+ if (position === undefined || isNaN(position) || position === -1) {
78
+ return 0;
79
+ }
80
+ return position;
81
+ };
82
+
83
+ // Returns one of the following states:
84
+ // 'stopped' - Not yet played for the first time, or otherwise reset to unplayed.
85
+ // 'ended' - Finished playing.
86
+ // 'paused' - Not playing, but not stopped or ended.
87
+ // 'buffering' - Momentarily paused to load, but will resume once data is loaded.
88
+ // 'playing' - Currently playing.
89
+ AblePlayer.prototype.getPlayerState = function () {
90
+ if (this.swappingSrc) {
91
+ return;
92
+ }
93
+ if (this.player === 'html5') {
94
+ if (this.media.paused) {
95
+ if (this.getElapsed() === 0) {
96
+ return 'stopped';
97
+ }
98
+ else if (this.media.ended) {
99
+ return 'ended';
100
+ }
101
+ else {
102
+ return 'paused';
103
+ }
104
+ }
105
+ else if (this.media.readyState !== 4) {
106
+ return 'buffering';
107
+ }
108
+ else {
109
+ return 'playing';
110
+ }
111
+ }
112
+ else if (this.player === 'jw' && this.jwPlayer) {
113
+ if (this.jwPlayer.getState() === 'PAUSED' || this.jwPlayer.getState() === 'IDLE' || this.jwPlayer.getState() === undefined) {
114
+
115
+ if (this.getElapsed() === 0) {
116
+ return 'stopped';
117
+ }
118
+ else if (this.getElapsed() === this.getDuration()) {
119
+ return 'ended';
120
+ }
121
+ else {
122
+ return 'paused';
123
+ }
124
+ }
125
+ else if (this.jwPlayer.getState() === 'BUFFERING') {
126
+ return 'buffering';
127
+ }
128
+ else if (this.jwPlayer.getState() === 'PLAYING') {
129
+ return 'playing';
130
+ }
131
+ }
132
+ else if (this.player === 'youtube' && this.youTubePlayer) {
133
+ var state = this.youTubePlayer.getPlayerState();
134
+ if (state === -1 || state === 5) {
135
+ return 'stopped';
136
+ }
137
+ else if (state === 0) {
138
+ return 'ended';
139
+ }
140
+ else if (state === 1) {
141
+ return 'playing';
142
+ }
143
+ else if (state === 2) {
144
+ return 'paused';
145
+ }
146
+ else if (state === 3) {
147
+ return 'buffering';
148
+ }
149
+ }
150
+ };
151
+
152
+ AblePlayer.prototype.isPlaybackRateSupported = function () {
153
+ if (this.player === 'html5') {
154
+ return this.media.playbackRate ? true : false;
155
+ }
156
+ else if (this.player === 'jw' && this.jwPlayer) {
157
+ // Not directly supported by JW player; can hack for HTML5 version by finding the dynamically generated video tag, but decided not to do that.
158
+ return false;
159
+ }
160
+ else if (this.player === 'youtube') {
161
+ // Youtube always supports a finite list of playback rates. Only expose controls if more than one is available.
162
+ return (this.youTubePlayer.getAvailablePlaybackRates().length > 1);
163
+ }
164
+ };
165
+
166
+ AblePlayer.prototype.setPlaybackRate = function (rate) {
167
+ rate = Math.max(0.5, rate);
168
+ if (this.player === 'html5') {
169
+ this.media.playbackRate = rate;
170
+ }
171
+ else if (this.player === 'youtube') {
172
+ this.youTubePlayer.setPlaybackRate(rate);
173
+ }
174
+ if (this.hasSignLanguage && this.signVideo) {
175
+ this.signVideo.playbackRate = rate;
176
+ }
177
+ this.$speed.text(this.tt.speed + ': ' + rate.toFixed(2).toString() + 'x');
178
+ };
179
+
180
+ AblePlayer.prototype.getPlaybackRate = function () {
181
+ if (this.player === 'html5') {
182
+ return this.media.playbackRate;
183
+ }
184
+ else if (this.player === 'jw' && this.jwPlayer) {
185
+ // Unsupported, always the normal rate.
186
+ return 1;
187
+ }
188
+ else if (this.player === 'youtube') {
189
+ return this.youTubePlayer.getPlaybackRate();
190
+ }
191
+ };
192
+
193
+ // Note there are three player states that count as paused in this sense,
194
+ // and one of them is named 'paused'.
195
+ // A better name would be 'isCurrentlyNotPlayingOrBuffering'
196
+ AblePlayer.prototype.isPaused = function () {
197
+ var state = this.getPlayerState();
198
+ return state === 'paused' || state === 'stopped' || state === 'ended';
199
+ };
200
+
201
+ AblePlayer.prototype.pauseMedia = function () {
202
+ if (this.player === 'html5') {
203
+ this.media.pause(true);
204
+ if (this.hasSignLanguage && this.signVideo) {
205
+ this.signVideo.pause(true);
206
+ }
207
+ }
208
+ else if (this.player === 'jw' && this.jwPlayer) {
209
+ this.jwPlayer.pause(true);
210
+ }
211
+ else if (this.player === 'youtube') {
212
+ this.youTubePlayer.pauseVideo();
213
+ }
214
+ };
215
+
216
+ AblePlayer.prototype.playMedia = function () {
217
+ if (this.player === 'html5') {
218
+ this.media.play(true);
219
+ if (this.hasSignLanguage && this.signVideo) {
220
+ this.signVideo.play(true);
221
+ }
222
+ }
223
+ else if (this.player === 'jw' && this.jwPlayer) {
224
+ this.jwPlayer.play(true);
225
+ }
226
+ else if (this.player === 'youtube') {
227
+ this.youTubePlayer.playVideo();
228
+ if (typeof this.$posterImg !== 'undefined') {
229
+ this.$posterImg.hide();
230
+ }
231
+ this.stoppingYouTube = false;
232
+ }
233
+ this.startedPlaying = true;
234
+ };
235
+
236
+ AblePlayer.prototype.refreshControls = function() {
237
+
238
+ var thisObj, duration, elapsed, lastChapterIndex, displayElapsed,
239
+ updateLive, textByState, timestamp, widthUsed,
240
+ leftControls, rightControls, seekbarWidth, seekbarSpacer, captionsCount,
241
+ buffered, newTop, svgLink, newSvgLink,
242
+ statusBarHeight, speedHeight, statusBarWidthBreakpoint;
243
+
244
+ thisObj = this;
245
+ if (this.swappingSrc) {
246
+ // wait until new source has loaded before refreshing controls
247
+ return;
248
+ }
249
+
250
+ duration = this.getDuration();
251
+ elapsed = this.getElapsed();
252
+
253
+ if (this.useChapterTimes) {
254
+ this.chapterDuration = this.getChapterDuration();
255
+ this.chapterElapsed = this.getChapterElapsed();
256
+ }
257
+
258
+ if (this.useFixedSeekInterval === false && this.seekIntervalCalculated === false && duration > 0) {
259
+ // couldn't calculate seekInterval previously; try again.
260
+ this.setSeekInterval();
261
+ }
262
+
263
+ if (this.seekBar) {
264
+
265
+ if (this.useChapterTimes) {
266
+ lastChapterIndex = this.selectedChapters.cues.length-1;
267
+ if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) {
268
+ // this is the last chapter
269
+ if (this.currentChapter.end !== duration) {
270
+ // chapter ends before or after video ends
271
+ // need to adjust seekbar duration to match video end
272
+ this.seekBar.setDuration(duration - this.currentChapter.start);
273
+ }
274
+ else {
275
+ this.seekBar.setDuration(this.chapterDuration);
276
+ }
277
+ }
278
+ else {
279
+ // this is not the last chapter
280
+ this.seekBar.setDuration(this.chapterDuration);
281
+ }
282
+ }
283
+ else {
284
+ this.seekBar.setDuration(duration);
285
+ }
286
+ if (!(this.seekBar.tracking)) {
287
+ // Only update the aria live region if we have an update pending (from a
288
+ // seek button control) or if the seekBar has focus.
289
+ // We use document.activeElement instead of $(':focus') due to a strange bug:
290
+ // When the seekHead element is focused, .is(':focus') is failing and $(':focus') is returning an undefined element.
291
+ updateLive = this.liveUpdatePending || this.seekBar.seekHead.is($(document.activeElement));
292
+ this.liveUpdatePending = false;
293
+ if (this.useChapterTimes) {
294
+ this.seekBar.setPosition(this.chapterElapsed, updateLive);
295
+ }
296
+ else {
297
+ this.seekBar.setPosition(elapsed, updateLive);
298
+ }
299
+ }
300
+
301
+ // When seeking, display the seek bar time instead of the actual elapsed time.
302
+ if (this.seekBar.tracking) {
303
+ displayElapsed = this.seekBar.lastTrackPosition;
304
+ }
305
+ else {
306
+ if (this.useChapterTimes) {
307
+ displayElapsed = this.chapterElapsed;
308
+ }
309
+ else {
310
+ displayElapsed = elapsed;
311
+ }
312
+ }
313
+ }
314
+ if (this.useChapterTimes) {
315
+ this.$durationContainer.text(' / ' + this.formatSecondsAsColonTime(this.chapterDuration));
316
+ }
317
+ else {
318
+ this.$durationContainer.text(' / ' + this.formatSecondsAsColonTime(duration));
319
+ }
320
+ this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed));
321
+
322
+ textByState = {
323
+ 'stopped': this.tt.statusStopped,
324
+ 'paused': this.tt.statusPaused,
325
+ 'playing': this.tt.statusPlaying,
326
+ 'buffering': this.tt.statusBuffering,
327
+ 'ended': this.tt.statusEnd
328
+ };
329
+
330
+ if (this.stoppingYouTube) {
331
+ // stoppingYouTube is true temporarily while video is paused and seeking to 0
332
+ // See notes in handleRestart()
333
+ // this.stoppingYouTube will be reset when seek to 0 is finished (in event.js > onMediaUpdateTime())
334
+ if (this.$status.text() !== this.tt.statusStopped) {
335
+ this.$status.text(this.tt.statusStopped);
336
+ }
337
+ if (this.$playpauseButton.find('span').first().hasClass('icon-pause')) {
338
+ if (this.iconType === 'font') {
339
+ this.$playpauseButton.find('span').first().removeClass('icon-pause').addClass('icon-play');
340
+ this.$playpauseButton.find('span.able-clipped').text(this.tt.play);
341
+ }
342
+ else if (this.iconType === 'svg') {
343
+ // TODO: Add play/pause toggle for SVG
344
+ }
345
+ else {
346
+ this.$playpauseButton.find('img').attr('src',this.playButtonImg);
347
+ }
348
+ }
349
+ }
350
+ else {
351
+ if (typeof this.$status !== 'undefined' && typeof this.seekBar !== 'undefined') {
352
+ // Update the text only if it's changed since it has role="alert";
353
+ // also don't update while tracking, since this may Pause/Play the player but we don't want to send a Pause/Play update.
354
+ if (this.$status.text() !== textByState[this.getPlayerState()] && !this.seekBar.tracking) {
355
+ // Debounce updates; only update after status has stayed steadily different for 250ms.
356
+ timestamp = (new Date()).getTime();
357
+ if (!this.statusDebounceStart) {
358
+ this.statusDebounceStart = timestamp;
359
+ // Make sure refreshControls gets called again at the appropriate time to check.
360
+ this.statusTimeout = setTimeout(function () {
361
+ thisObj.refreshControls();
362
+ }, 300);
363
+ }
364
+ else if ((timestamp - this.statusDebounceStart) > 250) {
365
+ this.$status.text(textByState[this.getPlayerState()]);
366
+ this.statusDebounceStart = null;
367
+ clearTimeout(this.statusTimeout);
368
+ this.statusTimeout = null;
369
+ }
370
+ }
371
+ else {
372
+ this.statusDebounceStart = null;
373
+ clearTimeout(this.statusTimeout);
374
+ this.statusTimeout = null;
375
+ }
376
+
377
+ // Don't change play/pause button display while using the seek bar (or if YouTube stopped)
378
+ if (!this.seekBar.tracking && !this.stoppingYouTube) {
379
+ if (this.isPaused()) {
380
+ this.$playpauseButton.attr('aria-label',this.tt.play);
381
+
382
+ if (this.iconType === 'font') {
383
+ this.$playpauseButton.find('span').first().removeClass('icon-pause').addClass('icon-play');
384
+ this.$playpauseButton.find('span.able-clipped').text(this.tt.play);
385
+ }
386
+ else if (this.iconType === 'svg') {
387
+ // Not currently working. SVG is a work in progress
388
+ this.$playpauseButton.find('svg').removeClass('svg-pause').addClass('svg-play');
389
+ svgLink = this.$playpauseButton.find('use').attr('xlink:href');
390
+ newSvgLink = svgLink.replace('svg-pause','svg-play');
391
+ this.$playpauseButton.find('use').attr(newSvgLink);
392
+ this.$playpauseButton.find('span.able-clipped').text(this.tt.play);
393
+ }
394
+ else {
395
+ this.$playpauseButton.find('img').attr('src',this.playButtonImg);
396
+ }
397
+ }
398
+ else {
399
+ this.$playpauseButton.attr('aria-label',this.tt.pause);
400
+
401
+ if (this.iconType === 'font') {
402
+ this.$playpauseButton.find('span').first().removeClass('icon-play').addClass('icon-pause');
403
+ this.$playpauseButton.find('span.able-clipped').text(this.tt.pause);
404
+ }
405
+ else if (this.iconType === 'svg') {
406
+ // Not currently working. SVG is a work in progress
407
+ this.$playpauseButton.find('svg').removeClass('svg-play').addClass('svg-pause');
408
+ svgLink = this.$playpauseButton.find('use').attr('xlink:href');
409
+ newSvgLink = svgLink.replace('svg-play','svg-pause');
410
+ this.$playpauseButton.find('use').attr(newSvgLink);
411
+ this.$playpauseButton.find('span.able-clipped').text(this.tt.pause);
412
+ }
413
+ else {
414
+ this.$playpauseButton.find('img').attr('src',this.pauseButtonImg);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // Update seekbar width.
422
+ // To do this, we need to calculate the width of all buttons surrounding it.
423
+ if (this.seekBar) {
424
+ widthUsed = 0;
425
+ seekbarSpacer = 40; // adjust for discrepancies in browsers' calculated button widths
426
+
427
+ leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls');
428
+ rightControls = leftControls.next('div.able-right-controls');
429
+ leftControls.children().each(function () {
430
+ if ($(this).prop('tagName')=='BUTTON') {
431
+ widthUsed += $(this).width();
432
+ }
433
+ });
434
+ rightControls.children().each(function () {
435
+ if ($(this).prop('tagName')=='BUTTON') {
436
+ widthUsed += $(this).width();
437
+ }
438
+ });
439
+ if (this.isFullscreen()) {
440
+ seekbarWidth = $(window).width() - widthUsed - seekbarSpacer;
441
+ }
442
+ else {
443
+ seekbarWidth = this.$ableWrapper.width() - widthUsed - seekbarSpacer;
444
+ }
445
+ // Sometimes some minor fluctuations based on browser weirdness, so set a threshold.
446
+ if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) {
447
+ this.seekBar.setWidth(seekbarWidth);
448
+ }
449
+ }
450
+
451
+ // Show/hide status bar content conditionally
452
+ if (!this.isFullscreen()) {
453
+ statusBarWidthBreakpoint = 300;
454
+ statusBarHeight = this.$statusBarDiv.height();
455
+ speedHeight = this.$statusBarDiv.find('span.able-speed').height();
456
+ if (speedHeight > (statusBarHeight + 5)) {
457
+ // speed bar is wrapping (happens often in German player)
458
+ this.$statusBarDiv.find('span.able-speed').hide();
459
+ this.hidingSpeed = true;
460
+ }
461
+ else {
462
+ if (this.hidingSpeed) {
463
+ this.$statusBarDiv.find('span.able-speed').show();
464
+ this.hidingSpeed = false;
465
+ }
466
+ if (this.$statusBarDiv.width() < statusBarWidthBreakpoint) {
467
+ // Player is too small for a speed span
468
+ this.$statusBarDiv.find('span.able-speed').hide();
469
+ this.hidingSpeed = true;
470
+ }
471
+ else {
472
+ if (this.hidingSpeed) {
473
+ this.$statusBarDiv.find('span.able-speed').show();
474
+ this.hidingSpeed = false;
475
+ }
476
+ }
477
+ }
478
+ }
479
+
480
+ if (this.$descButton) {
481
+ if (this.descOn) {
482
+ this.$descButton.removeClass('buttonOff').attr('aria-label',this.tt.turnOffDescriptions);
483
+ this.$descButton.find('span.able-clipped').text(this.tt.turnOffDescriptions);
484
+ }
485
+ else {
486
+ this.$descButton.addClass('buttonOff').attr('aria-label',this.tt.turnOnDescriptions);
487
+ this.$descButton.find('span.able-clipped').text(this.tt.turnOnDescriptions);
488
+ }
489
+ }
490
+
491
+ if (this.$ccButton) {
492
+ if (this.usingYouTubeCaptions) {
493
+ captionsCount = this.ytCaptions.length;
494
+ }
495
+ else {
496
+ captionsCount = this.captions.length;
497
+ }
498
+ // Button has a different title depending on the number of captions.
499
+ // If only one caption track, this is "Show captions" and "Hide captions"
500
+ // Otherwise, it is just always "Captions"
501
+ if (!this.captionsOn) {
502
+ this.$ccButton.addClass('buttonOff');
503
+ if (captionsCount === 1) {
504
+ this.$ccButton.attr('aria-label',this.tt.showCaptions);
505
+ this.$ccButton.find('span.able-clipped').text(this.tt.showCaptions);
506
+ }
507
+ }
508
+ else {
509
+ this.$ccButton.removeClass('buttonOff');
510
+ if (captionsCount === 1) {
511
+ this.$ccButton.attr('aria-label',this.tt.hideCaptions);
512
+ this.$ccButton.find('span.able-clipped').text(this.tt.hideCaptions);
513
+ }
514
+ }
515
+
516
+ if (captionsCount > 1) {
517
+ this.$ccButton.attr({
518
+ 'aria-label': this.tt.captions,
519
+ 'aria-haspopup': 'true',
520
+ 'aria-controls': this.mediaId + '-captions-menu'
521
+ });
522
+ this.$ccButton.find('span.able-clipped').text(this.tt.captions);
523
+ }
524
+ }
525
+
526
+ if (this.$chaptersButton) {
527
+ this.$chaptersButton.attr({
528
+ 'aria-label': this.tt.chapters,
529
+ 'aria-haspopup': 'true',
530
+ 'aria-controls': this.mediaId + '-chapters-menu'
531
+ });
532
+ }
533
+ if (this.$fullscreenButton) {
534
+ if (!this.isFullscreen()) {
535
+ this.$fullscreenButton.attr('aria-label', this.tt.enterFullScreen);
536
+ if (this.iconType === 'font') {
537
+ this.$fullscreenButton.find('span').first().removeClass('icon-fullscreen-collapse').addClass('icon-fullscreen-expand');
538
+ this.$fullscreenButton.find('span.able-clipped').text(this.tt.enterFullScreen);
539
+ }
540
+ else if (this.iconType === 'svg') {
541
+ // Not currently working. SVG is a work in progress.
542
+ this.$fullscreenButton.find('svg').removeClass('icon-fullscreen-collapse').addClass('icon-fullscreen-expand');
543
+ this.$fullscreenButton.find('span.able-clipped').text(this.tt.enterFullScreen);
544
+ }
545
+ else {
546
+ this.$fullscreenButton.find('img').attr('src',this.fullscreenExpandButtonImg);
547
+ }
548
+ }
549
+ else {
550
+ this.$fullscreenButton.attr('aria-label',this.tt.exitFullScreen);
551
+ if (this.iconType === 'font') {
552
+ this.$fullscreenButton.find('span').first().removeClass('icon-fullscreen-expand').addClass('icon-fullscreen-collapse');
553
+ this.$fullscreenButton.find('span.able-clipped').text(this.tt.exitFullScreen);
554
+ }
555
+ else if (this.iconType === 'svg') {
556
+ // Not currently working. SVG is a work in progress.
557
+ this.$fullscreenButton.find('svg').removeClass('icon-fullscreen-expand').addClass('icon-fullscreen-collapse');
558
+ this.$fullscreenButton.find('span.able-clipped').text(this.tt.exitFullScreen);
559
+ }
560
+ else {
561
+ this.$fullscreenButton.find('img').attr('src',this.fullscreenCollapseButtonImg);
562
+ }
563
+ }
564
+ }
565
+
566
+ if (typeof this.$bigPlayButton !== 'undefined') {
567
+ // Choose show/hide for big play button and adjust position.
568
+ if (this.isPaused() && !this.seekBar.tracking) {
569
+ if (!this.hideBigPlayButton) {
570
+ this.$bigPlayButton.show();
571
+ }
572
+ if (this.isFullscreen()) {
573
+ this.$bigPlayButton.width($(window).width());
574
+ this.$bigPlayButton.height($(window).height());
575
+ }
576
+ else {
577
+ this.$bigPlayButton.width(this.$mediaContainer.width());
578
+ this.$bigPlayButton.height(this.$mediaContainer.height());
579
+ }
580
+ }
581
+ else {
582
+ this.$bigPlayButton.hide();
583
+ }
584
+ }
585
+
586
+ if (this.transcriptType) {
587
+ // Sync checkbox and autoScrollTranscript with user preference
588
+ if (this.prefAutoScrollTranscript === 1) {
589
+ this.autoScrollTranscript = true;
590
+ this.$autoScrollTranscriptCheckbox.prop('checked',true);
591
+ }
592
+ else {
593
+ this.autoScrollTranscript = false;
594
+ this.$autoScrollTranscriptCheckbox.prop('checked',false);
595
+ }
596
+
597
+ // If transcript locked, scroll transcript to current highlight location.
598
+ if (this.autoScrollTranscript && this.currentHighlight) {
599
+ newTop = Math.floor($('.able-transcript').scrollTop() +
600
+ $(this.currentHighlight).position().top -
601
+ ($('.able-transcript').height() / 2) +
602
+ ($(this.currentHighlight).height() / 2));
603
+ if (newTop !== Math.floor($('.able-transcript').scrollTop())) {
604
+ // Set a flag to ignore the coming scroll event.
605
+ // there's no other way I know of to differentiate programmatic and user-initiated scroll events.
606
+ this.scrollingTranscript = true;
607
+ $('.able-transcript').scrollTop(newTop);
608
+ }
609
+ }
610
+ }
611
+
612
+ // Update buffering progress.
613
+ // TODO: Currently only using the first HTML5 buffered interval, but this fails sometimes when buffering is split into two or more intervals.
614
+ if (this.player === 'html5') {
615
+ if (this.media.buffered.length > 0) {
616
+ buffered = this.media.buffered.end(0)
617
+ if (this.useChapterTimes) {
618
+ if (buffered > this.chapterDuration) {
619
+ buffered = this.chapterDuration;
620
+ }
621
+ if (this.seekBar) {
622
+ this.seekBar.setBuffered(buffered / this.chapterDuration);
623
+ }
624
+ }
625
+ else {
626
+ if (this.seekBar) {
627
+ this.seekBar.setBuffered(buffered / duration);
628
+ }
629
+ }
630
+ }
631
+ }
632
+ else if (this.player === 'jw' && this.jwPlayer) {
633
+ if (this.seekBar) {
634
+ this.seekBar.setBuffered(this.jwPlayer.getBuffer() / 100);
635
+ }
636
+ }
637
+ else if (this.player === 'youtube') {
638
+ if (this.seekBar) {
639
+ this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction());
640
+ }
641
+ }
642
+ };
643
+
644
+ AblePlayer.prototype.getHiddenWidth = function($el) {
645
+
646
+ // jQuery returns for width() if element is hidden
647
+ // this function is a workaround
648
+
649
+ // save a reference to a cloned element that can be measured
650
+ var $hiddenElement = $el.clone().appendTo('body');
651
+
652
+ // calculate the width of the clone
653
+ var width = $hiddenElement.outerWidth();
654
+
655
+ // remove the clone from the DOM
656
+ $hiddenElement.remove();
657
+
658
+ return width;
659
+ };
660
+
661
+ AblePlayer.prototype.handlePlay = function(e) {
662
+ if (this.isPaused()) {
663
+ this.playMedia();
664
+ }
665
+ else {
666
+ this.pauseMedia();
667
+ }
668
+ this.refreshControls();
669
+ };
670
+
671
+ AblePlayer.prototype.handleRestart = function() {
672
+
673
+ this.seekTo(0);
674
+
675
+ /*
676
+ // Prior to 2.3.68, this function was handleStop()
677
+ // which was a bit more challenging to implement
678
+ // Preserved here in case Stop is ever cool again...
679
+
680
+ var thisObj = this;
681
+ if (this.player == 'html5') {
682
+ this.pauseMedia();
683
+ this.seekTo(0);
684
+ }
685
+ else if (this.player === 'jw' && this.jwPlayer) {
686
+ this.jwPlayer.stop();
687
+ }
688
+ else if (this.player === 'youtube') {
689
+ // YouTube API function stopVideo() does not reset video to 0
690
+ // Also, the stopped video is not seekable so seekTo(0) after stopping doesn't work
691
+ // Workaround is to use pauseVideo(), followed by seekTo(0) to emulate stopping
692
+ // However, the tradeoff is that YouTube doesn't restore the poster image when video is paused
693
+ // Added 12/29/15: After seekTo(0) is finished, stopVideo() to reset video and restore poster image
694
+ // This final step is handled in event.js > onMediaUpdate()
695
+ this.youTubePlayer.pauseVideo();
696
+ this.seekTo(0);
697
+ this.stoppingYouTube = true;
698
+ }
699
+ */
700
+ this.refreshControls();
701
+ };
702
+
703
+ AblePlayer.prototype.handleRewind = function() {
704
+
705
+ var elapsed, targetTime;
706
+
707
+ elapsed = this.getElapsed();
708
+ targetTime = elapsed - this.seekInterval;
709
+ if (this.useChapterTimes) {
710
+ if (targetTime < this.currentChapter.start) {
711
+ targetTime = this.currentChapter.start;
712
+ }
713
+ }
714
+ else {
715
+ if (targetTime < 0) {
716
+ targetTime = 0;
717
+ }
718
+ }
719
+ this.seekTo(targetTime);
720
+ };
721
+
722
+ AblePlayer.prototype.handleFastForward = function() {
723
+
724
+ var elapsed, duration, targetTime, lastChapterIndex;
725
+
726
+ elapsed = this.getElapsed();
727
+ duration = this.getDuration();
728
+ lastChapterIndex = this.chapters.length-1;
729
+ targetTime = elapsed + this.seekInterval;
730
+
731
+ if (this.useChapterTimes) {
732
+ if (this.chapters[lastChapterIndex] == this.currentChapter) {
733
+ // this is the last chapter
734
+ if (targetTime > duration || targetTime > this.currentChapter.end) {
735
+ // targetTime would exceed the end of the video (or chapter)
736
+ // scrub to end of whichever is earliest
737
+ targetTime = Math.min(duration, this.currentChapter.end);
738
+ }
739
+ else if (duration % targetTime < this.seekInterval) {
740
+ // nothing left but pocket change after seeking to targetTime
741
+ // go ahead and seek to end of video (or chapter), whichever is earliest
742
+ targetTime = Math.min(duration, this.currentChapter.end);
743
+ }
744
+ }
745
+ else {
746
+ // this is not the last chapter
747
+ if (targetTime > this.currentChapter.end) {
748
+ // targetTime would exceed the end of the chapter
749
+ // scrub exactly to end of chapter
750
+ targetTime = this.currentChapter.end;
751
+ }
752
+ }
753
+ }
754
+ else {
755
+ // not using chapter times
756
+ if (targetTime > duration) {
757
+ targetTime = duration;
758
+ }
759
+ }
760
+ this.seekTo(targetTime);
761
+ };
762
+
763
+ AblePlayer.prototype.handleRateIncrease = function() {
764
+ this.changeRate(1);
765
+ };
766
+
767
+ AblePlayer.prototype.handleRateDecrease = function() {
768
+ this.changeRate(-1);
769
+ };
770
+
771
+ // Increases or decreases playback rate, where dir is 1 or -1 indication direction.
772
+ AblePlayer.prototype.changeRate = function (dir) {
773
+ if (this.player === 'html5') {
774
+ this.setPlaybackRate(this.getPlaybackRate() + (0.25 * dir));
775
+ }
776
+ else if (this.player === 'youtube') {
777
+ var rates = this.youTubePlayer.getAvailablePlaybackRates();
778
+ var currentRate = this.getPlaybackRate();
779
+ var index = rates.indexOf(currentRate);
780
+ if (index === -1) {
781
+ console.log('ERROR: Youtube returning unknown playback rate ' + currentRate.toString());
782
+ }
783
+ else {
784
+ index += dir;
785
+ // Can only increase or decrease rate if there's another rate available.
786
+ if (index < rates.length && index >= 0) {
787
+ this.setPlaybackRate(rates[index]);
788
+ }
789
+ }
790
+ }
791
+ };
792
+
793
+ AblePlayer.prototype.handleCaptionToggle = function() {
794
+
795
+ var captions;
796
+
797
+ if (this.hidingPopup) {
798
+ // stopgap to prevent spacebar in Firefox from reopening popup
799
+ // immediately after closing it
800
+ this.hidingPopup = false;
801
+ return false;
802
+ }
803
+ if (this.captions.length) {
804
+ captions = this.captions;
805
+ }
806
+ else if (this.ytCaptions.length) {
807
+ captions = this.ytCaptions;
808
+ }
809
+ else {
810
+ captions = [];
811
+ }
812
+ if (captions.length === 1) {
813
+
814
+ // When there's only one set of captions, just do an on/off toggle.
815
+ if (this.captionsOn === true) {
816
+ // turn them off
817
+ this.captionsOn = false;
818
+ this.prefCaptions = 0;
819
+ this.updateCookie('prefCaptions');
820
+ if (this.usingYouTubeCaptions) {
821
+ this.youTubePlayer.unloadModule(this.ytCaptionModule);
822
+ }
823
+ else {
824
+ this.$captionsWrapper.hide();
825
+ }
826
+ }
827
+ else {
828
+ // captions are off. Turn them on.
829
+ this.captionsOn = true;
830
+ this.prefCaptions = 1;
831
+ this.updateCookie('prefCaptions');
832
+ if (this.usingYouTubeCaptions) {
833
+ if (typeof this.ytCaptionModule !== 'undefined') {
834
+ this.youTubePlayer.loadModule(this.ytCaptionModule);
835
+ }
836
+ }
837
+ else {
838
+ this.$captionsWrapper.show();
839
+ }
840
+ for (var i=0; i<captions.length; i++) {
841
+ if (captions[i].def === true) { // this is the default language
842
+ this.selectedCaptions = captions[i];
843
+ }
844
+ }
845
+ this.selectedCaptions = this.captions[0];
846
+ if (this.descriptions.length >= 0) {
847
+ this.selectedDescriptions = this.descriptions[0];
848
+ }
849
+ }
850
+ this.refreshControls();
851
+ }
852
+ else {
853
+
854
+ // there is more than one caption track.
855
+ // clicking on a track is handled via caption.js > getCaptionClickFunction()
856
+ if (this.captionsPopup.is(':visible')) {
857
+ this.captionsPopup.hide();
858
+ this.hidingPopup = false;
859
+ this.$ccButton.focus();
860
+ }
861
+ else {
862
+ this.closePopups();
863
+ this.captionsPopup.show();
864
+ this.captionsPopup.css('top', this.$ccButton.position().top - this.captionsPopup.outerHeight());
865
+ this.captionsPopup.css('left', this.$ccButton.position().left)
866
+ // Focus on the checked button, if any buttons are checked
867
+ // Otherwise, focus on the first button
868
+ this.captionsPopup.find('li').removeClass('able-focus');
869
+ if (this.captionsPopup.find('input:checked')) {
870
+ this.captionsPopup.find('input:checked').focus().parent().addClass('able-focus');
871
+ }
872
+ else {
873
+ this.captionsPopup.find('input').first().focus().parent().addClass('able-focus');
874
+ }
875
+ }
876
+ }
877
+ };
878
+
879
+ AblePlayer.prototype.handleChapters = function () {
880
+ if (this.hidingPopup) {
881
+ // stopgap to prevent spacebar in Firefox from reopening popup
882
+ // immediately after closing it
883
+ this.hidingPopup = false;
884
+ return false;
885
+ }
886
+ if (this.chaptersPopup.is(':visible')) {
887
+ this.chaptersPopup.hide();
888
+ this.hidingPopup = false;
889
+ this.$chaptersButton.focus();
890
+ }
891
+ else {
892
+ this.closePopups();
893
+ this.chaptersPopup.show();
894
+ this.chaptersPopup.css('top', this.$chaptersButton.position().top - this.chaptersPopup.outerHeight());
895
+ this.chaptersPopup.css('left', this.$chaptersButton.position().left)
896
+ // Focus on the checked button, if any buttons are checked
897
+ // Otherwise, focus on the first button
898
+ this.chaptersPopup.find('li').removeClass('able-focus');
899
+ if (this.chaptersPopup.find('input:checked')) {
900
+ this.chaptersPopup.find('input:checked').focus().parent().addClass('able-focus');
901
+ }
902
+ else {
903
+ this.chaptersPopup.find('input').first().focus().parent().addClass('able-focus');
904
+ }
905
+ }
906
+ };
907
+
908
+ AblePlayer.prototype.handleDescriptionToggle = function() {
909
+ this.descOn = !this.descOn;
910
+ this.prefDesc = + this.descOn; // convert boolean to integer
911
+ this.updateCookie('prefDesc');
912
+ this.refreshingDesc = true;
913
+ this.initDescription();
914
+ this.refreshControls();
915
+ };
916
+
917
+ AblePlayer.prototype.handlePrefsClick = function(pref) {
918
+
919
+ // NOTE: the prefs menu is positioned near the right edge of the player
920
+ // This assumes the Prefs button is also positioned in that vicinity
921
+ // (last or second-last button the right)
922
+
923
+ var prefsButtonPosition, prefsMenuRight, prefsMenuLeft;
924
+
925
+ if (this.hidingPopup) {
926
+ // stopgap to prevent spacebar in Firefox from reopening popup
927
+ // immediately after closing it
928
+ this.hidingPopup = false;
929
+ return false;
930
+ }
931
+ if (this.prefsPopup.is(':visible')) {
932
+ this.prefsPopup.hide();
933
+ this.hidingPopup = false;
934
+ this.$prefsButton.focus();
935
+ }
936
+ else {
937
+ this.closePopups();
938
+ this.prefsPopup.show();
939
+ prefsButtonPosition = this.$prefsButton.position();
940
+ prefsMenuRight = this.$ableDiv.width() - 5;
941
+ prefsMenuLeft = prefsMenuRight - this.prefsPopup.width();
942
+ this.prefsPopup.css('top', prefsButtonPosition.top - this.prefsPopup.outerHeight());
943
+ this.prefsPopup.css('left', prefsMenuLeft);
944
+ // remove prior focus and set focus on first item
945
+ this.prefsPopup.find('li').removeClass('able-focus');
946
+ this.prefsPopup.find('input').first().focus().parent().addClass('able-focus');
947
+ }
948
+ };
949
+
950
+ AblePlayer.prototype.handleHelpClick = function() {
951
+ this.setFullscreen(false);
952
+ this.helpDialog.show();
953
+ };
954
+
955
+ AblePlayer.prototype.handleTranscriptToggle = function () {
956
+ if (this.$transcriptDiv.is(':visible')) {
957
+ this.$transcriptArea.hide();
958
+ this.$transcriptButton.addClass('buttonOff').attr('aria-label',this.tt.showTranscript);
959
+ this.$transcriptButton.find('span.able-clipped').text(this.tt.showTranscript);
960
+ this.prefTranscript = 0;
961
+ }
962
+ else {
963
+ this.positionDraggableWindow('transcript');
964
+ this.$transcriptArea.show();
965
+ this.$transcriptButton.removeClass('buttonOff').attr('aria-label',this.tt.hideTranscript);
966
+ this.$transcriptButton.find('span.able-clipped').text(this.tt.hideTranscript);
967
+ this.prefTranscript = 1;
968
+ }
969
+ this.updateCookie('prefTranscript');
970
+ };
971
+
972
+ AblePlayer.prototype.handleSignToggle = function () {
973
+ if (this.$signWindow.is(':visible')) {
974
+ this.$signWindow.hide();
975
+ this.$signButton.addClass('buttonOff').attr('aria-label',this.tt.showSign);
976
+ this.$signButton.find('span.able-clipped').text(this.tt.showSign);
977
+ this.prefSign = 0;
978
+ }
979
+ else {
980
+ this.positionDraggableWindow('sign');
981
+ this.$signWindow.show();
982
+ this.$signButton.removeClass('buttonOff').attr('aria-label',this.tt.hideSign);
983
+ this.$signButton.find('span.able-clipped').text(this.tt.hideSign);
984
+ this.prefSign = 1;
985
+ }
986
+ this.updateCookie('prefSign');
987
+ };
988
+
989
+ AblePlayer.prototype.isFullscreen = function () {
990
+ if (this.nativeFullscreenSupported()) {
991
+ return (document.fullscreenElement ||
992
+ document.webkitFullscreenElement ||
993
+ document.webkitCurrentFullScreenElement ||
994
+ document.mozFullScreenElement ||
995
+ document.msFullscreenElement) ? true : false;
996
+ }
997
+ else {
998
+ return this.modalFullscreenActive ? true : false;
999
+ }
1000
+ }
1001
+
1002
+ AblePlayer.prototype.setFullscreen = function (fullscreen) {
1003
+ if (this.isFullscreen() == fullscreen) {
1004
+ return;
1005
+ }
1006
+ var thisObj = this;
1007
+ var $el = this.$ableWrapper;
1008
+ var el = $el[0];
1009
+
1010
+ if (this.nativeFullscreenSupported()) {
1011
+ // Note: many varying names for options for browser compatibility.
1012
+ if (fullscreen) {
1013
+ // Initialize fullscreen
1014
+
1015
+ // But first, capture current settings so they can be restored later
1016
+ this.preFullScreenWidth = this.$ableWrapper.width();
1017
+ this.preFullScreenHeight = this.$ableWrapper.height();
1018
+
1019
+ if (el.requestFullscreen) {
1020
+ el.requestFullscreen();
1021
+ }
1022
+ else if (el.webkitRequestFullscreen) {
1023
+ el.webkitRequestFullscreen();
1024
+ }
1025
+ else if (el.mozRequestFullScreen) {
1026
+ el.mozRequestFullScreen();
1027
+ }
1028
+ else if (el.msRequestFullscreen) {
1029
+ el.msRequestFullscreen();
1030
+ }
1031
+ }
1032
+ else {
1033
+ // Exit fullscreen
1034
+ if (document.exitFullscreen) {
1035
+ document.exitFullscreen();
1036
+ }
1037
+ else if (document.webkitExitFullscreen) {
1038
+ document.webkitExitFullscreen();
1039
+ }
1040
+ else if (document.webkitCancelFullScreen) {
1041
+ document.webkitCancelFullScreen();
1042
+ }
1043
+ else if (document.mozCancelFullScreen) {
1044
+ document.mozCancelFullScreen();
1045
+ }
1046
+ else if (document.msExitFullscreen) {
1047
+ document.msExitFullscreen();
1048
+ }
1049
+ }
1050
+ // add event handlers for changes in full screen mode
1051
+ // currently most changes are made in response to windowResize event
1052
+ // However, that alone is not resulting in a properly restored player size in Opera Mac
1053
+ // More on the Opera Mac bug: https://github.com/ableplayer/ableplayer/issues/162
1054
+ // this fullscreen event handler added specifically for Opera Mac,
1055
+ // but includes event listeners for all browsers in case its functionality could be expanded
1056
+ // Added functionality in 2.3.45 for handling YouTube return from fullscreen as well
1057
+ $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', function() {
1058
+ if (!thisObj.isFullscreen()) {
1059
+ // user has just exited full screen
1060
+ thisObj.restoringAfterFullScreen = true;
1061
+ thisObj.resizePlayer(thisObj.preFullScreenWidth,thisObj.preFullScreenHeight);
1062
+ }
1063
+ });
1064
+ }
1065
+ else {
1066
+ // Non-native fullscreen support through modal dialog.
1067
+ // Create dialog on first run through.
1068
+ if (!this.fullscreenDialog) {
1069
+ var $dialogDiv = $('<div>');
1070
+ // create a hidden alert, communicated to screen readers via aria-describedby
1071
+ var $fsDialogAlert = $('<p>',{
1072
+ 'class': 'able-screenreader-alert'
1073
+ }).text(this.tt.fullscreen); // In English: "Full screen"; TODO: Add alert text that is more descriptive
1074
+ $dialogDiv.append($fsDialogAlert);
1075
+ // now render this as a dialog
1076
+ this.fullscreenDialog = new AccessibleDialog($dialogDiv, this.$fullscreenButton, 'dialog', 'Fullscreen video player', $fsDialogAlert, this.tt.exitFullScreen, '100%', true, function () { thisObj.handleFullscreenToggle() });
1077
+ $('body').append($dialogDiv);
1078
+ }
1079
+
1080
+ // Track whether paused/playing before moving element; moving the element can stop playback.
1081
+ var wasPaused = this.isPaused();
1082
+
1083
+ if (fullscreen) {
1084
+ this.modalFullscreenActive = true;
1085
+ this.fullscreenDialog.show();
1086
+
1087
+ // Move player element into fullscreen dialog, then show.
1088
+ // Put a placeholder element where player was.
1089
+ this.$modalFullscreenPlaceholder = $('<div class="placeholder">');
1090
+ this.$modalFullscreenPlaceholder.insertAfter($el);
1091
+ $el.appendTo(this.fullscreenDialog.modal);
1092
+
1093
+ // Column left css is 50% by default; set to 100% for full screen.
1094
+ if ($el === this.$ableColumnLeft) {
1095
+ $el.width('100%');
1096
+ }
1097
+ var newHeight = $(window).height() - this.$playerDiv.height();
1098
+ if (!this.$descDiv.is(':hidden')) {
1099
+ newHeight -= this.$descDiv.height();
1100
+ }
1101
+ this.resizePlayer($(window).width(), newHeight);
1102
+ }
1103
+ else {
1104
+ this.modalFullscreenActive = false;
1105
+ if ($el === this.$ableColumnLeft) {
1106
+ $el.width('50%');
1107
+ }
1108
+ $el.insertAfter(this.$modalFullscreenPlaceholder);
1109
+ this.$modalFullscreenPlaceholder.remove();
1110
+ this.fullscreenDialog.hide();
1111
+ this.resizePlayer(this.$ableWrapper.width(), this.$ableWrapper.height());
1112
+ }
1113
+
1114
+ // TODO: JW Player freezes after being moved on iPads (instead of being reset as in most browsers)
1115
+ // Need to call setup again after moving?
1116
+
1117
+ // Resume playback if moving stopped it.
1118
+ if (!wasPaused && this.isPaused()) {
1119
+ this.playMedia();
1120
+ }
1121
+ }
1122
+ this.refreshControls();
1123
+ };
1124
+
1125
+ AblePlayer.prototype.handleFullscreenToggle = function () {
1126
+ var stillPaused = this.isPaused(); //add boolean variable reading return from isPaused function
1127
+ this.setFullscreen(!this.isFullscreen());
1128
+ if (stillPaused) {
1129
+ this.pauseMedia(); // when toggling fullscreen and media is just paused, keep media paused.
1130
+ }
1131
+ else if (!stillPaused) {
1132
+ this.playMedia(); // when toggling fullscreen and media is playing, continue playing.
1133
+ }
1134
+ };
1135
+
1136
+ AblePlayer.prototype.handleTranscriptLockToggle = function (val) {
1137
+
1138
+ this.autoScrollTranscript = val; // val is boolean
1139
+ this.prefAutoScrollTranscript = +val; // convert boolean to numeric 1 or 0 for cookie
1140
+ this.updateCookie('prefAutoScrollTranscript');
1141
+ this.refreshControls();
1142
+ };
1143
+
1144
+
1145
+ AblePlayer.prototype.showTooltip = function($tooltip) {
1146
+
1147
+ if (($tooltip).is(':animated')) {
1148
+ $tooltip.stop(true,true).show().delay(4000).fadeOut(1000);
1149
+ }
1150
+ else {
1151
+ $tooltip.stop().show().delay(4000).fadeOut(1000);
1152
+ }
1153
+ };
1154
+
1155
+ AblePlayer.prototype.showAlert = function( msg, location ) {
1156
+
1157
+ // location is either of the following:
1158
+ // 'main' (default)
1159
+ // 'screenreader
1160
+ // 'sign' (sign language window)
1161
+ // 'transcript' (trasncript window)
1162
+ var thisObj, $alertBox, $parentWindow, alertLeft, alertTop;
1163
+
1164
+ thisObj = this;
1165
+
1166
+ if (location === 'transcript') {
1167
+ $alertBox = this.$transcriptAlert;
1168
+ $parentWindow = this.$transcriptArea;
1169
+ }
1170
+ else if (location === 'sign') {
1171
+ $alertBox = this.$signAlert;
1172
+ $parentWindow = this.$signWindow;
1173
+ }
1174
+ else if (location === 'screenreader') {
1175
+ $alertBox = this.$srAlertBox;
1176
+ }
1177
+ else {
1178
+ $alertBox = this.$alertBox;
1179
+ }
1180
+ $alertBox.show();
1181
+ $alertBox.text(msg);
1182
+ if (location == 'transcript' || location === 'sign') {
1183
+ if ($parentWindow.width() > $alertBox.width()) {
1184
+ alertLeft = $parentWindow.width() / 2 - $alertBox.width() / 2;
1185
+ }
1186
+ else {
1187
+ // alert box is wider than its container. Position it far left and let it wrap
1188
+ alertLeft = 10;
1189
+ }
1190
+ if (location === 'sign') {
1191
+ // position alert in the lower third of the sign window (to avoid covering the signer)
1192
+ alertTop = ($parentWindow.height() / 3) * 2;
1193
+ }
1194
+ else if (location === 'transcript') {
1195
+ // position alert just beneath the toolbar to avoid getting lost among transcript text
1196
+ alertTop = this.$transcriptToolbar.height() + 30;
1197
+ }
1198
+ $alertBox.css({
1199
+ top: alertTop + 'px',
1200
+ left: alertLeft + 'px'
1201
+ });
1202
+ }
1203
+ else if (location !== 'screenreader') {
1204
+ // The original formula incorporated offset() into the calculation
1205
+ // but at some point this began resulting in an alert that's off-centered
1206
+ // Changed in v2.2.17, but here's the original for reference in case needed:
1207
+ // left: this.$playerDiv.offset().left + (this.$playerDiv.width() / 2) - ($alertBox.width() / 2)
1208
+ $alertBox.css({
1209
+ left: (this.$playerDiv.width() / 2) - ($alertBox.width() / 2)
1210
+ });
1211
+ }
1212
+ if (location !== 'screenreader') {
1213
+ setTimeout(function () {
1214
+ $alertBox.fadeOut(300);
1215
+ }, 3000);
1216
+ }
1217
+ };
1218
+
1219
+ AblePlayer.prototype.showedAlert = function (which) {
1220
+
1221
+ // returns true if the target alert has already been shown
1222
+ // useful for throttling alerts that only need to be shown once
1223
+ // e.g., move alerts with instructions for dragging a window
1224
+
1225
+ if (which === 'transcript') {
1226
+ if (this.showedTranscriptAlert) {
1227
+ return true;
1228
+ }
1229
+ else {
1230
+ return false;
1231
+ }
1232
+ }
1233
+ else if (which === 'sign') {
1234
+ if (this.showedSignAlert) {
1235
+ return true;
1236
+ }
1237
+ else {
1238
+ return false;
1239
+ }
1240
+ }
1241
+ return false;
1242
+ }
1243
+
1244
+ // Resizes all relevant player attributes.
1245
+ AblePlayer.prototype.resizePlayer = function (width, height) {
1246
+
1247
+ var jwHeight, captionSizeOkMin, captionSizeOkMax, captionSize, newCaptionSize, newLineHeight;
1248
+
1249
+ if (this.isFullscreen()) {
1250
+ if (typeof this.$vidcapContainer !== 'undefined') {
1251
+ this.$ableWrapper.css({
1252
+ 'width': width + 'px',
1253
+ 'max-width': ''
1254
+ })
1255
+ this.$vidcapContainer.css({
1256
+ 'height': height + 'px',
1257
+ 'width': width
1258
+ });
1259
+ this.$media.css({
1260
+ 'height': height + 'px',
1261
+ 'width': width
1262
+ })
1263
+ }
1264
+ if (typeof this.$transcriptArea !== 'undefined') {
1265
+ this.retrieveOffscreenWindow('transcript',width,height);
1266
+ }
1267
+ if (typeof this.$signWindow !== 'undefined') {
1268
+ this.retrieveOffscreenWindow('sign',width,height);
1269
+ }
1270
+ }
1271
+ else {
1272
+ // player resized
1273
+ if (this.restoringAfterFullScreen) {
1274
+ // User has just exited fullscreen mode. Restore to previous settings
1275
+ width = this.preFullScreenWidth;
1276
+ height = this.preFullScreenHeight;
1277
+ this.restoringAfterFullScreen = false;
1278
+ this.$ableWrapper.css({
1279
+ 'max-width': width + 'px',
1280
+ 'width': ''
1281
+ });
1282
+ if (typeof this.$vidcapContainer !== 'undefined') {
1283
+ this.$vidcapContainer.css({
1284
+ 'height': '',
1285
+ 'width': ''
1286
+ });
1287
+ }
1288
+ this.$media.css({
1289
+ 'width': '100%',
1290
+ 'height': 'auto'
1291
+ });
1292
+ }
1293
+ }
1294
+
1295
+ // resize YouTube or JW Player
1296
+ if (this.player === 'youtube' && this.youTubePlayer) {
1297
+ this.youTubePlayer.setSize(width, height);
1298
+ }
1299
+ else if (this.player === 'jw' && this.jwPlayer) {
1300
+ if (this.mediaType === 'audio') {
1301
+ // keep height set to 0 to prevent JW PLayer from showing its own player
1302
+ this.jwPlayer.resize(width,0);
1303
+ }
1304
+ else {
1305
+ this.jwPlayer.resize(width, jwHeight);
1306
+ }
1307
+ }
1308
+
1309
+ // Resize captions
1310
+ if (typeof this.$captionsDiv !== 'undefined') {
1311
+
1312
+ // Font-size is too small in full screen view & too large in small-width view
1313
+ // The following vars define a somewhat arbitary zone outside of which
1314
+ // caption size requires adjustment
1315
+ captionSizeOkMin = 400;
1316
+ captionSizeOkMax = 1000;
1317
+ captionSize = parseInt(this.prefCaptionsSize,10);
1318
+
1319
+ // TODO: Need a better formula so that it scales proportionally to viewport
1320
+ if (width > captionSizeOkMax) {
1321
+ newCaptionSize = captionSize * 1.5;
1322
+ }
1323
+ else if (width < captionSizeOkMin) {
1324
+ newCaptionSize = captionSize / 1.5;
1325
+ }
1326
+ else {
1327
+ newCaptionSize = captionSize;
1328
+ }
1329
+ newLineHeight = newCaptionSize + 25;
1330
+ this.$captionsDiv.css('font-size',newCaptionSize + '%');
1331
+ this.$captionsWrapper.css('line-height',newLineHeight + '%');
1332
+ }
1333
+
1334
+ this.refreshControls();
1335
+ };
1336
+
1337
+ AblePlayer.prototype.retrieveOffscreenWindow = function( which, width, height ) {
1338
+
1339
+ // check to be sure popup windows ('transcript' or 'sign') are positioned on-screen
1340
+ // (they sometimes disappear off-screen when entering fullscreen mode)
1341
+ // if off-screen, recalculate so they are back on screen
1342
+
1343
+ var window, windowPos, windowTop, windowLeft, windowRight, windowWidth, windowBottom, windowHeight;
1344
+
1345
+ if (which == 'transcript') {
1346
+ window = this.$transcriptArea;
1347
+ }
1348
+ else if (which == 'sign') {
1349
+ window = this.$signWindow;
1350
+ }
1351
+ windowWidth = window.width();
1352
+ windowHeight = window.height();
1353
+ windowPos = window.position();
1354
+ windowTop = windowPos.top;
1355
+ windowLeft = windowPos.left;
1356
+ windowRight = windowLeft + windowWidth;
1357
+ windowBottom = windowTop + windowHeight;
1358
+
1359
+ if (windowTop < 0) { // off-screen to the top
1360
+ windowTop = 10;
1361
+ window.css('top',windowTop);
1362
+ }
1363
+ if (windowLeft < 0) { // off-screen to the left
1364
+ windowLeft = 10;
1365
+ window.css('left',windowLeft);
1366
+ }
1367
+ if (windowRight > width) { // off-screen to the right
1368
+ windowLeft = (width - 20) - windowWidth;
1369
+ window.css('left',windowLeft);
1370
+ }
1371
+ if (windowBottom > height) { // off-screen to the bottom
1372
+ windowTop = (height - 10) - windowHeight;
1373
+ window.css('top',windowTop);
1374
+ }
1375
+ };
1376
+
1377
+ AblePlayer.prototype.getHighestZIndex = function() {
1378
+
1379
+ // returns the highest z-index on page
1380
+ // used to ensure dialogs (or potentially other windows) are on top
1381
+
1382
+ var max, $elements, z;
1383
+ max = 0;
1384
+
1385
+ // exclude the Able Player dialogs
1386
+ $elements = $('body *').not('.able-modal-dialog,.able-modal-dialog *,.able-modal-overlay,.able-modal-overlay *');
1387
+
1388
+ $elements.each(function(){
1389
+ z = $(this).css('z-index');
1390
+ if (Number.isInteger(+z)) { // work only with integer values, not 'auto'
1391
+ if (parseInt(z) > max) {
1392
+ max = parseInt(z);
1393
+ }
1394
+ }
1395
+ });
1396
+ return max;
1397
+ };
1398
+
1399
+ AblePlayer.prototype.updateZIndex = function(which) {
1400
+
1401
+ // update z-index of 'transcript' or 'sign', relative to each other
1402
+ // direction is always 'up' (i.e., move window to top)
1403
+ // windows come to the top when the user clicks on them
1404
+
1405
+ var transcriptZ, signZ, newHighZ, newLowZ;
1406
+
1407
+ if (typeof this.$transcriptArea === 'undefined' || typeof this.$signWindow === 'undefined' ) {
1408
+ // at least one of the windows doesn't exist, so there's no conflict
1409
+ return false;
1410
+ }
1411
+
1412
+ // get current values
1413
+ transcriptZ = parseInt(this.$transcriptArea.css('z-index'));
1414
+ signZ = parseInt(this.$signWindow.css('z-index'));
1415
+
1416
+ if (transcriptZ === signZ) {
1417
+ // the two windows are equal; move the target window the top
1418
+ newHighZ = transcriptZ + 1000;
1419
+ newLowZ = transcriptZ;
1420
+ }
1421
+ else if (transcriptZ > signZ) {
1422
+ if (which === 'transcript') {
1423
+ // transcript is already on top; nothing to do
1424
+ return false;
1425
+ }
1426
+ else {
1427
+ // swap z's
1428
+ newHighZ = transcriptZ;
1429
+ newLowZ = signZ;
1430
+ }
1431
+ }
1432
+ else { // signZ is greater
1433
+ if (which === 'sign') {
1434
+ return false;
1435
+ }
1436
+ else {
1437
+ newHighZ = signZ;
1438
+ newLowZ = transcriptZ;
1439
+ }
1440
+ }
1441
+
1442
+ // now assign the new values
1443
+ if (which === 'transcript') {
1444
+ this.$transcriptArea.css('z-index',newHighZ);
1445
+ this.$signWindow.css('z-index',newLowZ);
1446
+ }
1447
+ else if (which === 'sign') {
1448
+ this.$signWindow.css('z-index',newHighZ);
1449
+ this.$transcriptArea.css('z-index',newLowZ);
1450
+ }
1451
+ };
1452
+
1453
+ AblePlayer.prototype.syncTrackLanguages = function (source, language) {
1454
+
1455
+ // this function is called when the player is built (source == 'init')
1456
+ // and again when user changes the language of either 'captions' or 'transcript'
1457
+ // It syncs the languages of chapters, descriptions, and metadata tracks
1458
+ // NOTE: Caption and transcript languages are somewhat independent from one another
1459
+ // If a user changes the caption language, the transcript follows
1460
+ // However, if a user changes the transcript language, this only affects the transcript
1461
+ // This was a group decision based on the belief that users may want a transcript
1462
+ // that is in a different language than the captions
1463
+
1464
+ var i, captions, descriptions, chapters, meta;
1465
+
1466
+ // Captions
1467
+ for (i = 0; i < this.captions.length; i++) {
1468
+ if (this.captions[i].language === language) {
1469
+ captions = this.captions[i];
1470
+ }
1471
+ }
1472
+ // Chapters
1473
+ for (i = 0; i < this.chapters.length; i++) {
1474
+ if (this.chapters[i].language === language) {
1475
+ chapters = this.chapters[i];
1476
+ }
1477
+ }
1478
+ // Descriptions
1479
+ for (i = 0; i < this.descriptions.length; i++) {
1480
+ if (this.descriptions[i].language === language) {
1481
+ descriptions = this.descriptions[i];
1482
+ }
1483
+ }
1484
+ // Metadata
1485
+ for (i = 0; i < this.meta.length; i++) {
1486
+ if (this.meta[i].language === language) {
1487
+ meta = this.meta[i];
1488
+ }
1489
+ }
1490
+
1491
+ // regardless of source...
1492
+ this.transcriptLang = language;
1493
+
1494
+ if (source === 'init' || source === 'captions') {
1495
+ this.captionLang = language;
1496
+ this.selectedCaptions = captions;
1497
+ this.selectedChapters = chapters;
1498
+ this.selectedDescriptions = descriptions;
1499
+ this.selectedMeta = meta;
1500
+ this.transcriptCaptions = captions;
1501
+ this.transcriptChapters = chapters;
1502
+ this.transcriptDescriptions = descriptions;
1503
+ this.updateChaptersList();
1504
+ this.setupPopups('chapters');
1505
+ }
1506
+ else if (source === 'transcript') {
1507
+ this.transcriptCaptions = captions;
1508
+ this.transcriptChapters = chapters;
1509
+ this.transcriptDescriptions = descriptions;
1510
+ }
1511
+ this.updateTranscript();
1512
+ };
1513
+
1514
+ })(jQuery);