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,482 +1,699 @@
1
1
 
2
2
  (function ($) {
3
- AblePlayer.prototype.initYouTubePlayer = function () {
4
-
5
- var thisObj, deferred, promise, youTubeId, googleApiPromise, json;
6
- thisObj = this;
7
-
8
- deferred = new $.Deferred();
9
- promise = deferred.promise();
10
-
11
- // if a described version is available && user prefers desription
12
- // init player using the described version
13
- if (this.youTubeDescId && this.prefDesc) {
14
- youTubeId = this.youTubeDescId;
15
- }
16
- else {
17
- youTubeId = this.youTubeId;
18
- }
19
- this.activeYouTubeId = youTubeId;
20
- if (AblePlayer.youtubeIframeAPIReady) {
21
- // Script already loaded and ready.
22
- this.finalizeYoutubeInit().then(function() {
23
- deferred.resolve();
24
- });
25
- }
26
- else {
27
- // Has another player already started loading the script? If so, abort...
28
- if (!AblePlayer.loadingYoutubeIframeAPI) {
29
- $.getScript('https://www.youtube.com/iframe_api').fail(function () {
30
- deferred.fail();
31
- });
32
- }
33
-
34
- // Otherwise, keeping waiting for script load event...
35
- $('body').on('youtubeIframeAPIReady', function () {
36
- thisObj.finalizeYoutubeInit().then(function() {
37
- deferred.resolve();
38
- });
39
- });
40
- }
41
- return promise;
42
- };
43
-
44
- AblePlayer.prototype.finalizeYoutubeInit = function () {
45
-
46
- // This is called once we're sure the Youtube iFrame API is loaded -- see above
47
- var deferred, promise, thisObj, containerId, ccLoadPolicy, videoDimensions;
48
-
49
- deferred = new $.Deferred();
50
- promise = deferred.promise();
51
-
52
- thisObj = this;
53
-
54
- containerId = this.mediaId + '_youtube';
55
-
56
- this.$mediaContainer.prepend($('<div>').attr('id', containerId));
57
- // NOTE: Tried the following in place of the above in January 2016
58
- // because in some cases two videos were being added to the DOM
59
- // However, once v2.2.23 was fairly stable, unable to reproduce that problem
60
- // so maybe it's not an issue. This is preserved here temporarily, just in case it's needed...
61
- // thisObj.$mediaContainer.html($('<div>').attr('id', containerId));
62
-
63
- this.youTubeCaptionsReady = false;
64
-
65
- // if captions are provided locally via <track> elements, use those
66
- // and unload the captions provided by YouTube
67
- // Advantages of using <track>:
68
- // 1. Interactive transcript and searching within video is possible
69
- // 2. User has greater control over captions' display
70
- if (thisObj.captions.length) {
71
- // initialize YouTube player with cc_load_policy = 0
72
- // this doesn't disable captions;
73
- // it just doesn't show them automatically (depends on user's preference on YouTube)
74
- ccLoadPolicy = 0;
75
- this.usingYouTubeCaptions = false;
76
- }
77
- else {
78
- // set ccLoadPolicy to 1 only if captions are on;
79
- // this forces them on, regardless of user's preference on YouTube
80
- if (this.captionsOn) {
81
- ccLoadPolicy = 1;
82
- }
83
- else {
84
- ccLoadPolicy = 0;
85
- }
86
- }
87
- videoDimensions = this.getYouTubeDimensions(this.activeYouTubeId, containerId);
88
- if (videoDimensions) {
89
- this.ytWidth = videoDimensions[0];
90
- this.ytHeight = videoDimensions[1];
91
- this.aspectRatio = thisObj.ytWidth / thisObj.ytHeight;
92
- }
93
- else {
94
- // dimensions are initially unknown
95
- // sending null values to YouTube results in a video that uses the default YouTube dimensions
96
- // these can then be scraped from the iframe and applied to this.$ableWrapper
97
- this.ytWidth = null;
98
- this.ytHeight = null;
99
- }
100
-
101
- // NOTE: YouTube is changing the following parameters on or after Sep 25, 2018:
102
- // rel - No longer able to prevent YouTube from showing related videos
103
- // value of 0 now limits related videos to video's same channel
104
- // showinfo - No longer supported (previously, value of 0 hid title, share, & watch later buttons
105
- // Documentation https://developers.google.com/youtube/player_parameters
106
-
107
- this.youTubePlayer = new YT.Player(containerId, {
108
- videoId: this.activeYouTubeId,
109
- host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com',
110
- width: this.ytWidth,
111
- height: this.ytHeight,
112
- playerVars: {
113
- autoplay: 0,
114
- enablejsapi: 1,
115
- disableKb: 1, // disable keyboard shortcuts, using our own
116
- playsinline: this.playsInline,
117
- start: this.startTime,
118
- controls: 0, // no controls, using our own
119
- cc_load_policy: ccLoadPolicy,
120
- hl: this.lang, // use the default language UI
121
- modestbranding: 1, // no YouTube logo in controller
122
- rel: 0, // do not show related videos when video ends
123
- html5: 1, // force html5 if browser supports it (undocumented parameter; 0 does NOT force Flash)
124
- iv_load_policy: 3 // do not show video annotations
125
- },
126
- events: {
127
- onReady: function () {
128
- if (thisObj.swappingSrc) {
129
- // swap is now complete
130
- thisObj.swappingSrc = false;
131
- if (thisObj.playing) {
132
- // resume playing
133
- thisObj.playMedia();
134
- }
135
- }
136
- if (typeof thisObj.aspectRatio === 'undefined') {
137
- thisObj.resizeYouTubePlayer(thisObj.activeYouTubeId, containerId);
138
- }
139
- deferred.resolve();
140
- },
141
- onError: function (x) {
142
- deferred.fail();
143
- },
144
- onStateChange: function (x) {
145
- var playerState = thisObj.getPlayerState(x.data);
146
- if (playerState === 'playing') {
147
- thisObj.playing = true;
148
- thisObj.startedPlaying = true;
149
- }
150
- else {
151
- thisObj.playing = false;
152
- }
153
- if (thisObj.stoppingYouTube && playerState === 'paused') {
154
- if (typeof thisObj.$posterImg !== 'undefined') {
155
- thisObj.$posterImg.show();
156
- }
157
- thisObj.stoppingYouTube = false;
158
- thisObj.seeking = false;
159
- thisObj.playing = false;
160
- }
161
- },
162
- onPlaybackQualityChange: function () {
163
- // do something
164
- },
165
- onApiChange: function (x) {
166
- // As of Able Player v2.2.23, we are now getting caption data via the YouTube Data API
167
- // prior to calling initYouTubePlayer()
168
- // Previously we got caption data via the YouTube iFrame API, and doing so was an awful mess.
169
- // onApiChange fires to indicate that the player has loaded (or unloaded) a module with exposed API methods
170
- // it isn't fired until the video starts playing
171
- // if captions are available for this video (automated captions don't count)
172
- // the 'captions' (or 'cc') module is loaded. If no captions are available, this event never fires
173
- // So, to trigger this event we had to play the video briefly, then pause, then reset.
174
- // During that brief moment of playback, the onApiChange event was fired and we could setup captions
175
- // The 'captions' and 'cc' modules are very different, and have different data and methods
176
- // NOW, in v2.2.23, we still need to initialize the caption modules in order to control captions
177
- // but we don't have to do that on load in order to get caption data
178
- // Instead, we can wait until the video starts playing normally, then retrieve the modules
179
- thisObj.initYouTubeCaptionModule();
180
- }
181
- }
182
- });
183
- thisObj.injectPoster(thisObj.$mediaContainer, 'youtube');
184
- thisObj.$media.remove();
185
- return promise;
186
- };
187
-
188
- AblePlayer.prototype.getYouTubeDimensions = function (youTubeId, youTubeContainerId) {
189
-
190
- // get dimensions of YouTube video, return array with width & height
191
- // Sources, in order of priority:
192
- // 1. The width and height attributes on <video>
193
- // 2. YouTube (not yet supported; can't seem to get this data via YouTube Data API without OAuth!)
194
-
195
- var d, url, $iframe, width, height;
196
-
197
- d = [];
198
-
199
- if (typeof this.playerMaxWidth !== 'undefined') {
200
- d[0] = this.playerMaxWidth;
201
- // optional: set height as well; not required though since YouTube will adjust height to match width
202
- if (typeof this.playerMaxHeight !== 'undefined') {
203
- d[1] = this.playerMaxHeight;
204
- }
205
- return d;
206
- }
207
- else {
208
- if (typeof $('#' + youTubeContainerId) !== 'undefined') {
209
- $iframe = $('#' + youTubeContainerId);
210
- width = $iframe.width();
211
- height = $iframe.height();
212
- if (width > 0 && height > 0) {
213
- d[0] = width;
214
- d[1] = height;
215
- return d;
216
- }
217
- }
218
- }
219
- return false;
220
- };
221
-
222
- AblePlayer.prototype.resizeYouTubePlayer = function(youTubeId, youTubeContainerId) {
223
-
224
- // called after player is ready, if youTube dimensions were previously unknown
225
- // Now need to get them from the iframe element that YouTube injected
226
- // and resize Able Player to match
227
- var d, width, height;
228
- if (typeof this.aspectRatio !== 'undefined') {
229
- // video dimensions have already been collected
230
- if (this.restoringAfterFullScreen) {
231
- // restore using saved values
232
- if (this.youTubePlayer) {
233
- this.youTubePlayer.setSize(this.ytWidth, this.ytHeight);
234
- }
235
- this.restoringAfterFullScreen = false;
236
- }
237
- else {
238
- // recalculate with new wrapper size
239
- width = this.$ableWrapper.parent().width();
240
- height = Math.round(width / this.aspectRatio);
241
- this.$ableWrapper.css({
242
- 'max-width': width + 'px',
243
- 'width': ''
244
- });
245
- this.youTubePlayer.setSize(width, height);
246
- if (this.isFullscreen()) {
247
- this.youTubePlayer.setSize(width, height);
248
- }
249
- else {
250
- // resizing due to a change in window size, not full screen
251
- this.youTubePlayer.setSize(this.ytWidth, this.ytHeight);
252
- }
253
- }
254
- }
255
- else {
256
- d = this.getYouTubeDimensions(youTubeId, youTubeContainerId);
257
- if (d) {
258
- width = d[0];
259
- height = d[1];
260
- if (width > 0 && height > 0) {
261
- this.aspectRatio = width / height;
262
- this.ytWidth = width;
263
- this.ytHeight = height;
264
- if (width !== this.$ableWrapper.width()) {
265
- // now that we've retrieved YouTube's default width,
266
- // need to adjust to fit the current player wrapper
267
- width = this.$ableWrapper.width();
268
- height = Math.round(width / this.aspectRatio);
269
- if (this.youTubePlayer) {
270
- this.youTubePlayer.setSize(width, height);
271
- }
272
- }
273
- }
274
- }
275
- }
276
- };
277
-
278
- AblePlayer.prototype.setupYouTubeCaptions = function () {
279
-
280
- // called from setupAltCaptions if player is YouTube and there are no <track> captions
281
-
282
- // use YouTube Data API to get caption data from YouTube
283
- // function is called only if these conditions are met:
284
- // 1. this.player === 'youtube'
285
- // 2. there are no <track> elements with kind="captions"
286
- // 3. youTubeDataApiKey is defined
287
-
288
- var deferred = new $.Deferred();
289
- var promise = deferred.promise();
290
-
291
- var thisObj, googleApiPromise, youTubeId, i;
292
-
293
- thisObj = this;
294
-
295
- // this.ytCaptions has the same structure as this.captions
296
- // but unfortunately does not contain cues
297
- // Google *does* offer a captions.download service for downloading captions in WebVTT
298
- // https://developers.google.com/youtube/v3/docs/captions/download
299
- // However, this requires OAUTH 2.0 (user must login and give consent)
300
- // So, for now the best we can do is create an array of available caption/subtitle tracks
301
- // and provide a button & popup menu to allow users to control them
302
- this.ytCaptions = [];
303
-
304
- // if a described version is available && user prefers desription
305
- // Use the described version, and get its captions
306
- if (this.youTubeDescId && this.prefDesc) {
307
- youTubeId = this.youTubeDescId;
308
- }
309
- else {
310
- youTubeId = this.youTubeId;
311
- }
312
-
313
- // Wait until Google Client API is loaded
314
- // When loaded, it sets global var googleApiReady to true
315
-
316
- // Thanks to Paul Tavares for $.doWhen()
317
- // https://gist.github.com/purtuga/8257269
318
- $.doWhen({
319
- when: function(){
320
- return googleApiReady;
321
- },
322
- interval: 100, // ms
323
- attempts: 1000
324
- })
325
- .done(function(){
326
- thisObj.getYouTubeCaptionData(youTubeId).done(function() {
327
- deferred.resolve();
328
- });
329
- })
330
- .fail(function(){
331
- console.log('Unable to initialize Google API. YouTube captions are currently unavailable.');
332
- });
333
-
334
- return promise;
335
- };
336
-
337
- AblePlayer.prototype.getYouTubeCaptionData = function (youTubeId) {
338
- // get data via YouTube Data API, and push data to this.ytCaptions
339
- var deferred = new $.Deferred();
340
- var promise = deferred.promise();
341
-
342
- var thisObj, i, trackId, trackLang, trackLabel, trackKind, isDraft, isDefaultTrack;
343
-
344
- thisObj = this;
345
- gapi.client.setApiKey(youTubeDataAPIKey);
346
- gapi.client
347
- .load('youtube', 'v3')
348
- .then(function() {
349
- var request = gapi.client.youtube.captions.list({
350
- 'part': 'id, snippet',
351
- 'videoId': youTubeId
352
- });
353
- request.then(function(json) {
354
- if (json.result.items.length) { // video has captions!
355
- thisObj.hasCaptions = true;
356
- thisObj.usingYouTubeCaptions = true;
357
- if (thisObj.prefCaptions === 1) {
358
- thisObj.captionsOn = true;
359
- }
360
- else {
361
- thisObj.captionsOn = false;
362
- }
363
- // Step through results and add them to cues array
364
- for (i=0; i < json.result.items.length; i++) {
365
-
366
- trackId = json.result.items[i].id;
367
- trackLabel = json.result.items[i].snippet.name; // always seems to be empty
368
- trackLang = json.result.items[i].snippet.language;
369
- trackKind = json.result.items[i].snippet.trackKind; // ASR, standard, forced
370
- isDraft = json.result.items[i].snippet.isDraft; // Boolean
371
- // Other variables that could potentially be collected from snippet:
372
- // isCC - Boolean, always seems to be false
373
- // isLarge - Boolean
374
- // isEasyReader - Boolean
375
- // isAutoSynced Boolean
376
- // status - string, always seems to be "serving"
377
-
378
- if (trackKind !== 'ASR' && !isDraft) {
379
-
380
- // if track name is empty (it always seems to be), assign a name based on trackLang
381
- if (trackLabel === '') {
382
- trackLabel = thisObj.getLanguageName(trackLang);
383
- }
384
-
385
- // assign the default track based on language of the player
386
- if (trackLang === thisObj.lang) {
387
- isDefaultTrack = true;
388
- }
389
- else {
390
- isDefaultTrack = false;
391
- }
392
-
393
- thisObj.ytCaptions.push({
394
- 'language': trackLang,
395
- 'label': trackLabel,
396
- 'def': isDefaultTrack
397
- });
398
- }
399
- }
400
- // setupPopups again with new ytCaptions array, replacing original
401
- thisObj.setupPopups('captions');
402
- deferred.resolve();
403
- }
404
- else {
405
- thisObj.hasCaptions = false;
406
- thisObj.usingYouTubeCaptions = false;
407
- deferred.resolve();
408
- }
409
- }, function (reason) {
410
- console.log('Error: ' + reason.result.error.message);
411
- });
412
- });
413
- return promise;
414
- };
415
-
416
- AblePlayer.prototype.initYouTubeCaptionModule = function () {
417
- // This function is called when YouTube onApiChange event fires
418
- // to indicate that the player has loaded (or unloaded) a module with exposed API methods
419
- // it isn't fired until the video starts playing
420
- // and only fires if captions are available for this video (automated captions don't count)
421
- // If no captions are available, onApichange event never fires & this function is never called
422
-
423
- // YouTube iFrame API documentation is incomplete related to captions
424
- // Found undocumented features on user forums and by playing around
425
- // Details are here: http://terrillthompson.com/blog/648
426
- // Summary:
427
- // User might get either the AS3 (Flash) or HTML5 YouTube player
428
- // The API uses a different caption module for each player (AS3 = 'cc'; HTML5 = 'captions')
429
- // There are differences in the data and methods available through these modules
430
- // This function therefore is used to determine which captions module is being used
431
- // If it's a known module, this.ytCaptionModule will be used elsewhere to control captions
432
- var options, fontSize, displaySettings;
433
-
434
- options = this.youTubePlayer.getOptions();
435
- if (options.length) {
436
- for (var i=0; i<options.length; i++) {
437
- if (options[i] == 'cc') { // this is the AS3 (Flash) player
438
- this.ytCaptionModule = 'cc';
439
- if (!this.hasCaptions) {
440
- // there are captions available via other sources (e.g., <track>)
441
- // so use these
442
- this.hasCaptions = true;
443
- this.usingYouTubeCaptions = true;
444
- }
445
- break;
446
- }
447
- else if (options[i] == 'captions') { // this is the HTML5 player
448
- this.ytCaptionModule = 'captions';
449
- if (!this.hasCaptions) {
450
- // there are captions available via other sources (e.g., <track>)
451
- // so use these
452
- this.hasCaptions = true;
453
- this.usingYouTubeCaptions = true;
454
- }
455
- break;
456
- }
457
- }
458
- if (typeof this.ytCaptionModule !== 'undefined') {
459
- if (this.usingYouTubeCaptions) {
460
- // set default languaage
461
- this.youTubePlayer.setOption(this.ytCaptionModule, 'track', {'languageCode': this.captionLang});
462
- // set font size using Able Player prefs (values are -1, 0, 1, 2, and 3, where 0 is default)
463
- this.youTubePlayer.setOption(this.ytCaptionModule,'fontSize',this.translatePrefs('size',this.prefCaptionsSize,'youtube'));
464
- // ideally could set other display options too, but no others seem to be supported by setOption()
465
- }
466
- else {
467
- // now that we know which cc module was loaded, unload it!
468
- // we don't want it if we're using local <track> elements for captions
469
- this.youTubePlayer.unloadModule(this.ytCaptionModule)
470
- }
471
- }
472
- }
473
- else {
474
- // no modules were loaded onApiChange
475
- // unfortunately, gonna have to disable captions if we can't control them
476
- this.hasCaptions = false;
477
- this.usingYouTubeCaptions = false;
478
- }
479
- this.refreshControls();
480
- };
3
+ AblePlayer.prototype.initYouTubePlayer = function () {
4
+
5
+ var thisObj, deferred, promise, youTubeId, googleApiPromise, json;
6
+ thisObj = this;
7
+
8
+ deferred = new $.Deferred();
9
+ promise = deferred.promise();
10
+
11
+ // if a described version is available && user prefers desription
12
+ // init player using the described version
13
+ if (this.youTubeDescId && this.prefDesc) {
14
+ youTubeId = this.youTubeDescId;
15
+ }
16
+ else {
17
+ youTubeId = this.youTubeId;
18
+ }
19
+ this.activeYouTubeId = youTubeId;
20
+ if (AblePlayer.youtubeIframeAPIReady) {
21
+ // Script already loaded and ready.
22
+ this.finalizeYoutubeInit().then(function() {
23
+ deferred.resolve();
24
+ });
25
+ }
26
+ else {
27
+ // Has another player already started loading the script? If so, abort...
28
+ if (!AblePlayer.loadingYoutubeIframeAPI) {
29
+ $.getScript('https://www.youtube.com/iframe_api').fail(function () {
30
+ deferred.fail();
31
+ });
32
+ }
33
+
34
+ // Otherwise, keeping waiting for script load event...
35
+ $('body').on('youtubeIframeAPIReady', function () {
36
+ thisObj.finalizeYoutubeInit().then(function() {
37
+ deferred.resolve();
38
+ });
39
+ });
40
+ }
41
+ return promise;
42
+ };
43
+
44
+ AblePlayer.prototype.finalizeYoutubeInit = function () {
45
+
46
+ // This is called once we're sure the Youtube iFrame API is loaded -- see above
47
+ var deferred, promise, thisObj, containerId, ccLoadPolicy, videoDimensions, autoplay;
48
+
49
+ deferred = new $.Deferred();
50
+ promise = deferred.promise();
51
+
52
+ thisObj = this;
53
+
54
+ containerId = this.mediaId + '_youtube';
55
+
56
+ this.$mediaContainer.prepend($('<div>').attr('id', containerId));
57
+ // NOTE: Tried the following in place of the above in January 2016
58
+ // because in some cases two videos were being added to the DOM
59
+ // However, once v2.2.23 was fairly stable, unable to reproduce that problem
60
+ // so maybe it's not an issue. This is preserved here temporarily, just in case it's needed...
61
+ // thisObj.$mediaContainer.html($('<div>').attr('id', containerId));
62
+
63
+ // cc_load_policy:
64
+ // 0 - show captions depending on user's preference on YouTube
65
+ // 1 - show captions by default, even if the user has turned them off
66
+ // For Able Player, init player with value of 0
67
+ // and will turn them on or off after player is initialized
68
+ // based on availability of local tracks and user's Able Player prefs
69
+ ccLoadPolicy = 0;
70
+
71
+ videoDimensions = this.getYouTubeDimensions(this.activeYouTubeId, containerId);
72
+ if (videoDimensions) {
73
+ this.ytWidth = videoDimensions[0];
74
+ this.ytHeight = videoDimensions[1];
75
+ this.aspectRatio = thisObj.ytWidth / thisObj.ytHeight;
76
+ }
77
+ else {
78
+ // dimensions are initially unknown
79
+ // sending null values to YouTube results in a video that uses the default YouTube dimensions
80
+ // these can then be scraped from the iframe and applied to this.$ableWrapper
81
+ this.ytWidth = null;
82
+ this.ytHeight = null;
83
+ }
84
+
85
+ if (this.okToPlay) {
86
+ autoplay = 1;
87
+ }
88
+ else {
89
+ autoplay = 0;
90
+ }
91
+
92
+ // NOTE: YouTube is changing the following parameters on or after Sep 25, 2018:
93
+ // rel - No longer able to prevent YouTube from showing related videos
94
+ // value of 0 now limits related videos to video's same channel
95
+ // showinfo - No longer supported (previously, value of 0 hid title, share, & watch later buttons
96
+ // Documentation https://developers.google.com/youtube/player_parameters
97
+
98
+ this.youTubePlayer = new YT.Player(containerId, {
99
+ videoId: this.activeYouTubeId,
100
+ host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com',
101
+ width: this.ytWidth,
102
+ height: this.ytHeight,
103
+ playerVars: {
104
+ autoplay: autoplay,
105
+ enablejsapi: 1,
106
+ disableKb: 1, // disable keyboard shortcuts, using our own
107
+ playsinline: this.playsInline,
108
+ start: this.startTime,
109
+ controls: 0, // no controls, using our own
110
+ cc_load_policy: ccLoadPolicy,
111
+ hl: this.lang, // use the default language UI
112
+ modestbranding: 1, // no YouTube logo in controller
113
+ rel: 0, // do not show related videos when video ends
114
+ html5: 1, // force html5 if browser supports it (undocumented parameter; 0 does NOT force Flash)
115
+ iv_load_policy: 3 // do not show video annotations
116
+ },
117
+ events: {
118
+ onReady: function () {
119
+ if (thisObj.swappingSrc) {
120
+ // swap is now complete
121
+ thisObj.swappingSrc = false;
122
+ thisObj.cueingPlaylistItem = false;
123
+ if (thisObj.playing) {
124
+ // resume playing
125
+ thisObj.playMedia();
126
+ }
127
+ }
128
+ if (thisObj.userClickedPlaylist) {
129
+ thisObj.userClickedPlaylist = false; // reset
130
+ }
131
+ if (typeof thisObj.aspectRatio === 'undefined') {
132
+ thisObj.resizeYouTubePlayer(thisObj.activeYouTubeId, containerId);
133
+ }
134
+ deferred.resolve();
135
+ },
136
+ onError: function (x) {
137
+ deferred.fail();
138
+ },
139
+ onStateChange: function (x) {
140
+ thisObj.getPlayerState().then(function(playerState) {
141
+ // values of playerState: 'playing','paused','buffering','ended'
142
+ if (playerState === 'playing') {
143
+ thisObj.playing = true;
144
+ thisObj.startedPlaying = true;
145
+ thisObj.paused = false;
146
+ }
147
+ else if (playerState == 'ended') {
148
+ thisObj.onMediaComplete();
149
+ }
150
+ else {
151
+ thisObj.playing = false;
152
+ thisObj.paused = true;
153
+ }
154
+ if (thisObj.stoppingYouTube && playerState === 'paused') {
155
+ if (typeof thisObj.$posterImg !== 'undefined') {
156
+ thisObj.$posterImg.show();
157
+ }
158
+ thisObj.stoppingYouTube = false;
159
+ thisObj.seeking = false;
160
+ thisObj.playing = false;
161
+ thisObj.paused = true;
162
+ }
163
+ });
164
+ },
165
+ onPlaybackQualityChange: function () {
166
+ // do something
167
+ },
168
+ onApiChange: function (x) {
169
+ // As of Able Player v2.2.23, we are now getting caption data via the YouTube Data API
170
+ // prior to calling initYouTubePlayer()
171
+ // Previously we got caption data via the YouTube iFrame API, and doing so was an awful mess.
172
+ // onApiChange fires to indicate that the player has loaded (or unloaded) a module with exposed API methods
173
+ // it isn't fired until the video starts playing
174
+ // if captions are available for this video (automated captions don't count)
175
+ // the 'captions' (or 'cc') module is loaded. If no captions are available, this event never fires
176
+ // So, to trigger this event we had to play the video briefly, then pause, then reset.
177
+ // During that brief moment of playback, the onApiChange event was fired and we could setup captions
178
+ // The 'captions' and 'cc' modules are very different, and have different data and methods
179
+ // NOW, in v2.2.23, we still need to initialize the caption modules in order to control captions
180
+ // but we don't have to do that on load in order to get caption data
181
+ // Instead, we can wait until the video starts playing normally, then retrieve the modules
182
+ thisObj.initYouTubeCaptionModule();
183
+ }
184
+ }
185
+ });
186
+
187
+ this.injectPoster(this.$mediaContainer, 'youtube');
188
+ if (!this.hasPlaylist) {
189
+ // remove the media element, since YouTube replaces that with its own element in an iframe
190
+ // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem()
191
+ this.$media.remove();
192
+ }
193
+ return promise;
194
+ };
195
+
196
+ AblePlayer.prototype.getYouTubeDimensions = function (youTubeContainerId) {
197
+
198
+ // get dimensions of YouTube video, return array with width & height
199
+ // Sources, in order of priority:
200
+ // 1. The width and height attributes on <video>
201
+ // 2. YouTube (not yet supported; can't seem to get this data via YouTube Data API without OAuth!)
202
+
203
+ var d, url, $iframe, width, height;
204
+
205
+ d = [];
206
+
207
+ if (typeof this.playerMaxWidth !== 'undefined') {
208
+ d[0] = this.playerMaxWidth;
209
+ // optional: set height as well; not required though since YouTube will adjust height to match width
210
+ if (typeof this.playerMaxHeight !== 'undefined') {
211
+ d[1] = this.playerMaxHeight;
212
+ }
213
+ return d;
214
+ }
215
+ else {
216
+ if (typeof $('#' + youTubeContainerId) !== 'undefined') {
217
+ $iframe = $('#' + youTubeContainerId);
218
+ width = $iframe.width();
219
+ height = $iframe.height();
220
+ if (width > 0 && height > 0) {
221
+ d[0] = width;
222
+ d[1] = height;
223
+ return d;
224
+ }
225
+ }
226
+ }
227
+ return false;
228
+ };
229
+
230
+ AblePlayer.prototype.resizeYouTubePlayer = function(youTubeId, youTubeContainerId) {
231
+
232
+ // called after player is ready, if youTube dimensions were previously unknown
233
+ // Now need to get them from the iframe element that YouTube injected
234
+ // and resize Able Player to match
235
+ var d, width, height;
236
+ if (typeof this.aspectRatio !== 'undefined') {
237
+ // video dimensions have already been collected
238
+ if (this.restoringAfterFullScreen) {
239
+ // restore using saved values
240
+ if (this.youTubePlayer) {
241
+ this.youTubePlayer.setSize(this.ytWidth, this.ytHeight);
242
+ }
243
+ this.restoringAfterFullScreen = false;
244
+ }
245
+ else {
246
+ // recalculate with new wrapper size
247
+ width = this.$ableWrapper.parent().width();
248
+ height = Math.round(width / this.aspectRatio);
249
+ this.$ableWrapper.css({
250
+ 'max-width': width + 'px',
251
+ 'width': ''
252
+ });
253
+ this.youTubePlayer.setSize(width, height);
254
+ if (this.fullscreen) {
255
+ this.youTubePlayer.setSize(width, height);
256
+ }
257
+ else {
258
+ // resizing due to a change in window size, not full screen
259
+ this.youTubePlayer.setSize(this.ytWidth, this.ytHeight);
260
+ }
261
+ }
262
+ }
263
+ else {
264
+ d = this.getYouTubeDimensions(youTubeContainerId);
265
+ if (d) {
266
+ width = d[0];
267
+ height = d[1];
268
+ if (width > 0 && height > 0) {
269
+ this.aspectRatio = width / height;
270
+ this.ytWidth = width;
271
+ this.ytHeight = height;
272
+ if (width !== this.$ableWrapper.width()) {
273
+ // now that we've retrieved YouTube's default width,
274
+ // need to adjust to fit the current player wrapper
275
+ width = this.$ableWrapper.width();
276
+ height = Math.round(width / this.aspectRatio);
277
+ if (this.youTubePlayer) {
278
+ this.youTubePlayer.setSize(width, height);
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+ };
285
+
286
+ AblePlayer.prototype.setupYouTubeCaptions = function () {
287
+
288
+ // called from setupAltCaptions if player is YouTube and there are no <track> captions
289
+
290
+ // use YouTube Data API to get caption data from YouTube
291
+ // function is called only if these conditions are met:
292
+ // 1. this.player === 'youtube'
293
+ // 2. there are no <track> elements with kind="captions"
294
+ // 3. youTubeDataApiKey is defined
295
+
296
+ var deferred = new $.Deferred();
297
+ var promise = deferred.promise();
298
+
299
+ var thisObj, googleApiPromise, youTubeId, i;
300
+
301
+ thisObj = this;
302
+
303
+ // if a described version is available && user prefers desription
304
+ // Use the described version, and get its captions
305
+ if (this.youTubeDescId && this.prefDesc) {
306
+ youTubeId = this.youTubeDescId;
307
+ }
308
+ else {
309
+ youTubeId = this.youTubeId;
310
+ }
311
+
312
+ if (typeof youTubeDataAPIKey !== 'undefined') {
313
+ // Wait until Google Client API is loaded
314
+ // When loaded, it sets global var googleApiReady to true
315
+
316
+ // Thanks to Paul Tavares for $.doWhen()
317
+ // https://gist.github.com/purtuga/8257269
318
+ $.doWhen({
319
+ when: function(){
320
+ return googleApiReady;
321
+ },
322
+ interval: 100, // ms
323
+ attempts: 1000
324
+ })
325
+ .done(function(){
326
+ deferred.resolve();
327
+ })
328
+ .fail(function(){
329
+ console.log('Unable to initialize Google API. YouTube captions are currently unavailable.');
330
+ });
331
+ }
332
+ else {
333
+ deferred.resolve();
334
+ }
335
+ return promise;
336
+ };
337
+
338
+ AblePlayer.prototype.waitForGapi = function () {
339
+
340
+ // wait for Google API to initialize
341
+
342
+ var thisObj, deferred, promise, maxWaitTime, maxTries, tries, timer, interval;
343
+
344
+ thisObj = this;
345
+ deferred = new $.Deferred();
346
+ promise = deferred.promise();
347
+ maxWaitTime = 5000; // 5 seconds
348
+ maxTries = 100; // number of tries during maxWaitTime
349
+ tries = 0;
350
+ interval = Math.floor(maxWaitTime/maxTries);
351
+
352
+ timer = setInterval(function() {
353
+ tries++;
354
+ if (googleApiReady || tries >= maxTries) {
355
+ clearInterval(timer);
356
+ if (googleApiReady) { // success!
357
+ deferred.resolve(true);
358
+ }
359
+ else { // tired of waiting
360
+ deferred.resolve(false);
361
+ }
362
+ }
363
+ else {
364
+ thisObj.waitForGapi();
365
+ }
366
+ }, interval);
367
+ return promise;
368
+ };
369
+
370
+ AblePlayer.prototype.getYouTubeCaptionTracks = function (youTubeId) {
371
+
372
+ // get data via YouTube Data API, and push data to this.captions
373
+ var deferred = new $.Deferred();
374
+ var promise = deferred.promise();
375
+
376
+ var thisObj, useGoogleApi, i, trackId, trackLang, trackName, trackLabel, trackKind, isDraft, isDefaultTrack;
377
+
378
+ thisObj = this;
379
+
380
+ if (typeof youTubeDataAPIKey !== 'undefined') {
381
+ this.waitForGapi().then(function(waitResult) {
382
+
383
+ useGoogleApi = waitResult;
384
+
385
+ // useGoogleApi returns false if API failed to initalize after max wait time
386
+ // Proceed only if true. Otherwise can still use fallback method (see else loop below)
387
+ if (useGoogleApi === true) {
388
+ gapi.client.setApiKey(youTubeDataAPIKey);
389
+ gapi.client
390
+ .load('youtube', 'v3')
391
+ .then(function() {
392
+ var request = gapi.client.youtube.captions.list({
393
+ 'part': 'id, snippet',
394
+ 'videoId': youTubeId
395
+ });
396
+ request.then(function(json) {
397
+ if (json.result.items.length) { // video has captions!
398
+ thisObj.hasCaptions = true;
399
+ thisObj.usingYouTubeCaptions = true;
400
+ if (thisObj.prefCaptions === 1) {
401
+ thisObj.captionsOn = true;
402
+ }
403
+ else {
404
+ thisObj.captionsOn = false;
405
+ }
406
+ // Step through results and add them to cues array
407
+ for (i=0; i < json.result.items.length; i++) {
408
+ trackName = json.result.items[i].snippet.name; // usually seems to be empty
409
+ trackLang = json.result.items[i].snippet.language;
410
+ trackKind = json.result.items[i].snippet.trackKind; // ASR, standard, forced
411
+ isDraft = json.result.items[i].snippet.isDraft; // Boolean
412
+ // Other variables that could potentially be collected from snippet:
413
+ // isCC - Boolean, always seems to be false
414
+ // isLarge - Boolean
415
+ // isEasyReader - Boolean
416
+ // isAutoSynced Boolean
417
+ // status - string, always seems to be "serving"
418
+
419
+ var srcUrl = thisObj.getYouTubeTimedTextUrl(youTubeId,trackName,trackLang);
420
+ if (trackKind !== 'ASR' && !isDraft) {
421
+
422
+ if (trackName !== '') {
423
+ trackLabel = trackName;
424
+ }
425
+ else {
426
+ // if track name is empty (it always seems to be), assign a label based on trackLang
427
+ trackLabel = thisObj.getLanguageName(trackLang);
428
+ }
429
+
430
+ // assign the default track based on language of the player
431
+ if (trackLang === thisObj.lang) {
432
+ isDefaultTrack = true;
433
+ }
434
+ else {
435
+ isDefaultTrack = false;
436
+ }
437
+ thisObj.tracks.push({
438
+ 'kind': 'captions',
439
+ 'src': srcUrl,
440
+ 'language': trackLang,
441
+ 'label': trackLabel,
442
+ 'def': isDefaultTrack
443
+ });
444
+ }
445
+ }
446
+ // setupPopups again with new captions array, replacing original
447
+ thisObj.setupPopups('captions');
448
+ deferred.resolve();
449
+ }
450
+ else {
451
+ thisObj.hasCaptions = false;
452
+ thisObj.usingYouTubeCaptions = false;
453
+ deferred.resolve();
454
+ }
455
+ }, function (reason) {
456
+ // If video has no captions, YouTube returns an error.
457
+ // Should still proceed, but with captions disabled
458
+ // The specific error, if needed: reason.result.error.message
459
+ // If no captions, the error is: "The video identified by the <code>videoId</code> parameter could not be found."
460
+ console.log('Error retrieving captions.');
461
+ console.log('Check your video on YouTube to be sure captions are available and published.');
462
+ thisObj.hasCaptions = false;
463
+ thisObj.usingYouTubeCaptions = false;
464
+ deferred.resolve();
465
+ });
466
+ })
467
+ }
468
+ else {
469
+ // googleAPi never loaded.
470
+ this.getYouTubeCaptionTracks2(youTubeId).then(function() {
471
+ deferred.resolve();
472
+ });
473
+ }
474
+ });
475
+ }
476
+ else {
477
+ // web owner hasn't provided a Google API key
478
+ // attempt to get YouTube captions via the backup method
479
+ this.getYouTubeCaptionTracks2(youTubeId).then(function() {
480
+ deferred.resolve();
481
+ });
482
+ }
483
+ return promise;
484
+ };
485
+
486
+ AblePlayer.prototype.getYouTubeCaptionTracks2 = function (youTubeId) {
487
+
488
+ // Use alternative backup method of getting caption tracks from YouTube
489
+ // and pushing them to this.captions
490
+ // Called from getYouTubeCaptionTracks if no Google API key is defined
491
+ // or if Google API failed to initiatlize
492
+ // This method seems to be undocumented, but is referenced on StackOverflow
493
+ // We'll use that as a fallback but it could break at any moment
494
+
495
+ var deferred = new $.Deferred();
496
+ var promise = deferred.promise();
497
+
498
+ var thisObj, useGoogleApi, i, trackId, trackLang, trackName, trackLabel, trackKind, isDraft, isDefaultTrack;
499
+
500
+ thisObj = this;
501
+
502
+ $.ajax({
503
+ type: 'get',
504
+ url: 'https://www.youtube.com/api/timedtext?type=list&v=' + youTubeId,
505
+ dataType: 'xml',
506
+ success: function(xml) {
507
+ var $tracks = $(xml).find('track');
508
+ if ($tracks.length > 0) { // video has captions!
509
+ thisObj.hasCaptions = true;
510
+ thisObj.usingYouTubeCaptions = true;
511
+ if (thisObj.prefCaptions === 1) {
512
+ thisObj.captionsOn = true;
513
+ }
514
+ else {
515
+ thisObj.captionsOn = false;
516
+ }
517
+ // Step through results and add them to tracks array
518
+ $tracks.each(function() {
519
+ trackId = $(this).attr('id');
520
+ trackLang = $(this).attr('lang_code');
521
+ if ($(this).attr('name') !== '') {
522
+ trackName = $(this).attr('name');
523
+ trackLabel = trackName;
524
+ }
525
+ else {
526
+ // @name is typically null except for default track
527
+ // but lang_translated seems to be reliable
528
+ trackName = '';
529
+ trackLabel = $(this).attr('lang_translated');
530
+ }
531
+ if (trackLabel === '') {
532
+ trackLabel = thisObj.getLanguageName(trackLang);
533
+ }
534
+ // assign the default track based on language of the player
535
+ if (trackLang === thisObj.lang) {
536
+ isDefaultTrack = true;
537
+ }
538
+ else {
539
+ isDefaultTrack = false;
540
+ }
541
+
542
+ // Build URL for retrieving WebVTT source via YouTube's timedtext API
543
+ var srcUrl = thisObj.getYouTubeTimedTextUrl(youTubeId,trackName,trackLang);
544
+ thisObj.tracks.push({
545
+ 'kind': 'captions',
546
+ 'src': srcUrl,
547
+ 'language': trackLang,
548
+ 'label': trackLabel,
549
+ 'def': isDefaultTrack
550
+ });
551
+
552
+ });
553
+ // setupPopups again with new captions array, replacing original
554
+ thisObj.setupPopups('captions');
555
+ deferred.resolve();
556
+ }
557
+ else {
558
+ thisObj.hasCaptions = false;
559
+ thisObj.usingYouTubeCaptions = false;
560
+ deferred.resolve();
561
+ }
562
+ },
563
+ error: function(xhr, status) {
564
+ console.log('Error retrieving YouTube caption data for video ' + youTubeId);
565
+ deferred.resolve();
566
+ }
567
+ });
568
+ return promise;
569
+ };
570
+
571
+ AblePlayer.prototype.getYouTubeTimedTextUrl = function (youTubeId, trackName, trackLang) {
572
+
573
+ // return URL for retrieving WebVTT source via YouTube's timedtext API
574
+ // Note: This API seems to be undocumented, and could break anytime
575
+ var url = 'https://www.youtube.com/api/timedtext?fmt=vtt';
576
+ url += '&v=' + youTubeId;
577
+ url += '&lang=' + trackLang;
578
+ // if track has a value in the name field, it's *required* in the URL
579
+ if (trackName !== '') {
580
+ url += '&name=' + trackName;
581
+ }
582
+ return url;
583
+ };
584
+
585
+
586
+ AblePlayer.prototype.getYouTubeCaptionCues = function (youTubeId) {
587
+
588
+ var deferred, promise, thisObj;
589
+
590
+ var deferred = new $.Deferred();
591
+ var promise = deferred.promise();
592
+
593
+ thisObj = this;
594
+
595
+ this.tracks = [];
596
+ this.tracks.push({
597
+ 'kind': 'captions',
598
+ 'src': 'some_file.vtt',
599
+ 'language': 'en',
600
+ 'label': 'Fake English captions'
601
+ });
602
+
603
+ deferred.resolve();
604
+ return promise;
605
+ };
606
+
607
+ AblePlayer.prototype.initYouTubeCaptionModule = function () {
608
+
609
+ // This function is called when YouTube onApiChange event fires
610
+ // to indicate that the player has loaded (or unloaded) a module with exposed API methods
611
+ // it isn't fired until the video starts playing
612
+ // and only fires if captions are available for this video (automated captions don't count)
613
+ // If no captions are available, onApichange event never fires & this function is never called
614
+
615
+ // YouTube iFrame API documentation is incomplete related to captions
616
+ // Found undocumented features on user forums and by playing around
617
+ // Details are here: http://terrillthompson.com/blog/648
618
+ // Summary:
619
+ // User might get either the AS3 (Flash) or HTML5 YouTube player
620
+ // The API uses a different caption module for each player (AS3 = 'cc'; HTML5 = 'captions')
621
+ // There are differences in the data and methods available through these modules
622
+ // This function therefore is used to determine which captions module is being used
623
+ // If it's a known module, this.ytCaptionModule will be used elsewhere to control captions
624
+ var options, fontSize, displaySettings;
625
+
626
+ options = this.youTubePlayer.getOptions();
627
+ if (options.length) {
628
+ for (var i=0; i<options.length; i++) {
629
+ if (options[i] == 'cc') { // this is the AS3 (Flash) player
630
+ this.ytCaptionModule = 'cc';
631
+ if (!this.hasCaptions) {
632
+ // there are captions available via other sources (e.g., <track>)
633
+ // so use these
634
+ this.hasCaptions = true;
635
+ this.usingYouTubeCaptions = true;
636
+ }
637
+ break;
638
+ }
639
+ else if (options[i] == 'captions') { // this is the HTML5 player
640
+ this.ytCaptionModule = 'captions';
641
+ if (!this.hasCaptions) {
642
+ // there are captions available via other sources (e.g., <track>)
643
+ // so use these
644
+ this.hasCaptions = true;
645
+ this.usingYouTubeCaptions = true;
646
+ }
647
+ break;
648
+ }
649
+ }
650
+ if (typeof this.ytCaptionModule !== 'undefined') {
651
+ if (this.usingYouTubeCaptions) {
652
+ // set default languaage
653
+ this.youTubePlayer.setOption(this.ytCaptionModule, 'track', {'languageCode': this.captionLang});
654
+ // set font size using Able Player prefs (values are -1, 0, 1, 2, and 3, where 0 is default)
655
+ this.youTubePlayer.setOption(this.ytCaptionModule,'fontSize',this.translatePrefs('size',this.prefCaptionsSize,'youtube'));
656
+ // ideally could set other display options too, but no others seem to be supported by setOption()
657
+ }
658
+ else {
659
+ // now that we know which cc module was loaded, unload it!
660
+ // we don't want it if we're using local <track> elements for captions
661
+ this.youTubePlayer.unloadModule(this.ytCaptionModule)
662
+ }
663
+ }
664
+ }
665
+ else {
666
+ // no modules were loaded onApiChange
667
+ // unfortunately, gonna have to disable captions if we can't control them
668
+ this.hasCaptions = false;
669
+ this.usingYouTubeCaptions = false;
670
+ }
671
+ this.refreshControls('captions');
672
+ };
673
+
674
+ AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) {
675
+
676
+ // return a URL for retrieving a YouTube poster image
677
+ // supported values of width: 120, 320, 480, 640
678
+
679
+ var url = 'https://img.youtube.com/vi/' + youTubeId;
680
+ if (width == '120') {
681
+ // default (small) thumbnail, 120 x 90
682
+ return url + '/default.jpg';
683
+ }
684
+ else if (width == '320') {
685
+ // medium quality thumbnail, 320 x 180
686
+ return url + '/hqdefault.jpg';
687
+ }
688
+ else if (width == '480') {
689
+ // high quality thumbnail, 480 x 360
690
+ return url + '/hqdefault.jpg';
691
+ }
692
+ else if (width == '640') {
693
+ // standard definition poster image, 640 x 480
694
+ return url + '/sddefault.jpg';
695
+ }
696
+ return false;
697
+ };
481
698
 
482
699
  })(jQuery);