wai-website-theme 1.3.1 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
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);