wai-website-theme 1.3.1 → 1.4

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/_includes/different.html +2 -1
  3. data/_includes/external.html +2 -1
  4. data/_includes/header.html +2 -1
  5. data/_includes/menuitem.html +6 -2
  6. data/_includes/peoplelist.html +21 -0
  7. data/_includes/prevnext-navigation.html +56 -0
  8. data/_includes/{prevnext.html → prevnext-order.html} +9 -0
  9. data/_includes/translation-note-msg.html +5 -3
  10. data/_includes/video-player.html +2 -2
  11. data/_layouts/default.html +8 -1
  12. data/_layouts/news.html +7 -1
  13. data/_layouts/policy.html +7 -1
  14. data/_layouts/sidenav.html +8 -1
  15. data/_layouts/sidenavsidebar.html +8 -1
  16. data/assets/ableplayer/Gruntfile.js +2 -1
  17. data/assets/ableplayer/README.md +158 -85
  18. data/assets/ableplayer/build/ableplayer.dist.js +15445 -13823
  19. data/assets/ableplayer/build/ableplayer.js +15445 -13823
  20. data/assets/ableplayer/build/ableplayer.min.css +1 -2
  21. data/assets/ableplayer/build/ableplayer.min.js +3 -10
  22. data/assets/ableplayer/package-lock.json +944 -346
  23. data/assets/ableplayer/package.json +8 -8
  24. data/assets/ableplayer/scripts/ableplayer-base.js +515 -524
  25. data/assets/ableplayer/scripts/browser.js +158 -158
  26. data/assets/ableplayer/scripts/buildplayer.js +1750 -1682
  27. data/assets/ableplayer/scripts/caption.js +424 -401
  28. data/assets/ableplayer/scripts/chapters.js +259 -259
  29. data/assets/ableplayer/scripts/control.js +1831 -1594
  30. data/assets/ableplayer/scripts/description.js +333 -256
  31. data/assets/ableplayer/scripts/dialog.js +145 -145
  32. data/assets/ableplayer/scripts/dragdrop.js +746 -749
  33. data/assets/ableplayer/scripts/event.js +875 -696
  34. data/assets/ableplayer/scripts/initialize.js +819 -912
  35. data/assets/ableplayer/scripts/langs.js +979 -743
  36. data/assets/ableplayer/scripts/metadata.js +124 -124
  37. data/assets/ableplayer/scripts/misc.js +170 -137
  38. data/assets/ableplayer/scripts/preference.js +904 -904
  39. data/assets/ableplayer/scripts/search.js +172 -172
  40. data/assets/ableplayer/scripts/sign.js +82 -78
  41. data/assets/ableplayer/scripts/slider.js +449 -448
  42. data/assets/ableplayer/scripts/track.js +409 -309
  43. data/assets/ableplayer/scripts/transcript.js +684 -595
  44. data/assets/ableplayer/scripts/translation.js +63 -67
  45. data/assets/ableplayer/scripts/ttml2webvtt.js +85 -85
  46. data/assets/ableplayer/scripts/vimeo.js +448 -0
  47. data/assets/ableplayer/scripts/volume.js +395 -380
  48. data/assets/ableplayer/scripts/vts.js +1077 -1077
  49. data/assets/ableplayer/scripts/webvtt.js +766 -763
  50. data/assets/ableplayer/scripts/youtube.js +695 -478
  51. data/assets/ableplayer/styles/ableplayer.css +54 -46
  52. data/assets/ableplayer/translations/nl.js +54 -54
  53. data/assets/ableplayer/translations/pt-br.js +311 -0
  54. data/assets/ableplayer/translations/tr.js +311 -0
  55. data/assets/ableplayer/translations/zh-tw.js +1 -1
  56. data/assets/css/style.css +1 -1
  57. data/assets/css/style.css.map +1 -1
  58. data/assets/images/icons.svg +5 -5
  59. data/assets/scripts/main.js +7 -0
  60. data/assets/search/tipuesearch.js +3 -3
  61. metadata +8 -3
@@ -1,698 +1,877 @@
1
1
  (function ($) {
2
- // Media events
3
- AblePlayer.prototype.onMediaUpdateTime = function () {
4
-
5
- var currentTime = this.getElapsed();
6
- if (this.swappingSrc && (typeof this.swapTime !== 'undefined')) {
7
- if (this.swapTime === currentTime) {
8
- // described version been swapped and media has scrubbed to time of previous version
9
- if (this.playing) {
10
- // resume playback
11
- this.playMedia();
12
- // reset vars
13
- this.swappingSrc = false;
14
- this.swapTime = null;
15
- }
16
- }
17
- }
18
- else if (this.startedPlaying) {
19
- // do all the usual time-sync stuff during playback
20
- if (this.prefHighlight === 1) {
21
- this.highlightTranscript(currentTime);
22
- }
23
- this.updateCaption();
24
- this.showDescription(currentTime);
25
- this.updateChapter(currentTime);
26
- this.updateMeta();
27
- this.refreshControls();
28
- }
29
- };
30
-
31
- AblePlayer.prototype.onMediaPause = function () {
32
- if (this.controlsHidden) {
33
- this.fadeControls('in');
34
- this.controlsHidden = false;
35
- }
36
- if (this.hidingControls) { // a timeout is actively counting
37
- window.clearTimeout(this.hideControlsTimeout);
38
- this.hidingControls = false;
39
- }
40
- };
41
-
42
- AblePlayer.prototype.onMediaComplete = function () {
43
- // if there's a playlist, advance to next item and start playing
44
- if (this.hasPlaylist) {
45
- if (this.playlistIndex === (this.$playlist.length - 1)) {
46
- // this is the last track in the playlist
47
- if (this.loop) {
48
- this.playlistIndex = 0;
49
- this.swapSource(0);
50
- }
51
- }
52
- else {
53
- // this is not the last track. Play the next one.
54
- this.playlistIndex++;
55
- this.swapSource(this.playlistIndex)
56
- }
57
- }
58
- this.refreshControls();
59
- };
60
-
61
- AblePlayer.prototype.onMediaNewSourceLoad = function () {
62
-
63
- if (this.swappingSrc === true) {
64
- // new source file has just been loaded
65
- if (this.swapTime > 0) {
66
- // this.swappingSrc will be set to false after seek is complete
67
- // see onMediaUpdateTime()
68
- this.seekTo(this.swapTime);
69
- }
70
- else {
71
- if (this.playing) {
72
- // should be able to resume playback
73
- if (this.player === 'jw') {
74
- var player = this.jwPlayer;
75
- // Seems to be a bug in JW player, where this doesn't work when fired immediately.
76
- // Thus have to use a setTimeout
77
- setTimeout(function () {
78
- player.play(true);
79
- }, 500);
80
- }
81
- else {
82
- this.playMedia();
83
- }
84
- }
85
- this.swappingSrc = false; // swapping is finished
86
- this.refreshControls();
87
- }
88
- }
89
- };
90
-
91
- // End Media events
92
-
93
- AblePlayer.prototype.onWindowResize = function () {
94
-
95
- if (this.isFullscreen()) {
96
-
97
- var newWidth, newHeight;
98
-
99
- newWidth = $(window).width();
100
-
101
- // haven't isolated why, but some browsers return an innerHeight that's 20px too tall in fullscreen mode
102
- // Test results:
103
- // Browsers that require a 20px adjustment: Firefox, IE11 (Trident), Edge
104
- if (this.isUserAgent('Firefox') || this.isUserAgent('Trident') || this.isUserAgent('Edge')) {
105
- newHeight = window.innerHeight - this.$playerDiv.outerHeight() - 20;
106
- }
107
- else if (window.outerHeight >= window.innerHeight) {
108
- // Browsers that do NOT require adjustment: Chrome, Safari, Opera, MSIE 10
109
- newHeight = window.innerHeight - this.$playerDiv.outerHeight();
110
- }
111
- else {
112
- // Observed in Safari 9.0.1 on Mac OS X: outerHeight is actually less than innerHeight
113
- // Maybe a bug, or maybe window.outerHeight is already adjusted for controller height(?)
114
- // No longer observed in Safari 9.0.2
115
- newHeight = window.outerHeight;
116
- }
117
- if (!this.$descDiv.is(':hidden')) {
118
- newHeight -= this.$descDiv.height();
119
- }
120
- this.positionCaptions('overlay');
121
- }
122
- else { // not fullscreen
123
- if (this.restoringAfterFullScreen) {
124
- newWidth = this.preFullScreenWidth;
125
- newHeight = this.preFullScreenHeight;
126
- }
127
- else {
128
- // not restoring after full screen
129
- newWidth = this.$ableWrapper.width();
130
- if (typeof this.aspectRatio !== 'undefined') {
131
- newHeight = Math.round(newWidth / this.aspectRatio);
132
- }
133
- else {
134
- // not likely, since this.aspectRatio is defined during intialization
135
- // however, this is a fallback scenario just in case
136
- newHeight = this.$ableWrapper.height();
137
- }
138
- this.positionCaptions(); // reset with this.prefCaptionsPosition
139
- }
140
- }
141
- this.resizePlayer(newWidth, newHeight);
142
- };
143
-
144
- AblePlayer.prototype.addSeekbarListeners = function () {
145
- var thisObj = this;
146
-
147
- // Handle seek bar events.
148
- this.seekBar.bodyDiv.on('startTracking', function (e) {
149
- thisObj.pausedBeforeTracking = thisObj.isPaused();
150
- thisObj.pauseMedia();
151
- }).on('tracking', function (e, position) {
152
- // Scrub transcript, captions, and metadata.
153
- thisObj.highlightTranscript(position);
154
- thisObj.updateCaption(position);
155
- thisObj.showDescription(position);
156
- thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position));
157
- thisObj.updateMeta(position);
158
- thisObj.refreshControls();
159
- }).on('stopTracking', function (e, position) {
160
- if (thisObj.useChapterTimes) {
161
- thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position));
162
- }
163
- else {
164
- thisObj.seekTo(position);
165
- }
166
- if (!thisObj.pausedBeforeTracking) {
167
- setTimeout(function () {
168
- thisObj.playMedia();
169
- }, 200);
170
- }
171
- });
172
- };
173
-
174
- AblePlayer.prototype.onClickPlayerButton = function (el) {
175
- // TODO: This is super-fragile since we need to know the length of the class name to split off; update this to other way of dispatching?
176
- var whichButton = $(el).attr('class').split(' ')[0].substr(20);
177
- if (whichButton === 'play') {
178
- this.handlePlay();
179
- }
180
- else if (whichButton === 'restart') {
181
- this.seekTrigger = 'restart';
182
- this.handleRestart();
183
- }
184
- else if (whichButton === 'rewind') {
185
- this.seekTrigger = 'rewind';
186
- this.handleRewind();
187
- }
188
- else if (whichButton === 'forward') {
189
- this.seekTrigger = 'forward';
190
- this.handleFastForward();
191
- }
192
- else if (whichButton === 'mute') {
193
- this.handleMute();
194
- }
195
- else if (whichButton === 'volume') {
196
- this.handleVolume();
197
- }
198
- else if (whichButton === 'faster') {
199
- this.handleRateIncrease();
200
- }
201
- else if (whichButton === 'slower') {
202
- this.handleRateDecrease();
203
- }
204
- else if (whichButton === 'captions') {
205
- this.handleCaptionToggle();
206
- }
207
- else if (whichButton === 'chapters') {
208
- this.handleChapters();
209
- }
210
- else if (whichButton === 'descriptions') {
211
- this.handleDescriptionToggle();
212
- }
213
- else if (whichButton === 'sign') {
214
- this.handleSignToggle();
215
- }
216
- else if (whichButton === 'preferences') {
217
- this.handlePrefsClick();
218
- }
219
- else if (whichButton === 'help') {
220
- this.handleHelpClick();
221
- }
222
- else if (whichButton === 'transcript') {
223
- this.handleTranscriptToggle();
224
- }
225
- else if (whichButton === 'fullscreen') {
226
- this.handleFullscreenToggle();
227
- }
228
- };
229
-
230
- AblePlayer.prototype.okToHandleKeyPress = function () {
231
-
232
- // returns true unless user's focus is on a UI element
233
- // that is likely to need supported keystrokes, including space
234
-
235
- var activeElement = AblePlayer.getActiveDOMElement();
236
-
237
- if ($(activeElement).prop('tagName') === 'INPUT') {
238
- return false;
239
- }
240
- else {
241
- return true;
242
- }
243
- };
244
-
245
- AblePlayer.prototype.onPlayerKeyPress = function (e) {
246
-
247
- // handle keystrokes (using DHTML Style Guide recommended key combinations)
248
- // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer
249
- // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences
250
- // NOTE #1: Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down
251
- // The rest are reasonable best choices
252
- // NOTE #2: If there are multiple players on a single page, keystroke handlers
253
- // are only bound to the FIRST player
254
- // NOTE #3: The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions
255
- // including removal of the "media player" design pattern. There's an issue about that:
256
- // https://github.com/w3c/aria-practices/issues/27
257
-
258
- if (!this.okToHandleKeyPress()) {
259
- return false;
260
- }
261
- // Convert to lower case.
262
- var which = e.which;
263
-
264
- if (which >= 65 && which <= 90) {
265
- which += 32;
266
- }
267
-
268
- // Only use keypress to control player if focus is NOT on a form field or contenteditable element
269
- if (!(
270
- $(':focus').is('[contenteditable]') ||
271
- $(':focus').is('input') ||
272
- $(':focus').is('textarea') ||
273
- $(':focus').is('select') ||
274
- e.target.hasAttribute('contenteditable') ||
275
- e.target.tagName === 'INPUT' ||
276
- e.target.tagName === 'TEXTAREA' ||
277
- e.target.tagName === 'SELECT'
278
- )){
279
- if (which === 27) { // escape
280
- this.closePopups();
281
- }
282
- else if (which === 32) { // spacebar = play/pause
283
- if (this.$ableWrapper.find('.able-controller button:focus').length === 0) {
284
- // only toggle play if a button does not have focus
285
- // if a button has focus, space should activate that button
286
- this.handlePlay();
287
- }
288
- }
289
- else if (which === 112) { // p = play/pause
290
- if (this.usingModifierKeys(e)) {
291
- this.handlePlay();
292
- }
293
- }
294
- else if (which === 115) { // s = stop (now restart)
295
- if (this.usingModifierKeys(e)) {
296
- this.handleRestart();
297
- }
298
- }
299
- else if (which === 109) { // m = mute
300
- if (this.usingModifierKeys(e)) {
301
- this.handleMute();
302
- }
303
- }
304
- else if (which === 118) { // v = volume
305
- if (this.usingModifierKeys(e)) {
306
- this.handleVolume();
307
- }
308
- }
309
- else if (which >= 49 && which <= 57) { // set volume 1-9
310
- if (this.usingModifierKeys(e)) {
311
- this.handleVolume(which);
312
- }
313
- }
314
- else if (which === 99) { // c = caption toggle
315
- if (this.usingModifierKeys(e)) {
316
- this.handleCaptionToggle();
317
- }
318
- }
319
- else if (which === 100) { // d = description
320
- if (this.usingModifierKeys(e)) {
321
- this.handleDescriptionToggle();
322
- }
323
- }
324
- else if (which === 102) { // f = forward
325
- if (this.usingModifierKeys(e)) {
326
- this.handleFastForward();
327
- }
328
- }
329
- else if (which === 114) { // r = rewind
330
- if (this.usingModifierKeys(e)) {
331
- this.handleRewind();
332
- }
333
- }
334
- else if (which === 101) { // e = preferences
335
- if (this.usingModifierKeys(e)) {
336
- this.handlePrefsClick();
337
- }
338
- }
339
- else if (which === 13) { // Enter
340
- var thisElement = $(document.activeElement);
341
- if (thisElement.prop('tagName') === 'SPAN') {
342
- // register a click on this SPAN
343
- // if it's a transcript span the transcript span click handler will take over
344
- thisElement.click();
345
- }
346
- else if (thisElement.prop('tagName') === 'LI') {
347
- thisElement.click();
348
- }
349
- }
350
- }
351
- };
352
-
353
- AblePlayer.prototype.addHtml5MediaListeners = function () {
354
-
355
- var thisObj = this;
356
-
357
- // NOTE: iOS and some browsers do not support autoplay
358
- // and no events are triggered until media begins to play
359
- // Able Player gets around this by automatically loading media in some circumstances
360
- // (see initialize.js > initPlayer() for details)
361
- this.$media
362
- .on('emptied',function() {
363
- // do something
364
- })
365
- .on('loadedmetadata',function() {
366
- thisObj.onMediaNewSourceLoad();
367
- })
368
- .on('canplay',function() {
369
- // previously handled seeking to startTime here
370
- // but it's probably safer to wait for canplaythrough
371
- // so we know player can seek ahead to anything
372
- })
373
- .on('canplaythrough',function() {
374
- if (thisObj.seekTrigger == 'restart' || thisObj.seekTrigger == 'chapter' || thisObj.seekTrigger == 'transcript') {
375
- // by clicking on any of these elements, user is likely intending to play
376
- // Not included: elements where user might click multiple times in succession
377
- // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
378
- thisObj.playMedia();
379
- }
380
- else if (!thisObj.startedPlaying) {
381
- if (thisObj.startTime) {
382
- if (thisObj.seeking) {
383
- // a seek has already been initiated
384
- // since canplaythrough has been triggered, the seek is complete
385
- thisObj.seeking = false;
386
- if (thisObj.autoplay) {
387
- thisObj.playMedia();
388
- }
389
- }
390
- else {
391
- // haven't started seeking yet
392
- thisObj.seekTo(thisObj.startTime);
393
- }
394
- }
395
- else if (thisObj.defaultChapter && typeof thisObj.selectedChapters !== 'undefined') {
396
- thisObj.seekToChapter(thisObj.defaultChapter);
397
- }
398
- else {
399
- // there is now startTime, therefore no seeking required
400
- if (thisObj.autoplay) {
401
- thisObj.playMedia();
402
- }
403
- }
404
- }
405
- else if (thisObj.hasPlaylist) {
406
- if ((thisObj.playlistIndex !== (thisObj.$playlist.length - 1)) || thisObj.loop) {
407
- // this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
408
- thisObj.playMedia();
409
- }
410
- }
411
- else {
412
- // already started playing
413
- }
414
- })
415
- .on('playing',function() {
416
- thisObj.playing = true;
417
- thisObj.refreshControls();
418
- })
419
- .on('ended',function() {
420
- thisObj.playing = false;
421
- thisObj.onMediaComplete();
422
- })
423
- .on('progress', function() {
424
- thisObj.refreshControls();
425
- })
426
- .on('waiting',function() {
427
- thisObj.refreshControls();
428
- })
429
- .on('durationchange',function() {
430
- // Display new duration.
431
- thisObj.refreshControls();
432
- })
433
- .on('timeupdate',function() {
434
- thisObj.onMediaUpdateTime();
435
- })
436
- .on('play',function() {
437
- if (thisObj.debug) {
438
- console.log('media play event');
439
- }
440
- })
441
- .on('pause',function() {
442
- if (!thisObj.clickedPlay) {
443
- // 'pause' was triggered automatically, not initiated by user
444
- // this happens between tracks in a playlist
445
- if (thisObj.hasPlaylist) {
446
- // do NOT set playing to false.
447
- // doing so prevents continual playback after new track is loaded
448
- }
449
- else {
450
- thisObj.playing = false;
451
- }
452
- }
453
- else {
454
- thisObj.playing = false;
455
- }
456
- thisObj.clickedPlay = false; // done with this variable
457
- thisObj.onMediaPause();
458
- })
459
- .on('ratechange',function() {
460
- // do something
461
- })
462
- .on('volumechange',function() {
463
- thisObj.volume = thisObj.getVolume();
464
- if (thisObj.debug) {
465
- console.log('media volume change to ' + thisObj.volume + ' (' + thisObj.volumeButton + ')');
466
- }
467
- })
468
- .on('error',function() {
469
- if (thisObj.debug) {
470
- switch (thisObj.media.error.code) {
471
- case 1:
472
- console.log('HTML5 Media Error: MEDIA_ERR_ABORTED');
473
- break;
474
- case 2:
475
- console.log('HTML5 Media Error: MEDIA_ERR_NETWORK ');
476
- break;
477
- case 3:
478
- console.log('HTML5 Media Error: MEDIA_ERR_DECODE ');
479
- break;
480
- case 4:
481
- console.log('HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED ');
482
- break;
483
- }
484
- }
485
- });
486
- };
487
-
488
- AblePlayer.prototype.addJwMediaListeners = function () {
489
- var thisObj = this;
490
- // add listeners for JW Player events
491
- this.jwPlayer
492
- .onTime(function() {
493
- thisObj.onMediaUpdateTime();
494
- })
495
- .onComplete(function() {
496
- thisObj.onMediaComplete();
497
- })
498
- .onReady(function() {
499
- if (thisObj.debug) {
500
- console.log('JW Player onReady event fired');
501
- }
502
- // remove JW Player from tab order.
503
- // We don't want users tabbing into the Flash object and getting trapped
504
- $('#' + thisObj.jwId).removeAttr('tabindex');
505
-
506
- // JW Player was initialized with no explicit width or height; get them now
507
- thisObj.$fallbackWrapper = $('#' + thisObj.mediaId + '_fallback_wrapper');
508
- thisObj.fallbackDefaultWidth = thisObj.$fallbackWrapper.width();
509
- thisObj.fallbackDefaultHeight = thisObj.$fallbackWrapper.height();
510
- thisObj.fallbackRatio = thisObj.fallbackDefaultWidth / thisObj.fallbackDefaultHeight;
511
-
512
- if (thisObj.startTime > 0 && !thisObj.startedPlaying) {
513
- thisObj.seekTo(thisObj.startTime);
514
- thisObj.startedPlaying = true;
515
- }
516
- thisObj.refreshControls();
517
- })
518
- .onSeek(function(e) {
519
- // this is called when user scrubs ahead or back,
520
- // after the target offset is reached
521
- if (thisObj.debug) {
522
- console.log('Seeking to ' + e.position + '; target: ' + e.offset);
523
- }
524
-
525
- if (thisObj.jwSeekPause) {
526
- // media was temporarily paused
527
- thisObj.jwSeekPause = false;
528
- thisObj.playMedia();
529
- }
530
-
531
- setTimeout(function () {
532
- thisObj.refreshControls();
533
- }, 300);
534
- })
535
- .onPlay(function() {
536
- if (thisObj.debug) {
537
- console.log('JW Player onPlay event fired');
538
- }
539
- thisObj.refreshControls();
540
- })
541
- .onPause(function() {
542
- thisObj.onMediaPause();
543
- })
544
- .onBuffer(function() {
545
- if (thisObj.debug) {
546
- console.log('JW Player onBuffer event fired');
547
- }
548
- thisObj.refreshControls();
549
- })
550
- .onBufferChange(function() {
551
- thisObj.refreshControls();
552
- })
553
- .onIdle(function(e) {
554
- if (thisObj.debug) {
555
- console.log('JW Player onIdle event fired');
556
- }
557
- thisObj.refreshControls();
558
- })
559
- .onMeta(function() {
560
- if (thisObj.debug) {
561
- console.log('JW Player onMeta event fired');
562
- }
563
- })
564
- .onPlaylist(function() {
565
- if (thisObj.debug) {
566
- console.log('JW Player onPlaylist event fired');
567
- }
568
-
569
- // Playlist change includes new media source.
570
- thisObj.onMediaNewSourceLoad();
571
- });
572
- };
573
-
574
- AblePlayer.prototype.addEventListeners = function () {
575
- var thisObj, whichButton, thisElement;
576
-
577
- // Save the current object context in thisObj for use with inner functions.
578
- thisObj = this;
579
-
580
- // Appropriately resize media player for full screen.
581
- $(window).resize(function () {
582
- thisObj.onWindowResize();
583
- });
584
-
585
- // Refresh player if it changes from hidden to visible
586
- // There is no event triggered by a change in visibility
587
- // but MutationObserver works in most browsers (but NOT in IE 10 or earlier)
588
- // http://caniuse.com/#feat=mutationobserver
589
- if (window.MutationObserver) {
590
- var target = this.$ableDiv[0];
591
- var observer = new MutationObserver(function(mutations) {
592
- mutations.forEach(function(mutation) {
593
- if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
594
- // the player's style attribute has changed. Check to see if it's visible
595
- if (thisObj.$ableDiv.is(':visible')) {
596
- thisObj.refreshControls();
597
- }
598
- }
599
- });
600
- });
601
- var config = { attributes: true, childList: true, characterData: true };
602
- observer.observe(target, config);
603
- }
604
- else {
605
- // browser doesn't support MutationObserver
606
- // TODO: Figure out an alternative solution for this rare use case in older browsers
607
- // See example in buildplayer.js > useSvg()
608
- }
609
-
610
- this.addSeekbarListeners();
611
- // handle clicks on player buttons
612
- this.$controllerDiv.find('button').on('click',function(e){
613
- e.stopPropagation();
614
- thisObj.onClickPlayerButton(this);
615
- });
616
-
617
- // handle clicks (left only) anywhere on the page. If any popups are open, close them.
618
- $(document).on('click',function(e) {
619
- if (e.button !== 0) { // not a left click
620
- return false;
621
- }
622
- if ($('.able-popup:visible').length || $('.able-volume-popup:visible')) {
623
- // at least one popup is visible
624
- thisObj.closePopups();
625
- }
626
- });
627
-
628
- // handle mouse movement over player; make controls visible again if hidden
629
- this.$ableDiv.on('mousemove',function() {
630
- if (thisObj.controlsHidden) {
631
- thisObj.fadeControls('in');
632
- thisObj.controlsHidden = false;
633
- // after showing controls, wait another few seconds, then hide them again if video continues to play
634
- thisObj.hidingControls = true;
635
- thisObj.hideControlsTimeout = window.setTimeout(function() {
636
- if (typeof thisObj.playing !== 'undefined' && thisObj.playing === true) {
637
- thisObj.fadeControls('out');
638
- thisObj.controlsHidden = true;
639
- thisObj.hidingControls = false;
640
- }
641
- },3000);
642
- };
643
- });
644
-
645
- // if user presses a key from anywhere on the page, show player controls
646
- $(document).keydown(function() {
647
- if (thisObj.controlsHidden) {
648
- thisObj.fadeControls('in');
649
- thisObj.controlsHidden = false;
650
- }
651
- });
652
-
653
- // handle local keydown events if this isn't the only player on the page;
654
- // otherwise these are dispatched by global handler (see ableplayer-base,js)
655
- this.$ableDiv.keydown(function (e) {
656
- if (AblePlayer.nextIndex > 1) {
657
- thisObj.onPlayerKeyPress(e);
658
- }
659
- });
660
-
661
- // transcript is not a child of this.$ableDiv
662
- // therefore, must be added separately
663
- if (this.$transcriptArea) {
664
- this.$transcriptArea.keydown(function (e) {
665
- if (AblePlayer.nextIndex > 1) {
666
- thisObj.onPlayerKeyPress(e);
667
- }
668
- });
669
- }
670
-
671
- // handle clicks on playlist items
672
- if (this.$playlist) {
673
- this.$playlist.click(function() {
674
- thisObj.playlistIndex = $(this).index();
675
- thisObj.swapSource(thisObj.playlistIndex);
676
- });
677
- }
678
-
679
- // Also play/pause when clicking on the media.
680
- this.$media.click(function () {
681
- thisObj.handlePlay();
682
- });
683
-
684
- // add listeners for media events
685
- if (this.player === 'html5') {
686
- this.addHtml5MediaListeners();
687
- }
688
- else if (this.player === 'jw') {
689
- this.addJwMediaListeners();
690
- }
691
- else if (this.player === 'youtube') {
692
- // Youtube doesn't give us time update events, so we just periodically generate them ourselves
693
- setInterval(function () {
694
- thisObj.onMediaUpdateTime();
695
- }, 300);
696
- }
697
- };
2
+ // Media events
3
+ AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) {
4
+
5
+ // duration and elapsed are passed from callback functions of Vimeo API events
6
+ // duration is expressed as sss.xxx
7
+ // elapsed is expressed as sss.xxx
8
+ var thisObj = this;
9
+
10
+ this.getMediaTimes(duration,elapsed).then(function(mediaTimes) {
11
+ if (typeof duration === 'undefined') {
12
+ thisObj.duration = mediaTimes['duration'];
13
+ }
14
+ if (typeof elapsed === 'undefined') {
15
+ thisObj.elapsed = mediaTimes['elapsed'];
16
+ }
17
+ if (thisObj.swappingSrc && (typeof thisObj.swapTime !== 'undefined')) {
18
+ if (thisObj.swapTime === thisObj.elapsed) {
19
+ // described version been swapped and media has scrubbed to time of previous version
20
+ if (thisObj.playing) {
21
+ // resume playback
22
+ thisObj.playMedia();
23
+ // reset vars
24
+ thisObj.swappingSrc = false;
25
+ thisObj.swapTime = null;
26
+ }
27
+ }
28
+ }
29
+ else if (thisObj.startedPlaying) {
30
+ // do all the usual time-sync stuff during playback
31
+ if (thisObj.prefHighlight === 1) {
32
+ thisObj.highlightTranscript(thisObj.elapsed);
33
+ }
34
+ thisObj.updateCaption(thisObj.elapsed);
35
+ thisObj.showDescription(thisObj.elapsed);
36
+ thisObj.updateChapter(thisObj.elapsed);
37
+ thisObj.updateMeta(thisObj.elapsed);
38
+ thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed);
39
+ }
40
+ });
41
+ };
42
+
43
+ AblePlayer.prototype.onMediaPause = function () {
44
+
45
+ if (this.controlsHidden) {
46
+ this.fadeControls('in');
47
+ this.controlsHidden = false;
48
+ }
49
+ if (this.hideControlsTimeoutStatus === 'active') {
50
+ window.clearTimeout(this.hideControlsTimeout);
51
+ this.hideControlsTimeoutStatus = 'clear';
52
+
53
+ }
54
+ this.refreshControls('playpause');
55
+ };
56
+
57
+ AblePlayer.prototype.onMediaComplete = function () {
58
+
59
+ // if there's a playlist, advance to next item and start playing
60
+ if (this.hasPlaylist && !this.cueingPlaylistItem) {
61
+ if (this.playlistIndex === (this.$playlist.length - 1)) {
62
+ // this is the last track in the playlist
63
+ if (this.loop) {
64
+ this.playlistIndex = 0;
65
+ this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
66
+ this.cuePlaylistItem(0);
67
+ }
68
+ }
69
+ else {
70
+ // this is not the last track. Play the next one.
71
+ this.playlistIndex++;
72
+ this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
73
+ this.cuePlaylistItem(this.playlistIndex)
74
+ }
75
+ }
76
+ this.refreshControls('init');
77
+ };
78
+
79
+ AblePlayer.prototype.onMediaNewSourceLoad = function () {
80
+
81
+ if (this.cueingPlaylistItem) {
82
+ // this variable was set in order to address bugs caused by multiple firings of media 'end' event
83
+ // safe to reset now
84
+ this.cueingPlaylistItem = false;
85
+ }
86
+ if (this.swappingSrc === true) {
87
+ // new source file has just been loaded
88
+ if (this.swapTime > 0) {
89
+ // this.swappingSrc will be set to false after seek is complete
90
+ // see onMediaUpdateTime()
91
+ this.seekTo(this.swapTime);
92
+ }
93
+ else {
94
+ if (this.playing) {
95
+ // should be able to resume playback
96
+ this.playMedia();
97
+ }
98
+ this.swappingSrc = false; // swapping is finished
99
+ this.refreshControls('init');
100
+ }
101
+ }
102
+ };
103
+
104
+ // End Media events
105
+
106
+ AblePlayer.prototype.onWindowResize = function () {
107
+
108
+ if (this.fullscreen) { // replace isFullscreen() with a Boolean. see function for explanation
109
+
110
+ var newWidth, newHeight;
111
+
112
+ newWidth = $(window).width();
113
+
114
+ // haven't isolated why, but some browsers return an innerHeight that's 20px too tall in fullscreen mode
115
+ // Test results:
116
+ // Browsers that require a 20px adjustment: Firefox, IE11 (Trident), Edge
117
+ if (this.isUserAgent('Firefox') || this.isUserAgent('Trident') || this.isUserAgent('Edge')) {
118
+ newHeight = window.innerHeight - this.$playerDiv.outerHeight() - 20;
119
+ }
120
+ else if (window.outerHeight >= window.innerHeight) {
121
+ // Browsers that do NOT require adjustment: Chrome, Safari, Opera, MSIE 10
122
+ newHeight = window.innerHeight - this.$playerDiv.outerHeight();
123
+ }
124
+ else {
125
+ // Observed in Safari 9.0.1 on Mac OS X: outerHeight is actually less than innerHeight
126
+ // Maybe a bug, or maybe window.outerHeight is already adjusted for controller height(?)
127
+ // No longer observed in Safari 9.0.2
128
+ newHeight = window.outerHeight;
129
+ }
130
+ if (!this.$descDiv.is(':hidden')) {
131
+ newHeight -= this.$descDiv.height();
132
+ }
133
+ this.positionCaptions('overlay');
134
+ }
135
+ else { // not fullscreen
136
+ if (this.restoringAfterFullScreen) {
137
+ newWidth = this.preFullScreenWidth;
138
+ newHeight = this.preFullScreenHeight;
139
+ }
140
+ else {
141
+ // not restoring after full screen
142
+ newWidth = this.$ableWrapper.width();
143
+ if (typeof this.aspectRatio !== 'undefined') {
144
+ newHeight = Math.round(newWidth / this.aspectRatio);
145
+ }
146
+ else {
147
+ // not likely, since this.aspectRatio is defined during intialization
148
+ // however, this is a fallback scenario just in case
149
+ newHeight = this.$ableWrapper.height();
150
+ }
151
+ this.positionCaptions(); // reset with this.prefCaptionsPosition
152
+ }
153
+ }
154
+ this.resizePlayer(newWidth, newHeight);
155
+ };
156
+
157
+ AblePlayer.prototype.addSeekbarListeners = function () {
158
+
159
+ var thisObj = this;
160
+
161
+ // Handle seek bar events.
162
+ this.seekBar.bodyDiv.on('startTracking', function (e) {
163
+ thisObj.pausedBeforeTracking = thisObj.paused;
164
+ thisObj.pauseMedia();
165
+ }).on('tracking', function (e, position) {
166
+ // Scrub transcript, captions, and metadata.
167
+ thisObj.highlightTranscript(position);
168
+ thisObj.updateCaption(position);
169
+ thisObj.showDescription(position);
170
+ thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position));
171
+ thisObj.updateMeta(position);
172
+ thisObj.refreshControls('init');
173
+ }).on('stopTracking', function (e, position) {
174
+ if (thisObj.useChapterTimes) {
175
+ thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position));
176
+ }
177
+ else {
178
+ thisObj.seekTo(position);
179
+ }
180
+ if (!thisObj.pausedBeforeTracking) {
181
+ setTimeout(function () {
182
+ thisObj.playMedia();
183
+ }, 200);
184
+ }
185
+ });
186
+ };
187
+
188
+ AblePlayer.prototype.onClickPlayerButton = function (el) {
189
+
190
+ // TODO: This is super-fragile since we need to know the length of the class name to split off; update this to other way of dispatching?
191
+ var whichButton = $(el).attr('class').split(' ')[0].substr(20);
192
+ if (whichButton === 'play') {
193
+ this.handlePlay();
194
+ }
195
+ else if (whichButton === 'restart') {
196
+ this.seekTrigger = 'restart';
197
+ this.handleRestart();
198
+ }
199
+ else if (whichButton === 'rewind') {
200
+ this.seekTrigger = 'rewind';
201
+ this.handleRewind();
202
+ }
203
+ else if (whichButton === 'forward') {
204
+ this.seekTrigger = 'forward';
205
+ this.handleFastForward();
206
+ }
207
+ else if (whichButton === 'mute') {
208
+ this.handleMute();
209
+ }
210
+ else if (whichButton === 'volume') {
211
+ this.handleVolume();
212
+ }
213
+ else if (whichButton === 'faster') {
214
+ this.handleRateIncrease();
215
+ }
216
+ else if (whichButton === 'slower') {
217
+ this.handleRateDecrease();
218
+ }
219
+ else if (whichButton === 'captions') {
220
+ this.handleCaptionToggle();
221
+ }
222
+ else if (whichButton === 'chapters') {
223
+ this.handleChapters();
224
+ }
225
+ else if (whichButton === 'descriptions') {
226
+ this.handleDescriptionToggle();
227
+ }
228
+ else if (whichButton === 'sign') {
229
+ this.handleSignToggle();
230
+ }
231
+ else if (whichButton === 'preferences') {
232
+ this.handlePrefsClick();
233
+ }
234
+ else if (whichButton === 'help') {
235
+ this.handleHelpClick();
236
+ }
237
+ else if (whichButton === 'transcript') {
238
+ this.handleTranscriptToggle();
239
+ }
240
+ else if (whichButton === 'fullscreen') {
241
+ this.clickedFullscreenButton = true;
242
+ this.handleFullscreenToggle();
243
+ }
244
+ };
245
+
246
+ AblePlayer.prototype.okToHandleKeyPress = function () {
247
+
248
+ // returns true unless user's focus is on a UI element
249
+ // that is likely to need supported keystrokes, including space
250
+
251
+ var activeElement = AblePlayer.getActiveDOMElement();
252
+
253
+ if ($(activeElement).prop('tagName') === 'INPUT') {
254
+ return false;
255
+ }
256
+ else {
257
+ return true;
258
+ }
259
+ };
260
+
261
+ AblePlayer.prototype.onPlayerKeyPress = function (e) {
262
+
263
+ // handle keystrokes (using DHTML Style Guide recommended key combinations)
264
+ // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer
265
+ // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences
266
+ // NOTE #1: Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down
267
+ // The rest are reasonable best choices
268
+ // NOTE #2: If there are multiple players on a single page, keystroke handlers
269
+ // are only bound to the FIRST player
270
+ // NOTE #3: The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions
271
+ // including removal of the "media player" design pattern. There's an issue about that:
272
+ // https://github.com/w3c/aria-practices/issues/27
273
+
274
+ if (!this.okToHandleKeyPress()) {
275
+ return false;
276
+ }
277
+ // Convert to lower case.
278
+ var which = e.which;
279
+
280
+ if (which >= 65 && which <= 90) {
281
+ which += 32;
282
+ }
283
+
284
+ // Only use keypress to control player if focus is NOT on a form field or contenteditable element
285
+ if (!(
286
+ $(':focus').is('[contenteditable]') ||
287
+ $(':focus').is('input') ||
288
+ $(':focus').is('textarea') ||
289
+ $(':focus').is('select') ||
290
+ e.target.hasAttribute('contenteditable') ||
291
+ e.target.tagName === 'INPUT' ||
292
+ e.target.tagName === 'TEXTAREA' ||
293
+ e.target.tagName === 'SELECT'
294
+ )){
295
+ if (which === 27) { // escape
296
+ this.closePopups();
297
+ }
298
+ else if (which === 32) { // spacebar = play/pause
299
+ if (this.$ableWrapper.find('.able-controller button:focus').length === 0) {
300
+ // only toggle play if a button does not have focus
301
+ // if a button has focus, space should activate that button
302
+ this.handlePlay();
303
+ }
304
+ }
305
+ else if (which === 112) { // p = play/pause
306
+ if (this.usingModifierKeys(e)) {
307
+ this.handlePlay();
308
+ }
309
+ }
310
+ else if (which === 115) { // s = stop (now restart)
311
+ if (this.usingModifierKeys(e)) {
312
+ this.handleRestart();
313
+ }
314
+ }
315
+ else if (which === 109) { // m = mute
316
+ if (this.usingModifierKeys(e)) {
317
+ this.handleMute();
318
+ }
319
+ }
320
+ else if (which === 118) { // v = volume
321
+ if (this.usingModifierKeys(e)) {
322
+ this.handleVolume();
323
+ }
324
+ }
325
+ else if (which >= 49 && which <= 57) { // set volume 1-9
326
+ if (this.usingModifierKeys(e)) {
327
+ this.handleVolume(which);
328
+ }
329
+ }
330
+ else if (which === 99) { // c = caption toggle
331
+ if (this.usingModifierKeys(e)) {
332
+ this.handleCaptionToggle();
333
+ }
334
+ }
335
+ else if (which === 100) { // d = description
336
+ if (this.usingModifierKeys(e)) {
337
+ this.handleDescriptionToggle();
338
+ }
339
+ }
340
+ else if (which === 102) { // f = forward
341
+ if (this.usingModifierKeys(e)) {
342
+ this.handleFastForward();
343
+ }
344
+ }
345
+ else if (which === 114) { // r = rewind
346
+ if (this.usingModifierKeys(e)) {
347
+ this.handleRewind();
348
+ }
349
+ }
350
+ else if (which === 101) { // e = preferences
351
+ if (this.usingModifierKeys(e)) {
352
+ this.handlePrefsClick();
353
+ }
354
+ }
355
+ else if (which === 13) { // Enter
356
+ var thisElement = $(document.activeElement);
357
+ if (thisElement.prop('tagName') === 'SPAN') {
358
+ // register a click on this SPAN
359
+ // if it's a transcript span the transcript span click handler will take over
360
+ thisElement.click();
361
+ }
362
+ else if (thisElement.prop('tagName') === 'LI') {
363
+ thisElement.click();
364
+ }
365
+ }
366
+ }
367
+ };
368
+
369
+ AblePlayer.prototype.addHtml5MediaListeners = function () {
370
+
371
+ var thisObj = this;
372
+
373
+ // NOTE: iOS and some browsers do not support autoplay
374
+ // and no events are triggered until media begins to play
375
+ // Able Player gets around this by automatically loading media in some circumstances
376
+ // (see initialize.js > initPlayer() for details)
377
+ this.$media
378
+ .on('emptied',function() {
379
+ // do something
380
+ })
381
+ .on('loadedmetadata',function() {
382
+ thisObj.onMediaNewSourceLoad();
383
+ })
384
+ .on('canplay',function() {
385
+ // previously handled seeking to startTime here
386
+ // but it's probably safer to wait for canplaythrough
387
+ // so we know player can seek ahead to anything
388
+ })
389
+ .on('canplaythrough',function() {
390
+ if (thisObj.userClickedPlaylist) {
391
+ if (!thisObj.startedPlaying) {
392
+ // start playing; no further user action is required
393
+ thisObj.playMedia();
394
+ }
395
+ thisObj.userClickedPlaylist = false; // reset
396
+ }
397
+ if (thisObj.seekTrigger == 'restart' || thisObj.seekTrigger == 'chapter' || thisObj.seekTrigger == 'transcript') {
398
+ // by clicking on any of these elements, user is likely intending to play
399
+ // Not included: elements where user might click multiple times in succession
400
+ // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
401
+ thisObj.playMedia();
402
+ }
403
+ else if (!thisObj.startedPlaying) {
404
+ if (thisObj.startTime > 0) {
405
+ if (thisObj.seeking) {
406
+ // a seek has already been initiated
407
+ // since canplaythrough has been triggered, the seek is complete
408
+ thisObj.seeking = false;
409
+ if (thisObj.autoplay || thisObj.okToPlay) {
410
+ thisObj.playMedia();
411
+ }
412
+ }
413
+ else {
414
+ // haven't started seeking yet
415
+ thisObj.seekTo(thisObj.startTime);
416
+ }
417
+ }
418
+ else if (thisObj.defaultChapter && typeof thisObj.selectedChapters !== 'undefined') {
419
+ thisObj.seekToChapter(thisObj.defaultChapter);
420
+ }
421
+ else {
422
+ // there is no startTime, therefore no seeking required
423
+ if (thisObj.autoplay || thisObj.okToPlay) {
424
+ thisObj.playMedia();
425
+ }
426
+ }
427
+ }
428
+ else if (thisObj.hasPlaylist) {
429
+ if ((thisObj.playlistIndex !== thisObj.$playlist.length) || thisObj.loop) {
430
+ // this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
431
+ thisObj.playMedia();
432
+ }
433
+ }
434
+ else {
435
+ // already started playing
436
+ // we're here because a new media source has been loaded and is ready to resume playback
437
+ thisObj.getPlayerState().then(function(currentState) {
438
+ if (thisObj.swappingSrc && currentState === 'stopped') {
439
+ // Safari is the only browser that returns value of 'stopped' (observed in 12.0.1 on MacOS)
440
+ // This prevents 'timeupdate' events from triggering, which prevents the new media src
441
+ // from resuming playback at swapTime
442
+ // This is a hack to jump start Safari
443
+ thisObj.startedPlaying = false;
444
+ if (thisObj.swapTime > 0) {
445
+ thisObj.seekTo(thisObj.swapTime);
446
+ }
447
+ else {
448
+ thisObj.playMedia();
449
+ }
450
+ }
451
+ });
452
+ }
453
+ })
454
+ .on('play',function() {
455
+ // both 'play' and 'playing' seem to be fired in all browsers (including IE11)
456
+ // therefore, doing nothing here & doing everything when 'playing' is triggered
457
+ thisObj.refreshControls('playpause');
458
+ })
459
+ .on('playing',function() {
460
+ thisObj.playing = true;
461
+ thisObj.paused = false;
462
+ thisObj.refreshControls('playpause');
463
+ })
464
+ .on('ended',function() {
465
+ thisObj.playing = false;
466
+ thisObj.paused = true;
467
+ thisObj.onMediaComplete();
468
+ })
469
+ .on('progress', function() {
470
+ thisObj.refreshControls('timeline');
471
+ })
472
+ .on('waiting',function() {
473
+ // do something
474
+ // previously called refreshControls() here but this event probably doesn't warrant a refresh
475
+ })
476
+ .on('durationchange',function() {
477
+ // Display new duration.
478
+ thisObj.refreshControls('timeline');
479
+ })
480
+ .on('timeupdate',function() {
481
+ thisObj.onMediaUpdateTime(); // includes a call to refreshControls()
482
+ })
483
+ .on('pause',function() {
484
+ if (!thisObj.clickedPlay) {
485
+ // 'pause' was triggered automatically, not initiated by user
486
+ // this happens in some browsers (not Chrome, as of 70.x)
487
+ // when swapping source (e.g., between tracks in a playlist, or swapping description)
488
+ if (thisObj.hasPlaylist || thisObj.swappingSrc) {
489
+ // do NOT set playing to false.
490
+ // doing so prevents continual playback after new track is loaded
491
+ }
492
+ else {
493
+ thisObj.playing = false;
494
+ thisObj.paused = true;
495
+ }
496
+ }
497
+ else {
498
+ thisObj.playing = false;
499
+ thisObj.paused = true;
500
+ }
501
+ thisObj.clickedPlay = false; // done with this variable
502
+ thisObj.onMediaPause(); // includes a call to refreshControls()
503
+ })
504
+ .on('ratechange',function() {
505
+ // do something
506
+ })
507
+ .on('volumechange',function() {
508
+ thisObj.volume = thisObj.getVolume();
509
+ if (thisObj.debug) {
510
+ console.log('media volume change to ' + thisObj.volume + ' (' + thisObj.volumeButton + ')');
511
+ }
512
+ })
513
+ .on('error',function() {
514
+ if (thisObj.debug) {
515
+ switch (thisObj.media.error.code) {
516
+ case 1:
517
+ console.log('HTML5 Media Error: MEDIA_ERR_ABORTED');
518
+ break;
519
+ case 2:
520
+ console.log('HTML5 Media Error: MEDIA_ERR_NETWORK ');
521
+ break;
522
+ case 3:
523
+ console.log('HTML5 Media Error: MEDIA_ERR_DECODE ');
524
+ break;
525
+ case 4:
526
+ console.log('HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED ');
527
+ break;
528
+ }
529
+ }
530
+ });
531
+ };
532
+
533
+ AblePlayer.prototype.addVimeoListeners = function () {
534
+
535
+ // The following content is orphaned. It was in 'canplaythrough' but there's no equivalent event in Vimeo.
536
+ // Maybe it should go under 'loaded' or 'progress' ???
537
+ /*
538
+ if (thisObj.userClickedPlaylist) {
539
+ if (!thisObj.startedPlaying) {
540
+ // start playing; no further user action is required
541
+ thisObj.playMedia();
542
+ }
543
+ thisObj.userClickedPlaylist = false; // reset
544
+ }
545
+ if (thisObj.seekTrigger == 'restart' || thisObj.seekTrigger == 'chapter' || thisObj.seekTrigger == 'transcript') {
546
+ // by clicking on any of these elements, user is likely intending to play
547
+ // Not included: elements where user might click multiple times in succession
548
+ // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
549
+ thisObj.playMedia();
550
+ }
551
+ else if (!thisObj.startedPlaying) {
552
+ if (thisObj.startTime > 0) {
553
+ if (thisObj.seeking) {
554
+ // a seek has already been initiated
555
+ // since canplaythrough has been triggered, the seek is complete
556
+ thisObj.seeking = false;
557
+ if (thisObj.autoplay || thisObj.okToPlay) {
558
+ thisObj.playMedia();
559
+ }
560
+ }
561
+ else {
562
+ // haven't started seeking yet
563
+ thisObj.seekTo(thisObj.startTime);
564
+ }
565
+ }
566
+ else if (thisObj.defaultChapter && typeof thisObj.selectedChapters !== 'undefined') {
567
+ thisObj.seekToChapter(thisObj.defaultChapter);
568
+ }
569
+ else {
570
+ // there is no startTime, therefore no seeking required
571
+ if (thisObj.autoplay || thisObj.okToPlay) {
572
+ thisObj.playMedia();
573
+ }
574
+ }
575
+ }
576
+ else if (thisObj.hasPlaylist) {
577
+ if ((thisObj.playlistIndex !== thisObj.$playlist.length) || thisObj.loop) {
578
+ // this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
579
+ thisObj.playMedia();
580
+ }
581
+ }
582
+ else {
583
+ // already started playing
584
+ // we're here because a new media source has been loaded and is ready to resume playback
585
+ thisObj.getPlayerState().then(function(currentState) {
586
+ if (thisObj.swappingSrc && currentState === 'stopped') {
587
+ // Safari is the only browser that returns value of 'stopped' (observed in 12.0.1 on MacOS)
588
+ // This prevents 'timeupdate' events from triggering, which prevents the new media src
589
+ // from resuming playback at swapTime
590
+ // This is a hack to jump start Safari
591
+ thisObj.startedPlaying = false;
592
+ if (thisObj.swapTime > 0) {
593
+ thisObj.seekTo(thisObj.swapTime);
594
+ }
595
+ else {
596
+ thisObj.playMedia();
597
+ }
598
+ }
599
+ });
600
+ }
601
+
602
+ */
603
+
604
+ var thisObj = this;
605
+
606
+ // Vimeo doesn't seem to support chaining of on() functions
607
+ // so each event listener must be attached separately
608
+ this.vimeoPlayer.on('loaded', function(vimeoId) {
609
+ // Triggered when a new video is loaded in the player
610
+ thisObj.onMediaNewSourceLoad();
611
+ });
612
+ this.vimeoPlayer.on('play', function(data) {
613
+ // Triggered when the video plays
614
+ thisObj.playing = true;
615
+ thisObj.startedPlaying = true;
616
+ thisObj.paused = false;
617
+ thisObj.refreshControls('playpause');
618
+ });
619
+ this.vimeoPlayer.on('ended', function(data) {
620
+ // Triggered any time the video playback reaches the end.
621
+ // Note: when loop is turned on, the ended event will not fire.
622
+ thisObj.playing = false;
623
+ thisObj.paused = true;
624
+ thisObj.onMediaComplete();
625
+ });
626
+ this.vimeoPlayer.on('bufferstart', function() {
627
+ // Triggered when buffering starts in the player.
628
+ // This is also triggered during preload and while seeking.
629
+ // There is no associated data with this event.
630
+ });
631
+ this.vimeoPlayer.on('bufferend', function() {
632
+ // Triggered when buffering ends in the player.
633
+ // This is also triggered at the end of preload and seeking.
634
+ // There is no associated data with this event.
635
+ });
636
+ this.vimeoPlayer.on('progress', function(data) {
637
+ // Triggered as the video is loaded.
638
+ // Reports back the amount of the video that has been buffered (NOT the amount played)
639
+ // Data has keys duration, percent, and seconds
640
+ });
641
+ this.vimeoPlayer.on('seeking', function(data) {
642
+ // Triggered when the player starts seeking to a specific time.
643
+ // A timeupdate event will also be fired at the same time.
644
+ });
645
+ this.vimeoPlayer.on('seeked', function(data) {
646
+ // Triggered when the player seeks to a specific time.
647
+ // A timeupdate event will also be fired at the same time.
648
+ });
649
+ this.vimeoPlayer.on('timeupdate',function(data) {
650
+ // Triggered as the currentTime of the video updates.
651
+ // It generally fires every 250ms, but it may vary depending on the browser.
652
+ thisObj.onMediaUpdateTime(data['duration'], data['seconds']);
653
+ });
654
+ this.vimeoPlayer.on('pause',function(data) {
655
+ // Triggered when the video pauses
656
+ if (!thisObj.clickedPlay) {
657
+ // 'pause' was triggered automatically, not initiated by user
658
+ // this happens in some browsers (not Chrome, as of 70.x)
659
+ // when swapping source (e.g., between tracks in a playlist, or swapping description)
660
+ if (thisObj.hasPlaylist || thisObj.swappingSrc) {
661
+ // do NOT set playing to false.
662
+ // doing so prevents continual playback after new track is loaded
663
+ }
664
+ else {
665
+ thisObj.playing = false;
666
+ thisObj.paused = true;
667
+ }
668
+ }
669
+ else {
670
+ thisObj.playing = false;
671
+ thisObj.paused = true;
672
+ }
673
+ thisObj.clickedPlay = false; // done with this variable
674
+ thisObj.onMediaPause();
675
+ thisObj.refreshControls('playpause');
676
+ });
677
+ this.vimeoPlayer.on('playbackratechange',function(data) {
678
+ // Triggered when the playback rate of the video in the player changes.
679
+ // The ability to change rate can be disabled by the creator
680
+ // and the event will not fire for those videos.
681
+ // data contains one key: 'playbackRate'
682
+ thisObj.vimeoPlaybackRate = data['playbackRate'];
683
+ });
684
+ this.vimeoPlayer.on('texttrackchange', function(data) {
685
+ // Triggered when the active text track (captions/subtitles) changes.
686
+ // The values will be null if text tracks are turned off.
687
+ // data contains three keys: kind, label, language
688
+ });
689
+ this.vimeoPlayer.on('volumechange',function(data) {
690
+ // Triggered when the volume in the player changes.
691
+ // Some devices do not support setting the volume of the video
692
+ // independently from the system volume,
693
+ // so this event will never fire on those devices.
694
+ thisObj.volume = data['volume'] * 10;
695
+ });
696
+ this.vimeoPlayer.on('error',function(data) {
697
+ // do something with the available data
698
+ // data contains three keys: message, method, name
699
+ // message: A user-friendly error message
700
+ // method: The Vimeo API method call that triggered the error
701
+ // name: Name of the error (not necesssarily user-friendly)
702
+ });
703
+ };
704
+
705
+ AblePlayer.prototype.addEventListeners = function () {
706
+
707
+ var thisObj, whichButton, thisElement;
708
+
709
+ // Save the current object context in thisObj for use with inner functions.
710
+ thisObj = this;
711
+
712
+ // Appropriately resize media player for full screen.
713
+ $(window).resize(function () {
714
+ thisObj.onWindowResize();
715
+ });
716
+
717
+ // Refresh player if it changes from hidden to visible
718
+ // There is no event triggered by a change in visibility
719
+ // but MutationObserver works in most browsers (but NOT in IE 10 or earlier)
720
+ // http://caniuse.com/#feat=mutationobserver
721
+ if (window.MutationObserver) {
722
+ var target = this.$ableDiv[0];
723
+ var observer = new MutationObserver(function(mutations) {
724
+ mutations.forEach(function(mutation) {
725
+ if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
726
+ // the player's style attribute has changed. Check to see if it's visible
727
+ if (thisObj.$ableDiv.is(':visible')) {
728
+ thisObj.refreshControls('init');
729
+ }
730
+ }
731
+ });
732
+ });
733
+ var config = { attributes: true, childList: true, characterData: true };
734
+ observer.observe(target, config);
735
+ }
736
+ else {
737
+ // browser doesn't support MutationObserver
738
+ // TODO: Figure out an alternative solution for this rare use case in older browsers
739
+ // See example in buildplayer.js > useSvg()
740
+ }
741
+ if (typeof this.seekBar !== 'undefined') {
742
+ this.addSeekbarListeners();
743
+ }
744
+ else {
745
+ // wait a bit and try again
746
+ // TODO: Should set this up to keep trying repeatedly.
747
+ // Seekbar listeners are critical.
748
+ setTimeout(function() {
749
+ if (typeof thisObj.seekBar !== 'undefined') {
750
+ thisObj.addSeekbarListeners();
751
+ }
752
+ },2000);
753
+ }
754
+
755
+ // handle clicks on player buttons
756
+ this.$controllerDiv.find('button').on('click',function(e){
757
+ e.stopPropagation();
758
+ thisObj.onClickPlayerButton(this);
759
+ });
760
+
761
+ // handle clicks (left only) anywhere on the page. If any popups are open, close them.
762
+ $(document).on('click',function(e) {
763
+
764
+ if (e.button !== 0) { // not a left click
765
+ return false;
766
+ }
767
+ if ($('.able-popup:visible').length || $('.able-volume-popup:visible')) {
768
+ // at least one popup is visible
769
+ thisObj.closePopups();
770
+ }
771
+ });
772
+
773
+ // handle mouse movement over player; make controls visible again if hidden
774
+ this.$ableDiv.on('mousemove',function() {
775
+ if (thisObj.controlsHidden) {
776
+ thisObj.fadeControls('in');
777
+ thisObj.controlsHidden = false;
778
+ // if there's already an active timeout, clear it and start timer again
779
+ if (thisObj.hideControlsTimeoutStatus === 'active') {
780
+ window.clearTimeout(thisObj.hideControlsTimeout);
781
+ thisObj.hideControlsTimeoutStatus = 'clear';
782
+ }
783
+ if (thisObj.hideControls) {
784
+ // after showing controls, hide them again after a brief timeout
785
+ thisObj.invokeHideControlsTimeout();
786
+ }
787
+ }
788
+ else {
789
+ // if there's already an active timeout, clear it and start timer again
790
+ if (thisObj.hideControlsTimeoutStatus === 'active') {
791
+ window.clearTimeout(thisObj.hideControlsTimeout);
792
+ thisObj.hideControlsTimeoutStatus = 'clear';
793
+ if (thisObj.hideControls) {
794
+ thisObj.invokeHideControlsTimeout();
795
+ }
796
+ }
797
+ }
798
+ });
799
+
800
+ // if user presses a key from anywhere on the page, show player controls
801
+ $(document).keydown(function() {
802
+ if (thisObj.controlsHidden) {
803
+ thisObj.fadeControls('in');
804
+ thisObj.controlsHidden = false;
805
+ if (thisObj.hideControlsTimeoutStatus === 'active') {
806
+ window.clearTimeout(thisObj.hideControlsTimeout);
807
+ thisObj.hideControlsTimeoutStatus = 'clear';
808
+ }
809
+ if (thisObj.hideControls) {
810
+ // after showing controls, hide them again after a brief timeout
811
+ thisObj.invokeHideControlsTimeout();
812
+ }
813
+ }
814
+ else {
815
+ // controls are visible
816
+ // if there's already an active timeout, clear it and start timer again
817
+ if (thisObj.hideControlsTimeoutStatus === 'active') {
818
+ window.clearTimeout(thisObj.hideControlsTimeout);
819
+ thisObj.hideControlsTimeoutStatus = 'clear';
820
+
821
+ if (thisObj.hideControls) {
822
+ thisObj.invokeHideControlsTimeout();
823
+ }
824
+ }
825
+ }
826
+ });
827
+
828
+ // handle local keydown events if this isn't the only player on the page;
829
+ // otherwise these are dispatched by global handler (see ableplayer-base,js)
830
+ this.$ableDiv.keydown(function (e) {
831
+ if (AblePlayer.nextIndex > 1) {
832
+ thisObj.onPlayerKeyPress(e);
833
+ }
834
+ });
835
+
836
+ // transcript is not a child of this.$ableDiv
837
+ // therefore, must be added separately
838
+ if (this.$transcriptArea) {
839
+ this.$transcriptArea.keydown(function (e) {
840
+ if (AblePlayer.nextIndex > 1) {
841
+ thisObj.onPlayerKeyPress(e);
842
+ }
843
+ });
844
+ }
845
+
846
+ // handle clicks on playlist items
847
+ if (this.$playlist) {
848
+ this.$playlist.click(function(e) {
849
+ if (!thisObj.userClickedPlaylist) {
850
+ // stopgap in case multiple clicks are fired on the same playlist item
851
+ thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered
852
+ thisObj.playlistIndex = $(this).index();
853
+ thisObj.cuePlaylistItem(thisObj.playlistIndex);
854
+ }
855
+ });
856
+ }
857
+
858
+ // Also play/pause when clicking on the media.
859
+ this.$media.click(function () {
860
+ thisObj.handlePlay();
861
+ });
862
+
863
+ // add listeners for media events
864
+ if (this.player === 'html5') {
865
+ this.addHtml5MediaListeners();
866
+ }
867
+ else if (this.player === 'vimeo') {
868
+ this.addVimeoListeners();
869
+ }
870
+ else if (this.player === 'youtube') {
871
+ // Youtube doesn't give us time update events, so we just periodically generate them ourselves
872
+ setInterval(function () {
873
+ thisObj.onMediaUpdateTime();
874
+ }, 300);
875
+ }
876
+ };
698
877
  })(jQuery);