1/* 2 // JavaScript for Able Player 3 4 // HTML5 Media API: 5 // http://www.w3.org/TR/html5/embedded-content-0.html#htmlmediaelement 6 // http://dev.w3.org/html5/spec-author-view/video.html 7 8 // W3C API Test Page: 9 // http://www.w3.org/2010/05/video/mediaevents.html 10 11 // YouTube Player API for iframe Embeds 12 https://developers.google.com/youtube/iframe_api_reference 13 14 // YouTube Player Parameters 15 https://developers.google.com/youtube/player_parameters?playerVersion=HTML5 16 17 // YouTube Data API 18 https://developers.google.com/youtube/v3 19 20 // Vimeo Player API 21 https://github.com/vimeo/player.js 22 23 // Google API Client Library for JavaScript 24 https://developers.google.com/api-client-library/javascript/dev/dev_jscript 25 26 // Google API Explorer: YouTube services and methods 27 https://developers.google.com/apis-explorer/#s/youtube/v3/ 28 29 // Web Speech API (Speech Synthesis) 30 // https://w3c.github.io/speech-api/#tts-section 31 // https://developer.mozilla.org/en-US/docs/Web/API/Window/speechSynthesis 32*/ 33 34/*jslint node: true, browser: true, white: true, indent: 2, unparam: true, plusplus: true */ 35/*global $, jQuery */ 36"use strict"; 37 38// maintain an array of Able Player instances for use globally (e.g., for keeping prefs in sync) 39var AblePlayerInstances = []; 40 41(function ($) { 42 $(function () { 43 44 $('video, audio').each(function (index, element) { 45 if ($(element).data('able-player') !== undefined) { 46 AblePlayerInstances.push(new AblePlayer($(this),$(element))); 47 } 48 }); 49 }); 50 51 // YouTube player support; pass ready event to jQuery so we can catch in player. 52 window.onYouTubeIframeAPIReady = function() { 53 AblePlayer.youTubeIframeAPIReady = true; 54 $('body').trigger('youTubeIframeAPIReady', []); 55 }; 56 // If there is only one player on the page, dispatch global keydown events to it 57 // Otherwise, keydowwn events are handled locally (see event.js > handleEventListeners()) 58 $(window).on('keydown',function(e) { 59 if (AblePlayer.nextIndex === 1) { 60 AblePlayer.lastCreated.onPlayerKeyPress(e); 61 } 62 }); 63 64 // Construct an AblePlayer object 65 // Parameters are: 66 // media - jQuery selector or element identifying the media. 67 window.AblePlayer = function(media) { 68 69 70 var thisObj = this; 71 72 // Keep track of the last player created for use with global events. 73 AblePlayer.lastCreated = this; 74 this.media = media; 75 76 if ($(media).length === 0) { 77 this.provideFallback(); 78 return; 79 } 80 81 /////////////////////////////// 82 // 83 // Default variables assignment 84 // 85 /////////////////////////////// 86 87 // The following variables CAN be overridden with HTML attributes 88 89 // autoplay (Boolean; if present always resolves to true, regardless of value) 90 if ($(media).attr('autoplay') !== undefined) { 91 this.autoplay = true; // this value remains constant 92 this.okToPlay = true; // this value can change dynamically 93 } 94 else { 95 this.autoplay = false; 96 this.okToPlay = false; 97 } 98 99 // loop (Boolean; if present always resolves to true, regardless of value) 100 if ($(media).attr('loop') !== undefined) { 101 this.loop = true; 102 } 103 else { 104 this.loop = false; 105 } 106 107 // playsinline (Boolean; if present always resolves to true, regardless of value) 108 if ($(media).attr('playsinline') !== undefined) { 109 this.playsInline = '1'; // this value gets passed to YT.Player contructor in youtube.js 110 } 111 else { 112 this.playsInline = '0'; 113 } 114 115 // poster (Boolean, indicating whether media element has a poster attribute) 116 if ($(media).attr('poster')) { 117 this.hasPoster = true; 118 } 119 else { 120 this.hasPoster = false; 121 } 122 123 // get height and width attributes, if present 124 // and add them to variables 125 // Not currently used, but might be useful for resizing player 126 if ($(media).attr('width')) { 127 this.width = $(media).attr('width'); 128 } 129 if ($(media).attr('height')) { 130 this.height = $(media).attr('height'); 131 } 132 133 // start-time 134 if ($(media).data('start-time') !== undefined && $.isNumeric($(media).data('start-time'))) { 135 this.startTime = $(media).data('start-time'); 136 } 137 else { 138 this.startTime = 0; 139 } 140 141 // debug 142 if ($(media).data('debug') !== undefined && $(media).data('debug') !== false) { 143 this.debug = true; 144 } 145 else { 146 this.debug = false;
147 } 148 149 // Path to root directory of Able Player code 150 if ($(media).data('root-path') !== undefined) { 151 // add a trailing slash if there is none 152 this.rootPath = $(media).data('root-path').replace(/\/?$/, '/'); 153 } 154 else { 155 this.rootPath = this.getRootPath(); 156 } 157 158 // Volume 159 // Range is 0 to 10. Best not to crank it to avoid overpowering screen readers 160 this.defaultVolume = 7; 161 if ($(media).data('volume') !== undefined && $(media).data('volume') !== "") { 162 var volume = $(media).data('volume'); 163 if (volume >= 0 && volume <= 10) { 164 this.defaultVolume = volume; 165 } 166 } 167 this.volume = this.defaultVolume; 168 169 // Optional Buttons 170 // Buttons are added to the player controller if relevant media is present 171 // However, in some applications it might be undesirable to show buttons 172 // (e.g., if chapters or transcripts are provided in an external container) 173 174 if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) { 175 this.useChaptersButton = false; 176 } 177 else { 178 this.useChaptersButton = true; 179 } 180 181 // Control whether text descriptions are read aloud 182 // set to "false" if the sole purpose of the WebVTT descriptions file 183 // is to integrate text description into the transcript 184 // set to "true" to write description text to a div 185 // This variable does *not* control the method by which description is read. 186 // For that, see below (this.descMethod) 187 if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { 188 this.readDescriptionsAloud = false; 189 } 190 else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { 191 // support both singular and plural spelling of attribute 192 this.readDescriptionsAloud = false; 193 } 194 else { 195 this.readDescriptionsAloud = true; 196 } 197 198 // Method by which text descriptions are read 199 // valid values of data-desc-reader are: 200 // 'brower' (default) - text-based audio description is handled by the browser, if supported 201 // 'screenreader' - text-based audio description is always handled by screen readers 202 // The latter may be preferable by owners of websites in languages that are not well supported 203 // by the Web Speech API 204 if ($(media).data('desc-reader') == 'screenreader') { 205 this.descReader = 'screenreader'; 206 } 207 else { 208 this.descReader = 'browser'; 209 } 210 211 // Default state of captions and descriptions 212 // This setting is overridden by user preferences, if they exist 213 // values for data-state-captions and data-state-descriptions are 'on' or 'off' 214 if ($(media).data('state-captions') == 'off') { 215 this.defaultStateCaptions = 0; // off 216 } 217 else { 218 this.defaultStateCaptions = 1; // on by default 219 } 220 if ($(media).data('state-descriptions') == 'on') { 221 this.defaultStateDescriptions = 1; // on 222 } 223 else { 224 this.defaultStateDescriptions = 0; // off by default 225 } 226 227 // Default setting for prefDescPause 228 // Extended description (i.e., pausing during description) is on by default 229 // but this settings give website owners control over that 230 // since they know the nature of their videos, and whether pausing is necessary 231 // This setting is overridden by user preferences, if they exist 232 if ($(media).data('desc-pause-default') == 'off') { 233 this.defaultDescPause = 0; // off 234 } 235 else { 236 this.defaultDescPause = 1; // on by default 237 } 238 239 // Headings 240 // By default, an off-screen heading is automatically added to the top of the media player 241 // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel() 242 // Authors can override this behavior by manually assigning a heading level using data-heading-level 243 // Accepted values are 1-6, or 0 which indicates "no heading" 244 // (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this) 245 if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") { 246 var headingLevel = $(media).data('heading-level'); 247 if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0 248 this.playerHeadingLevel = headingLevel; 249 } 250 } 251 252 // Transcripts 253 // There are three types of interactive transcripts. 254 // In descending of order of precedence (in case there are conflicting tags), they are: 255 // 1. "manual" - A manually coded external transcript (requires data-transcript-src) 256 // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div) 257 // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button 258 // If data-include-transcript="false", there is no "popup" transcript 259 260 if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") { 261 this.transcriptDivLocation = $(media).data('transcript-div'); 262 } 263 else { 264 this.transcriptDivLocation = null; 265 } 266 if ($(media).data('include-transcript') !== undefined && $(media).data('include-transcript') === false) { 267 this.hideTranscriptButton = true; 268 } 269 else { 270 this.hideTranscriptButton = null; 271 } 272 273 this.transcriptType = null; 274 if ($(media).data('transcript-src') !== undefined) { 275 this.transcriptSrc = $(media).data('transcript-src'); 276 if (this.transcriptSrcHasRequiredParts()) { 277 this.transcriptType = 'manual'; 278 } 279 else { 280 console.log('ERROR: Able Player transcript is missing required parts'); 281 } 282 } 283 else if ($(media).find('track[kind="captions"], track[kind="subtitles"]').length > 0) { 284 // required tracks are present. COULD automatically generate a transcript 285 if (this.transcriptDivLocation) { 286 this.transcriptType = 'external'; 287 } 288 else { 289 this.transcriptType = 'popup'; 290 } 291 } 292 293 // In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript 294 // If false (default), line breaks are are removed from transcripts in order to provide a more seamless reading experience 295 // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem 296 if ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) { 297 this.lyricsMode = true; 298 } 299 else { 300 this.lyricsMode = false;
301 } 302 303 // Transcript Title 304 if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { 305 this.transcriptTitle = $(media).data('transcript-title'); 306 } 307 else { 308 // do nothing. The default title will be defined later (see transcript.js) 309 } 310 311 // Captions 312 // data-captions-position can be used to set the default captions position 313 // this is only the default, and can be overridden by user preferences 314 // valid values of data-captions-position are 'below' and 'overlay' 315 if ($(media).data('captions-position') === 'overlay') { 316 this.defaultCaptionsPosition = 'overlay'; 317 } 318 else { // the default, even if not specified 319 this.defaultCaptionsPosition = 'below'; 320 } 321 322 // Chapters 323 if ($(media).data('chapters-div') !== undefined && $(media).data('chapters-div') !== "") { 324 this.chaptersDivLocation = $(media).data('chapters-div'); 325 } 326 327 if ($(media).data('chapters-title') !== undefined) { 328 // NOTE: empty string is valid; results in no title being displayed 329 this.chaptersTitle = $(media).data('chapters-title'); 330 } 331 332 if ($(media).data('chapters-default') !== undefined && $(media).data('chapters-default') !== "") { 333 this.defaultChapter = $(media).data('chapters-default'); 334 } 335 else { 336 this.defaultChapter = null; 337 } 338 339 // Slower/Faster buttons 340 // valid values of data-speed-icons are 'animals' (default) and 'arrows' 341 // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows 342 if ($(media).data('speed-icons') === 'arrows') { 343 this.speedIcons = 'arrows'; 344 } 345 else { 346 this.speedIcons = 'animals'; 347 } 348 349 // Seekbar 350 // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' 351 if ($(media).data('seekbar-scope') === 'chapter' || $(media).data('seekbar-scope') === 'chapters') { 352 this.seekbarScope = 'chapter'; 353 } 354 else { 355 this.seekbarScope = 'video'; 356 } 357 358 // YouTube 359 if ($(media).data('youtube-id') !== undefined && $(media).data('youtube-id') !== "") { 360 this.youTubeId = this.getYouTubeId($(media).data('youtube-id')); 361 } 362 363 if ($(media).data('youtube-desc-id') !== undefined && $(media).data('youtube-desc-id') !== "") { 364 this.youTubeDescId = this.getYouTubeId($(media).data('youtube-desc-id')); 365 } 366 367 if ($(media).data('youtube-nocookie') !== undefined && $(media).data('youtube-nocookie')) { 368 this.youTubeNoCookie = true; 369 } 370 else { 371 this.youTubeNoCookie = false; 372 } 373 374 // Vimeo 375 if ($(media).data('vimeo-id') !== undefined && $(media).data('vimeo-id') !== "") { 376 this.vimeoId = this.getVimeoId($(media).data('vimeo-id')); 377 } 378 if ($(media).data('vimeo-desc-id') !== undefined && $(media).data('vimeo-desc-id') !== "") { 379 this.vimeoDescId = this.getVimeoId($(media).data('vimeo-desc-id')); 380 } 381 382 // Skin 383 // valid values of data-skin are: 384 // 'legacy' (default), two rows of controls; seekbar positioned in available space within top row 385 // '2020', all buttons in one row beneath a full-width seekbar 386 if ($(media).data('skin') == '2020') { 387 this.skin = '2020'; 388 } 389 else { 390 this.skin = 'legacy'; 391 } 392 393 // Size 394 // width of Able Player is determined using the following order of precedence: 395 // 1. data-width attribute 396 // 2. width attribute (for video or audio, although it is not valid HTML for audio) 397 // 3. Intrinsic size from video (video only, determined later) 398 if ($(media).data('width') !== undefined) { 399 this.playerWidth = parseInt($(media).data('width')); 400 } 401 else if ($(media)[0].getAttribute('width')) { 402 // NOTE: jQuery attr() returns null for all invalid HTML attributes 403 // (e.g., width on <audio>) 404 // but it can be acessed via JavaScript getAttribute() 405 this.playerWidth = parseInt($(media)[0].getAttribute('width')); 406 } 407 else { 408 this.playerWidth = null; 409 } 410 411 // Icon type 412 // By default, AblePlayer 3.0.33 and higher uses SVG icons for the player controls 413 // Fallback for browsers that don't support SVG is scalable icomoon fonts 414 // Ultimate fallback is images, if the user has a custom style sheet that overrides font-family 415 // Use data-icon-type to force controls to use either 'svg', 'font', or 'images' 416 this.iconType = 'font'; 417 this.forceIconType = false;
418 if ($(media).data('icon-type') !== undefined && $(media).data('icon-type') !== "") { 419 var iconType = $(media).data('icon-type'); 420 if (iconType === 'font' || iconType == 'image' || iconType == 'svg') { 421 this.iconType = iconType; 422 this.forceIconType = true; 423 } 424 } 425 426 if ($(media).data('allow-fullscreen') !== undefined && $(media).data('allow-fullscreen') === false) { 427 this.allowFullscreen = false; 428 } 429 else { 430 this.allowFullscreen = true; 431 } 432 // Define other variables that are used in fullscreen program flow 433 this.clickedFullscreenButton = false; 434 this.restoringAfterFullscreen = false; 435 436 // Seek interval 437 // Number of seconds to seek forward or back with Rewind & Forward buttons 438 // Unless specified with data-seek-interval, the default value is re-calculated in initialize.js > setSeekInterval(); 439 // Calculation attempts to intelligently assign a reasonable interval based on media length 440 this.defaultSeekInterval = 10; 441 this.useFixedSeekInterval = false; // will change to true if media has valid data-seek-interval attribute 442 if ($(media).data('seek-interval') !== undefined && $(media).data('seek-interval') !== "") { 443 var seekInterval = $(media).data('seek-interval'); 444 if (/^[1-9][0-9]*$/.test(seekInterval)) { // must be a whole number greater than 0 445 this.seekInterval = seekInterval; 446 this.useFixedSeekInterval = true; // do not override with calculuation 447 } 448 } 449 450 // Now Playing 451 // Shows "Now Playing:" plus the title of the current track above player 452 // Only used if there is a playlist 453 if ($(media).data('show-now-playing') !== undefined && $(media).data('show-now-playing') === false) { 454 this.showNowPlaying = false; 455 } 456 else { 457 this.showNowPlaying = true; 458 } 459 460 // TTML support (experimental); enabled for testing with data-use-ttml (Boolean) 461 if ($(media).data('use-ttml') !== undefined) { 462 this.useTtml = true; 463 // The following may result in a console error. 464 this.convert = require('xml-js'); 465 } 466 else { 467 this.useTtml = false; 468 } 469 470 // Fallback 471 // The data-test-fallback attribute can be used to test the fallback solution in any browser 472 if ($(media).data('test-fallback') !== undefined && $(media).data('test-fallback') !== false) { 473 if ($(media).data('test-fallback') == '2') { 474 this.testFallback = 2; // emulate browser that doesn't support HTML5 media 475 } 476 else { 477 this.testFallback = 1; // emulate failure to load Able Player 478 } 479 } 480 else { 481 this.testFallback = false; 482 } 483 484 // Language 485 // Player language is determined given the following precedence: 486 // 1. The value of data-lang on the media element, if provided and a matching translation file is available 487 // 2. Lang attribute on <html> or <body>, if a matching translation file is available 488 // 3. English 489 // Final calculation occurs in translation.js > getTranslationText() 490 if ($(media).data('lang') !== undefined && $(media).data('lang') !== "") { 491 this.lang = $(media).data('lang').toLowerCase(); 492 } 493 else { 494 this.lang = null; 495 } 496 497 // Metadata Tracks 498 if ($(media).data('meta-type') !== undefined && $(media).data('meta-type') !== "") { 499 this.metaType = $(media).data('meta-type'); 500 } 501 502 if ($(media).data('meta-div') !== undefined && $(media).data('meta-div') !== "") { 503 this.metaDiv = $(media).data('meta-div'); 504 } 505 506 // Search 507 // conducting a search requires an external div in which to write the results 508 if ($(media).data('search-div') !== undefined && $(media).data('search-div') !== "") { 509 510 this.searchDiv = $(media).data('search-div'); 511 512 // Search term (optional; could be assigned later in a JavaScript application) 513 if ($(media).data('search') !== undefined && $(media).data('search') !== "") { 514 this.searchString = $(media).data('search'); 515 } 516 517 // Search Language 518 if ($(media).data('search-lang') !== undefined && $(media).data('search-lang') !== "") { 519 this.searchLang = $(media).data('search-lang'); 520 } 521 else { 522 this.searchLang = null; // will change to final value of this.lang in translation.js > getTranslationText() 523 } 524
525 // Search option: Ignore capitalization in search terms 526 if ($(media).data('search-ignore-caps') !== undefined && $(media).data('search-ignore-caps') !== false) { 527 this.searchIgnoreCaps = true; 528 } 529 else { 530 this.searchIgnoreCaps = false; 531 } 532 533 // conducting a search currently requires an external div in which to write the results 534 if ($(media).data('search-div') !== undefined && $(media).data('search-div') !== "") { 535 this.searchString = $(media).data('search'); 536 this.searchDiv = $(media).data('search-div'); 537 } 538 } 539 540 // Hide controls when video starts playing 541 // They will reappear again when user presses a key or moves the mouse 542 // As of v4.0, controls are hidden automatically on playback in fullscreen mode 543 if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) { 544 this.hideControls = true; 545 this.hideControlsOriginal = true; // a copy of hideControls, since the former may change if user enters full screen mode 546 } 547 else { 548 this.hideControls = false; 549 this.hideControlsOriginal = false;
550 } 551 552 // Steno mode 553 // Enable support for Able Player keyboard shortcuts in textaarea fields 554 // so users can control the player while transcribing 555 if ($(media).data('steno-mode') !== undefined && $(media).data('steno-mode') !== false) { 556 this.stenoMode = true; 557 // Add support for stenography in an iframe via data-steno-iframe-id 558 if ($(media).data('steno-iframe-id') !== undefined && $(media).data('steno-iframe-id') !== "") { 559 this.stenoFrameId = $(media).data('steno-iframe-id'); 560 this.$stenoFrame = $('#' + this.stenoFrameId); 561 if (!(this.$stenoFrame.length)) { 562 // iframe not found 563 this.stenoFrameId = null; 564 this.$stenoFrame = null; 565 } 566 } 567 else { 568 this.stenoFrameId = null; 569 this.$stenoFrame = null; 570 } 571 } 572 else { 573 this.stenoMode = false; 574 this.stenoFrameId = null; 575 this.$stenoFrame = null; 576 } 577 578 // Define built-in variables that CANNOT be overridden with HTML attributes 579 this.setDefaults(); 580 581 //////////////////////////////////////// 582 // 583 // End assignment of default variables 584 // 585 //////////////////////////////////////// 586 587 this.ableIndex = AblePlayer.nextIndex; 588 AblePlayer.nextIndex += 1; 589 590 this.title = $(media).attr('title'); 591 592 // populate translation object with localized versions of all labels and prompts 593 // use defer method to defer additional processing until text is retrieved 594 this.tt = {}; 595 var thisObj = this; 596 $.when(this.getTranslationText()).then( 597 function () { 598 if (thisObj.countProperties(thisObj.tt) > 50) { 599 // close enough to ensure that most text variables are populated 600 thisObj.setup(); 601 } 602 else { 603 // can't continue loading player with no text 604 thisObj.provideFallback(); 605 } 606 } 607 ). 608 fail(function() { 609 thisObj.provideFallback(); 610 }); 611 }; 612 613 // Index to increment every time new player is created. 614 AblePlayer.nextIndex = 0; 615 616 AblePlayer.prototype.setup = function() { 617 618 var thisObj = this; 619 this.initializing = true; // will remain true until entire sequence of function calls is complete 620 621 this.reinitialize().then(function () { 622 if (!thisObj.player) { 623 // No player for this media, show last-line fallback. 624 thisObj.provideFallback(); 625 } 626 else { 627 thisObj.setupInstance().then(function () { 628 thisObj.setupInstancePlaylist(); 629 if (thisObj.hasPlaylist) { 630 // for playlists, recreatePlayer() is called from within cuePlaylistItem() 631 } 632 else { 633 thisObj.recreatePlayer().then(function() { 634 thisObj.initializing = false; 635 thisObj.playerCreated = true; // remains true until browser is refreshed 636 }); 637 } 638 }); 639 } 640 }); 641 }; 642 643 AblePlayer.getActiveDOMElement = function () { 644 var activeElement = document.activeElement; 645 646 // For shadow DOMs we need to keep digging down through the DOMs 647 while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { 648 activeElement = activeElement.shadowRoot.activeElement; 649 } 650 651 return activeElement; 652 }; 653 654 AblePlayer.localGetElementById = function(element, id) { 655 if (element.getRootNode) 656 { 657 // Use getRootNode() and querySelector() where supported (for shadow DOM support) 658 return $(element.getRootNode().querySelector('#' + id)); 659 } 660 else 661 { 662 // If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support) 663 return $(document.getElementById(id)); 664 } 665 }; 666 667 AblePlayer.youTubeIframeAPIReady = false; 668 AblePlayer.loadingYouTubeIframeAPI = false; 669})(jQuery); 670 671(function ($) { 672 // Set default variable values. 673 AblePlayer.prototype.setDefaults = function () { 674 675 this.playerCreated = false; // will set to true after recreatePlayer() is complete the first time 676 this.playing = false; // will change to true after 'playing' event is triggered 677 this.paused = true; // will always be the opposite of this.playing (available for convenience) 678 this.clickedPlay = false; // will change to true temporarily if user clicks 'play' (or pause) 679 this.fullscreen = false; // will change to true if player is in full screen mode 680 this.swappingSrc = false; // will change to true temporarily while media source is being swapped 681 this.initializing = false; // will change to true temporarily while initPlayer() is processing 682 this.cueingPlaylistItems = false; // will change to true temporarily while cueing next playlist item 683 this.buttonWithFocus = null; // will change to 'previous' or 'next' if user clicks either of those buttons 684 this.speechEnabled = null; // will change either to 'true' in initSpeech(), or false if not supported 685 686 this.setIconColor(); 687 this.setButtonImages(); 688 }; 689 690 AblePlayer.prototype.getRootPath = function() { 691 692 // returns Able Player root path (assumes ableplayer.js is in /build, one directory removed from root) 693 var scripts, i, scriptSrc, scriptFile, fullPath, ablePath, parentFolderIndex, rootPath; 694 scripts= document.getElementsByTagName('script'); 695 for (i=0; i < scripts.length; i++) { 696 scriptSrc = scripts[i].src; 697 scriptFile = scriptSrc.substring(scriptSrc.lastIndexOf('/')); 698 if (scriptFile.indexOf('ableplayer') !== -1) { 699 // this is the ableplayerscript 700 fullPath = scriptSrc.split('?')[0]; // remove any ? params 701 break; 702 } 703 } 704 ablePath= fullPath.split('/').slice(0, -1).join('/'); // remove last filename part of path 705 parentFolderIndex = ablePath.lastIndexOf('/'); 706 rootPath = ablePath.substring(0, parentFolderIndex) + '/'; 707 return rootPath; 708 } 709 710 AblePlayer.prototype.setIconColor = function() { 711 712 // determine the best color choice (white or black) for icons, 713 // given the background-color of their container elements 714 // Source for relative luminance formula: 715 // https://en.wikipedia.org/wiki/Relative_luminance 716 717 // We need to know the color *before* creating the element 718 // so the element doesn't exist yet when this function is called 719 // therefore, need to create a temporary element then remove it after color is determined 720 // Temp element must be added to the DOM or WebKit can't retrieve its CSS properties 721 722 var $elements, i, $el, bgColor, rgb, red, green, blue, luminance, iconColor; 723 724 $elements = ['controller', 'toolbar']; 725 for (i=0; i<$elements.length; i++) { 726 if ($elements[i] == 'controller') { 727 $el = $('<div>', { 728 'class': 'able-controller' 729 }).hide(); 730 } 731 else if ($elements[i] === 'toolbar') { 732 $el = $('<div>', { 733 'class': 'able-window-toolbar' 734 }).hide(); 735 } 736 $('body').append($el); 737 bgColor = $el.css('background-color'); 738 // bgColor is a string in the form 'rgb(R, G, B)', perhaps with a 4th item for alpha; 739 // split the 3 or 4 channels into an array 740 rgb = bgColor.replace(/[^\d,]/g, '').split(','); 741 red = rgb[0]; 742 green = rgb[1]; 743 blue = rgb[2]; 744 luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); 745 // range is 1 - 255; therefore 125 is the tipping point 746 if (luminance < 125) { // background is dark 747 iconColor = 'white'; 748 } 749 else { // background is light 750 iconColor = 'black'; 751 } 752 if ($elements[i] === 'controller') { 753 this.iconColor = iconColor; 754 } 755 else if ($elements[i] === 'toolbar') { 756 this.toolbarIconColor = iconColor; 757 } 758 $el.remove(); 759 } 760 }; 761 762 AblePlayer.prototype.setButtonImages = function() { 763 764 // NOTE: volume button images are now set dynamically within volume.js 765 this.imgPath = this.rootPath + 'button-icons/' + this.iconColor + '/'; 766 this.playButtonImg = this.imgPath + 'play.png'; 767 this.pauseButtonImg = this.imgPath + 'pause.png'; 768 769 this.restartButtonImg = this.imgPath + 'restart.png'; 770 771 this.rewindButtonImg = this.imgPath + 'rewind.png'; 772 this.forwardButtonImg = this.imgPath + 'forward.png'; 773 774 this.previousButtonImg = this.imgPath + 'previous.png'; 775 this.nextButtonImg = this.imgPath + 'next.png'; 776 777 if (this.speedIcons === 'arrows') { 778 this.fasterButtonImg = this.imgPath + 'slower.png'; 779 this.slowerButtonImg = this.imgPath + 'faster.png'; 780 } 781 else if (this.speedIcons === 'animals') { 782 this.fasterButtonImg = this.imgPath + 'rabbit.png'; 783 this.slowerButtonImg = this.imgPath + 'turtle.png'; 784 } 785 786 this.captionsButtonImg = this.imgPath + 'captions.png'; 787 this.chaptersButtonImg = this.imgPath + 'chapters.png'; 788 this.signButtonImg = this.imgPath + 'sign.png'; 789 this.transcriptButtonImg = this.imgPath + 'transcript.png'; 790 this.descriptionsButtonImg = this.imgPath + 'descriptions.png'; 791 792 this.fullscreenExpandButtonImg = this.imgPath + 'fullscreen-expand.png'; 793 this.fullscreenCollapseButtonImg = this.imgPath + 'fullscreen-collapse.png'; 794 795 this.prefsButtonImg = this.imgPath + 'preferences.png'; 796 this.helpButtonImg = this.imgPath + 'help.png'; 797 }; 798 799 AblePlayer.prototype.getSvgData = function(button) { 800 801 // returns array of values for creating <svg> tag for specified button 802 // 0 = <svg> viewBox attribute 803 // 1 = <path> d (description) attribute 804 var svg = Array(); 805 806 switch (button) { 807 808 case 'play': 809 svg[0] = '0 0 16 20'; 810 svg[1] = 'M0 18.393v-16.429q0-0.29 0.184-0.402t0.441 0.033l14.821 8.237q0.257 0.145 0.257 0.346t-0.257 0.346l-14.821 8.237q-0.257 0.145-0.441 0.033t-0.184-0.402z'; 811 break; 812 813 case 'pause': 814 svg[0] = '0 0 20 20'; 815 svg[1] = 'M0 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502zM10 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502z'; 816 break; 817 818 case 'stop': 819 svg[0] = '0 0 20 20'; 820 svg[1] = 'M0 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h15.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-15.714q-0.29 0-0.502-0.212t-0.212-0.502z'; 821 break; 822 823 case 'restart': 824 svg[0] = '0 0 20 20'; 825 svg[1] = 'M18 8h-6l2.243-2.243c-1.133-1.133-2.64-1.757-4.243-1.757s-3.109 0.624-4.243 1.757c-1.133 1.133-1.757 2.64-1.757 4.243s0.624 3.109 1.757 4.243c1.133 1.133 2.64 1.757 4.243 1.757s3.109-0.624 4.243-1.757c0.095-0.095 0.185-0.192 0.273-0.292l1.505 1.317c-1.466 1.674-3.62 2.732-6.020 2.732-4.418 0-8-3.582-8-8s3.582-8 8-8c2.209 0 4.209 0.896 5.656 2.344l2.344-2.344v6z'; 826 break; 827 828 case 'rewind': 829 svg[0] = '0 0 24 24'; 830 svg[1] = 'M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z'; 831 break; 832 833 case 'forward': 834 svg[0] = '0 0 24 24'; 835 svg[1] = 'M18 13c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6v4l5-5-5-5v4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8h-2z'; 836 break; 837 838 case 'previous': 839 svg[0] = '0 0 20 20'; 840 svg[1] = 'M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z'; 841 break; 842 843 case 'next': 844 svg[0] = '0 0 20 20'; 845 svg[1] = 'M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z'; 846 break; 847 848 case 'slower': 849 svg[0] = '0 0 20
vendor: 11,454 bytes, lines 849-921
849 20'; 850 svg[1] = 'M0 7.321q0-0.29 0.212-0.502t0.502-0.212h10q0.29 0 0.502 0.212t0.212 0.502-0.212 0.502l-5 5q-0.212 0.212-0.502 0.212t-0.502-0.212l-5-5q-0.212-0.212-0.212-0.502z'; 851 break; 852 853 case 'faster': 854 svg[0] = '0 0 11 20'; 855 svg[1] = 'M0 12.411q0-0.29 0.212-0.502l5-5q0.212-0.212 0.502-0.212t0.502 0.212l5 5q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-10q-0.29 0-0.502-0.212t-0.212-0.502z'; 856 break; 857 858 case 'turtle': 859 svg[0] = '0 0 20 20'; 860 svg[1] = 'M17.212 3.846c-0.281-0.014-0.549 0.025-0.817 0.144-1.218 0.542-1.662 2.708-2.163 3.942-1.207 2.972-7.090 4.619-11.755 5.216-0.887 0.114-1.749 0.74-2.428 1.466 0.82-0.284 2.126-0.297 2.74 0.144 0.007 0.488-0.376 1.062-0.625 1.37-0.404 0.5-0.398 0.793 0.12 0.793 0.473 0 0.752 0.007 1.635 0 0.393-0.003 0.618-0.16 1.49-1.49 3.592 0.718 5.986-0.264 5.986-0.264s0.407 1.755 1.418 1.755h1.49c0.633 0 0.667-0.331 0.625-0.433-0.448-1.082-0.68-1.873-0.769-2.5-0.263-1.857 0.657-3.836 2.524-5.457 0.585 0.986 2.253 0.845 2.909-0.096s0.446-2.268-0.192-3.221c-0.49-0.732-1.345-1.327-2.188-1.37zM8.221 4.663c-0.722-0.016-1.536 0.111-2.5 0.409-4.211 1.302-4.177 4.951-3.51 5.745 0 0-0.955 0.479-0.409 1.274 0.448 0.652 3.139 0.191 5.409-0.529s4.226-1.793 5.312-2.692c0.948-0.785 0.551-2.106-0.505-1.947-0.494-0.98-1.632-2.212-3.798-2.26zM18.846 5.962c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577z'; 861 break; 862 863 case 'rabbit': 864 svg[0] = '0 0 20 20'; 865 svg[1] = 'M10.817 0c-2.248 0-1.586 0.525-1.154 0.505 1.551-0.072 5.199 0.044 6.851 2.428 0 0-1.022-2.933-5.697-2.933zM10.529 0.769c-2.572 0-2.837 0.51-2.837 1.106 0 0.545 1.526 0.836 2.524 0.697 2.778-0.386 4.231-0.12 5.264 0.865-1.010 0.779-0.75 1.401-1.274 1.851-1.093 0.941-2.643-0.673-4.976-0.673-2.496 0-4.712 1.92-4.712 4.76-0.157-0.537-0.769-0.913-1.442-0.913-0.974 0-1.514 0.637-1.514 1.49 0 0.769 1.13 1.791 2.861 0.938 0.499 1.208 2.265 1.364 2.452 1.418 0.538 0.154 1.875 0.098 1.875 0.865 0 0.794-1.034 1.094-1.034 1.707 0 1.070 1.758 0.873 2.284 1.034 1.683 0.517 2.103 1.214 2.788 2.212 0.771 1.122 2.572 1.408 2.572 0.625 0-3.185-4.413-4.126-4.399-4.135 0.608-0.382 2.139-1.397 2.139-3.534 0-1.295-0.703-2.256-1.755-2.861 1.256 0.094 2.572 1.205 2.572 2.74 0 1.877-0.653 2.823-0.769 2.957 1.975-1.158 3.193-3.91 3.029-6.37 0.61 0.401 1.27 0.577 1.971 0.625 0.751 0.052 1.475-0.225 1.635-0.529 0.38-0.723 0.162-2.321-0.12-2.837-0.763-1.392-2.236-1.73-3.606-1.683-1.202-1.671-3.812-2.356-5.529-2.356zM1.37 3.077l-0.553 1.538h3.726c0.521-0.576 1.541-1.207 2.284-1.538h-5.457zM18.846 5.192c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577zM0.553 5.385l-0.553 1.538h3.197c0.26-0.824 0.586-1.328 0.769-1.538h-3.413z'; 866 break; 867 868 case 'ellipsis': 869 svg[0] = '0 0 20 20'; 870 svg[1] = 'M10.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2zM3.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.986 2.199-2.2s-0.984-2.2-2.199-2.2zM17.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2z'; 871 break; 872 873 case 'pipe': 874 svg[0] = '0 0 20 20'; 875 svg[1] = 'M10.15 0.179h0.623c0.069 0 0.127 0.114 0.127 0.253v19.494c0 0.139-0.057 0.253-0.127 0.253h-1.247c-0.069 0-0.126-0.114-0.126-0.253v-19.494c0-0.139 0.057-0.253 0.126-0.253h0.623z'; 876 break; 877 878 case 'captions': 879 svg[0] = '0 0 20 20'; 880 svg[1] = 'M0.033 3.624h19.933v12.956h-19.933v-12.956zM18.098 10.045c-0.025-2.264-0.124-3.251-0.743-3.948-0.112-0.151-0.322-0.236-0.496-0.344-0.606-0.386-3.465-0.526-6.782-0.526s-6.313 0.14-6.907 0.526c-0.185 0.108-0.396 0.193-0.519 0.344-0.607 0.697-0.693 1.684-0.731 3.948 0.037 2.265 0.124 3.252 0.731 3.949 0.124 0.161 0.335 0.236 0.519 0.344 0.594 0.396 3.59 0.526 6.907 0.547 3.317-0.022 6.176-0.151 6.782-0.547 0.174-0.108 0.384-0.183 0.496-0.344 0.619-0.697 0.717-1.684 0.743-3.949v0 0zM9.689 9.281c-0.168-1.77-1.253-2.813-3.196-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.442-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.977zM16.607 9.281c-0.167-1.77-1.252-2.813-3.194-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.441-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.976z'; 881 break; 882 883 case 'descriptions': 884 svg[0] = '0 0 20 20'; 885 svg[1] = 'M17.623 3.57h-1.555c1.754 1.736 2.763 4.106 2.763 6.572 0 2.191-0.788 4.286-2.189 5.943h1.484c1.247-1.704 1.945-3.792 1.945-5.943-0-2.418-0.886-4.754-2.447-6.572v0zM14.449 3.57h-1.55c1.749 1.736 2.757 4.106 2.757 6.572 0 2.191-0.788 4.286-2.187 5.943h1.476c1.258-1.704 1.951-3.792 1.951-5.943-0-2.418-0.884-4.754-2.447-6.572v0zM11.269 3.57h-1.542c1.752 1.736 2.752 4.106 2.752 6.572 0 2.191-0.791 4.286-2.181 5.943h1.473c1.258-1.704 1.945-3.792 1.945-5.943 0-2.418-0.876-4.754-2.447-6.572v0zM10.24 9.857c0 3.459-2.826 6.265-6.303 6.265v0.011h-3.867v-12.555h3.896c3.477 0 6.274 2.806 6.274 6.279v0zM6.944 9.857c0-1.842-1.492-3.338-3.349-3.338h-0.876v6.686h0.876c1.858 0 3.349-1.498 3.349-3.348v0z'; 886 break; 887 888 case 'sign': 889 svg[0] = '0 0 20 20'; 890 svg[1] = 'M10.954 10.307c0.378 0.302 0.569 1.202 0.564 1.193 0.697 0.221 1.136 0.682 1.136 0.682 1.070-0.596 1.094-0.326 1.558-0.682 0.383-0.263 0.366-0.344 0.567-1.048 0.187-0.572-0.476-0.518-1.021-1.558-0.95 0.358-1.463 0.196-1.784 0.167-0.145-0.020-0.12 0.562-1.021 1.247zM14.409 17.196c-0.133 0.182-0.196 0.218-0.363 0.454-0.28 0.361 0.076 0.906 0.253 0.82 0.206-0.076 0.341-0.488 0.567-0.623 0.115-0.061 0.422-0.513 0.709-0.82 0.211-0.238 0.363-0.344 0.564-0.594 0.341-0.422 0.412-0.744 0.709-1.193 0.184-0.236 0.312-0.307 0.481-0.594 0.886-1.679 0.628-2.432 1.475-3.629 0.26-0.353 0.552-0.442 0.964-0.653 0.383-2.793-0.888-4.356-0.879-4.361-1.067 0.623-1.644 0.879-2.751 0.82-0.417-0.005-0.636-0.182-1.048-0.145-0.385 0.015-0.582 0.159-0.964 0.29-0.589 0.182-0.91 0.344-1.529 0.535-0.393 0.11-0.643 0.115-1.050 0.255-0.348 0.147-0.182 0.029-0.427 0.312-0.317 0.348-0.238 0.623-0.535 1.222-0.371 0.785-0.326 0.891-0.115 0.987-0.14 0.402-0.174 0.672-0.14 1.107 0.039 0.331-0.101 0.562 0.255 0.825 0.483 0.361 1.499 1.205 1.757 1.217 0.39-0.012 1.521 0.029 2.096-0.368 0.13-0.081 0.167-0.162 0.056 0.145-0.022 0.037-1.433 1.136-1.585 1.131-1.794 0.056-1.193 0.157-1.303 0.115-0.091 0-0.955-1.055-1.477-0.682-0.196 0.12-0.287 0.236-0.363 0.452 0.066 0.137 0.383 0.358 0.675 0.54 0.422 0.27 0.461 0.552 0.881 0.653 0.513 0.115 1.060 0.039 1.387 0.081 0.125 0.034 1.256-0.297 1.961-0.675 0.65-0.336-0.898 0.648-1.276 1.131-1.141 0.358-0.82 0.373-1.362 0.483-0.503 0.115-0.479 0.086-0.822 0.196-0.356 0.086-0.648 0.572-0.312 0.825 0.201 0.167 0.827-0.066 1.445-0.086 0.275-0.005 1.391-0.518 1.644-0.653 0.633-0.339 1.099-0.81 1.472-1.077 0.518-0.361-0.584 0.991-1.050 1.558zM8.855 9.799c-0.378-0.312-0.569-1.212-0.564-1.217-0.697-0.206-1.136-0.667-1.136-0.653-1.070 0.582-1.099 0.312-1.558 0.653-0.388 0.277-0.366 0.363-0.567 1.045-0.187 0.594 0.471 0.535 1.021 1.561 0.95-0.344 1.463-0.182 1.784-0.142 0.145 0.010 0.12-0.572 1.021-1.247zM5.4 2.911c0.133-0.191 0.196-0.228 0.368-0.454 0.27-0.371-0.081-0.915-0.253-0.849-0.211 0.096-0.346 0.508-0.599 0.653-0.093 0.052-0.4 0.503-0.682 0.82-0.211 0.228-0.363 0.334-0.564 0.599-0.346 0.407-0.412 0.729-0.709 1.161-0.184 0.258-0.317 0.324-0.481 0.621-0.886 1.669-0.631 2.422-1.475 3.6-0.26 0.38-0.552 0.461-0.964 0.682-0.383 2.788 0.883 4.346 0.879 4.336 1.068-0.609 1.639-0.861 2.751-0.825 0.417 0.025 0.636 0.201 1.048 0.174 0.385-0.025 0.582-0.169 0.964-0.285 0.589-0.196 0.91-0.358 1.499-0.54 0.422-0.12 0.672-0.125 1.080-0.285 0.348-0.128 0.182-0.010 0.427-0.282 0.312-0.358 0.238-0.633 0.508-1.217 0.398-0.8 0.353-0.906 0.142-0.991 0.135-0.412 0.174-0.677 0.14-1.107-0.044-0.336 0.101-0.572-0.255-0.82-0.483-0.375-1.499-1.22-1.752-1.222-0.395 0.002-1.526-0.039-2.101 0.339-0.13 0.101-0.167 0.182-0.056-0.11 0.022-0.052 1.433-1.148 1.585-1.163 1.794-0.039 1.193-0.14 1.303-0.088 0.091-0.007 0.955 1.045 1.477 0.682 0.191-0.13 0.287-0.245 0.368-0.452-0.071-0.147-0.388-0.368-0.68-0.537-0.422-0.282-0.464-0.564-0.881-0.655-0.513-0.125-1.065-0.049-1.387-0.11-0.125-0.015-1.256 0.317-1.956 0.68-0.66 0.351 0.893-0.631 1.276-1.136 1.136-0.339 0.81-0.353 1.36-0.479 0.501-0.101 0.476-0.071 0.82-0.172 0.351-0.096 0.648-0.577 0.312-0.849-0.206-0.152-0.827 0.081-1.44 0.086-0.28 0.020-1.396 0.533-1.649 0.677-0.633 0.329-1.099 0.8-1.472 1.048-0.523 0.38 0.584-0.967 1.050-1.529z'; 891 break; 892 893 case 'mute': 894 case 'volume-mute': 895 svg[0] = '0 0 20 20'; 896 svg[1] = 'M7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714zM18.75 12.093v1.657h-1.657l-2.093-2.093-2.093 2.093h-1.657v-1.657l2.093-2.093-2.093-2.093v-1.657h1.657l2.093 2.093 2.093-2.093h1.657v1.657l-2.093 2.093z'; 897 break; 898 899 case 'volume-soft': 900 svg[0] = '0 0 20 20'; 901 svg[1] = 'M10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z'; 902 break; 903 904 case 'volume-medium': 905 svg[0] = '0 0 20 20'; 906 svg[1] = 'M14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z'; 907 break; 908 909 case 'volume-loud': 910 svg[0] = '0 0 21 20'; 911 svg[1] = 'M17.384 18.009c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.712-1.712 2.654-3.988 2.654-6.408s-0.943-4.696-2.654-6.408c-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.066 2.066 3.204 4.813 3.204 7.734s-1.138 5.668-3.204 7.734c-0.183 0.183-0.423 0.275-0.663 0.275zM14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z'; 912 break; 913 914 case 'chapters': 915 svg[0] = '0 0 20 20'; 916 svg[1] = 'M5 2.5v17.5l6.25-6.25 6.25 6.25v-17.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z'; 917 break; 918 919 case 'transcript': 920 svg[0] = '0 0 20 20'; 921 svg[1] = 'M0
921.033 3.624h19.933v12.956h-19.933v-12.956zM18.098 10.045c-0.025-2.264-0.124-3.251-0.743-3.948-0.112-0.151-0.322-0.236-0.496-0.344-0.606-0.386-3.465-0.526-6.782-0.526s-6.313 0.14-6.907 0.526c-0.185 0.108-0.396 0.193-0.519 0.344-0.607 0.697-0.693 1.684-0.731 3.948 0.037 2.265 0.124 3.252 0.731 3.949 0.124 0.161 0.335 0.236 0.519 0.344 0.594 0.396 3.59 0.526 6.907 0.547 3.317-0.022 6.176-0.151 6.782-0.547 0.174-0.108 0.384-0.183 0.496-0.344 0.619-0.697 0.717-1.684 0.743-3.949v0 0zM9.689 9.281c-0.168-1.77-1.253-2.813-3.196-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.442-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.977zM16.607 9.281c-0.167-1.77-1.252-2.813-3.194-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.441-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.976z'; 922 break; 923 924 case 'preferences': 925 svg[0] = '0 0 20 20'; 926 svg[1] = 'M18.238 11.919c-1.049-1.817-0.418-4.147 1.409-5.205l-1.965-3.404c-0.562 0.329-1.214 0.518-1.911 0.518-2.1 0-3.803-1.714-3.803-3.828h-3.931c0.005 0.653-0.158 1.314-0.507 1.919-1.049 1.818-3.382 2.436-5.212 1.382l-1.965 3.404c0.566 0.322 1.056 0.793 1.404 1.396 1.048 1.815 0.42 4.139-1.401 5.2l1.965 3.404c0.56-0.326 1.209-0.513 1.902-0.513 2.094 0 3.792 1.703 3.803 3.808h3.931c-0.002-0.646 0.162-1.3 0.507-1.899 1.048-1.815 3.375-2.433 5.203-1.387l1.965-3.404c-0.562-0.322-1.049-0.791-1.395-1.391zM10 14.049c-2.236 0-4.050-1.813-4.050-4.049s1.813-4.049 4.050-4.049 4.049 1.813 4.049 4.049c-0 2.237-1.813 4.049-4.049 4.049z'; 927 break; 928 929 case 'close': 930 svg[0] = '0 0 16 20'; 931 svg[1] = 'M1.228 14.933q0-0.446 0.312-0.759l3.281-3.281-3.281-3.281q-0.313-0.313-0.313-0.759t0.313-0.759l1.518-1.518q0.313-0.313 0.759-0.313t0.759 0.313l3.281 3.281 3.281-3.281q0.313-0.313 0.759-0.313t0.759 0.313l1.518 1.518q0.313 0.313 0.313 0.759t-0.313 0.759l-3.281 3.281 3.281 3.281q0.313 0.313 0.313 0.759t-0.313 0.759l-1.518 1.518q-0.313 0.313-0.759 0.313t-0.759-0.313l-3.281-3.281-3.281 3.281q-0.313 0.313-0.759 0.313t-0.759-0.313l-1.518-1.518q-0.313-0.313-0.313-0.759z'; 932 break; 933 934 case 'fullscreen-expand': 935 svg[0] = '0 0 20 20'; 936 svg[1] = 'M0 18.036v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502zM8.717 8.393q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257z'; 937 break; 938 939 case 'fullscreen-collapse': 940 svg[0] = '0 0 20 20'; 941 svg[1] = 'M0.145 16.964q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257zM8.571 9.464v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502z'; 942 break; 943 944 case 'help': 945 svg[0] = '0 0 11 20'; 946 svg[1] = 'M0.577 6.317q-0.028-0.167 0.061-0.313 1.786-2.969 5.179-2.969 0.893 0 1.797 0.346t1.629 0.926 1.183 1.423 0.458 1.769q0 0.603-0.173 1.127t-0.391 0.854-0.614 0.664-0.642 0.485-0.681 0.396q-0.458 0.257-0.765 0.725t-0.307 0.748q0 0.19-0.134 0.363t-0.313 0.173h-2.679q-0.167 0-0.285-0.206t-0.117-0.419v-0.502q0-0.926 0.725-1.747t1.596-1.211q0.658-0.301 0.938-0.625t0.279-0.848q0-0.469-0.519-0.826t-1.2-0.357q-0.725 0-1.205 0.324-0.391 0.279-1.194 1.283-0.145 0.179-0.346 0.179-0.134 0-0.279-0.089l-1.83-1.395q-0.145-0.112-0.173-0.279zM3.786 16.875v-2.679q0-0.179 0.134-0.313t0.313-0.134h2.679q0.179 0 0.313 0.134t0.134 0.313v2.679q0 0.179-0.134 0.313t-0.313 0.134h-2.679q-0.179 0-0.313-0.134t-0.134-0.313z'; 947 break; 948 } 949 950 return svg; 951 }; 952 953 // Initialize player based on data on page. 954 // This sets some variables, but does not modify anything. Safe to call multiple times. 955 // Can call again after updating this.media so long as new media element has the same ID. 956 AblePlayer.prototype.reinitialize = function () { 957 958 var deferred, promise, thisObj, errorMsg, srcFile; 959 960 deferred = new $.Deferred(); 961 promise = deferred.promise(); 962 thisObj = this; 963 964 // if F12 Developer Tools aren't open in IE (through 9, no longer a problen in IE10) 965 // console.log causes an error - can't use debug without a console to log messages to 966 if (! window.console) { 967 this.debug = false;
968 } 969 970 this.startedPlaying = false; 971 // TODO: Move this setting to cookie. 972 this.autoScrollTranscript = true; 973 //this.autoScrollTranscript = this.getCookie(autoScrollTranscript); // (doesn't work) 974 975 // Bootstrap from this.media possibly being an ID or other selector. 976 this.$media = $(this.media).first(); 977 this.media = this.$media[0]; 978 979 // Set media type to 'audio' or 'video'; this determines some of the behavior of player creation. 980 if (this.$media.is('audio')) { 981 this.mediaType = 'audio'; 982 } 983 else if (this.$media.is('video')) { 984 this.mediaType = 'video'; 985 } 986 else { 987 // Able Player was initialized with some element other than <video> or <audio> 988 this.provideFallback(); 989 deferred.fail(); 990 return promise; 991 } 992 993 this.$sources = this.$media.find('source'); 994 995 this.player = this.getPlayer(); 996 if (!this.player) { 997 // an error was generated in getPlayer() 998 this.provideFallback(); 999 } 1000 this.setIconType(); 1001 1002 deferred.resolve(); 1003 return promise; 1004 }; 1005 1006 AblePlayer.prototype.setPlayerSize = function(width, height) { 1007 1008 var mediaId = this.$media.attr('id'); 1009 1010 // Called again after width and height are known 1011 1012 if (this.mediaType === 'audio') { 1013 if (this.playerWidth) { 1014 this.$ableWrapper.css('width',this.playerWidth + 'px'); 1015 } 1016 } 1017 else if (width > 0 && height > 0) { 1018 this.playerWidth = width; 1019 this.playerHeight = height; 1020 this.aspectRatio = height / width; 1021 } 1022 }; 1023 1024 AblePlayer.prototype.setIconType = function() { 1025 1026 // returns either "svg", "font" or "image" (in descending order of preference) 1027 // Test for support of each type. If not supported, test the next type. 1028 // last resort is image icons 1029 1030 var $tempButton, $testButton, controllerFont; 1031 1032 if (this.forceIconType) { 1033 // use value specified in data-icon-type 1034 return false; 1035 } 1036 1037 // test for SVG support 1038 // Test this method widely; failed as expected on IE8 and below 1039 // https://stackoverflow.com/a/27568129/744281 1040 if (!!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect)) { 1041 // browser supports SVG 1042 this.iconType = 'svg'; 1043 } 1044 else { 1045 // browser does NOT support SVG 1046 // test whether browser can support icon fonts, and whether user has overriding the default style sheet 1047 // which could cause problems with proper display of the icon fonts 1048 if (window.getComputedStyle) { 1049 1050 // webkit doesn't return calculated styles unless element has been added to the DOM 1051 // and is visible (note: visibly clipped is considered "visible") 1052 // use playpauseButton for font-family test if it exists; otherwise must create a new temp button 1053 if ($('span.icon-play').length) { 1054 $testButton = $('span.icon-play'); 1055 } 1056 else { 1057 $tempButton = $('<span>',{ 1058 'class': 'icon-play able-clipped' 1059 }); 1060 $('body').append($tempButton); 1061 $testButton = $tempButton; 1062 } 1063 1064 // the following retrieves the computed value of font-family 1065 // tested in Firefox 45.x with "Allow pages to choose their own fonts" unchecked - works! 1066 // tested in Chrome 49.x with Font Changer plugin - works! 1067 // tested in IE with user-defined style sheet enables - works! 1068 // It does NOT account for users who have "ignore font styles on web pages" checked in IE 1069 // There is no known way to check for that ??? 1070 controllerFont = window.getComputedStyle($testButton.get(0), null).getPropertyValue('font-family'); 1071 if (typeof controllerFont !== 'undefined') { 1072 if (controllerFont.indexOf('able') !== -1) { 1073 this.iconType = 'font'; 1074 } 1075 else { 1076 this.iconType = 'image'; 1077 } 1078 } 1079 else { 1080 // couldn't get computed font-family; use images to be safe 1081 this.iconType = 'image'; 1082 } 1083 } 1084 else { 1085 // window.getComputedStyle is not supported (IE 8 and earlier) 1086 // No known way to detect computed font 1087 // The following retrieves the value from the style sheet, not the computed font 1088 // controllerFont = $tempButton.get(0).currentStyle.fontFamily; 1089 // It will therefore return "able", even if the user is overriding that with a custom style sheet 1090 // To be safe, use images 1091 this.iconType = 'image'; 1092 } 1093 if (typeof $tempButton !== 'undefined') { 1094 $tempButton.remove(); 1095 } 1096 } 1097 }; 1098 1099 // Perform one-time setup for this instance of player; called after player is first initialized. 1100 AblePlayer.prototype.setupInstance = function () { 1101 1102 var deferred = new $.Deferred(); 1103 var promise = deferred.promise(); 1104 1105 if (this.$media.attr('id')) { 1106 this.mediaId = this.$media.attr('id'); 1107 } 1108 else {
vendor: 4,087 bytes, lines 1109-1225
1109 // Ensure the base media element always has an ID. 1110 this.mediaId = "ableMediaId_" + this.ableIndex; 1111 this.$media.attr('id', this.mediaId); 1112 } 1113 deferred.resolve(); 1114 return promise; 1115 }; 1116 1117 AblePlayer.prototype.setupInstancePlaylist = function() { 1118 1119 // find a matching playlist and set this.hasPlaylist 1120 // if there is one, also set this.$playlist, this.playlistIndex, & this.playlistEmbed 1121 var thisObj = this; 1122 1123 this.hasPlaylist = false; // will change to true if a matching playlist is found 1124 1125 $('.able-playlist').each(function() { 1126 if ($(this).data('player') === thisObj.mediaId) { 1127 // this is the playlist for the current player 1128 thisObj.hasPlaylist = true; 1129 // If using an embedded player, we'll replace $playlist with the clone later. 1130 thisObj.$playlist = $(this).find('li'); 1131 1132 // check to see if list item has YouTube as its source 1133 // if it does, inject a thumbnail from YouTube 1134 var $youTubeVideos = $(this).find('li[data-youtube-id]'); 1135 $youTubeVideos.each(function() { 1136 var youTubeId = $(this).attr('data-youtube-id'); 1137 var youTubePoster = thisObj.getYouTubePosterUrl(youTubeId,'120'); 1138 var $youTubeImg = $('<img>',{ 1139 'src': youTubePoster, 1140 'alt': '' 1141 }); 1142 $(this).find('button').prepend($youTubeImg); 1143 }); 1144 1145 // check to see if list item has Vimeo as its source 1146 // if it does, inject a thumbnail from Vimeo 1147 var $vimeoVideos = $(this).find('li[data-vimeo-id]'); 1148 $vimeoVideos.each(function() { 1149 var vimeoId = $(this).attr('data-youtube-id'); 1150 var vimeoPoster = thisObj.getVimeoPosterUrl(vimeoId,'120'); 1151 var $vimeoImg = $('<img>',{ 1152 'src': vimeoPoster, 1153 'alt': '' 1154 }); 1155 $(this).find('button').prepend($vimeoImg); 1156 }); 1157 1158 // add accessibility to the list markup 1159 $(this).find('li span').attr('aria-hidden','true'); 1160 thisObj.playlistIndex = 0; 1161 var dataEmbedded = $(this).data('embedded'); 1162 if (typeof dataEmbedded !== 'undefined' && dataEmbedded !== false) { 1163 // embed playlist within player 1164 thisObj.playlistEmbed = true; 1165 } 1166 else { 1167 thisObj.playlistEmbed = false; 1168 } 1169 } 1170 }); 1171 1172 if (this.hasPlaylist && this.loop) { 1173 // browser will loop the current track in the playlist, rather than the playlist 1174 // therefore, need to remove loop attribute from media element 1175 // but keep this.loop as true and handle the playlist looping ourselves 1176 this.media.removeAttribute('loop'); 1177 } 1178 if (this.hasPlaylist && this.playlistEmbed) { 1179 // Copy the playlist out of the dom, so we can reinject when we build the player. 1180 var parent = this.$playlist.parent(); 1181 this.$playlistDom = parent.clone(); 1182 parent.remove(); 1183 } 1184 if (this.hasPlaylist && this.$sources.length === 0) { 1185 // no source elements were provided. Construct them from the first playlist item 1186 this.cuePlaylistItem(0); 1187 // redefine this.$sources now that media contains one or more <source> elements 1188 this.$sources = this.$media.find('source'); 1189 } 1190 }; 1191 1192 AblePlayer.prototype.recreatePlayer = function () { 1193 1194 // Creates the appropriate player for the current source. 1195 // This function is called each time a new media instance is loaded 1196 // e.g., 1197 // User clicks on an item in a playlist 1198 // User swaps to/from described version of video 1199 // Blocks of code that only need to be executed once are controlled 1200 // by this.playerCreated 1201 1202 // TODO: Ensure when recreating player that we carry over the mediaId 1203 if (!this.player) { 1204 console.log("Can't create player; no appropriate player type detected."); 1205 return; 1206 } 1207 1208 var deferred, promise, thisObj, prefsGroups, i; 1209 1210 deferred = new $.Deferred(); 1211 promise = deferred.promise(); 1212 thisObj = this; 1213 1214 this.playerDeleted = false; // reset after deletePlayer() 1215 1216 // set temp stopgap to prevent this function from executing again before finished 1217 this.recreatingPlayer = true; 1218 1219 if (!this.playerCreated) { 1220 // only call these functions once 1221 this.loadCurrentPreferences(); 1222 this.injectPlayerCode(); 1223 this.resizePlayer(this.media.videoWidth,this.media.videoHeight); 1224 } 1225
1226 this.getSampleDescriptionText(); 1227 1228 this.initSignLanguage(); 1229 1230 this.initPlayer().then(function() { 1231 1232 thisObj.getTracks().then(function() { 1233 1234 thisObj.initDescription().then(function() { 1235 1236 thisObj.setupTracks().then(function() { 1237 if (thisObj.hasClosedDesc) { 1238 if (!thisObj.$descDiv || 1239 (thisObj.$descDiv && !($.contains(thisObj.$ableDiv[0], thisObj.$descDiv[0])))) { 1240 // descDiv either doesn't exist, or exists in an orphaned state 1241 // Either way, it needs to be rebuilt... 1242 thisObj.injectTextDescriptionArea(); 1243 } 1244 } 1245 thisObj.initSpeech('init'); 1246 1247 thisObj.setupTranscript().then(function() { 1248 1249 thisObj.initStenoFrame().then(function() { 1250 1251 if (thisObj.stenoMode && thisObj.$stenoFrame) { 1252 thisObj.stenoFrameContents = thisObj.$stenoFrame.contents(); 1253 } 1254 thisObj.getMediaTimes().then(function(mediaTimes) { 1255 1256 thisObj.duration = mediaTimes['duration']; 1257 thisObj.elapsed = mediaTimes['elapsed']; 1258 thisObj.setFullscreen(false); 1259 1260 if (typeof thisObj.volume === 'undefined') { 1261 thisObj.volume = thisObj.defaultVolume; 1262 } 1263 if (thisObj.volume) { 1264 thisObj.setVolume(thisObj.volume); 1265 } 1266 if (thisObj.transcriptType) { 1267 thisObj.addTranscriptAreaEvents(); 1268 thisObj.updateTranscript(); 1269 } 1270 if (thisObj.captions.length) { 1271 thisObj.initDefaultCaption(); 1272 } 1273 1274 // setMediaAttributes() sets textTrack.mode to 'disabled' for all tracks 1275 // This tells browsers to ignore the text tracks so Able Player can handle them 1276 // However, timing is critical as browsers - especially Safari - tend to ignore this request 1277 // unless it's sent late in the intialization process. 1278 // If browsers ignore the request, the result is redundant captions 1279 thisObj.setMediaAttributes(); 1280 thisObj.addControls(); 1281 thisObj.addEventListeners(); 1282 1283 // inject each of the hidden forms that will be accessed from the Preferences popup menu 1284 prefsGroups = thisObj.getPreferencesGroups(); 1285 for (i = 0; i < prefsGroups.length; i++) { 1286 thisObj.injectPrefsForm(prefsGroups[i]); 1287 } 1288 thisObj.setupPopups(); 1289 thisObj.updateCaption(); 1290 thisObj.injectVTS(); 1291 if (thisObj.chaptersDivLocation) { 1292 thisObj.populateChaptersDiv(); 1293 } 1294 thisObj.showSearchResults(); 1295 1296 // Go ahead and load media, without user requesting it 1297 // Ideally, we would wait until user clicks play, rather than unnecessarily consume their bandwidth 1298 // However, the media needs to load for us to get the media's duration 1299 if (thisObj.player === 'html5') { 1300 if (!thisObj.loadingMedia) { 1301 thisObj.$media[0].load(); 1302 thisObj.loadingMedia = true; 1303 } 1304 } 1305 // refreshControls is called twice building/initializing the player 1306 // this is the second. Best to pause a bit before executing, to be sure all prior steps are complete 1307 setTimeout(function() { 1308 thisObj.refreshControls('init'); 1309 deferred.resolve(); 1310 },100); 1311 }); 1312 }); 1313 }); 1314 }); 1315 }); 1316 }); 1317 }, 1318 function() { // initPlayer fail 1319 thisObj.provideFallback(); 1320 }); 1321 return promise; 1322 }; 1323 1324 AblePlayer.prototype.initPlayer = function () { 1325 1326 var thisObj = this; 1327 var playerPromise; 1328 // First run player specific initialization. 1329 if (this.player === 'html5') { 1330 playerPromise = this.initHtml5Player(); 1331 } 1332 else if (this.player === 'youtube') { 1333 playerPromise = this.initYouTubePlayer(); 1334 } 1335 else if (this.player === 'vimeo') { 1336 playerPromise = this.initVimeoPlayer(); 1337 } 1338 // After player specific initialization is done, run remaining general initialization. 1339 var deferred = new $.Deferred(); 1340 var promise = deferred.promise(); 1341 playerPromise.done( 1342 function () { // done/resolved 1343 if (thisObj.useFixedSeekInterval) { 1344 if (!thisObj.seekInterval) { 1345 thisObj.seekInterval = thisObj.defaultSeekInterval; 1346 } 1347 else { 1348 // fixed seekInterval was already assigned, using value of data-seek-interval attribute 1349 } 1350 thisObj.seekIntervalCalculated = true; 1351 } 1352 else { 1353 thisObj.setSeekInterval(); 1354 } 1355 deferred.resolve(); 1356 } 1357 ).fail(function () { // failed 1358 deferred.reject(); 1359 } 1360 ); 1361 1362 return promise; 1363 }; 1364 1365 AblePlayer.prototype.initStenoFrame = function() { 1366 1367 var thisObj, deferred, promise, $iframe; 1368 thisObj = this; 1369 1370 deferred = new $.Deferred(); 1371 promise = deferred.promise(); 1372 1373 if (this.stenoMode && this.$stenoFrame) { 1374 1375 if (this.$stenoFrame[0].contentWindow,document.readyState == 'complete') { 1376 // iframe has already loaded 1377 deferred.resolve(); 1378 } 1379 else { 1380 // iframe has not loaded. Wait for it. 1381 this.$stenoFrame.on('load',function() { 1382 deferred.resolve(); 1383 }); 1384 } 1385 } 1386 else { 1387 // there is no stenoFrame to initialize 1388 deferred.resolve(); 1389 } 1390 return promise; 1391 }; 1392 1393 AblePlayer.prototype.setSeekInterval = function () { 1394
1395 // this function is only called if this.useFixedSeekInterval is false 1396 // if this.useChapterTimes, this is called as each new chapter is loaded 1397 // otherwise, it's called once, as the player is initialized 1398 var thisObj, duration; 1399 thisObj = this; 1400 this.seekInterval = this.defaultSeekInterval; 1401 if (this.useChapterTimes) { 1402 duration = this.chapterDuration; 1403 } 1404 else { 1405 duration = this.duration; 1406 } 1407 if (typeof duration === 'undefined' || duration < 1) { 1408 // no duration; just use default for now but keep trying until duration is available 1409 this.seekIntervalCalculated = false; 1410 return; 1411 } 1412 else { 1413 if (duration <= 20) { 1414 this.seekInterval = 5; // 4 steps max 1415 } 1416 else if (duration <= 30) { 1417 this.seekInterval = 6; // 5 steps max 1418 } 1419 else if (duration <= 40) { 1420 this.seekInterval = 8; // 5 steps max 1421 } 1422 else if (duration <= 100) { 1423 this.seekInterval = 10; // 10 steps max 1424 } 1425 else { 1426 // never more than 10 steps from start to end 1427 this.seekInterval = (duration / 10); 1428 } 1429 this.seekIntervalCalculated = true; 1430 } 1431 }; 1432 1433 AblePlayer.prototype.initDefaultCaption = function () { 1434 1435 var captions, i; 1436 1437 captions = this.captions; 1438 if (captions.length > 0) { 1439 for (i=0; i<captions.length; i++) { 1440 if (captions[i].def === true) { 1441 this.captionLang = captions[i].language; 1442 this.selectedCaptions = captions[i]; 1443 } 1444 } 1445 if (typeof this.captionLang === 'undefined') { 1446 // No caption track was flagged as default 1447 // find and use a caption language that matches the player language 1448 for (i=0; i<captions.length; i++) { 1449 if (captions[i].language === this.lang) { 1450 this.captionLang = captions[i].language; 1451 this.selectedCaptions = captions[i]; 1452 } 1453 } 1454 } 1455 if (typeof this.captionLang === 'undefined') { 1456 // Still no matching caption track 1457 // just use the first track 1458 this.captionLang = captions[0].language; 1459 this.selectedCaptions = captions[0]; 1460 } 1461 if (typeof this.captionLang !== 'undefined') { 1462 // reset transcript selected <option> to this.captionLang 1463 if (this.$transcriptLanguageSelect) { 1464 this.$transcriptLanguageSelect.find('option[lang=' + this.captionLang + ']').prop('selected',true); 1465 } 1466 // sync all other tracks to this same languge 1467 this.syncTrackLanguages('init',this.captionLang); 1468 } 1469 if (this.player === 'vimeo') { 1470 if (this.usingVimeoCaptions && this.prefCaptions == 1) { 1471 // initialize Vimeo captions to the default language 1472 this.vimeoPlayer.enableTextTrack(this.captionLang).then(function(track) { 1473 // track.language = the iso code for the language 1474 // track.kind = 'captions' or 'subtitles' 1475 // track.label = the human-readable label 1476 }).catch(function(error) { 1477 switch (error.name) { 1478 case 'InvalidTrackLanguageError': 1479 // no track was available with the specified language 1480 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 1481 break; 1482 case 'InvalidTrackError': 1483 // no track was available with the specified language and kind 1484 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 1485 break; 1486 default: 1487 // some other error occurred 1488 console.log('Error loading ' + track.label + ' ' + track.kind + ' track'); 1489 break; 1490 } 1491 }); 1492 } 1493 else { 1494 // disable Vimeo captions. 1495 this.vimeoPlayer.disableTextTrack().then(function() { 1496 // Vimeo captions disabled 1497 }).catch(function(error) { 1498 console.log('Error disabling Vimeo text track: ',error); 1499 }); 1500 } 1501 } 1502 } 1503 }; 1504 1505 AblePlayer.prototype.initHtml5Player = function () { 1506 // Nothing special to do! 1507 var deferred = new $.Deferred(); 1508 var promise = deferred.promise(); 1509 deferred.resolve(); 1510 return promise; 1511 }; 1512 1513 // Sets media/track/source attributes; is called whenever player is recreated since $media may have changed. 1514 AblePlayer.prototype.setMediaAttributes = function () { 1515 // Firefox puts videos in tab order; remove. 1516 this.$media.attr('tabindex', -1); 1517 1518 // Keep native player from displaying captions/subtitles by setting textTrack.mode='disabled' 1519 // https://dev.w3.org/html5/spec-author-view/video.html#text-track-mode 1520 // This *should* work but historically hasn't been supported in all browsers 1521 // Workaround for non-supporting browsers is to remove default attribute 1522 // We're doing that too in track.js > setupCaptions() 1523 var textTracks = this.$media.get(0).textTracks; 1524 if (textTracks) { 1525 var i = 0; 1526 while (i < textTracks.length) { 1527 textTracks[i].mode = 'disabled'; 1528 i += 1; 1529 } 1530 } 1531 }; 1532 1533 AblePlayer.prototype.getPlayer = function() { 1534 1535 // Determine which player to use, if any 1536 // return 'html5', 'youtube', 'vimeo', or null 1537 1538 var i, sourceType, $newItem; 1539 if (this.testFallback) { 1540 return null; 1541 } 1542 else if (this.youTubeId) { 1543 if (this.mediaType !== 'video') { 1544 // attempting to play a YouTube video using an element other than <video> 1545 return null; 1546 } 1547 else { 1548 return 'youtube'; 1549 } 1550 } 1551 else if (this.vimeoId) { 1552 if (this.mediaType !== 'video') { 1553 // attempting to play a Vimeo video using an element other than <video> 1554 return null; 1555 } 1556 else { 1557 return 'vimeo'; 1558 } 1559 1560 } 1561 else if (this.media.canPlayType) { 1562 return 'html5'; 1563 } 1564 else { 1565 // Browser does not support the available media file 1566 return null; 1567 } 1568 }; 1569 1570})(jQuery); 1571 1572(function ($) { 1573 AblePlayer.prototype.setCookie = function(cookieValue) { 1574 1575 Cookies.set('Able-Player', JSON.stringify(cookieValue), { 1576 expires: 90, 1577 sameSite: 'strict' 1578 }); 1579 }; 1580 1581 AblePlayer.prototype.getCookie = function() { 1582 1583 var defaultCookie = { 1584 preferences: {}, 1585 sign: {}, 1586 transcript: {}, 1587 voices: [] 1588 }; 1589 1590 var cookie; 1591 try {
vendor: 10,388 bytes, lines 1592-1926
1592 cookie = JSON.parse(Cookies.get('Able-Player')); 1593 } 1594 catch (err) { 1595 // Original cookie can't be parsed; update to default 1596 this.setCookie(defaultCookie); 1597 cookie = defaultCookie; 1598 } 1599 if (cookie) { 1600 return cookie; 1601 } 1602 else { 1603 return defaultCookie; 1604 } 1605 }; 1606 1607 AblePlayer.prototype.updateCookie = function( setting ) { 1608 1609 // called when a particular setting had been updated 1610 // useful for settings updated indepedently of Preferences dialog 1611 // e.g., prefAutoScrollTranscript, which is updated in control.js > handleTranscriptLockToggle() 1612 // setting is any supported preference name (e.g., "prefCaptions") 1613 // OR 'transcript' or 'sign' (not user-defined preferences, used to save position of draggable windows) 1614 var cookie, $window, windowPos, available, i, prefName, voiceLangFound, newVoice; 1615 cookie = this.getCookie(); 1616 if (setting === 'transcript' || setting === 'sign') { 1617 if (setting === 'transcript') { 1618 $window = this.$transcriptArea; 1619 windowPos = $window.position(); 1620 if (typeof cookie.transcript === 'undefined') { 1621 cookie.transcript = {}; 1622 } 1623 cookie.transcript['position'] = $window.css('position'); // either 'relative' or 'absolute' 1624 cookie.transcript['zindex'] = $window.css('z-index'); 1625 cookie.transcript['top'] = windowPos.top; 1626 cookie.transcript['left'] = windowPos.left; 1627 cookie.transcript['width'] = $window.width(); 1628 cookie.transcript['height'] = $window.height(); 1629 } 1630 else if (setting === 'sign') { 1631 $window = this.$signWindow; 1632 windowPos = $window.position(); 1633 if (typeof cookie.sign === 'undefined') { 1634 cookie.sign = {}; 1635 } 1636 cookie.sign['position'] = $window.css('position'); // either 'relative' or 'absolute' 1637 cookie.sign['zindex'] = $window.css('z-index'); 1638 cookie.sign['top'] = windowPos.top; 1639 cookie.sign['left'] = windowPos.left; 1640 cookie.sign['width'] = $window.width(); 1641 cookie.sign['height'] = $window.height(); 1642 } 1643 } 1644 else if (setting === 'voice') { 1645 if (typeof cookie.voices === 'undefined') { 1646 cookie.voices = []; 1647 } 1648 // replace preferred voice for this lang in cookie.voices array, if one exists 1649 // otherwise, add it to the array 1650 voiceLangFound = false; 1651 for (var v=0; v < cookie.voices.length; v++) { 1652 if (cookie.voices[v].lang === this.prefDescVoiceLang) { 1653 voiceLangFound = true; 1654 cookie.voices[v].name = this.prefDescVoice; 1655 } 1656 } 1657 if (!voiceLangFound) { 1658 // no voice has been saved yet for this language. Add it to array. 1659 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 1660 cookie.voices.push(newVoice); 1661 } 1662 } 1663 else { 1664 available = this.getAvailablePreferences(); 1665 // Rebuild cookie with current cookie values, 1666 // replacing the one value that's been changed 1667 for (i = 0; i < available.length; i++) { 1668 prefName = available[i]['name']; 1669 if (prefName == setting) { 1670 // this is the one that requires an update 1671 cookie.preferences[prefName] = this[prefName]; 1672 } 1673 } 1674 } 1675 // Save updated cookie 1676 this.setCookie(cookie); 1677 }; 1678 1679 AblePlayer.prototype.getPreferencesGroups = function() { 1680 1681 // return array of groups in the order in which they will appear 1682 // in the Preferences popup menu 1683 // Human-readable label for each group is defined in translation table 1684 if (this.usingYouTubeCaptions) { 1685 // no transcript is possible 1686 return ['captions','descriptions','keyboard']; 1687 } 1688 else if (this.usingVimeoCaptions) { 1689 // users cannot control caption appearance 1690 // and no transcript is possible 1691 return ['descriptions','keyboard']; 1692 } 1693 else { 1694 return ['captions','descriptions','keyboard','transcript']; 1695 } 1696 } 1697 1698 AblePlayer.prototype.getAvailablePreferences = function() { 1699 1700 // Return the list of currently available preferences. 1701 // Preferences with no 'label' are set within player, not shown in Prefs dialog 1702 var prefs = []; 1703 1704 // Modifier keys preferences 1705 prefs.push({ 1706 'name': 'prefAltKey', // use alt key with shortcuts 1707 'label': this.tt.prefAltKey, 1708 'group': 'keyboard', 1709 'default': 1 1710 }); 1711 prefs.push({ 1712 'name': 'prefCtrlKey', // use ctrl key with shortcuts 1713 'label': this.tt.prefCtrlKey, 1714 'group': 'keyboard', 1715 'default': 1 1716 }); 1717 prefs.push({ 1718 'name': 'prefShiftKey', 1719 'label': this.tt.prefShiftKey, 1720 'group': 'keyboard', 1721 'default': 0 1722 }); 1723 1724 // Transcript preferences 1725 prefs.push({ 1726 'name': 'prefTranscript', // transcript default state 1727 'label': null, 1728 'group': 'transcript', 1729 'default': 0 // off because turning it on has a certain WOW factor 1730 }); 1731 prefs.push({ 1732 'name': 'prefHighlight', // highlight transcript as media plays 1733 'label': this.tt.prefHighlight, 1734 'group': 'transcript', 1735 'default': 1 // on because many users can benefit 1736 }); 1737 prefs.push({ 1738 'name': 'prefAutoScrollTranscript', 1739 'label': null, 1740 'group': 'transcript', 1741 'default': 1 1742 }); 1743 prefs.push({ 1744 'name': 'prefTabbable', // tab-enable transcript 1745 'label': this.tt.prefTabbable, 1746 'group': 'transcript', 1747 'default': 0 // off because if users don't need it, it impedes tabbing elsewhere on the page 1748 }); 1749 1750 // Caption preferences 1751 1752 prefs.push({ 1753 'name': 'prefCaptions', // closed captions default state 1754 'label': null, 1755 'group': 'captions', 1756 'default': this.defaultStateCaptions 1757 }); 1758 1759 if (!this.usingYouTubeCaptions) { 1760 1761 /* // not supported yet 1762 prefs.push({ 1763 'name': 'prefCaptionsStyle', 1764 'label': this.tt.prefCaptionsStyle, 1765 'group': 'captions', 1766 'default': this.tt.captionsStylePopOn 1767 }); 1768 */ 1769 // captions are always positioned above the player for audio 1770 if (this.mediaType === 'video') { 1771 prefs.push({ 1772 'name': 'prefCaptionsPosition', 1773 'label': this.tt.prefCaptionsPosition, 1774 'group': 'captions', 1775 'default': this.defaultCaptionsPosition 1776 }); 1777 } 1778 prefs.push({ 1779 'name': 'prefCaptionsFont', 1780 'label': this.tt.prefCaptionsFont, 1781 'group': 'captions', 1782 'default': 'sans-serif' 1783 }); 1784 } 1785 // This is the one option that is supported by YouTube IFrame API 1786 prefs.push({ 1787 'name': 'prefCaptionsSize', 1788 'label': this.tt.prefCaptionsSize, 1789 'group': 'captions', 1790 'default': '100%' 1791 }); 1792 1793 if (!this.usingYouTubeCaptions) { 1794 1795 prefs.push({ 1796 'name': 'prefCaptionsColor', 1797 'label': this.tt.prefCaptionsColor, 1798 'group': 'captions', 1799 'default': 'white' 1800 }); 1801 prefs.push({ 1802 'name': 'prefCaptionsBGColor', 1803 'label': this.tt.prefCaptionsBGColor, 1804 'group': 'captions', 1805 'default': 'black' 1806 }); 1807 prefs.push({ 1808 'name': 'prefCaptionsOpacity', 1809 'label': this.tt.prefCaptionsOpacity, 1810 'group': 'captions', 1811 'default': '100%' 1812 }); 1813 } 1814 1815 if (this.mediaType === 'video') { 1816 // Description preferences 1817 prefs.push({ 1818 'name': 'prefDesc', // audio description default state 1819 'label': null, 1820 'group': 'descriptions', 1821 'default': this.defaultStateDescriptions 1822 }); 1823 prefs.push({ 1824 'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available) 1825 'label': null, 1826 'group': 'descriptions', 1827 'default': 'video' // video (an alternative described version) always wins 1828 }); 1829 prefs.push({ 1830 'name': 'prefDescVoice', 1831 'label': this.tt.prefDescVoice, 1832 'group': 'descriptions', 1833 'default': null // will be set later, in injectPrefsForm() 1834 }); 1835 prefs.push({ 1836 'name': 'prefDescPitch', 1837 'label': this.tt.prefDescPitch, 1838 'group': 'descriptions', 1839 'default': 1 // 0 to 2 1840 }); 1841 prefs.push({ 1842 'name': 'prefDescRate', 1843 'label': this.tt.prefDescRate, 1844 'group': 'descriptions', 1845 'default': 1 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast) 1846 }); 1847 prefs.push({ 1848 'name': 'prefDescVolume', 1849 'label': this.tt.volume, 1850 'group': 'descriptions', 1851 'default': 1 // 0 to 1 1852 }); 1853 prefs.push({ 1854 'name': 'prefDescPause', // automatically pause when closed description starts 1855 'label': this.tt.prefDescPause, 1856 'group': 'descriptions', 1857 'default': this.defaultDescPause 1858 }); 1859 prefs.push({ 1860 'name': 'prefDescVisible', // visibly show closed description (if avilable and used) 1861 'label': this.tt.prefDescVisible, 1862 'group': 'descriptions', 1863 'default': 0 // off as of 4.3.16, to avoid overloading the player with visible features 1864 }); 1865 } 1866 // Preferences without a category (not shown in Preferences dialogs) 1867 prefs.push({ 1868 'name': 'prefSign', // open sign language window by default if avilable 1869 'label': null, 1870 'group': null, 1871 'default': 0 // off because clicking an icon to see the sign window has a powerful impact 1872 }); 1873 1874 return prefs; 1875 }; 1876 1877 AblePlayer.prototype.loadCurrentPreferences = function () { 1878 1879 // Load current/default preferences from cookie into the AblePlayer object. 1880 1881 var available = this.getAvailablePreferences(); 1882 var cookie = this.getCookie(); 1883 // Copy current cookie values into this object, and fill in any default values. 1884 for (var ii = 0; ii < available.length; ii++) { 1885 var prefName = available[ii]['name']; 1886 var defaultValue = available[ii]['default']; 1887 if (cookie.preferences[prefName] !== undefined) { 1888 this[prefName] = cookie.preferences[prefName]; 1889 } 1890 else { 1891 cookie.preferences[prefName] = defaultValue; 1892 this[prefName] = defaultValue; 1893 } 1894 } 1895 1896 // Also load array of preferred voices from cookie 1897 if (typeof cookie.voices !== 'undefined') { 1898 this.prefVoices = cookie.voices; 1899 } 1900 1901 this.setCookie(cookie); 1902 }; 1903 1904 AblePlayer.prototype.injectPrefsForm = function (form) { 1905 1906 // Creates a preferences form and injects it. 1907 // form is one of the supported forms (groups) defined in getPreferencesGroups() 1908 1909 var thisObj, available, descLangs, 1910 $prefsDiv, formTitle, introText, 1911 $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j, 1912 $fieldset, fieldsetClass, fieldsetId, 1913 $descFieldset, $descLegend, $legend, legendId, 1914 thisPref, $thisDiv, thisClass, thisId, $thisLabel, $thisField, 1915 $div1,id1,$radio1,$label1, 1916 $div2,id2,$radio2,$label2, 1917 options,$thisOption,optionValue,optionLang,optionText,sampleCapsDiv, 1918 changedPref,changedSpan,changedText, 1919 currentDescState, prefDescVoice, 1920 $kbHeading,$kbList,kbLabels,keys,kbListText,$kbListItem, 1921 dialog,saveButton,cancelButton; 1922 1923 thisObj = this; 1924 available = this.getAvailablePreferences(); 1925 1926 // outer container, will be assigned role="dialog"
1927 $prefsDiv = $('<div>',{ 1928 'class': 'able-prefs-form ' 1929 }); 1930 var customClass = 'able-prefs-form-' + form; 1931 $prefsDiv.addClass(customClass); 1932 1933 // add titles and intros 1934 if (form == 'captions') { 1935 formTitle = this.tt.prefTitleCaptions; 1936 // Intro text removed in 4.4.32 to cut down on unnecessary verbiage 1937 } 1938 else if (form == 'descriptions') { 1939 formTitle = this.tt.prefTitleDescriptions; 1940 var $prefsIntro = $('<p>',{ 1941 text: this.tt.prefIntroDescription1 1942 }); 1943 var $prefsIntroUL = $('<ul>'); 1944 var $prefsIntroLI1 = $('<li>',{ 1945 text: this.tt.prefDescFormatOption1 1946 }); 1947 var $prefsIntroLI2 = $('<li>',{ 1948 text: this.tt.prefDescFormatOption2 1949 }); 1950 1951 $prefsIntroUL.append($prefsIntroLI1,$prefsIntroLI2); 1952 if (this.hasOpenDesc && this.hasClosedDesc) { 1953 currentDescState = this.tt.prefIntroDescription2 + ' '; 1954 currentDescState += '<strong>' + this.tt.prefDescFormatOption1b + '</strong>'; 1955 currentDescState += ' <em>' + this.tt.and + '</em> <strong>' + this.tt.prefDescFormatOption2b + '</strong>.'; 1956 } 1957 else if (this.hasOpenDesc) { 1958 currentDescState = this.tt.prefIntroDescription2; 1959 currentDescState += ' <strong>' + this.tt.prefDescFormatOption1b + '</strong>.'; 1960 } 1961 else if (this.hasClosedDesc) { 1962 currentDescState = this.tt.prefIntroDescription2; 1963 currentDescState += ' <strong>' + this.tt.prefDescFormatOption2b + '</strong>.'; 1964 } 1965 else { 1966 currentDescState = this.tt.prefIntroDescriptionNone; 1967 } 1968 $prefsIntroP2 = $('<p>',{ 1969 html: currentDescState 1970 }); 1971 1972 p3Text = this.tt.prefIntroDescription3; 1973 if (this.hasOpenDesc || this.hasClosedDesc) { 1974 p3Text += ' ' + this.tt.prefIntroDescription4; 1975 } 1976 $prefsIntroP3 = $('<p>',{ 1977 text: p3Text 1978 }); 1979 1980 $prefsDiv.append($prefsIntro,$prefsIntroUL,$prefsIntroP2,$prefsIntroP3); 1981 } 1982 else if (form == 'keyboard') { 1983 formTitle = this.tt.prefTitleKeyboard; 1984 introText = this.tt.prefIntroKeyboard1; 1985 introText += ' ' + this.tt.prefIntroKeyboard2; 1986 introText += ' ' + this.tt.prefIntroKeyboard3; 1987 $prefsIntro = $('<p>',{ 1988 text: introText 1989 }); 1990 $prefsDiv.append($prefsIntro); 1991 } 1992 else if (form == 'transcript') { 1993 formTitle = this.tt.prefTitleTranscript; 1994 // Intro text removed in 4.4.32 to cut down on unnecessary verbiage 1995 } 1996 1997 $fieldset = $('<div>').attr('role','group'); 1998 fieldsetClass = 'able-prefs-' + form; 1999 fieldsetId = this.mediaId + '-prefs-' + form; 2000 legendId = fieldsetId + '-legend'; 2001 $fieldset.addClass(fieldsetClass).attr('id',fieldsetId); 2002 if (form === 'keyboard') { 2003 $legend = $('<h2>' + this.tt.prefHeadingKeyboard1 + '</h2>'); 2004 $legend.attr('id',legendId); 2005 $fieldset.attr('aria-labelledby',legendId); 2006 $fieldset.append($legend); 2007 } 2008 else if (form === 'descriptions') { 2009 $legend = $('<h2>' + this.tt.prefHeadingTextDescription + '</h2>'); 2010 $legend.attr('id',legendId); 2011 $fieldset.attr('aria-labelledby',legendId); 2012 $fieldset.append($legend); 2013 } 2014 for (i=0; i<available.length; i++) { 2015 2016 // only include prefs on the current form if they have a label 2017 if ((available[i]['group'] == form) && available[i]['label']) { 2018 2019 thisPref = available[i]['name']; 2020 thisClass = 'able-' + thisPref; 2021 thisId = this.mediaId + '_' + thisPref; 2022 $thisDiv = $('<div>').addClass(thisClass); 2023 2024 if (form === 'captions') { 2025 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 2026 $thisField = $('<select>',{ 2027 name: thisPref, 2028 id: thisId, 2029 }); 2030 if (thisPref !== 'prefCaptions' && thisPref !== 'prefCaptionsStyle') { 2031 // add a change handler that updates the style of the sample caption text 2032 $thisField.change(function() { 2033 changedPref = $(this).attr('name'); 2034 thisObj.stylizeCaptions(thisObj.$sampleCapsDiv,changedPref); 2035 }); 2036 } 2037 options = this.getCaptionsOptions(thisPref); 2038 for (j=0; j < options.length; j++) { 2039 if (thisPref === 'prefCaptionsPosition') { 2040 optionValue = options[j]; 2041 if (optionValue === 'overlay') { 2042 optionText = this.tt.captionsPositionOverlay; 2043 } 2044 else if (optionValue === 'below') { 2045 optionValue = options[j]; 2046 optionText = this.tt.captionsPositionBelow; 2047 } 2048 } 2049 else if (thisPref === 'prefCaptionsFont' || thisPref === 'prefCaptionsColor' || thisPref === 'prefCaptionsBGColor') { 2050 optionValue = options[j][0]; 2051 optionText = options[j][1]; 2052 } 2053 else if (thisPref === 'prefCaptionsOpacity') { 2054 optionValue = options[j]; 2055 optionText = options[j]; 2056 if (optionValue === '0%') {
vendor: 5,370 bytes, lines 2057-2215
2057 optionText += ' (' + this.tt.transparent + ')'; 2058 } 2059 else if (optionValue === '100%') { 2060 optionText += ' (' + this.tt.solid + ')'; 2061 } 2062 } 2063 else { 2064 optionValue = options[j]; 2065 optionText = options[j]; 2066 } 2067 $thisOption = $('<option>',{ 2068 value: optionValue, 2069 text: optionText 2070 }); 2071 if (this[thisPref] === optionValue) { 2072 $thisOption.prop('selected',true); 2073 } 2074 $thisField.append($thisOption); 2075 } 2076 $thisDiv.append($thisLabel,$thisField); 2077 } 2078 else if (form === 'descriptions') { 2079 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 2080 if (thisPref === 'prefDescPause' || thisPref === 'prefDescVisible') { 2081 // these preferences are checkboxes 2082 $thisDiv.addClass('able-prefs-checkbox'); 2083 $thisField = $('<input>',{ 2084 type: 'checkbox', 2085 name: thisPref, 2086 id: thisId, 2087 value: 'true' 2088 }); 2089 // check current active value for this preference 2090 if (this[thisPref] === 1) { 2091 $thisField.prop('checked',true); 2092 } 2093 $thisDiv.append($thisField,$thisLabel); 2094 } 2095 else if (this.synth) { 2096 // Only show these options if browser supports speech synthesis 2097 $thisDiv.addClass('able-prefs-select'); 2098 $thisField = $('<select>',{ 2099 name: thisPref, 2100 id: thisId, 2101 }); 2102 if (thisPref === 'prefDescVoice' && this.descVoices) { 2103 prefDescVoice = this.getPrefDescVoice(); 2104 for (j=0; j < this.descVoices.length; j++) { 2105 optionValue = this.descVoices[j].name; 2106 optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase(); 2107 optionText = optionValue + ' (' + this.descVoices[j].lang + ')'; 2108 $thisOption = $('<option>',{ 2109 'value': optionValue, 2110 'data-lang': optionLang, 2111 text: optionText 2112 }); 2113 if (prefDescVoice === optionValue) { 2114 $thisOption.prop('selected',true); 2115 } 2116 $thisField.append($thisOption); 2117 } 2118 this.$voiceSelectField = $thisField; 2119 } 2120 else { 2121 if (thisPref == 'prefDescPitch') { // 0 to 2 2122 options = [0,0.5,1,1.5,2]; 2123 } 2124 else if (thisPref == 'prefDescRate') { // 0.1 to 10 2125 // Tests with a variety of voices on MacOS and Windows 2126 // yielded the following choices that seem reasonable for audio description: 2127 // 0.5 - too slow (exclude this) 2128 // 0.7 - casual 2129 // 0.8 - add this 2130 // 0.9 - add this 2131 // 1 - normal 2132 // 1.1 - add this 2133 // 1.2 - add this 2134 // 1.5 - quick 2135 // 2 - speedy 2136 // 2.5 - fleet 2137 // 3 - fast! (some voices don't get any faster than this 2138 2139 // Note: if these values are modified, must also modfiy them 2140 // in makePrefsValueReadable() 2141 options = [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3]; 2142 } 2143 else if (thisPref == 'prefDescVolume') { // 0 (mute) to 1 2144 options = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]; 2145 } 2146 if (typeof options !== 'undefined') { 2147 for (j=0; j < options.length; j++) { 2148 optionValue = options[j]; 2149 optionText = this.makePrefsValueReadable(thisPref,optionValue); 2150 $thisOption = $('<option>',{ 2151 value: optionValue, 2152 text: optionText 2153 }); 2154 if (this[thisPref] == optionValue) { 2155 $thisOption.prop('selected',true); 2156 } 2157 $thisField.append($thisOption); 2158 $thisDiv.append($thisLabel,$thisField); 2159 } 2160 } 2161 } 2162 // add a change handler that announces the sample description text 2163 $thisField.on('change',function() { 2164 thisObj.announceDescriptionText('sample',thisObj.currentSampleText); 2165 }); 2166 $thisDiv.append($thisLabel,$thisField); 2167 } 2168 } 2169 else { // all other fields are checkboxes 2170 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 2171 $thisField = $('<input>',{ 2172 type: 'checkbox', 2173 name: thisPref, 2174 id: thisId, 2175 value: 'true' 2176 }); 2177 // check current active value for this preference 2178 if (this[thisPref] === 1) { 2179 $thisField.prop('checked',true); 2180 } 2181 if (form === 'keyboard') { 2182 // add a change handler that updates the list of current keyboard shortcuts 2183 $thisField.on('change',function() { 2184 changedPref = $(this).attr('name'); 2185 if (changedPref === 'prefAltKey') { 2186 changedSpan = '.able-modkey-alt'; 2187 changedText = thisObj.tt.prefAltKey + ' + '; 2188 } 2189 else if (changedPref === 'prefCtrlKey') { 2190 changedSpan = '.able-modkey-ctrl'; 2191 changedText = thisObj.tt.prefCtrlKey + ' + '; 2192 } 2193 else if (changedPref === 'prefShiftKey') { 2194 changedSpan = '.able-modkey-shift'; 2195 changedText = thisObj.tt.prefShiftKey + ' + '; 2196 } 2197 if ($(this).is(':checked')) { 2198 $(changedSpan).text(changedText); 2199 } 2200 else { 2201 $(changedSpan).text(''); 2202 } 2203 }); 2204 } 2205 $thisDiv.append($thisField,$thisLabel); 2206 } 2207 $fieldset.append($thisDiv); 2208 } 2209 } 2210 $prefsDiv.append($fieldset); 2211 2212 if (form === 'captions') { 2213 // add a sample closed caption div to prefs dialog 2214 // do not show this for YouTube captions, since it's not an accurate reflection 2215 if (!this.usingYouTubeCaptions) {
2216 this.$sampleCapsDiv = $('<div>',{ 2217 'class': 'able-captions-sample' 2218 }).text(this.tt.sampleCaptionText); 2219 $prefsDiv.append(this.$sampleCapsDiv); 2220 this.stylizeCaptions(this.$sampleCapsDiv); 2221 } 2222 } 2223 else if (form === 'descriptions') { 2224 if (this.synth) { 2225 // add a div with sample audio description text 2226 this.$sampleDescDiv = $('<div>',{ 2227 'class': 'able-desc-sample' 2228 }).text(this.tt.sampleDescriptionText); 2229 $prefsDiv.append(this.$sampleDescDiv); 2230 this.currentSampleText = this.tt.sampleDescriptionText; 2231 } 2232 } 2233 else if (form === 'keyboard') { 2234 // add a current list of keyboard shortcuts 2235 $kbHeading = $('<h2>',{ 2236 text: this.tt.prefHeadingKeyboard2 2237 }); 2238 $kbList = $('<ul>'); 2239 // create arrays of kbLabels and keys 2240 kbLabels = []; 2241 keys = []; 2242 for (i=0; i<this.controls.length; i++) { 2243 if (this.controls[i] === 'play') { 2244 kbLabels.push(this.tt.play + '/' + this.tt.pause); 2245 keys.push('p</span> <em>' + this.tt.or + '</em> <span class="able-help-modifiers"> ' + this.tt.spacebar); 2246 } 2247 else if (this.controls[i] === 'restart') { 2248 kbLabels.push(this.tt.restart); 2249 keys.push('s'); 2250 } 2251 else if (this.controls[i] === 'previous') { 2252 kbLabels.push(this.tt.prevTrack); 2253 keys.push('b'); // b = back 2254 } 2255 else if (this.controls[i] === 'next') { 2256 kbLabels.push(this.tt.nextTrack); 2257 keys.push('n'); 2258 } 2259 else if (this.controls[i] === 'rewind') { 2260 kbLabels.push(this.tt.rewind); 2261 keys.push('r'); 2262 } 2263 else if (this.controls[i] === 'forward') { 2264 kbLabels.push(this.tt.forward); 2265 keys.push('f'); 2266 } 2267 else if (this.controls[i] === 'volume') { 2268 kbLabels.push(this.tt.volume); 2269 keys.push('v</span> <em>' + this.tt.or + '</em> <span class="able-modkey">1-9'); 2270 // mute toggle 2271 kbLabels.push(this.tt.mute + '/' + this.tt.unmute); 2272 keys.push('m'); 2273 } 2274 else if (this.controls[i] === 'captions') { 2275 if (this.captions.length > 1) { 2276 // caption button launches a Captions popup menu 2277 kbLabels.push(this.tt.captions); 2278 } 2279 else { 2280 // there is only one caption track 2281 // therefore caption button is a toggle 2282 if (this.captionsOn) { 2283 kbLabels.push(this.tt.hideCaptions); 2284 } 2285 else { 2286 kbLabels.push(this.tt.showCaptions); 2287 } 2288 } 2289 keys.push('c'); 2290 } 2291 else if (this.controls[i] === 'descriptions') { 2292 if (this.descOn) { 2293 kbLabels.push(this.tt.turnOffDescriptions); 2294 } 2295 else { 2296 kbLabels.push(this.tt.turnOnDescriptions); 2297 } 2298 keys.push('d'); 2299 } 2300 else if (this.controls[i] === 'prefs') { 2301 kbLabels.push(this.tt.preferences); 2302 keys.push('e'); 2303 } 2304 else if (this.controls[i] === 'help') { 2305 kbLabels.push(this.tt.help); 2306 keys.push('h'); 2307 } 2308 } 2309 for (i=0; i<keys.length; i++) { 2310 // alt 2311 kbListText = '<span class="able-modkey-alt">'; 2312 if (this.prefAltKey === 1) { 2313 kbListText += this.tt.prefAltKey + ' + '; 2314 } 2315 kbListText += '</span>'; 2316 // ctrl 2317 kbListText += '<span class="able-modkey-ctrl">'; 2318 if (this.prefCtrlKey === 1) { 2319 kbListText += this.tt.prefCtrlKey + ' + '; 2320 } 2321 kbListText += '</span>'; 2322 // shift 2323 kbListText += '<span class="able-modkey-shift">'; 2324 if (this.prefShiftKey === 1) { 2325 kbListText += this.tt.prefShiftKey + ' + '; 2326 } 2327 kbListText += '</span>'; 2328 kbListText += '<span class="able-modkey">' + keys[i] + '</span>'; 2329 kbListText += ' = ' + kbLabels[i]; 2330 $kbListItem = $('<li>',{ 2331 html: kbListText 2332 }); 2333 $kbList.append($kbListItem); 2334 } 2335 // add Escape key 2336 kbListText = '<span class="able-modkey">' + this.tt.escapeKey + '</span>'; 2337 kbListText += ' = ' + this.tt.escapeKeyFunction; 2338 $kbListItem = $('<li>',{ 2339 html: kbListText 2340 }); 2341 $kbList.append($kbListItem); 2342 // put it all together 2343 $prefsDiv.append($kbHeading,$kbList); 2344 } 2345 2346 // $prefsDiv (dialog) must be appended to the BODY! 2347 // otherwise when aria-hidden="true" is applied to all background content 2348 // that will include an ancestor of the dialog, 2349 // which will render the dialog unreadable by screen readers 2350 $('body').append($prefsDiv); 2351 dialog = new AccessibleDialog($prefsDiv, this.$prefsButton, 'dialog', true, formTitle, $prefsIntro, thisObj.tt.closeButtonLabel, '32em'); 2352 2353 // Add save and cancel buttons. 2354 $prefsDiv.append('<hr>'); 2355 saveButton = $('<button class="modal-button">' + this.tt.save + '</button>'); 2356 cancelButton = $('<button class="modal-button">' + this.tt.cancel + '</button>'); 2357 saveButton.click(function () { 2358 dialog.hide(); 2359 thisObj.savePrefsFromForm(); 2360 }); 2361 cancelButton.click(function () { 2362 dialog.hide(); 2363 thisObj.resetPrefsForm(); 2364 }); 2365 2366 $prefsDiv.append(saveButton); 2367 $prefsDiv.append(cancelButton); 2368 2369 // Associate the dialog's H1 as aria-labelledby for groups of fields 2370 // (alternative to fieldset and legend) 2371 if (form === 'captions' || form === 'transcript') { 2372 $fieldset.attr('aria-labelledby',dialog.titleH1.attr('id')); 2373 } 2374 2375 // add global reference for future control 2376 if (form === 'captions') { 2377 this.captionPrefsDialog = dialog; 2378 } 2379 else if (form === 'descriptions') { 2380 this.descPrefsDialog = dialog; 2381 } 2382 else if (form === 'keyboard') { 2383 this.keyboardPrefsDialog = dialog; 2384 } 2385 else if (form === 'transcript') { 2386 this.transcriptPrefsDialog = dialog; 2387 } 2388 2389 // Add click handler for dialog close button 2390 // (button is added in dialog.js) 2391 $('div.able-prefs-form button.modalCloseButton').click(function() { 2392 thisObj.resetPrefsForm(); 2393 }) 2394 // Add handler for escape key 2395 $('div.able-prefs-form').keydown(function(e) { 2396 if (e.which === 27) { // escape 2397 thisObj.resetPrefsForm(); 2398 } 2399 }); 2400 }; 2401 2402 AblePlayer.prototype.getPrefDescVoice = function () { 2403 2404 // return user's preferred voice for the current language from cookie.voices 2405 var lang, cookie, i; 2406 2407 if (this.selectedDescriptions) { 2408 lang = this.selectedDescriptions.language; 2409 } 2410 else if (this.captionLang) { 2411 lang = this.captionLang; 2412 } 2413 else { 2414 lang = this.lang; 2415 } 2416 cookie = this.getCookie(); 2417 if (cookie.voices) { 2418 for (i=0; i < cookie.voices.length; i++) { 2419 if (cookie.voices[i].lang === lang) { 2420 return cookie.voices[i].name; 2421 } 2422 } 2423 } 2424 return null; // user has no saved preference 2425 } 2426 2427 AblePlayer.prototype.rebuildDescPrefsForm = function () { 2428
vendor: 7,890 bytes, lines 2429-2664
2429 // Called if this.descVoices changes, which may happen if: 2430 // getBrowserVoices() succeeds after an earlier failure 2431 // user changes language of captions/subtitles and descVoices changes to match the new language 2432 2433 var i, optionValue, optionText, $thisOption; 2434 2435 this.$voiceSelectField = $('#' + this.mediaId + '_prefDescVoice'); 2436 this.$voiceSelectField.empty(); 2437 for (i=0; i < this.descVoices.length; i++) { 2438 optionValue = this.descVoices[i].name; 2439 optionText = optionValue + ' (' + this.descVoices[i].lang + ')'; 2440 $thisOption = $('<option>',{ 2441 'value': optionValue, 2442 'data-lang': this.descVoices[i].lang.substring(0,2).toLowerCase(), 2443 text: optionText 2444 }); 2445 if (this.prefDescVoice == optionValue) { 2446 $thisOption.prop('selected',true); 2447 } 2448 this.$voiceSelectField.append($thisOption); 2449 } 2450 }; 2451 2452 AblePlayer.prototype.makePrefsValueReadable = function(pref,value) { 2453 2454 // The values for pitch, rate, and volume (web speech API) 2455 // are strange and inconsistent between variables 2456 // this function returns text that is more readable than the values themselves 2457 2458 if (pref === 'prefDescPitch') { 2459 if (value === 0) { 2460 return this.tt.prefDescPitch1; 2461 } 2462 else if (value === 0.5) { 2463 return this.tt.prefDescPitch2; 2464 } 2465 else if (value === 1) { 2466 return this.tt.prefDescPitch3; 2467 } 2468 else if (value === 1.5) { 2469 return this.tt.prefDescPitch4; 2470 } 2471 else if (value === 2) { 2472 return this.tt.prefDescPitch5; 2473 } 2474 } 2475 else if (pref === 'prefDescRate') { 2476 // default in the API is 0.1 to 10, where 1 is normal speaking voice 2477 // our custom range offers several rates close to 1 2478 // plus a couple of crazy fast ones for sport 2479 // Our more readable options (1-10) or mapped here to API values 2480 if (value === 0.7) { 2481 return 1; 2482 } 2483 else if (value === 0.8) { 2484 return 2; 2485 } 2486 else if (value === 0.9) { 2487 return 3; 2488 } 2489 else if (value === 1) { 2490 return 4; 2491 } 2492 else if (value === 1.1) { 2493 return 5; 2494 } 2495 else if (value === 1.2) { 2496 return 6; 2497 } 2498 else if (value === 1.5) { 2499 return 7; 2500 } 2501 else if (value === 2) { 2502 return 8; 2503 } 2504 else if (value === 2.5) { 2505 return 9; 2506 } 2507 else if (value === 3) { 2508 return 10; 2509 } 2510 } 2511 else if (pref === 'prefDescVolume') { 2512 // values range from 0.1 to 1.0 2513 return value * 10; 2514 } 2515 return value; 2516 }; 2517 2518 AblePlayer.prototype.resetPrefsForm = function () { 2519 2520 // Reset preferences form with default values from cookie 2521 // Called when: 2522 // User clicks cancel or close button in Prefs Dialog 2523 // User presses Escape to close Prefs dialog 2524 // User clicks Save in Prefs dialog, & there's more than one player on page 2525 2526 var thisObj, cookie, available, i, prefName, prefId, thisDiv, thisId; 2527 2528 thisObj = this; 2529 cookie = this.getCookie(); 2530 available = this.getAvailablePreferences(); 2531 for (i=0; i<available.length; i++) { 2532 prefName = available[i]['name']; 2533 prefId = this.mediaId + '_' + prefName; 2534 if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 2535 // this is a caption-related select box 2536 $('select[name="' + prefName + '"]').val(cookie.preferences[prefName]); 2537 } 2538 else { // all others are checkboxes 2539 if (this[prefName] === 1) { 2540 $('input[name="' + prefName + '"]').prop('checked',true); 2541 } 2542 else { 2543 $('input[name="' + prefName + '"]').prop('checked',false); 2544 } 2545 } 2546 } 2547 // also restore style of sample caption div 2548 this.stylizeCaptions(this.$sampleCapsDiv); 2549 }; 2550 2551 AblePlayer.prototype.savePrefsFromForm = function () { 2552 2553 // Return a prefs object constructed from the form. 2554 // called when user saves the Preferences form 2555 // update cookie with new value 2556 var cookie, available, prefName, prefId, 2557 voiceSelectId, newVoice, newVoiceLang, numChanges, voiceLangFound, 2558 numCapChanges, capSizeChanged, capSizeValue, newValue; 2559 2560 numChanges = 0; 2561 numCapChanges = 0; // changes to caption-style-related preferences 2562 capSizeChanged = false; 2563 cookie = this.getCookie(); 2564 available = this.getAvailablePreferences(); 2565 for (var i=0; i < available.length; i++) { 2566 // only prefs with labels are used in the Prefs form 2567 if (available[i]['label']) { 2568 prefName = available[i]['name']; 2569 prefId = this.mediaId + '_' + prefName; 2570 if (prefName === 'prefDescVoice') { 2571 if (typeof cookie.voices === 'undefined') { 2572 cookie.voices = []; 2573 } 2574 voiceSelectId = this.mediaId + '_prefDescVoice'; 2575 this.prefDescVoice = $('select#' + voiceSelectId).find(':selected').val(); 2576 this.prefDescVoiceLang = $('select#' + voiceSelectId).find(':selected').attr('data-lang'); 2577 // replace preferred voice for this lang in cookie.voices array, if one exists 2578 // otherwise, add it to the array 2579 voiceLangFound = false; 2580 for (var v=0; v < cookie.voices.length; v++) { 2581 if (cookie.voices[v].lang === this.prefDescVoiceLang) { 2582 voiceLangFound = true; 2583 cookie.voices[v].name = this.prefDescVoice; 2584 } 2585 } 2586 if (!voiceLangFound) { 2587 // no voice has been saved yet for this language. Add it to array. 2588 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 2589 cookie.voices.push(newVoice); 2590 } 2591 numChanges++; 2592 } 2593 else if (prefName == 'prefDescMethod') { 2594 // As of v4.0.10, prefDescMethod is no longer a choice 2595 // this.prefDescMethod = $('input[name="' + prefName + '"]:checked').val(); 2596 this.prefDescMethod = 'video'; 2597 if (this.prefDescMethod !== cookie.preferences['prefDescMethod']) { // user's preference has changed 2598 cookie.preferences['prefDescMethod'] = this.prefDescMethod; 2599 numChanges++; 2600 } 2601 } 2602 else if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 2603 // this is one of the caption-related select fields 2604 newValue = $('select[id="' + prefId + '"]').val(); 2605 if (cookie.preferences[prefName] !== newValue) { // user changed setting 2606 cookie.preferences[prefName] = newValue; 2607 // also update global var for this pref (for caption fields, not done elsewhere) 2608 this[prefName] = newValue; 2609 numChanges++; 2610 numCapChanges++; 2611 } 2612 if (prefName === 'prefCaptionsSize') { 2613 capSizeChanged = true; 2614 capSizeValue = newValue; 2615 } 2616 } 2617 else if ((prefName.indexOf('Desc') !== -1) && (prefName !== 'prefDescPause') && prefName !== 'prefDescVisible') { 2618 // this is one of the description-related select fields 2619 newValue = $('select[id="' + prefId + '"]').val(); 2620 if (cookie.preferences[prefName] !== newValue) { // user changed setting 2621 cookie.preferences[prefName] = newValue; 2622 // also update global var for this pref 2623 this[prefName] = newValue; 2624 numChanges++; 2625 } 2626 } 2627 else { // all other fields are checkboxes 2628 if ($('input[id="' + prefId + '"]').is(':checked')) { 2629 cookie.preferences[prefName] = 1; 2630 if (this[prefName] === 1) { 2631 // nothing has changed 2632 } 2633 else { 2634 // user has just turned this pref on 2635 this[prefName] = 1; 2636 numChanges++; 2637 } 2638 } 2639 else { // thisPref is not checked 2640 cookie.preferences[prefName] = 0; 2641 if (this[prefName] === 1) { 2642 // user has just turned this pref off 2643 this[prefName] = 0; 2644 numChanges++; 2645 } 2646 else { 2647 // nothing has chaged 2648 } 2649 } 2650 } 2651 } 2652 } 2653 if (numChanges > 0) { 2654 this.setCookie(cookie); 2655 this.showAlert(this.tt.prefSuccess); 2656 } 2657 else { 2658 this.showAlert(this.tt.prefNoChange); 2659 } 2660 if (this.player === 'youtube' && 2661 (typeof this.usingYouTubeCaptions !== 'undefined' && this.usingYouTubeCaptions) && 2662 capSizeChanged) { 2663 // update font size of YouTube captions 2664 this.youTubePlayer.setOption('captions','fontSize',this.translatePrefs('size',capSizeValue,'y
2664outube')); 2665 } 2666 if (AblePlayerInstances.length > 1) { 2667 // there are multiple players on this page. 2668 // update prefs for ALL of them 2669 for (var i=0; i<AblePlayerInstances.length; i++) { 2670 AblePlayerInstances[i].updatePrefs(); 2671 AblePlayerInstances[i].geteferences(); 2672 AblePlayerInstances[i].resetPrefsForm(); 2673 if (numCapChanges > 0) { 2674 AblePlayerInstances[i].stylizeCaptions(AblePlayerInstances[i].$captionsDiv); 2675 // also apply same changes to descriptions, if present 2676 if (typeof AblePlayerInstances[i].$descDiv !== 'undefined') { 2677 AblePlayerInstances[i].stylizeCaptions(AblePlayerInstances[i].$descDiv); 2678 } 2679 } 2680 } 2681 } 2682 else { 2683 // there is only one player 2684 this.updatePrefs(); 2685 if (numCapChanges > 0) { 2686 this.stylizeCaptions(this.$captionsDiv); 2687 // also apply same changes to descriptions, if present 2688 if (typeof this.$descDiv !== 'undefined') { 2689 this.stylizeCaptions(this.$descDiv); 2690 } 2691 } 2692 } 2693 } 2694 2695 AblePlayer.prototype.updatePrefs = function () { 2696 2697 // Update player based on current prefs. Safe to call multiple times. 2698 2699 if (this.$transcriptDiv) { 2700 // tabbable transcript 2701 if (this.prefTabbable === 1) { 2702 this.$transcriptDiv.find('span.able-transcript-seekpoint').attr('tabindex','0'); 2703 } 2704 else { 2705 this.$transcriptDiv.find('span.able-transcript-seekpoint').removeAttr('tabindex'); 2706 } 2707 2708 // transcript highlights 2709 if (this.prefHighlight === 0) { 2710 // user doesn't want highlights; remove any existing highlights 2711 this.$transcriptDiv.find('span').removeClass('able-highlight'); 2712 } 2713 } 2714 2715 // Re-initialize caption and description in case relevant settings have changed 2716 this.updateCaption(); 2717 this.initDescription(); 2718 }; 2719 2720 AblePlayer.prototype.usingModifierKeys = function(e) { 2721 2722 // return true if user is holding down required modifier keys 2723 if ((this.prefAltKey === 1) && !e.altKey) { 2724 return false; 2725 } 2726 if ((this.prefCtrlKey === 1) && !e.ctrlKey) { 2727 return false; 2728 } 2729 if ((this.prefShiftKey === 1) && !e.shiftKey) { 2730 return false; 2731 } 2732 return true; 2733 }; 2734 2735})(jQuery); 2736 2737(function ($) { 2738 // See section 4.1 of dev.w3.org/html5/webvtt for format details. 2739 AblePlayer.prototype.parseWebVTT = function(srcFile,text) { 2740 2741// var deferred = new $.Deferred(); 2742// var promise = deferred.promise(); 2743 2744 // Normalize line ends to \n. 2745 text = text.replace(/(\r\n|\n|\r)/g,'\n'); 2746 2747 var parserState = { 2748 src: srcFile, 2749 text: text, 2750 error: null, 2751 metadata: {}, 2752 cues: [], 2753 line: 1, 2754 column: 1 2755 }; 2756 2757 try { 2758 act(parserState, parseFileBody); 2759 } 2760 catch (err) { 2761 var errString = 'Invalid WebVTT file: ' + parserState.src + '\n'; 2762 errString += 'Line: ' + parserState.line + ', '; 2763 errString += 'Column: ' + parserState.column + '\n'; 2764 errString += err; 2765 if (console.warn) {
vendor: 8,711 bytes, lines 2766-3092
2766 console.warn(errString); 2767 } 2768 else if (console.log) { 2769 console.log(errString); 2770 } 2771 } 2772 return parserState; 2773 } 2774 2775 function actList(state, list) { 2776 var results = []; 2777 for (var ii = 0; ii < list.length; ii++) { 2778 results.push(act(state, list[ii])); 2779 } 2780 return results; 2781 } 2782 2783 // Applies the action and checks for errors. 2784 function act(state, action) { 2785 var val = action(state); 2786 if (state.error !== null) { 2787 throw state.error; 2788 } 2789 return val; 2790 } 2791 2792 function updatePosition(state, cutText) { 2793 for (var ii = 0; ii < cutText.length; ii++) { 2794 if (cutText[ii] === '\n') { 2795 state.column = 1; 2796 state.line += 1; 2797 } 2798 else { 2799 state.column += 1; 2800 } 2801 } 2802 } 2803 2804 function cut(state, length) { 2805 var returnText = state.text.substring(0, length); 2806 updatePosition(state, returnText); 2807 state.text = state.text.substring(length); 2808 return returnText; 2809 } 2810 2811 function cutLine(state, length) { 2812 var nextEOL = state.text.indexOf('\n'); 2813 var returnText; 2814 if (nextEOL === -1) { 2815 returnText = state.text; 2816 updatePosition(state, returnText); 2817 state.text = ''; 2818 } 2819 else { 2820 returnText = state.text.substring(0, nextEOL); 2821 updatePosition(state, returnText + '\n'); 2822 state.text = state.text.substring(nextEOL + 1); 2823 } 2824 return returnText; 2825 } 2826 2827 function peekLine(state) { 2828 var nextEOL = state.text.indexOf('\n'); 2829 if (nextEOL === -1) { 2830 return state.text; 2831 } 2832 else { 2833 return state.text.substring(0, nextEOL); 2834 } 2835 } 2836 2837 function parseFileBody(state) { 2838 actList(state, [ 2839 eatOptionalBOM, 2840 eatSignature]); 2841 var c = state.text[0]; 2842 if (c === ' ' || c === '\t' || c === '\n') { 2843 actList(state, [ 2844 eatUntilEOLInclusive, 2845 parseMetadataHeaders, 2846 eatAtLeast1EmptyLines, 2847 parseCuesAndComments]); 2848 } 2849 else { 2850 state.error = "WEBVTT signature not followed by whitespace."; 2851 } 2852 } 2853 2854 // Parses all metadata headers until a cue is discovered. 2855 function parseMetadataHeaders(state) { 2856 while (true) { 2857 var nextLine = peekLine(state); 2858 if (nextLine.indexOf('-->') !== -1) { 2859 return; 2860 } 2861 else if (nextLine.length === 0) { 2862 return; 2863 } 2864 else { 2865 var keyValue = act(state, getMetadataKeyValue); 2866 state.metadata[keyValue[0]] = keyValue[1]; 2867 act(state, eatUntilEOLInclusive); 2868 } 2869 } 2870 } 2871 2872 function nextSpaceOrNewline(s) { 2873 var possible = []; 2874 var spaceIndex = s.indexOf(' '); 2875 if (spaceIndex >= 0) { 2876 possible.push(spaceIndex); 2877 } 2878 var tabIndex = s.indexOf('\t'); 2879 if (tabIndex >= 0) { 2880 possible.push(tabIndex); 2881 } 2882 var lineIndex = s.indexOf('\n'); 2883 if (lineIndex >= 0) { 2884 possible.push(lineIndex); 2885 } 2886 2887 return Math.min.apply(null, possible); 2888 } 2889 2890 function getMetadataKeyValue(state) { 2891 var next = state.text.indexOf('\n'); 2892 var pair = cut(state, next); 2893 var colon = pair.indexOf(':'); 2894 if (colon === -1) { 2895 state.error = 'Missing colon.'; 2896 return; 2897 } 2898 else { 2899 var pairName = pair.substring(0, colon); 2900 var pairValue = pair.substring(colon + 1); 2901 return [pairName, pairValue]; 2902 } 2903 } 2904 2905 function getSettingsKeyValue(state) { 2906 var next = nextSpaceOrNewline(state.text); 2907 var pair = cut(state, next); 2908 var colon = pair.indexOf(':'); 2909 if (colon === -1) { 2910 state.error = 'Missing colon.'; 2911 return; 2912 } 2913 else { 2914 var pairName = pair.substring(0, colon); 2915 var pairValue = pair.substring(colon + 1); 2916 return [pairName, pairValue]; 2917 } 2918 } 2919 2920 function parseCuesAndComments(state) { 2921 while (true) { 2922 var nextLine = peekLine(state); 2923 // If NOTE is not on a line all its own, it must be followed by a space or tab. 2924 if (nextLine.indexOf('NOTE') === 0 && ((nextLine.length === 4) || (nextLine[4] === ' ') || (nextLine[4] === '\t'))) { 2925 actList(state, [eatComment, eatEmptyLines]); 2926 } 2927 else if ($.trim(nextLine).length === 0 && state.text.length > 0) { 2928 act(state, eatEmptyLines); 2929 } 2930 else if ($.trim(nextLine).length > 0) { 2931 act(state, parseCue); 2932 } 2933 else { 2934 // Everythings parsed! 2935 return; 2936 } 2937 } 2938 } 2939 2940 function parseCue(state) { 2941 2942 var nextLine = peekLine(state); 2943 var cueId; 2944 var errString; 2945 2946 if(nextLine.indexOf('-->') === -1) { 2947 cueId = cutLine(state); 2948 nextLine = peekLine(state); 2949 if(nextLine.indexOf('-->') === -1) { 2950 errString = 'Invalid WebVTT file: ' + state.src + '\n'; 2951 errString += 'Line: ' + state.line + ', '; 2952 errString += 'Column: ' + state.column + '\n'; 2953 errString += 'Expected cue timing for cueId \''+cueId+'\' but found: ' + nextLine + '\n'; 2954 if (console.warn) { 2955 console.warn(errString); 2956 } 2957 else if (console.log) { 2958 console.log(errString); 2959 } 2960 return; // Return leaving line for parseCuesAndComments to handle 2961 } 2962 } 2963 2964 var cueTimings = actList(state, [getTiming, 2965 eatAtLeast1SpacesOrTabs, 2966 eatArrow, 2967 eatAtLeast1SpacesOrTabs, 2968 getTiming]); 2969 2970 var startTime = cueTimings[0]; 2971 var endTime = cueTimings[4]; 2972 if (startTime >= endTime) { 2973 state.error = 'Start time is not sooner than end time.'; 2974 return; 2975 } 2976 2977 act(state, eatSpacesOrTabs); 2978 var cueSettings = act(state, getCueSettings); 2979 // Cut the newline. 2980 cut(state, 1); 2981 var components = act(state, getCuePayload); 2982 2983 if (typeof cueId === 'undefined') { 2984 cueId = state.cues.length + 1; 2985 } 2986 state.cues.push({ 2987 id: cueId, 2988 start: startTime, 2989 end: endTime, 2990 settings: cueSettings, 2991 components: components 2992 }); 2993 } 2994 2995 function getCueSettings(state) { 2996 var cueSettings = {}; 2997 while (state.text.length > 0 && state.text[0] !== '\n') { 2998 var keyValue = act(state, getSettingsKeyValue); 2999 cueSettings[keyValue[0]] = keyValue[1]; 3000 act(state, eatSpacesOrTabs); 3001 } 3002 return cueSettings; 3003 } 3004 3005 function getCuePayload(state) { 3006 // Parser based on instructions in draft. 3007 var result = {type: 'internal', tagName: '', value: '', classes: [], annotation: '', parent: null, children: [], language: ''}; 3008 var current = result; 3009 var languageStack = []; 3010 while (state.text.length > 0) { 3011 var nextLine = peekLine(state); 3012 if (nextLine.indexOf('-->') !== -1 || /^\s+$/.test(nextLine)) { 3013 break; // Handle empty cues 3014 } 3015 // Have to separately detect double-lines ending cue due to our non-standard parsing. 3016 // TODO: Redo outer algorithm to conform to W3 spec? 3017 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 3018 cut(state, 2); 3019 break; 3020 } 3021 3022 var token = getCueToken(state); 3023 // We'll use the tokens themselves as objects where possible. 3024 if (token.type === 'string') { 3025 current.children.push(token); 3026 } 3027 else if (token.type === 'startTag') { 3028 token.type = token.tagName; 3029 // Define token.parent; added by Terrill to fix bug end 'endTag' loop 3030 token.parent = current; 3031 if ($.inArray(token.tagName, ['i', 'b', 'u', 'ruby']) !== -1) { 3032 if (languageStack.length > 0) { 3033 current.language = languageStack[languageStack.length - 1]; 3034 } 3035 current.children.push(token); 3036 current = token; 3037 } 3038 else if (token.tagName === 'rt' && current.tagName === 'ruby') { 3039 if (languageStack.length > 0) { 3040 current.language = languageStack[languageStack.length - 1]; 3041 } 3042 current.children.push(token); 3043 current = token; 3044 } 3045 else if (token.tagName === 'c') { 3046 token.value = token.annotation; 3047 if (languageStack.length > 0) { 3048 current.language = languageStack[languageStack.length - 1]; 3049 } 3050 current.children.push(token); 3051 current = token; 3052 } 3053 else if (token.tagName === 'v') { 3054 token.value = token.annotation; 3055 if (languageStack.length > 0) { 3056 current.language = languageStack[languageStack.length - 1]; 3057 } 3058 current.children.push(token); 3059 current = token; 3060 } 3061 else if (token.tagName === 'lang') { 3062 languageStack.push(token.annotation); 3063 if (languageStack.length > 0) { 3064 current.language = languageStack[languageStack.length - 1]; 3065 } 3066 current.children.push(token); 3067 current = token; 3068 } 3069 } 3070 else if (token.type === 'endTag') { 3071 if (token.tagName === current.type && $.inArray(token.tagName, ['c', 'i', 'b', 'u', 'ruby', 'rt', 'v']) !== -1) { 3072 // NOTE from Terrill: This was resulting in an error because current.parent was undefined 3073 // Fixed (I think) by assigning current token to token.parent in 'startTag' loop 3074 current = current.parent; 3075 } 3076 else if (token.tagName === 'lang' && current.type === 'lang') { 3077 current = current.parent; 3078 languageStack.pop(); 3079 } 3080 else if (token.tagName === 'ruby' && current.type === 'rt') { 3081 current = current.parent.parent; 3082 } 3083 } 3084 else if (token.type === 'timestampTag') { 3085 var tempState = { 3086 text: token.value, 3087 error: null, 3088 metadata: {}, 3089 cues: [], 3090 line: 1, 3091 column: 1 3092 };
vendor: 7,277 bytes, lines 3093-3424
3093 try { 3094 var timing = act(tempState, getTiming); 3095 if (tempState.text.length === 0) { 3096 token.value = timing; 3097 current.push(token); 3098 } 3099 } 3100 catch (err) { 3101 } 3102 } 3103 } 3104 return result; 3105 } 3106 3107 // Gets a single cue token; uses the method in the w3 specification. 3108 function getCueToken(state) { 3109 var tokenState = 'data'; 3110 var result = []; 3111 var buffer = ''; 3112 var token = {type: '', tagName: '', value: '', classes: [], annotation: '', children: []} 3113 3114 while (true) { 3115 var c; 3116 // Double newlines indicate end of token. 3117 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 3118 c = '\u0004'; 3119 } 3120 else if (state.text.length > 0) { 3121 c = state.text[0]; 3122 } 3123 else { 3124 // End of file. 3125 c = '\u0004'; 3126 } 3127 if (tokenState === 'data') { 3128 if (c === '&') { 3129 buffer = '&'; 3130 tokenState = 'escape'; 3131 } 3132 else if (c === '<') { 3133 if (result.length === 0) { 3134 tokenState = 'tag'; 3135 } 3136 else { 3137 token.type = 'string'; 3138 token.value = result.join(''); 3139 return token; 3140 } 3141 } 3142 else if (c === '\u0004') { 3143 return {type: 'string', value: result.join('')}; 3144 } 3145 else { 3146 result.push(c); 3147 } 3148 } 3149 else if (tokenState === 'escape') { 3150 if (c === '&') { 3151 result.push(buffer); 3152 buffer = '&'; 3153 } 3154 else if (c.match(/[0-9a-z]/)) { 3155 buffer += c; 3156 } 3157 else if (c === ';') { 3158 if (buffer === '&') { 3159 result.push('&'); 3160 } 3161 else if (buffer === '<') { 3162 result.push('<'); 3163 } 3164 else if (buffer === '>') { 3165 result.push('>'); 3166 } 3167 else if (buffer === '&lrm') { 3168 result.push('\u200e'); 3169 } 3170 else if (buffer === '&rlm') { 3171 result.push('\u200f'); 3172 } 3173 else if (buffer === ' ') { 3174 result.push('\u00a0'); 3175 } 3176 else { 3177 result.push(buffer); 3178 result.push(';'); 3179 } 3180 tokenState = 'data'; 3181 } 3182 else if (c === '<' || c === '\u0004') { 3183 result.push(buffer); 3184 token.type = 'string'; 3185 token.value = result.join(''); 3186 return token; 3187 } 3188 else if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { // Handle unescaped & chars as strings 3189 result.push(buffer); 3190 token.type = 'string'; 3191 token.value = result.join(''); 3192 return token; 3193 } 3194 else { 3195 result.push(buffer); 3196 tokenState = 'data'; 3197 } 3198 } 3199 else if (tokenState === 'tag') { 3200 if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { 3201 tokenState = 'startTagAnnotation'; 3202 } 3203 else if (c === '.') { 3204 tokenState = 'startTagClass'; 3205 } 3206 else if (c === '/') { 3207 tokenState = 'endTag'; 3208 } 3209 else if (c.match('[0-9]')) { 3210 tokenState = 'timestampTag'; 3211 result.push(c); 3212 } 3213 else if (c === '>') { 3214 cut(state, 1); 3215 break; 3216 } 3217 else if (c === '\u0004') { 3218 token.tagName = ''; 3219 token.type = 'startTag'; 3220 return token; 3221 } 3222 else { 3223 result.push(c); 3224 tokenState = 'startTag'; 3225 } 3226 } 3227 else if (tokenState === 'startTag') { 3228 if (c === '\t' || c === '\u000c' || c === ' ') { 3229 tokenState = 'startTagAnnotation'; 3230 } 3231 else if (c === '\n') { 3232 buffer = c; 3233 tokenState = 'startTagAnnotation'; 3234 } 3235 else if (c === '.') { 3236 tokenState = 'startTagClass'; 3237 } 3238 else if (c === '>') { 3239 cut(state, 1); 3240 token.tagName = result.join(''); 3241 token.type = 'startTag'; 3242 return token; 3243 } 3244 else if (c === '\u0004') { 3245 token.tagName = result.join(''); 3246 token.type = 'startTag'; 3247 return token; 3248 } 3249 else { 3250 result.push(c); 3251 } 3252 } 3253 else if (tokenState === 'startTagClass') { 3254 if (c === '\t' || c === '\u000c' || c === ' ') { 3255 token.classes.push(buffer); 3256 buffer = ''; 3257 tokenState = 'startTagAnnotation'; 3258 } 3259 else if (c === '\n') { 3260 token.classes.push(buffer); 3261 buffer = c; 3262 tokenState = 'startTagAnnotation'; 3263 } 3264 else if (c === '.') { 3265 token.classes.push(buffer); 3266 buffer = ""; 3267 } 3268 else if (c === '>') { 3269 cut(state, 1); 3270 token.classes.push(buffer); 3271 token.type = 'startTag'; 3272 token.tagName = result.join(''); 3273 return token; 3274 } 3275 else if (c === '\u0004') { 3276 token.classes.push(buffer); 3277 token.type = 'startTag'; 3278 token.tagName = result.join(''); 3279 return token; 3280 } 3281 else { 3282 buffer += 'c'; 3283 } 3284 } 3285 else if (tokenState === 'startTagAnnotation') { 3286 if (c === '>') { 3287 cut(state, 1); 3288 buffer = $.trim(buffer).replace(/ +/, ' '); 3289 token.type = 'startTag'; 3290 token.tagName = result.join(''); 3291 token.annotation = buffer; 3292 return token; 3293 } 3294 else if (c === '\u0004') { 3295 buffer = $.trim(buffer).replace(/ +/, ' '); 3296 token.type = 'startTag'; 3297 token.tagName = result.join(''); 3298 token.annotation = buffer; 3299 return token; 3300 } 3301 else { 3302 buffer += c; 3303 } 3304 } 3305 else if (tokenState === 'endTag') { 3306 if (c === '>') { 3307 cut(state, 1); 3308 token.type = 'endTag'; 3309 token.tagName = result.join(''); 3310 return token; 3311 } 3312 else if (c === '\u0004') { 3313 token.type = 'endTag'; 3314 token.tagName = result.join(''); 3315 return token; 3316 } 3317 else { 3318 result.push(c); 3319 } 3320 } 3321 else if (tokenState === 'timestampTag') { 3322 if (c === '>') { 3323 cut(state, 1); 3324 token.type = 'timestampTag'; 3325 token.name = result.join(''); 3326 return token; 3327 } 3328 else if (c === '\u0004') { 3329 token.type = 'timestampTag'; 3330 token.name = result.join(''); 3331 return token; 3332 } 3333 else { 3334 result.push(c); 3335 } 3336 } 3337 else { 3338 throw 'Unknown tokenState ' + tokenState; 3339 } 3340 3341 cut(state, 1); 3342 } 3343 } 3344 3345 function eatComment(state) { 3346 // Cut the NOTE line. 3347 var noteLine = cutLine(state); 3348 if (noteLine.indexOf('-->') !== -1) { 3349 state.error = 'Invalid syntax: --> in NOTE line.'; 3350 return; 3351 } 3352 while (true) { 3353 var nextLine = peekLine(state); 3354 if ($.trim(nextLine).length === 0) { 3355 // End of comment. 3356 return; 3357 } 3358 else if (nextLine.indexOf('-->') !== -1) { 3359 state.error = 'Invalid syntax: --> in comment.'; 3360 return; 3361 } 3362 else { 3363 cutLine(state); 3364 } 3365 } 3366 } 3367 3368 // Initial byte order mark. 3369 function eatOptionalBOM(state) { 3370 if (state.text[0] === '\ufeff') { 3371 cut(state, 1); 3372 } 3373 3374 } 3375 3376 // "WEBVTT" string. 3377 function eatSignature(state) { 3378 if (state.text.substring(0,6) === 'WEBVTT') { 3379 cut(state, 6); 3380 } 3381 else { 3382 state.error = 'Invalid signature.'; 3383 } 3384 } 3385 3386 function eatArrow(state) { 3387 if (state.text.length < 3 || state.text.substring(0,3) !== '-->') { 3388 state.error = 'Missing -->'; 3389 } 3390 else { 3391 cut(state, 3); 3392 } 3393 } 3394 3395 function eatSingleSpaceOrTab(state) { 3396 if (state.text[0] === '\t' || state.text[0] === ' ') { 3397 cut(state, 1); 3398 } 3399 else { 3400 state.error = 'Missing space.'; 3401 } 3402 } 3403 3404 function eatSpacesOrTabs(state) { 3405 while (state.text[0] === '\t' || state.text[0] === ' ') { 3406 cut(state, 1); 3407 } 3408 } 3409 3410 function eatAtLeast1SpacesOrTabs(state) { 3411 var numEaten = 0; 3412 while (state.text[0] === '\t' || state.text[0] === ' ') { 3413 cut(state, 1); 3414 numEaten += 1; 3415 } 3416 if (numEaten === 0) { 3417 state.error = 'Missing space.'; 3418 } 3419 } 3420 3421 function eatUntilEOLInclusive(state) { 3422 var nextEOL = state.text.indexOf('\n'); 3423 if (nextEOL === -1) { 3424 state.error = 'Missing EOL.';
3425 } 3426 else { 3427 cut(state, nextEOL + 1); 3428 } 3429 } 3430 3431 function eatEmptyLines(state) { 3432 while (state.text.length > 0) { 3433 var nextLine = peekLine(state); 3434 if ($.trim(nextLine).length === 0) { 3435 cutLine(state); 3436 } 3437 else { 3438 break; 3439 } 3440 } 3441 } 3442 3443 // Eats empty lines, but throws an error if there's not at least one. 3444 function eatAtLeast1EmptyLines(state) { 3445 var linesEaten = 0; 3446 while (state.text.length > 0) { 3447 var nextLine = peekLine(state); 3448 if ($.trim(nextLine).length === 0) { 3449 cutLine(state); 3450 linesEaten += 1; 3451 } 3452 else { 3453 break; 3454 } 3455 } 3456 if (linesEaten === 0) { 3457 state.error = 'Missing empty line.'; 3458 } 3459 } 3460 3461 function getTiming(state) { 3462 var nextSpace = nextSpaceOrNewline(state.text); 3463 if (nextSpace === -1) { 3464 state.error('Missing timing.'); 3465 return; 3466 } 3467 var timestamp = cut(state, nextSpace); 3468 3469 var results = /((\d\d):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp); 3470 3471 if (!results) { 3472 state.error = 'Unable to parse timestamp'; 3473 return; 3474 } 3475 var time = 0; 3476 var hours = results[2]; 3477 var minutes = results[4]; 3478 3479 if (minutes) { 3480 if (parseInt(minutes, 10) > 59) { 3481 state.error = 'Invalid minute range'; 3482 return; 3483 } 3484 if (hours) { 3485 time += 3600 * parseInt(hours, 10); 3486 } 3487 time += 60 * parseInt(minutes, 10); 3488 var seconds = results[5]; 3489 if (parseInt(seconds, 10) > 59) { 3490 state.error = 'Invalid second range'; 3491 return; 3492 } 3493 3494 time += parseInt(seconds, 10); 3495 time += parseInt(results[6], 10) / 1000; 3496 } 3497 else { 3498 time += parseInt(results[7], 10); 3499 time += parseInt(results[8], 10) / 1000; 3500 } 3501 3502 return time; 3503 } 3504})(jQuery); 3505 3506(function ($) { 3507 3508 AblePlayer.prototype.injectPlayerCode = function() { 3509 3510 // create and inject surrounding HTML structure 3511 // If IOS: 3512 // If video: 3513 // IOS does not support any of the player's functionality 3514 // - everything plays in its own player 3515 // Therefore, AblePlayer is not loaded & all functionality is disabled 3516 // (this all determined. If this is IOS && video, this function is never called) 3517 // If audio: 3518 // HTML cannot be injected as a *parent* of the <audio> element 3519 // It is therefore injected *after* the <audio> element 3520 // This is only a problem in IOS 6 and earlier, 3521 // & is a known bug, fixed in IOS 7 3522 3523 var thisObj, captionsContainer, prefsGroups, i; 3524 thisObj = this; 3525 3526 // create three wrappers and wrap them around the media element. 3527 // From inner to outer: 3528 // $mediaContainer - contains the original media element 3529 // $ableDiv - contains the media player and all its objects (e.g., captions, controls, descriptions) 3530 // $ableWrapper - contains additional widgets (e.g., transcript window, sign window) 3531 this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent(); 3532 this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent(); 3533 this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent(); 3534 this.$ableWrapper.addClass('able-skin-' + this.skin); 3535 3536 this.$ableWrapper.css({ 3537 'width': this.playerWidth + 'px' 3538 }); 3539 3540 if (this.mediaType === 'video') { 3541 // youtube adds its own big play button 3542 // don't show ours *unless* video has a poster attribute 3543 // (which obstructs the YouTube poster & big play button) 3544 if (this.iconType != 'image' && (this.player !== 'youtube' || this.hasPoster)) { 3545 this.injectBigPlayButton(); 3546 } 3547 } 3548 3549 // add container that captions or description will be appended to 3550 // Note: new Jquery object must be assigned _after_ wrap, hence the temp captionsContainer variable
3551 captionsContainer = $('<div>'); 3552 if (this.mediaType === 'video') { 3553 captionsContainer.addClass('able-vidcap-container'); 3554 } 3555 else if (this.mediaType === 'audio') { 3556 captionsContainer.addClass('able-audcap-container'); 3557 // hide this by default. It will be shown if captions are available 3558 captionsContainer.addClass('captions-off'); 3559 } 3560 3561 this.injectPlayerControlArea(); // this may need to be injected after captions??? 3562 this.$captionsContainer = this.$mediaContainer.wrap(captionsContainer).parent(); 3563 this.injectAlert(); 3564 this.injectPlaylist(); 3565 3566 // Do this last, as it should be prepended to the top of this.$ableDiv 3567 // after everything else has prepended 3568 this.injectOffscreenHeading(); 3569 }; 3570 3571 AblePlayer.prototype.injectOffscreenHeading = function () { 3572 3573 // Inject an offscreen heading to the media container. 3574 // If heading hasn't already been manually defined via data-heading-level, 3575 // automatically assign a level that is one level deeper than the closest parent heading 3576 // as determined by getNextHeadingLevel() 3577 var headingType; 3578 if (this.playerHeadingLevel == '0') { 3579 // do NOT inject a heading (at author's request) 3580 } 3581 else { 3582 if (typeof this.playerHeadingLevel === 'undefined') { 3583 this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6 3584 } 3585 headingType = 'h' + this.playerHeadingLevel.toString(); 3586 this.$headingDiv = $('<' + headingType + '>'); 3587 this.$ableDiv.prepend(this.$headingDiv); 3588 this.$headingDiv.addClass('able-offscreen'); 3589 this.$headingDiv.text(this.tt.playerHeading); 3590 } 3591 }; 3592 3593 AblePlayer.prototype.injectBigPlayButton = function () { 3594 3595 var thisObj, svgData, buttonIcon, svgPath; 3596 3597 thisObj = this; 3598 3599 this.$bigPlayButton = $('<button>', { 3600 'class': 'able-big-play-button', 3601 'aria-hidden': false, 3602 'aria-label': this.tt.play, 3603 'tabindex': 0 3604 }); 3605 3606 if (this.iconType == 'svg') { 3607 svgData = this.getSvgData('play'); 3608 buttonIcon = $('<svg>',{
3609 'focusable': 'false', 3610 'aria-hidden': 'true', 3611 'viewBox': svgData[0] 3612 }); 3613 svgPath = $('<path>',{ 3614 'd': svgData[1] 3615 }); 3616 buttonIcon.append(svgPath); 3617 this.$bigPlayButton.html(buttonIcon); 3618 3619 // Final step: Need to refresh the DOM in order for browser to process & display the SVG 3620 this.$bigPlayButton.html(this.$bigPlayButton.html()); 3621 } 3622 else { // use icon font 3623 this.$bigPlayIcon = $('<span>', { 3624 'class': 'icon-play', 3625 }); 3626 this.$bigPlayButton.append(this.$bigPlayIcon); 3627 } 3628 3629 this.$bigPlayButton.click(function (event) { 3630 event.preventDefault(); 3631 thisObj.handlePlay(); 3632 }); 3633 3634 this.$mediaContainer.append(this.$bigPlayButton); 3635 }; 3636 3637 AblePlayer.prototype.injectPlayerControlArea = function () { 3638 3639 this.$playerDiv = $('<div>', { 3640 'class' : 'able-player', 3641 'role' : 'region', 3642 'aria-label' : this.mediaType + ' player' 3643 }); 3644 this.$playerDiv.addClass('able-'+this.mediaType); 3645 if (this.hasPlaylist && this.showNowPlaying) { 3646 this.$nowPlayingDiv = $('<div>',{ 3647 'class' : 'able-now-playing', 3648 'aria-live' : 'assertive', 3649 'aria-atomic': 'true' 3650 }); 3651 } 3652 this.$controllerDiv = $('<div>',{ 3653 'class' : 'able-controller' 3654 }); 3655 this.$controllerDiv.addClass('able-' + this.iconColor + '-controls'); 3656 3657 this.$statusBarDiv = $('<div>',{ 3658 'class' : 'able-status-bar' 3659 }); 3660 this.$timer = $('<span>',{ 3661 'class' : 'able-timer' 3662 }); 3663 this.$elapsedTimeContainer = $('<span>',{ 3664 'class': 'able-elapsedTime', 3665 text: '0:00' 3666 }); 3667 this.$durationContainer = $('<span>',{ 3668 'class': 'able-duration' 3669 }); 3670 this.$timer.append(this.$elapsedTimeContainer).append(this.$durationContainer); 3671 3672 this.$speed = $('<span>',{ 3673 'class' : 'able-speed', 3674 'aria-live' : 'assertive' 3675 }).text(this.tt.speed + ': 1x'); 3676 3677 this.$status = $('<span>',{ 3678 'class' : 'able-status', 3679 'aria-live' : 'polite' 3680 }); 3681 3682 // Put everything together. 3683 this.$statusBarDiv.append(this.$timer, this.$speed, this.$status); 3684 if (this.showNowPlaying) { 3685 this.$playerDiv.append(this.$nowPlayingDiv, this.$controllerDiv, this.$statusBarDiv); 3686 } 3687 else { 3688 this.$playerDiv.append(this.$controllerDiv, this.$statusBarDiv); 3689 } 3690 if (this.mediaType === 'video') { 3691 // the player controls go after the media & captions 3692 this.$ableDiv.append(this.$playerDiv); 3693 } 3694 else { 3695 // the player controls go before the media & captions 3696 this.$ableDiv.prepend(this.$playerDiv); 3697 } 3698 }; 3699 3700 AblePlayer.prototype.injectTextDescriptionArea = function () { 3701 3702 // create a div for writing description text 3703 this.$descDiv = $('<div>',{ 3704 'class': 'able-descriptions' 3705 }); 3706 // Add ARIA so description will be announced by screen readers 3707 // Later (in description.js > showDescription()), 3708 // if browser supports Web Speech API and this.descMethod === 'browser' 3709 // these attributes will be removed 3710 this.$descDiv.attr({ 3711 'aria-live': 'assertive', 3712 'aria-atomic': 'true' 3713 }); 3714 // Start off with description hidden. 3715 // It will be exposed conditionally within description.js > initDescription() 3716 this.$descDiv.hide(); 3717 this.$ableDiv.append(this.$descDiv); 3718 }; 3719 3720 AblePlayer.prototype.getDefaultWidth = function(which) { 3721 3722 // return default width of resizable elements 3723 // these values are somewhat arbitrary, but seem to result in good usability 3724 // if users disagree, they can resize (and resposition) them 3725 if (which === 'transcript') { 3726 return 450; 3727 } 3728 else if (which === 'sign') { 3729 return 400; 3730 } 3731 }; 3732 3733 AblePlayer.prototype.positionDraggableWindow = function (which, width) { 3734 3735 // which is either 'transcript' or 'sign' 3736 3737 var cookie, cookiePos, $window, dragged, windowPos, currentWindowPos, firstTime, zIndex; 3738 3739 cookie = this.getCookie(); 3740 if (which === 'transcript') { 3741 $window = this.$transcriptArea; 3742 if (typeof cookie.transcript !== 'undefined') { 3743 cookiePos = cookie.transcript; 3744 } 3745 } 3746 else if (which === 'sign') { 3747 $window = this.$signWindow; 3748 if (typeof cookie.transcript !== 'undefined') { 3749 cookiePos = cookie.sign; 3750 } 3751 } 3752 if (typeof cookiePos !== 'undefined' && !($.isEmptyObject(cookiePos))) { 3753 // position window using stored values from cookie 3754 $window.css({ 3755 'position': cookiePos['position'], 3756 'width': cookiePos['width'], 3757 'z-index': cookiePos['zindex'] 3758 }); 3759 if (cookiePos['position'] === 'absolute') { 3760 $window.css({ 3761 'top': cookiePos['top'], 3762 'left': cookiePos['left'] 3763 }); 3764 } 3765 // since cookie is not page-specific, z-index needs may vary across different pages 3766 this.updateZIndex(which); 3767 } 3768 else { 3769 // position window using default values 3770 windowPos = this.getOptimumPosition(which, width); 3771 if (typeof width === 'undefined') { 3772 width = this.getDefaultWidth(which); 3773 } 3774 $window.css({ 3775 'position': windowPos[0], 3776 'width': width, 3777 'z-index': windowPos[3] 3778 }); 3779 if (windowPos[0] === 'absolute') { 3780 $window.css({ 3781 'top': windowPos[1] + 'px', 3782 'left': windowPos[2] + 'px', 3783 }); 3784 } 3785 } 3786 }; 3787 3788 AblePlayer.prototype.getOptimumPosition = function (targetWindow, targetWidth) { 3789 3790 // returns optimum position for targetWindow, as an array with the following structure: 3791 // 0 - CSS position ('absolute' or 'relative') 3792 // 1 - top 3793 // 2 - left 3794 // 3 - zindex (if not default) 3795 // targetWindow is either 'transcript' or 'sign' 3796 // if there is room to the right of the player, position element there 3797 // else if there is room the left of the player, position element there 3798 // else position element beneath player 3799 3800 var gap, position, ableWidth, ableHeight, ableOffset, ableTop, ableLeft, 3801 windowWidth, otherWindowWidth, zIndex; 3802 3803 if (typeof targetWidth === 'undefined') { 3804 targetWidth = this.getDefaultWidth(targetWindow); 3805 } 3806 3807 gap = 5; // number of pixels to preserve between Able Player objects 3808 3809 position = []; // position, top, left 3810 3811 ableWidth = this.$ableDiv.width(); 3812 ableHeight = this.$ableDiv.height(); 3813 ableOffset = this.$ableDiv.offset(); 3814 ableTop = ableOffset.top; 3815 ableLeft = ableOffset.left; 3816 windowWidth = $(window).width(); 3817 otherWindowWidth = 0; // width of other visiable draggable windows will be added to this 3818 3819 if (targetWindow === 'transcript') { 3820 if (typeof this.$signWindow !== 'undefined') { 3821 if (this.$signWindow.is(':visible')) { 3822 otherWindowWidth = this.$signWindow.width() + gap; 3823 } 3824 } 3825 } 3826 else if (targetWindow === 'sign') { 3827 if (typeof this.$transcriptArea !== 'undefined') { 3828 if (this.$transcriptArea.is(':visible')) { 3829 otherWindowWidth = this.$transcriptArea.width() + gap; 3830 } 3831 } 3832 } 3833 if (targetWidth < (windowWidth - (ableLeft + ableWidth + gap + otherWindowWidth))) { 3834 // there's room to the left of $ableDiv 3835 position[0] = 'absolute'; 3836 position[1] = 0; 3837 position[2] = ableWidth + otherWindowWidth + gap; 3838 } 3839 else if (targetWidth + gap < ableLeft) { 3840 // there's room to the right of $ableDiv 3841 position[0] = 'absolute'; 3842 position[1] = 0; 3843 position[2] = ableLeft - targetWidth - gap; 3844 } 3845 else { 3846 // position element below $ableDiv 3847 position[0] = 'relative'; 3848 // no need to define top, left, or z-index 3849 } 3850 return position; 3851 }; 3852 3853 AblePlayer.prototype.injectAlert = function () { 3854 3855 // inject two alerts, one visible for all users and one for screen reader users only 3856 3857 var top; 3858 3859 this.$alertBox = $('<div role="alert"></div>'); 3860 this.$alertBox.addClass('able-alert'); 3861 this.$alertBox.hide(); 3862 this.$alertBox.appendTo(this.$ableDiv); 3863 if (this.mediaType == 'audio') { 3864 top = '-10'; 3865 } 3866 else { 3867 // position just below top of video by default 3868 // but this will change after video player is fully sized 3869 // see control.js > resizePlayer() 3870 top = '10'; 3871 } 3872 this.$alertBox.css({ 3873 top: top + 'px' 3874 }); 3875 3876 this.$srAlertBox = $('<div role="alert"></div>'); 3877 this.$srAlertBox.addClass('able-screenreader-alert'); 3878 this.$srAlertBox.appendTo(this.$ableDiv); 3879 }; 3880 3881 AblePlayer.prototype.injectPlaylist = function () { 3882 3883 if (this.playlistEmbed === true) { 3884 // move playlist into player, immediately before statusBarDiv 3885 var playlistClone = this.$playlistDom.clone(); 3886 playlistClone.insertBefore(this.$statusBarDiv); 3887 // Update to the new playlist copy. 3888 this.$playlist = playlistClone.find('li'); 3889 } 3890 }; 3891 3892 AblePlayer.prototype.createPopup = function (which, tracks) { 3893 3894 // Create popup menu and append to player 3895 // 'which' parameter is either 'captions', 'chapters', 'prefs', 'transcript-window' or 'sign-window' 3896 // 'tracks', if provided, is a list of tracks to be used as menu items 3897 3898 var thisObj, $menu, includeMenuItem, prefCats, i, $menuItem, prefCat, whichPref, 3899 hasDefault, track, windowOptions, whichPref, whichMenu, 3900 $thisItem, $prevItem, $nextItem; 3901 3902 thisObj = this; 3903 3904 $menu = $('<ul>',{ 3905 'id': this.mediaId + '-' + which + '-menu', 3906 'class': 'able-popup', 3907 'role': 'menu' 3908 }).hide(); 3909 3910 if (which === 'captions') { 3911 $menu.addClass('able-popup-captions'); 3912 } 3913 3914 // Populate menu with menu items 3915 if (which === 'prefs') { 3916 if (this.prefCats.length > 1) { 3917 for (i = 0; i < this.prefCats.length; i++) { 3918 $menuItem = $('<li></li>',{ 3919 'role': 'menuitem', 3920 'tabindex': '-1' 3921 }); 3922 prefCat = this.prefCats[i]; 3923 if (prefCat === 'captions') { 3924 $menuItem.text(this.tt.prefMenuCaptions); 3925 } 3926 else if (prefCat === 'descriptions') { 3927 $menuItem.text(this.tt.prefMenuDescriptions); 3928 } 3929 else if (prefCat === 'keyboard') { 3930 $menuItem.text(this.tt.prefMenuKeyboard); 3931 } 3932 else if (prefCat === 'transcript') { 3933 $menuItem.text(this.tt.prefMenuTranscript); 3934 } 3935 $menuItem.on('click',function() { 3936 whichPref = $(this).text(); 3937 thisObj.showingPrefsDialog = true; 3938 thisObj.setFullscreen(false); 3939 if (whichPref === thisObj.tt.prefMenuCaptions) { 3940 thisObj.captionPrefsDialog.show(); 3941 } 3942 else if (whichPref === thisObj.tt.prefMenuDescriptions) { 3943 thisObj.descPrefsDialog.show(); 3944 } 3945 else if (whichPref === thisObj.tt.prefMenuKeyboard) { 3946 thisObj.keyboardPrefsDialog.show(); 3947 } 3948 else if (whichPref === thisObj.tt.prefMenuTranscript) { 3949 thisObj.transcriptPrefsDialog.show(); 3950 } 3951 thisObj.closePopups(); 3952 thisObj.showingPrefsDialog = false;
vendor: 14,879 bytes, lines 3953-4417
3953 }); 3954 $menu.append($menuItem); 3955 } 3956 this.$prefsButton.attr('data-prefs-popup','menu'); 3957 } 3958 else if (this.prefCats.length == 1) { 3959 // only 1 category, so don't create a popup menu. 3960 // Instead, open dialog directly when user clicks Prefs button 3961 this.$prefsButton.attr('data-prefs-popup',this.prefCats[0]); 3962 } 3963 } 3964 else if (which === 'captions' || which === 'chapters') { 3965 hasDefault = false; 3966 for (i = 0; i < tracks.length; i++) { 3967 track = tracks[i]; 3968 if (which === 'captions' && this.player === 'html5' && typeof track.cues === 'undefined') { 3969 includeMenuItem = false; 3970 } 3971 else { 3972 includeMenuItem = true; 3973 } 3974 if (includeMenuItem) { 3975 $menuItem = $('<li></li>',{ 3976 'role': 'menuitemradio', 3977 'tabindex': '-1', 3978 'lang': track.language 3979 }); 3980 if (track.def && this.prefCaptions == 1) { 3981 $menuItem.attr('aria-checked','true'); 3982 hasDefault = true; 3983 } 3984 else { 3985 $menuItem.attr('aria-checked','false'); 3986 } 3987 // Get a label using track data 3988 if (which == 'captions') { 3989 $menuItem.text(track.label); 3990 $menuItem.on('click',this.getCaptionClickFunction(track)); 3991 } 3992 else if (which == 'chapters') { 3993 $menuItem.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start)); 3994 $menuItem.on('click',this.getChapterClickFunction(track.start)); 3995 } 3996 $menu.append($menuItem); 3997 } 3998 } 3999 if (which === 'captions') { 4000 // add a 'captions off' menu item 4001 $menuItem = $('<li></li>',{ 4002 'role': 'menuitemradio', 4003 'tabindex': '-1', 4004 }).text(this.tt.captionsOff); 4005 if (this.prefCaptions === 0) { 4006 $menuItem.attr('aria-checked','true'); 4007 hasDefault = true; 4008 } 4009 else { 4010 $menuItem.attr('aria-checked','false'); 4011 } 4012 $menuItem.on('click',this.getCaptionOffFunction()); 4013 $menu.append($menuItem); 4014 } 4015 } 4016 else if (which === 'transcript-window' || which === 'sign-window') { 4017 windowOptions = []; 4018 windowOptions.push({ 4019 'name': 'move', 4020 'label': this.tt.windowMove 4021 }); 4022 windowOptions.push({ 4023 'name': 'resize', 4024 'label': this.tt.windowResize 4025 }); 4026 windowOptions.push({ 4027 'name': 'close', 4028 'label': this.tt.windowClose 4029 }); 4030 for (i = 0; i < windowOptions.length; i++) { 4031 $menuItem = $('<li></li>',{ 4032 'role': 'menuitem', 4033 'tabindex': '-1', 4034 'data-choice': windowOptions[i].name 4035 }); 4036 $menuItem.text(windowOptions[i].label); 4037 $menuItem.on('click mousedown',function(e) { 4038 e.stopPropagation(); 4039 if (typeof e.button !== 'undefined' && e.button !== 0) { 4040 // this was a mouse click (if click is triggered by keyboard, e.button is undefined) 4041 // and the button was not a left click (left click = 0) 4042 // therefore, ignore this click 4043 return false; 4044 } 4045 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 4046 thisObj.windowMenuClickRegistered = true; 4047 thisObj.handleMenuChoice(which.substring(0, which.indexOf('-')), $(this).attr('data-choice'), e); 4048 } 4049 }); 4050 $menu.append($menuItem); 4051 } 4052 } 4053 // assign default item, if there isn't one already 4054 if (which === 'captions' && !hasDefault) { 4055 // check the menu item associated with the default language 4056 // as determined in control.js > syncTrackLanguages() 4057 if ($menu.find('li[lang=' + this.captionLang + ']')) { 4058 // a track exists for the default language. Check that item in the menu 4059 $menu.find('li[lang=' + this.captionLang + ']').attr('aria-checked','true'); 4060 } 4061 else { 4062 // check the last item (captions off) 4063 $menu.find('li').last().attr('aria-checked','true'); 4064 } 4065 } 4066 else if (which === 'chapters') { 4067 if ($menu.find('li:contains("' + this.defaultChapter + '")')) { 4068 $menu.find('li:contains("' + this.defaultChapter + '")').attr('aria-checked','true').addClass('able-focus'); 4069 } 4070 else { 4071 $menu.find('li').first().attr('aria-checked','true').addClass('able-focus'); 4072 } 4073 } 4074 // add keyboard handlers for navigating within popups 4075 $menu.on('keydown',function (e) { 4076 4077 whichMenu = $(this).attr('id').split('-')[1]; 4078 $thisItem = $(this).find('li:focus'); 4079 if ($thisItem.is(':first-child')) { 4080 // this is the first item in the menu 4081 $prevItem = $(this).find('li').last(); // wrap to bottom 4082 $nextItem = $thisItem.next(); 4083 } 4084 else if ($thisItem.is(':last-child')) { 4085 // this is the last Item 4086 $prevItem = $thisItem.prev(); 4087 $nextItem = $(this).find('li').first(); // wrap to top 4088 } 4089 else { 4090 $prevItem = $thisItem.prev(); 4091 $nextItem = $thisItem.next(); 4092 } 4093 if (e.which === 9) { // Tab 4094 if (e.shiftKey) { 4095 $thisItem.removeClass('able-focus'); 4096 $prevItem.focus().addClass('able-focus'); 4097 } 4098 else { 4099 $thisItem.removeClass('able-focus'); 4100 $nextItem.focus().addClass('able-focus'); 4101 } 4102 } 4103 else if (e.which === 40 || e.which === 39) { // down or right arrow 4104 $thisItem.removeClass('able-focus'); 4105 $nextItem.focus().addClass('able-focus'); 4106 } 4107 else if (e.which == 38 || e.which === 37) { // up or left arrow 4108 $thisItem.removeClass('able-focus'); 4109 $prevItem.focus().addClass('able-focus'); 4110 } 4111 else if (e.which === 32 || e.which === 13) { // space or enter 4112 $thisItem.click(); 4113 } 4114 else if (e.which === 27) { // Escape 4115 $thisItem.removeClass('able-focus'); 4116 thisObj.closePopups(); 4117 e.stopPropagation; 4118 } 4119 e.preventDefault(); 4120 }); 4121 this.$controllerDiv.append($menu); 4122 return $menu; 4123 }; 4124 4125 AblePlayer.prototype.closePopups = function () { 4126 4127 var thisObj = this; 4128 4129 if (this.chaptersPopup && this.chaptersPopup.is(':visible')) { 4130 this.chaptersPopup.hide(); 4131 this.$chaptersButton.attr('aria-expanded','false').focus(); 4132 } 4133 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 4134 this.captionsPopup.hide(); 4135 this.$ccButton.attr('aria-expanded', 'false'); 4136 this.waitThenFocus(this.$ccButton); 4137 } 4138 if (this.prefsPopup && this.prefsPopup.is(':visible') && !this.hidingPopup) { 4139 this.hidingPopup = true; // stopgap to prevent popup from re-opening again on keypress 4140 this.prefsPopup.hide(); 4141 // restore menu items to their original state 4142 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 4143 this.$prefsButton.attr('aria-expanded', 'false'); 4144 if (!this.showingPrefsDialog) { 4145 this.waitThenFocus(thisObj.$prefsButton); 4146 } 4147 // wait briefly, then reset hidingPopup 4148 setTimeout(function() { 4149 thisObj.hidingPopup = false; 4150 },100); 4151 } 4152 if (this.$volumeSlider && this.$volumeSlider.is(':visible')) { 4153 this.$volumeSlider.hide().attr('aria-hidden','true'); 4154 this.$volumeButton.attr('aria-expanded', 'false').focus(); 4155 } 4156 if (this.$transcriptPopup && this.$transcriptPopup.is(':visible')) { 4157 this.hidingPopup = true; 4158 this.$transcriptPopup.hide(); 4159 // restore menu items to their original state 4160 this.$transcriptPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 4161 this.$transcriptPopupButton.attr('aria-expanded','false').focus(); 4162 // wait briefly, then reset hidingPopup 4163 setTimeout(function() { 4164 thisObj.hidingPopup = false; 4165 },100); 4166 } 4167 if (this.$signPopup && this.$signPopup.is(':visible')) { 4168 this.$signPopup.hide(); 4169 // restore menu items to their original state 4170 this.$signPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 4171 this.$signPopupButton.attr('aria-expanded','false').focus(); 4172 } 4173 }; 4174 4175 AblePlayer.prototype.setupPopups = function (which) { 4176 4177 // Create and fill in the popup menu forms for various controls. 4178 // parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters') 4179 // If which is undefined, automatically setup 'captions', 'chapters', and 'prefs' popups 4180 // However, only setup 'transcript-window' and 'sign-window' popups if passed as value of which 4181 var popups, thisObj, hasDefault, i, j, 4182 tracks, track, $trackButton, $trackLabel, 4183 radioName, radioId, $menu, $menuItem, 4184 prefCats, prefCat, prefLabel; 4185 4186 popups = []; 4187 if (typeof which === 'undefined') { 4188 popups.push('prefs'); 4189 } 4190 4191 if (which === 'captions' || (typeof which === 'undefined')) { 4192 if (this.captions.length > 0) { 4193 popups.push('captions'); 4194 } 4195 } 4196 if (which === 'chapters' || (typeof which === 'undefined')) { 4197 if (this.chapters.length > 0 && this.useChaptersButton) { 4198 popups.push('chapters'); 4199 } 4200 } 4201 if (which === 'transcript-window' && this.transcriptType === 'popup') { 4202 popups.push('transcript-window'); 4203 } 4204 if (which === 'sign-window' && this.hasSignLanguage) { 4205 popups.push('sign-window'); 4206 } 4207 if (popups.length > 0) { 4208 thisObj = this; 4209 for (var i=0; i<popups.length; i++) { 4210 var popup = popups[i]; 4211 hasDefault = false; 4212 if (popup == 'prefs') { 4213 this.prefsPopup = this.createPopup('prefs'); 4214 } 4215 else if (popup == 'captions') { 4216 if (typeof this.captionsPopup === 'undefined' || !this.captionsPopup) { 4217 this.captionsPopup = this.createPopup('captions',this.captions); 4218 } 4219 } 4220 else if (popup == 'chapters') { 4221 if (this.selectedChapters) { 4222 tracks = this.selectedChapters.cues; 4223 } 4224 else if (this.chapters.length >= 1) { 4225 tracks = this.chapters[0].cues; 4226 } 4227 else { 4228 tracks = []; 4229 } 4230 if (typeof this.chaptersPopup === 'undefined' || !this.chaptersPopup) { 4231 this.chaptersPopup = this.createPopup('chapters',tracks); 4232 } 4233 } 4234 else if (popup == 'transcript-window') { 4235 return this.createPopup('transcript-window'); 4236 } 4237 else if (popup == 'sign-window') { 4238 return this.createPopup('sign-window'); 4239 } 4240 } 4241 } 4242 }; 4243 4244 AblePlayer.prototype.provideFallback = function() { 4245 4246 // provide fallback in case of a critical error building the player 4247 // to test, set data-test-fallback to either of the following values: 4248 // 1 = emulate failure to build Able Player 4249 // 2 = emulate browser that doesn't support HTML5 media 4250 4251 var i, $fallback; 4252 4253 if (this.usingFallback) { 4254 // fallback has already been implemented. 4255 // stopgap to prevent this function from executing twice on the same media element 4256 return; 4257 } 4258 else { 4259 this.usingFallback = true; 4260 } 4261 4262 if (!this.testFallback) { 4263 // this is not a test. 4264 // an actual error has resulted in this function being called. 4265 // use scenario 1 4266 this.testFallback = 1; 4267 } 4268 4269 if (typeof this.$media === 'undefined') { 4270 // this function has been called prior to initialize.js > reinitialize() 4271 // before doing anything, need to create the jQuery media object 4272 this.$media = $(this.media); 4273 } 4274 4275 // get/assign an id for the media element 4276 if (this.$media.attr('id')) { 4277 this.mediaId = this.$media.attr('id'); 4278 } 4279 else { 4280 this.mediaId = 'media' + Math.floor(Math.random() * 1000000000).toString(); 4281 } 4282 4283 // check whether element has nested fallback content 4284 this.hasFallback = false; 4285 if (this.$media.children().length) { 4286 i = 0; 4287 while (i < this.$media.children().length && !this.hasFallback) { 4288 if (!(this.$media.children()[i].tagName === 'SOURCE' || 4289 this.$media.children()[i].tagName === 'TRACK')) { 4290 // this element is something other than <source> or <track> 4291 this.hasFallback = true; 4292 } 4293 i++; 4294 } 4295 } 4296 if (!this.hasFallback) { 4297 // the HTML code does not include any nested fallback content 4298 // inject our own 4299 // NOTE: this message is not translated, since fallback may be needed 4300 // due to an error loading the translation file 4301 // This will only be needed on very rare occasions, so English is ok. 4302 $fallback = $('<p>').text('Media player unavailable.'); 4303 this.$media.append($fallback); 4304 } 4305 4306 // get height and width attributes, if present 4307 // and add them to a style attribute 4308 if (this.$media.attr('width')) { 4309 this.$media.css('width',this.$media.attr('width') + 'px'); 4310 } 4311 if (this.$media.attr('height')) { 4312 this.$media.css('height',this.$media.attr('height') + 'px'); 4313 } 4314 // Remove data-able-player attribute 4315 this.$media.removeAttr('data-able-player'); 4316 4317 // Add controls attribute (so browser will add its own controls) 4318 this.$media.prop('controls',true); 4319 4320 if (this.testFallback == 2) { 4321 4322 // emulate browser failure to support HTML5 media by changing the media tag name 4323 // browsers should display the supported content that's nested inside 4324 $(this.$media).replaceWith($('<foobar id="foobar-' + this.mediaId + '">')); 4325 this.$newFallbackElement = $('#foobar-' + this.mediaId); 4326 4327 // append all children from the original media 4328 if (this.$media.children().length) { 4329 i = this.$media.children().length - 1; 4330 while (i >= 0) { 4331 this.$newFallbackElement.prepend($(this.$media.children()[i])); 4332 i--; 4333 } 4334 } 4335 if (!this.hasFallback) { 4336 // inject our own fallback content, defined above 4337 this.$newFallbackElement.append($fallback); 4338 } 4339 } 4340 return; 4341 }; 4342 4343 AblePlayer.prototype.calculateControlLayout = function () { 4344 4345 // Calculates the layout for controls based on media and options. 4346 // Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin) 4347 // Keys are the following order: 4348 // 0 = Top left 4349 // 1 = Top right 4350 // 2 = Bottom left (legacy skin only) 4351 // 3 = Bottom right (legacy skin only) 4352 // Each key contains an array of control names to put in that section. 4353 4354 var controlLayout, volumeSupported, playbackSupported, totalButtonWidth, numA11yButtons; 4355 4356 controlLayout = []; 4357 controlLayout[0] = []; 4358 controlLayout[1] = []; 4359 if (this.skin === 'legacy') { 4360 controlLayout[2] = []; 4361 controlLayout[3] = []; 4362 } 4363 4364 controlLayout[0].push('play'); 4365 controlLayout[0].push('restart'); 4366 controlLayout[0].push('rewind'); 4367 controlLayout[0].push('forward'); 4368 4369 if (this.skin === 'legacy') { 4370 controlLayout[1].push('seek'); 4371 } 4372 4373 if (this.hasPlaylist) { 4374 if (this.skin === 'legacy') { 4375 controlLayout[0].push('previous'); 4376 controlLayout[0].push('next'); 4377 } 4378 else if (this.skin == '2020') { 4379 controlLayout[0].push('previous'); 4380 controlLayout[0].push('next'); 4381 } 4382 } 4383 4384 if (this.isPlaybackRateSupported()) { 4385 playbackSupported = true; 4386 if (this.skin === 'legacy') { 4387 controlLayout[2].push('slower'); 4388 controlLayout[2].push('faster'); 4389 } 4390 } 4391 else { 4392 playbackSupported = false; 4393 } 4394 4395 numA11yButtons = 0; 4396 if (this.hasCaptions) { 4397 numA11yButtons++; 4398 if (this.skin === 'legacy') { 4399 controlLayout[2].push('captions'); 4400 } 4401 else if (this.skin == '2020') { 4402 controlLayout[1].push('captions'); 4403 } 4404 } 4405 if (this.hasSignLanguage) { 4406 numA11yButtons++; 4407 if (this.skin === 'legacy') { 4408 controlLayout[2].push('sign'); 4409 } 4410 else if (this.skin == '2020') { 4411 controlLayout[1].push('sign'); 4412 } 4413 } 4414 if (this.mediaType === 'video') { 4415 if (this.hasOpenDesc || this.hasClosedDesc) { 4416 numA11yButtons++; 4417 if (this.skin === 'legacy') {
4418 controlLayout[2].push('descriptions'); 4419 } 4420 else if (this.skin == '2020') { 4421 controlLayout[1].push('descriptions'); 4422 } 4423 } 4424 } 4425 if (this.transcriptType === 'popup' && !(this.hideTranscriptButton)) { 4426 numA11yButtons++; 4427 if (this.skin === 'legacy') { 4428 controlLayout[2].push('transcript'); 4429 } 4430 else if (this.skin == '2020') { 4431 controlLayout[1].push('transcript'); 4432 } 4433 } 4434 if (this.hasChapters && this.useChaptersButton) { 4435 numA11yButtons++; 4436 if (this.skin === 'legacy') { 4437 controlLayout[2].push('chapters'); 4438 } 4439 else if (this.skin == '2020') { 4440 controlLayout[1].push('chapters'); 4441 } 4442 } 4443 4444 if (this.skin == '2020' && numA11yButtons > 0) { 4445 controlLayout[1].push('pipe'); 4446 } 4447 4448 if (playbackSupported && this.skin === '2020') { 4449 controlLayout[1].push('faster'); 4450 controlLayout[1].push('slower'); 4451 controlLayout[1].push('pipe'); 4452 } 4453 4454 if (this.skin === 'legacy') { 4455 controlLayout[3].push('preferences'); 4456 } 4457 else if (this.skin == '2020') { 4458 controlLayout[1].push('preferences'); 4459 } 4460 4461 if (this.mediaType === 'video' && this.allowFullscreen) { 4462 if (this.skin === 'legacy') { 4463 controlLayout[3].push('fullscreen'); 4464 } 4465 else { 4466 controlLayout[1].push('fullscreen'); 4467 } 4468 } 4469 4470 if (this.browserSupportsVolume()) { 4471 volumeSupported = true; // defined in case we decide to move volume button elsewhere 4472 this.volumeButton = 'volume-' + this.getVolumeName(this.volume); 4473 if (this.skin === 'legacy') { 4474 controlLayout[1].push('volume'); 4475 } 4476 else if (this.skin == '2020') { 4477 controlLayout[1].push('volume'); 4478 } 4479 } 4480 else { 4481 volumeSupported = false; 4482 this.volume = false; 4483 } 4484 return controlLayout; 4485 }; 4486 4487 AblePlayer.prototype.addControls = function() { 4488 4489 // determine which controls to show based on several factors: 4490 // mediaType (audio vs video) 4491 // availability of tracks (e.g., for closed captions & audio description) 4492 // browser support (e.g., for sliders and speedButtons) 4493 // user preferences (???) 4494 // some controls are aligned on the left, and others on the right 4495 4496 var thisObj, baseSliderWidth, controlLayout, numSections, 4497 i, j, k, controls, $controllerSpan, $sliderDiv, sliderLabel, $pipe, $pipeImg, 4498 svgData, svgPath, control, 4499 $buttonLabel, $buttonImg, buttonImgSrc, buttonTitle, $newButton, iconClass, buttonIcon, 4500 buttonUse, buttonText, position, buttonHeight, buttonWidth, buttonSide, controllerWidth, 4501 tooltipId, tooltipY, tooltipX, tooltipWidth, tooltipStyle, tooltip, tooltipTimerId, 4502 captionLabel, popupMenuId; 4503 4504 thisObj = this; 4505 4506 baseSliderWidth = 100; // arbitrary value, will be recalculated in refreshControls() 4507 4508 // Initialize the layout into the this.controlLayout variable. 4509 controlLayout = this.calculateControlLayout(); 4510 numSections = controlLayout.length; 4511 4512 // add an empty div to serve as a tooltip 4513 tooltipId = this.mediaId + '-tooltip'; 4514 this.$tooltipDiv = $('<div>',{ 4515 'id': tooltipId, 4516 'class': 'able-tooltip' 4517 }).hide(); 4518 this.$controllerDiv.append(this.$tooltipDiv); 4519 4520 if (this.skin == '2020') { 4521 // add a full-width seek bar 4522 $sliderDiv = $('<div class="able-seekbar"></div>'); 4523 sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel; 4524 this.$controllerDiv.append($sliderDiv); 4525 this.seekBar = new AccessibleSlider(this.mediaType, $sliderDiv, 'horizontal', baseSliderWidth, 0, this.duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible'); 4526 } 4527 4528 for (i = 0; i < numSections; i++) { 4529 controls = controlLayout[i]; 4530 if ((i % 2) === 0) { // even keys on the left 4531 $controllerSpan = $('<div>',{
4532 'class': 'able-left-controls' 4533 }); 4534 } 4535 else { // odd keys on the right 4536 $controllerSpan = $('<div>',{ 4537 'class': 'able-right-controls' 4538 }); 4539 } 4540 this.$controllerDiv.append($controllerSpan); 4541 4542 for (j=0; j<controls.length; j++) { 4543 control = controls[j]; 4544 if (control === 'seek') { 4545 $sliderDiv = $('<div class="able-seekbar"></div>'); 4546 sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel; 4547 $controllerSpan.append($sliderDiv); 4548 if (typeof this.duration === 'undefined' || this.duration === 0) { 4549 // set arbitrary starting duration, and change it when duration is known 4550 this.duration = 60; 4551 // also set elapsed to 0 4552 this.elapsed = 0; 4553 } 4554 this.seekBar = new AccessibleSlider(this.mediaType, $sliderDiv, 'horizontal', baseSliderWidth, 0, this.duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible'); 4555 } 4556 else if (control === 'pipe') { 4557 $pipe = $('<span>', { 4558 'tabindex': '-1', 4559 'aria-hidden': 'true' 4560 }); 4561 if (this.iconType === 'font') { 4562 $pipe.addClass('icon-pipe'); 4563 } 4564 else { 4565 $pipeImg = $('<img>', { 4566 src: this.rootPath + 'button-icons/' + this.iconColor + '/pipe.png', 4567 alt: '', 4568 role: 'presentation' 4569 }); 4570 $pipe.append($pipeImg); 4571 } 4572 $controllerSpan.append($pipe); 4573 } 4574 else { 4575 // this control is a button 4576 if (control === 'volume') { 4577 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/' + this.volumeButton + '.png'; 4578 } 4579 else if (control === 'fullscreen') { 4580 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/fullscreen-expand.png'; 4581 } 4582 else if (control === 'slower') { 4583 if (this.speedIcons === 'animals') { 4584 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/turtle.png'; 4585 } 4586 else { 4587 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/slower.png'; 4588 } 4589 } 4590 else if (control === 'faster') { 4591 if (this.speedIcons === 'animals') { 4592 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/rabbit.png'; 4593 } 4594 else { 4595 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/faster.png'; 4596 } 4597 } 4598 else { 4599 buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/' + control + '.png'; 4600 } 4601 buttonTitle = this.getButtonTitle(control); 4602 4603 // icomoon documentation recommends the following markup for screen readers: 4604 // 1. link element (or in our case, button). Nested inside this element: 4605 // 2. span that contains the icon font (in our case, buttonIcon) 4606 // 3. span that contains a visually hidden label for screen readers (buttonLabel) 4607 // In addition, we are adding aria-label to the button (but not title) 4608 // And if iconType === 'image', we are replacing #2 with an image (with alt="" and role="presentation") 4609 // This has been thoroughly tested and works well in all screen reader/browser combinations 4610 // See https://github.com/ableplayer/ableplayer/issues/81 4611 4612 // NOTE: Changed from <button> to <div role="button" as of 4.2.18 4613 // because <button> elements are rendered poorly in high contrast mode 4614 // in some OS/browser/plugin combinations 4615 $newButton = $('<div>',{ 4616 'role': 'button', 4617 'tabindex': '0', 4618 'aria-label': buttonTitle, 4619 'class': 'able-button-handler-' + control 4620 }); 4621 4622 if (control === 'volume' || control === 'preferences' || control === 'captions') { 4623 if (control == 'preferences') { 4624 this.prefCats = this.getPreferencesGroups(); 4625 if (this.prefCats.length > 1) { 4626 // Prefs button will trigger a menu 4627 popupMenuId = this.mediaId + '-prefs-menu'; 4628 $newButton.attr({ 4629 'aria-controls': popupMenuId, 4630 'aria-haspopup': 'menu', 4631 'aria-expanded': 'false' 4632 }); 4633 } 4634 else if (this.prefCats.length === 1) { 4635 // Prefs button will trigger a dialog 4636 $newButton.attr({ 4637 'aria-haspopup': 'dialog' 4638 }); 4639 } 4640 } 4641 else if (control === 'volume') { 4642 popupMenuId = this.mediaId + '-volume-slider'; 4643 // volume slider popup is not a menu or a dialog 4644 // therefore, using aria-expanded rather than aria-haspopup to communicate properties/state 4645 $newButton.attr({ 4646 'aria-controls': popupMenuId, 4647 'aria-expanded': 'false' 4648 }); 4649 } else if (control === 'captions' && this.captions) { 4650 if (this.captions.length > 1) { 4651 $newButton.attr('aria-expanded', 'false') 4652 } else { 4653 $newButton.attr('aria-pressed', 'false') 4654 } 4655 } 4656 } 4657 if (this.iconType === 'font') { 4658 if (control === 'volume') { 4659 iconClass = 'icon-' + this.volumeButton; 4660 } 4661 else if (control === 'slower') { 4662 if (this.speedIcons === 'animals') { 4663 iconClass = 'icon-turtle'; 4664 } 4665 else { 4666 iconClass = 'icon-slower'; 4667 } 4668 } 4669 else if (control === 'faster') { 4670 if (this.speedIcons === 'animals') { 4671 iconClass = 'icon-rabbit'; 4672 } 4673 else { 4674 iconClass = 'icon-faster'; 4675 } 4676 } 4677 else { 4678 iconClass = 'icon-' + control; 4679 } 4680 buttonIcon = $('<span>',{ 4681 'class': iconClass, 4682 'aria-hidden': 'true' 4683 }); 4684 $newButton.append(buttonIcon); 4685 } 4686 else if (this.iconType === 'svg') { 4687 4688 /* 4689 // Unused option for adding SVG: 4690 // Use <use> element to link to button-icons/able-icons.svg 4691 // Advantage: SVG file can be cached 4692 // Disadvantage: Not supported by Safari 6, IE 6-11, or Edge 12 4693 // Instead, adding <svg> element within each <button> 4694 if (control === 'volume') { 4695 iconClass = 'svg-' + this.volumeButton; 4696 } 4697 else if (control === 'fullscreen') { 4698 iconClass = 'svg-fullscreen-expand'; 4699 } 4700 else if (control === 'slower') { 4701 if (this.speedIcons === 'animals') { 4702 iconClass = 'svg-turtle'; 4703 } 4704 else { 4705 iconClass = 'svg-slower'; 4706 } 4707 } 4708 else if (control === 'faster') { 4709 if (this.speedIcons === 'animals') { 4710 iconClass = 'svg-rabbit'; 4711 } 4712 else { 4713 iconClass = 'svg-faster'; 4714 } 4715 } 4716 else { 4717 iconClass = 'svg-' + control; 4718 } 4719 buttonIcon = $('<svg>',{ 4720 'class': iconClass 4721 }); 4722 buttonUse = $('<use>',{ 4723 'xlink:href': this.rootPath + 'button-icons/able-icons.svg#' + iconClass 4724 }); 4725 buttonIcon.append(buttonUse); 4726 */ 4727 var svgData; 4728 if (control === 'volume') {
4729 svgData = this.getSvgData(this.volumeButton); 4730 } 4731 else if (control === 'fullscreen') { 4732 svgData = this.getSvgData('fullscreen-expand'); 4733 } 4734 else if (control === 'slower') { 4735 if (this.speedIcons === 'animals') { 4736 svgData = this.getSvgData('turtle'); 4737 } 4738 else { 4739 svgData = this.getSvgData('slower'); 4740 } 4741 } 4742 else if (control === 'faster') { 4743 if (this.speedIcons === 'animals') { 4744 svgData = this.getSvgData('rabbit'); 4745 } 4746 else { 4747 svgData = this.getSvgData('faster'); 4748 } 4749 } 4750 else { 4751 svgData = this.getSvgData(control); 4752 } 4753 buttonIcon = $('<svg>',{ 4754 'focusable': 'false', 4755 'aria-hidden': 'true', 4756 'viewBox': svgData[0] 4757 }); 4758 svgPath = $('<path>',{ 4759 'd': svgData[1] 4760 }); 4761 buttonIcon.append(svgPath); 4762 $newButton.html(buttonIcon); 4763 4764 // Final step: Need to refresh the DOM in order for browser to process & display the SVG 4765 $newButton.html($newButton.html()); 4766 } 4767 else { 4768 // use images 4769 $buttonImg = $('<img>',{ 4770 'src': buttonImgSrc, 4771 'alt': '', 4772 'role': 'presentation' 4773 }); 4774 $newButton.append($buttonImg); 4775 } 4776 // add the visibly-hidden label for screen readers that don't support aria-label on the button 4777 var $buttonLabel = $('<span>',{ 4778 'class': 'able-clipped' 4779 }).text(buttonTitle); 4780 $newButton.append($buttonLabel); 4781 // add an event listener that displays a tooltip on mouseenter or focus 4782 $newButton.on('mouseenter focus',function(e) { 4783 4784 // when entering a new tooltip, we can forget about hiding the previous tooltip. 4785 // since the same tooltip div is used, it's location just changes. 4786 clearTimeout(tooltipTimerId); 4787 4788 var buttonText = $(this).attr('aria-label'); 4789 // get position of this button 4790 var position = $(this).position(); 4791 var buttonHeight = $(this).height(); 4792 var buttonWidth = $(this).width(); 4793 // position() is expressed using top and left (of button); 4794 // add right (of button) too, for convenience 4795 var controllerWidth = thisObj.$controllerDiv.width(); 4796 position.right = controllerWidth - position.left - buttonWidth; 4797 4798 // The following formula positions tooltip above the button 4799 // var tooltipY = position.top - buttonHeight - 15; 4800 4801 // The following formula positions tooltip below the button 4802 // which allows the tooltip to be hoverable as per WCAG 2.x SC 1.4.13 4803 // without obstructing the seekbar 4804 var tooltipY = position.top + buttonHeight + 5; 4805 4806 if ($(this).parent().hasClass('able-right-controls')) { 4807 // this control is on the right side 4808 var buttonSide = 'right'; 4809 } 4810 else { 4811 // this control is on the left side 4812 var buttonSide = 'left'; 4813 } 4814 // populate tooltip, then calculate its width before showing it 4815 var tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).width(); 4816 // center the tooltip horizontally over the button 4817 if (buttonSide == 'left') { 4818 var tooltipX = position.left - tooltipWidth/2; 4819 if (tooltipX < 0) { 4820 // tooltip would exceed the bounds of the player. Adjust. 4821 tooltipX = 2; 4822 } 4823 var tooltipStyle = { 4824 left: tooltipX + 'px', 4825 right: '', 4826 top: tooltipY + 'px' 4827 }; 4828 } 4829 else { 4830 var tooltipX = position.right - tooltipWidth/2; 4831 if (tooltipX < 0) { 4832 // tooltip would exceed the bounds of the player. Adjust. 4833 tooltipX = 2; 4834 } 4835 var tooltipStyle = { 4836 left: '', 4837 right: tooltipX + 'px', 4838 top: tooltipY + 'px' 4839 }; 4840 } 4841 var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).css(tooltipStyle); 4842 thisObj.showTooltip(tooltip); 4843 $(this).on('mouseleave blur',function() { 4844 4845 // hide tooltip (original line of code) 4846 // AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 4847 4848 // The above line was replaced with the following block 4849 // in order to meet WCAG 2.x SC 1.4.13 4850 // (keep the tooltip visible if user hovers over it) 4851 // This causes unwanted side effects if tooltips are positioned above the buttons 4852 // as the persistent tooltip obstructs the seekbar, 4853 // blocking users from being able to move a pointer from a button to the seekbar 4854 // This limitation was addressed in 4.4.49 by moving the tooltip below the buttons 4855 4856 // clear existing timeout before reassigning variable 4857 clearTimeout(tooltipTimerId); 4858 tooltipTimerId = setTimeout(function() { 4859 // give the user a half second to move cursor to tooltip before removing 4860 // see https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus#hoverable 4861 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 4862 }, 500); 4863 4864 thisObj.$tooltipDiv.on('mouseenter focus', function() { 4865 clearTimeout(tooltipTimerId); 4866 }); 4867 4868 thisObj.$tooltipDiv.on('mouseleave blur', function() { 4869 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 4870 }); 4871 4872 }); 4873 }); 4874 4875 if (control === 'captions') { 4876 if (!this.prefCaptions || this.prefCaptions !== 1) { 4877 // captions are available, but user has them turned off 4878 if (this.captions.length > 1) { 4879 captionLabel = this.tt.captions; 4880 } 4881 else { 4882 captionLabel = this.tt.showCaptions; 4883 } 4884 $newButton.addClass('buttonOff').attr('title',captionLabel); 4885 $newButton.attr('aria-pressed', 'false'); 4886 4887 4888 } 4889 } 4890 else if (control === 'descriptions') { 4891 if (!this.prefDesc || this.prefDesc !== 1) { 4892 // user prefer non-audio described version 4893 // Therefore, load media without description 4894 // Description can be toggled on later with this button 4895 $newButton.addClass('buttonOff').attr('title',this.tt.turnOnDescriptions); 4896 } 4897 } 4898 4899 $controllerSpan.append($newButton); 4900 4901 // create variables of buttons that are referenced throughout the AblePlayer object 4902 if (control === 'play') { 4903 this.$playpauseButton = $newButton; 4904 } 4905 else if (control == 'previous') { 4906 this.$prevButton = $newButton; 4907 // if player is being rebuilt because user clicked the Prev button 4908 // return focus to that (newly built) button 4909 if (this.buttonWithFocus == 'previous') { 4910 this.$prevButton.focus(); 4911 this.buttonWithFocus = null; 4912 } 4913 } 4914 else if (control == 'next') { 4915 this.$nextButton = $newButton; 4916 // if player is being rebuilt because user clicked the Next button 4917 // return focus to that (newly built) button 4918 if (this.buttonWithFocus == 'next') { 4919 this.$nextButton.focus(); 4920 this.buttonWithFocus = null; 4921 } 4922 } 4923 else if (control === 'captions') { 4924 this.$ccButton = $newButton; 4925 } 4926 else if (control === 'sign') { 4927 this.$signButton = $newButton; 4928 // gray out sign button if sign language window is not active 4929 if (!(this.$signWindow.is(':visible'))) { 4930 this.$signButton.addClass('buttonOff'); 4931 } 4932 } 4933 else if (control === 'descriptions') { 4934 this.$descButton = $newButton; 4935 // button will be enabled or disabled in description.js > initDescription() 4936 } 4937 else if (control === 'mute') { 4938 this.$muteButton = $newButton; 4939 } 4940 else if (control === 'transcript') { 4941 this.$transcriptButton = $newButton; 4942 // gray out transcript button if transcript is not active 4943 if (!(this.$transcriptDiv.is(':visible'))) { 4944 this.$transcriptButton.addClass('buttonOff').attr('title',this.tt.showTranscript); 4945 } 4946 } 4947 else if (control === 'fullscreen') { 4948 this.$fullscreenButton = $newButton; 4949 } 4950 else if (control === 'chapters') { 4951 this.$chaptersButton = $newButton; 4952 } 4953 else if (control === 'preferences') { 4954 this.$prefsButton = $newButton; 4955 } 4956 else if (control === 'volume') { 4957 this.$volumeButton = $newButton; 4958 } 4959 } 4960 if (control === 'volume') { 4961 // in addition to the volume button, add a hidden slider 4962 this.addVolumeSlider($controllerSpan); 4963 } 4964 } 4965 if ((i % 2) == 1) { 4966 this.$controllerDiv.append('<div style="clear:both;"></div>'); 4967 } 4968 } 4969 4970 if (typeof this.$captionsDiv !== 'undefined') { 4971 // stylize captions based on user prefs 4972 this.stylizeCaptions(this.$captionsDiv); 4973 } 4974 if (typeof this.$descDiv !== 'undefined') { 4975 // stylize descriptions based on user's caption prefs 4976 this.stylizeCaptions(this.$descDiv); 4977 } 4978
4979 // combine left and right controls arrays for future reference 4980 this.controls = []; 4981 for (var sec in controlLayout) if (controlLayout.hasOwnProperty(sec)) { 4982 this.controls = this.controls.concat(controlLayout[sec]); 4983 } 4984 4985 // Update state-based display of controls. 4986 this.refreshControls('init'); 4987 }; 4988 4989 AblePlayer.prototype.useSvg = function () { 4990 4991 // Modified from IcoMoon.io svgxuse 4992 // @copyright Copyright (c) 2016 IcoMoon.io 4993 // @license Licensed under MIT license 4994 // See https://github.com/Keyamoon/svgxuse 4995 // @version 1.1.16 4996 4997 var cache = Object.create(null); // holds xhr objects to prevent multiple requests 4998 var checkUseElems, 4999 tid; // timeout id 5000 var debouncedCheck = function () { 5001 clearTimeout(tid); 5002 tid = setTimeout(checkUseElems, 100); 5003 }; 5004 var unobserveChanges = function () { 5005 return; 5006 }; 5007 var observeChanges = function () { 5008 var observer; 5009 window.addEventListener('resize', debouncedCheck, false); 5010 window.addEventListener('orientationchange', debouncedCheck, false); 5011 if (window.MutationObserver) { 5012 observer = new MutationObserver(debouncedCheck); 5013 observer.observe(document.documentElement, { 5014 childList: true, 5015 subtree: true, 5016 attributes: true 5017 }); 5018 unobserveChanges = function () { 5019 try { 5020 observer.disconnect(); 5021 window.removeEventListener('resize', debouncedCheck, false); 5022 window.removeEventListener('orientationchange', debouncedCheck, false); 5023 } catch (ignore) {} 5024 }; 5025 } 5026 else { 5027 document.documentElement.addEventListener('DOMSubtreeModified', debouncedCheck, false); 5028 unobserveChanges = function () { 5029 document.documentElement.removeEventListener('DOMSubtreeModified', debouncedCheck, false); 5030 window.removeEventListener('resize', debouncedCheck, false); 5031 window.removeEventListener('orientationchange', debouncedCheck, false); 5032 }; 5033 } 5034 }; 5035 var xlinkNS = 'http://www.w3.org/1999/xlink'; 5036 checkUseElems = function () { 5037 var base, 5038 bcr, 5039 fallback = '', // optional fallback URL in case no base path to SVG file was given and no symbol definition was found. 5040 hash, 5041 i, 5042 Request, 5043 inProgressCount = 0, 5044 isHidden, 5045 url, 5046 uses, 5047 xhr; 5048 if (window.XMLHttpRequest) { 5049 Request = new XMLHttpRequest(); 5050 if (Request.withCredentials !== undefined) { 5051 Request = XMLHttpRequest; 5052 } 5053 else { 5054 Request = XDomainRequest || undefined; 5055 } 5056 } 5057 if (Request === undefined) { 5058 return; 5059 } 5060 function observeIfDone() { 5061 // If done with making changes, start watching for chagnes in DOM again 5062 inProgressCount -= 1; 5063 if (inProgressCount === 0) { // if all xhrs were resolved 5064 observeChanges(); // watch for changes to DOM 5065 } 5066 } 5067 function attrUpdateFunc(spec) { 5068 return function () { 5069 if (cache[spec.base] !== true) { 5070 spec.useEl.setAttributeNS(xlinkNS, 'xlink:href', '#' + spec.hash); 5071 } 5072 }; 5073 } 5074 function onloadFunc(xhr) { 5075 return function () { 5076 var body = document.body; 5077 var x = document.createElement('x'); 5078 var svg; 5079 xhr.onload = null; 5080 x.innerHTML = xhr.responseText; 5081 svg = x.getElementsByTagName('svg')[0]; 5082 if (svg) { 5083 svg.setAttribute('aria-hidden', 'true'); 5084 svg.style.position = 'absolute'; 5085 svg.style.width = 0; 5086 svg.style.height = 0; 5087 svg.style.overflow = 'hidden'; 5088 body.insertBefore(svg, body.firstChild); 5089 } 5090 observeIfDone(); 5091 }; 5092 } 5093 function onErrorTimeout(xhr) { 5094 return function () { 5095 xhr.onerror = null; 5096 xhr.ontimeout = null; 5097 observeIfDone(); 5098 }; 5099 } 5100 unobserveChanges(); // stop watching for changes to DOM 5101 // find all use elements 5102 uses = document.getElementsByTagName('use'); 5103 for (i = 0; i < uses.length; i += 1) { 5104 try { 5105 bcr = uses[i].getBoundingClientRect(); 5106 } catch (ignore) { 5107 // failed to get bounding rectangle of the use element 5108 bcr = false; 5109 } 5110 url = uses[i].getAttributeNS(xlinkNS, 'href').split('#'); 5111 base = url[0]; 5112 hash = url[1]; 5113 isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0;
5114 if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) { 5115 // the use element is empty 5116 // if there is a reference to an external SVG, try to fetch it 5117 // use the optional fallback URL if there is no reference to an external SVG 5118 if (fallback && !base.length && hash && !document.getElementById(hash)) { 5119 base = fallback; 5120 } 5121 if (base.length) { 5122 // schedule updating xlink:href 5123 xhr = cache[base]; 5124 if (xhr !== true) { 5125 // true signifies that prepending the SVG was not required 5126 setTimeout(attrUpdateFunc({ 5127 useEl: uses[i], 5128 base: base, 5129 hash: hash 5130 }), 0); 5131 } 5132 if (xhr === undefined) { 5133 xhr = new Request(); 5134 cache[base] = xhr; 5135 xhr.onload = onloadFunc(xhr); 5136 xhr.onerror = onErrorTimeout(xhr); 5137 xhr.ontimeout = onErrorTimeout(xhr); 5138 xhr.open('GET', base); 5139 xhr.send(); 5140 inProgressCount += 1; 5141 } 5142 } 5143 } 5144 else { 5145 if (!isHidden) { 5146 if (cache[base] === undefined) { 5147 // remember this URL if the use element was not empty and no request was sent 5148 cache[base] = true; 5149 } 5150 else if (cache[base].onload) { 5151 // if it turns out that prepending the SVG is not necessary, 5152 // abort the in-progress xhr. 5153 cache[base].abort(); 5154 cache[base].onload = undefined; 5155 cache[base] = true; 5156 } 5157 } 5158 } 5159 } 5160 uses = ''; 5161 inProgressCount += 1; 5162 observeIfDone(); 5163 }; 5164/* 5165 // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty. 5166 window.addEventListener('load', function winLoad() { 5167 window.removeEventListener('load', winLoad, false); // to prevent memory leaks 5168 tid = setTimeout(checkUseElems, 0); 5169 }, false); 5170*/ 5171 }; 5172 5173 AblePlayer.prototype.cuePlaylistItem = function(sourceIndex) { 5174 5175 // Move to a new item in a playlist. 5176 // NOTE: Swapping source for audio description is handled elsewhere; 5177 // see description.js > swapDescription() 5178 5179 /* 5180 // Decided against preventing a reload of the current item in the playlist. 5181 // If it's clickable, users should be able to click on it and expect something to happen. 5182 // Leaving here though in case it's determined to be desirable. 5183 if (sourceIndex === this.playlistItemIndex) { 5184 // user has requested the item that's currently playing 5185 // just ignore the request 5186 return; 5187 } 5188 this.playlistItemIndex = sourceIndex; 5189 */ 5190 5191 var $newItem, prevPlayer, newPlayer, itemTitle, itemLang, sources, s, i, $newSource, nowPlayingSpan; 5192 5193 var thisObj = this; 5194 5195 prevPlayer = this.player; 5196 5197 if (this.initializing) { // this is the first track - user hasn't pressed play yet 5198 // do nothing. 5199 } 5200 else { 5201 if (this.playerCreated) { 5202 // remove the old 5203 this.deletePlayer('playlist'); 5204 } 5205 } 5206 5207 // set swappingSrc; needs to be true within recreatePlayer(), called below 5208 this.swappingSrc = true; 5209 5210 // if a new playlist item is being requested, and playback has already started, 5211 // it should be ok to play automatically, regardless of how it was requested 5212 if (this.startedPlaying) { 5213 this.okToPlay = true; 5214 } 5215 else { 5216 this.okToPlay = false; 5217 } 5218 5219 // We are no longer loading the previous media source 5220 // Only now, as a new source is requested, is it safe to reset this var 5221 // It will be reset to true when media.load() is called 5222 this.loadingMedia = false; 5223 5224 // Determine appropriate player to play this media 5225 $newItem = this.$playlist.eq(sourceIndex); 5226 if (this.hasAttr($newItem,'data-youtube-id')) { 5227 this.youTubeId = this.getYouTubeId($newItem.attr('data-youtube-id')); 5228 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 5229 this.youTubeDescId = this.getYouTubeId($newItem.attr('data-youtube-desc-id')); 5230 } 5231 newPlayer = 'youtube'; 5232 } 5233 else if (this.hasAttr($newItem,'data-vimeo-id')) { 5234 this.vimeoId = this.getVimeoId($newItem.attr('data-vimeo-id')); 5235 if (this.hasAttr($newItem,'data-vimeo-desc-id')) { 5236 this.vimeoDescId = this.getVimeoId($newItem.attr('data-vimeo-desc-id')); 5237 } 5238 newPlayer = 'vimeo'; 5239 } 5240 else { 5241 newPlayer = 'html5'; 5242 } 5243 if (newPlayer === 'youtube') { 5244 if (prevPlayer === 'html5') { 5245 // pause and hide the previous media 5246 if (this.playing) { 5247 this.pauseMedia(); 5248 } 5249 this.$media.hide(); 5250 } 5251 } 5252 else { 5253 // the new player is not youtube 5254 this.youTubeId = false;
5255 if (prevPlayer === 'youtube') { 5256 // unhide the media element 5257 this.$media.show(); 5258 } 5259 } 5260 this.player = newPlayer; 5261 5262 // remove source and track elements from previous playlist item 5263 this.$media.empty(); 5264 5265 // transfer media attributes from playlist to media element 5266 if (this.hasAttr($newItem,'data-poster')) { 5267 this.$media.attr('poster',$newItem.attr('data-poster')); 5268 } 5269 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 5270 this.$media.attr('data-youtube-desc-id',$newItem.attr('data-youtube-desc-id')); 5271 } 5272 if (this.youTubeId) { 5273 this.$media.attr('data-youtube-id',$newItem.attr('data-youtube-id')); 5274 } 5275 5276 // add new <source> elements from playlist data 5277 var $sourceSpans = $newItem.children('span.able-source'); 5278 if ($sourceSpans.length) { 5279 $sourceSpans.each(function() { 5280 if (thisObj.hasAttr($(this),'data-src')) { 5281 // this is the only required attribute 5282 var $newSource = $('<source>',{ 5283 'src': $(this).attr('data-src') 5284 }); 5285 if (thisObj.hasAttr($(this),'data-type')) { 5286 $newSource.attr('type',$(this).attr('data-type')); 5287 } 5288 if (thisObj.hasAttr($(this),'data-desc-src')) { 5289 $newSource.attr('data-desc-src',$(this).attr('data-desc-src')); 5290 } 5291 if (thisObj.hasAttr($(this),'data-sign-src')) { 5292 $newSource.attr('data-sign-src',$(this).attr('data-sign-src')); 5293 } 5294 thisObj.$media.append($newSource); 5295 } 5296 }); 5297 } 5298 5299 // add new <track> elements from playlist data 5300 var $trackSpans = $newItem.children('span.able-track'); 5301 if ($trackSpans.length) { 5302 // for each element in $trackSpans, create a new <track> element 5303 $trackSpans.each(function() { 5304 if (thisObj.hasAttr($(this),'data-src') && 5305 thisObj.hasAttr($(this),'data-kind') && 5306 thisObj.hasAttr($(this),'data-srclang')) { 5307 // all required attributes are present 5308 var $newTrack = $('<track>',{ 5309 'src': $(this).attr('data-src'), 5310 'kind': $(this).attr('data-kind'), 5311 'srclang': $(this).attr('data-srclang') 5312 }); 5313 if (thisObj.hasAttr($(this),'data-label')) { 5314 $newTrack.attr('label',$(this).attr('data-label')); 5315 } 5316 if (thisObj.hasAttr($(this),'data-desc')) { 5317 $newTrack.attr('data-desc',$(this).attr('data-desc')); 5318 } 5319 thisObj.$media.append($newTrack); 5320 } 5321 }); 5322 } 5323 5324 itemTitle = $newItem.text(); 5325 if (this.hasAttr($newItem,'lang')) { 5326 itemLang = $newItem.attr('lang'); 5327 } 5328 // Update relevant arrays 5329 this.$sources = this.$media.find('source'); 5330 5331 // recreate player, informed by new attributes and track elements 5332 if (this.recreatingPlayer) { 5333 // stopgap to prevent multiple firings of recreatePlayer() 5334 return; 5335 } 5336 this.recreatePlayer().then(function() { 5337 5338 // update playlist to indicate which item is playing 5339 thisObj.$playlist.removeClass('able-current') 5340 .children('button').removeAttr('aria-current'); 5341 thisObj.$playlist.eq(sourceIndex).addClass('able-current') 5342 .children('button').attr('aria-current','true'); 5343 5344 // update Now Playing div 5345 if (thisObj.showNowPlaying === true) { 5346 if (typeof thisObj.$nowPlayingDiv !== 'undefined') { 5347 nowPlayingSpan = $('<span>'); 5348 if (typeof itemLang !== 'undefined') { 5349 nowPlayingSpan.attr('lang',itemLang); 5350 } 5351 nowPlayingSpan.html('<span>' + thisObj.tt.selectedTrack + ':</span>' + itemTitle); 5352 thisObj.$nowPlayingDiv.html(nowPlayingSpan); 5353 } 5354 } 5355 5356 // if thisObj.swappingSrc is true, media will autoplay when ready 5357 if (thisObj.initializing) { // this is the first track - user hasn't pressed play yet 5358 thisObj.swappingSrc = false; 5359 } 5360 else { 5361 if (thisObj.player === 'html5') { 5362 if (!thisObj.loadingMedia) { 5363 thisObj.media.load(); 5364 thisObj.loadingMedia = true; 5365 } 5366 } 5367 else if (thisObj.player === 'youtube') { 5368 thisObj.okToPlay = true; 5369 } 5370 } 5371 thisObj.initializing = false; 5372 thisObj.playerCreated = true; // remains true until browser is refreshed 5373 }); 5374 }; 5375 5376 AblePlayer.prototype.deletePlayer = function(context) { 5377 5378 // remove player components that need to be rebuilt 5379 // after swapping media sources that have different durations 5380 // or explicitly declared data-desc attributes 5381 5382 // Context is one of the following: 5383 // playlist - called from cuePlaylistItem() 5384 // swap-desc-html - called from swapDescription with this.player == 'html' 5385 // swap-desc-youtube - called from swapDescription with this.player == 'youtube' 5386 // swap-desc-vimeo - called from swapDescription with this.player == 'vimeo' 5387 5388 if (this.player === 'youtube' && this.youTubePlayer) { 5389 this.youTubePlayer.destroy(); 5390 } 5391 5392 if (this.player === 'vimeo' && this.vimeoPlayer) { 5393 this.vimeoPlayer.destroy(); 5394 } 5395 5396 5397/* TODO - Investigate: when is this needed? 5398 // remove previous video's attributes and child elements from media element 5399 this.$media.removeAttr('poster width height'); 5400 this.$media.empty(); 5401*/ 5402 // Empty elements that will be rebuilt 5403 this.$controllerDiv.empty(); 5404 // this.$statusBarDiv.empty(); 5405 // this.$timer.empty(); 5406 this.$elapsedTimeContainer.empty().text('0:00'); // span.able-elapsedTime 5407 this.$durationContainer.empty(); // span.able-duration 5408 5409 // Remove popup windows and modal dialogs; these too will be rebuilt 5410 if (this.$signWindow) { 5411 this.$signWindow.remove(); 5412 } 5413 if (this.$transcriptArea) { 5414 this.$transcriptArea.remove(); 5415 } 5416 $('.able-modal-dialog').remove(); 5417 5418 // Remove caption and description wrappers 5419 if (this.$captionsWrapper) { 5420 this.$captionsWrapper.remove(); 5421 } 5422 if (this.$descDiv) { 5423 this.$descDiv.remove(); 5424 } 5425 5426 // reset key variables 5427 this.hasCaptions = false;
5428 this.hasChapters = false; 5429 this.hasDescTracks = false; 5430 this.hasOpenDesc = false; 5431 this.hasClosedDesc = false; 5432 5433 this.captionsPopup = null; 5434 this.chaptersPopup = null; 5435 this.transcriptType = null; 5436 5437 this.playerDeleted = true; // will reset to false in recreatePlayer() 5438 }; 5439 5440 AblePlayer.prototype.getButtonTitle = function(control) { 5441 5442 if (control === 'playpause') { 5443 return this.tt.play; 5444 } 5445 else if (control === 'play') { 5446 return this.tt.play; 5447 } 5448 else if (control === 'pause') { 5449 return this.tt.pause; 5450 } 5451 else if (control === 'restart') { 5452 return this.tt.restart; 5453 } 5454 else if (control === 'previous') { 5455 return this.tt.prevTrack; 5456 } 5457 else if (control === 'next') { 5458 return this.tt.nextTrack; 5459 } 5460 else if (control === 'rewind') { 5461 return this.tt.rewind; 5462 } 5463 else if (control === 'forward') { 5464 return this.tt.forward; 5465 } 5466 else if (control === 'captions') { 5467 if (this.captions.length > 1) { 5468 return this.tt.captions; 5469 } 5470 else { 5471 if (this.captionsOn) { 5472 return this.tt.hideCaptions; 5473 } 5474 else { 5475 return this.tt.showCaptions; 5476 } 5477 } 5478 } 5479 else if (control === 'descriptions') { 5480 if (this.descOn) { 5481 return this.tt.turnOffDescriptions; 5482 } 5483 else { 5484 return this.tt.turnOnDescriptions; 5485 } 5486 } 5487 else if (control === 'transcript') { 5488 if (this.$transcriptDiv.is(':visible')) { 5489 return this.tt.hideTranscript; 5490 } 5491 else { 5492 return this.tt.showTranscript; 5493 } 5494 } 5495 else if (control === 'chapters') { 5496 return this.tt.chapters; 5497 } 5498 else if (control === 'sign') { 5499 return this.tt.sign; 5500 } 5501 else if (control === 'volume') { 5502 return this.tt.volume; 5503 } 5504 else if (control === 'faster') { 5505 return this.tt.faster; 5506 } 5507 else if (control === 'slower') { 5508 return this.tt.slower; 5509 } 5510 else if (control === 'preferences') { 5511 return this.tt.preferences; 5512 } 5513 else if (control === 'help') { 5514 // return this.tt.help; 5515 } 5516 else if (control === 'fullscreen') { 5517 if (!this.fullscreen) { 5518 return this.tt.enterFullscreen; 5519 } 5520 else { 5521 return this.tt.exitFullscreen; 5522 } 5523 } 5524 else { 5525 // there should be no other controls, but just in case: 5526 // return the name of the control with first letter in upper case 5527 // ultimately will need to get a translated label from this.tt 5528 if (this.debug) { 5529 console.log('Found an untranslated label: ' + control); 5530 } 5531 return control.charAt(0).toUpperCase() + control.slice(1); 5532 } 5533 }; 5534 5535 5536})(jQuery); 5537 5538(function ($) { 5539 // Loads files referenced in track elements, and performs appropriate setup. 5540 // For example, captions and text descriptions. 5541 // This will be called whenever the player is recreated. 5542 // Added in v2.2.23: Also handles YouTube caption tracks 5543 5544 AblePlayer.prototype.setupTracks = function () { 5545 5546 var thisObj, deferred, promise, loadingPromises, loadingPromise, 5547 i, tracks, track, kind; 5548 5549 thisObj = this; 5550 5551 deferred = new $.Deferred(); 5552 promise = deferred.promise(); 5553 5554 loadingPromises = []; 5555 5556 if ($('#able-vts').length) { 5557 // Page includes a container for a VTS instance 5558 this.vtsTracks = []; 5559 this.hasVts = true; 5560 } else { 5561 this.hasVts = false; 5562 } 5563 5564 // Source array for populating the above arrays 5565 // varies, depending on whether there are dedicated description tracks 5566 if (this.hasDescTracks && this.descOn) { 5567 tracks = this.altTracks; 5568 } else { 5569 tracks = this.tracks; 5570 } 5571 for (i = 0; i < tracks.length; i++) { 5572 5573 track = tracks[i]; 5574 kind = track.kind; 5575 5576 if (!track.src) { 5577 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 5578 // skip all the hullabaloo and go straight to setupCaptions 5579 thisObj.setupCaptions(track); 5580 } else { 5581 // Nothing to load! 5582 // Skip this track; move on to next i 5583 } 5584 continue; 5585 } 5586 var trackSrc = track.src; 5587 loadingPromise = this.loadTextObject(track.src); // resolves with src, trackText 5588 loadingPromises.push(loadingPromise.catch(function (src) { 5589 console.warn('Failed to load captions track from ' + src); 5590 })); 5591 loadingPromise.then((function (track, kind) { 5592 var trackSrc = track.src; 5593 var trackLang = track.language; 5594 var trackLabel = track.label; 5595 var trackDesc = track.desc; 5596 5597 return function (trackSrc, trackText) { 5598 // these are the two vars returned from loadTextObject 5599 5600 var trackContents = trackText; 5601 var cues = thisObj.parseWebVTT(trackSrc, trackContents).cues; 5602 if (thisObj.hasVts) { 5603 5604 // setupVtsTracks() is in vts.js 5605 thisObj.setupVtsTracks(kind, trackLang, trackDesc, trackLabel, trackSrc, trackContents); 5606 } 5607 if (kind === 'captions' || kind === 'subtitles') { 5608 thisObj.setupCaptions(track, cues); 5609 } else if (kind === 'descriptions') { 5610 thisObj.setupDescriptions(track, cues); 5611 } else if (kind === 'chapters') { 5612 thisObj.setupChapters(track, cues); 5613 } else if (kind === 'metadata') { 5614 thisObj.setupMetadata(track, cues); 5615 } 5616 } 5617 })(track, kind)); 5618 } 5619 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 5620 deferred.resolve(); 5621 } 5622 else { 5623 $.when.apply($, loadingPromises).then(function () { 5624 deferred.resolve(); 5625 }); 5626 } 5627 return promise; 5628 }; 5629 5630 AblePlayer.prototype.getTracks = function () { 5631 5632 // define an array tracks with the following structure: 5633 // kind - string, e.g. "captions", "descriptions" 5634 // src - string, URL of WebVTT source file
vendor: 2,390 bytes, lines 5635-5705
5635 // language - string, lang code 5636 // label - string to display, e.g., in CC menu 5637 // def - Boolean, true if this is the default track 5638 // cues - array with startTime, endTime, and payload 5639 // desc - Boolean, true if track includes a data-desc attribute 5640 5641 var thisObj, deferred, promise, captionTracks, altCaptionTracks, 5642 trackLang, trackLabel, isDefault, forDesc, hasDefault, hasTrackInDefLang, 5643 trackFound, i, j, capLabel, inserted; 5644 5645 thisObj = this; 5646 hasDefault = false; 5647 5648 deferred = new $.Deferred(); 5649 promise = deferred.promise(); 5650 5651 this.$tracks = this.$media.find('track'); 5652 this.tracks = []; // only includes tracks that do NOT have data-desc 5653 this.altTracks = []; // only includes tracks that DO have data-desc 5654 5655 // Arrays for each kind, to be populated later 5656 this.captions = []; 5657 this.descriptions = []; 5658 this.chapters = []; 5659 this.meta = []; 5660 5661 this.hasCaptionsTrack = false; // will change to true if one or more tracks has kind="captions" 5662 this.hasDescTracks = false; // will change to true if one or more tracks has data-desc 5663 5664 if (this.$tracks.length) { 5665 5666 this.usingYouTubeCaptions = false; 5667 // create object from HTML5 tracks 5668 this.$tracks.each(function (index, element) { 5669 5670 if ($(this).attr('kind') === 'captions') { 5671 thisObj.hasCaptionsTrack = true; 5672 } 5673 else if ($(this).attr('kind') === 'descriptions') { 5674 thisObj.hasClosedDesc = true; 5675 } 5676 5677 // srcLang should always be included with <track>, but HTML5 spec doesn't require it 5678 // if not provided, assume track is the same language as the default player language 5679 if ($(this).attr('srclang')) { 5680 trackLang = $(this).attr('srclang'); 5681 } 5682 else { 5683 trackLang = thisObj.lang; 5684 } 5685 if ($(this).attr('label')) { 5686 trackLabel = $(this).attr('label'); 5687 } 5688 else { 5689 trackLabel = thisObj.getLanguageName(trackLang); 5690 } 5691 5692 if (typeof $(this).attr('default') !== 'undefined' && !hasDefault) { 5693 isDefault = true; 5694 hasDefault = true; 5695 } 5696 else if (trackLang === thisObj.lang) { 5697 // this track is in the default lang of the player 5698 // save this for later 5699 // if there is no other default track specified 5700 // this will be the default 5701 hasTrackInDefLang = true; 5702 isDefault = false; // for now; this could change if there's no default attribute 5703 } 5704 else { 5705 isDefault = false;
5706 } 5707 if (isDefault) { 5708 // this.captionLang will also be the default language for non-caption tracks 5709 thisObj.captionLang = trackLang; 5710 } 5711 5712 if ($(this).data('desc') !== undefined) { 5713 forDesc = true; 5714 thisObj.hasDescTracks = true; 5715 } 5716 else { 5717 forDesc = false; 5718 } 5719 if (forDesc) { 5720 thisObj.altTracks.push({ 5721 'kind': $(this).attr('kind'), 5722 'src': $(this).attr('src'), 5723 'language': trackLang, 5724 'label': trackLabel, 5725 'def': isDefault, 5726 'desc': forDesc 5727 }); 5728 } else { 5729 thisObj.tracks.push({ 5730 'kind': $(this).attr('kind'), 5731 'src': $(this).attr('src'), 5732 'language': trackLang, 5733 'label': trackLabel, 5734 'def': isDefault, 5735 'desc': forDesc 5736 }); 5737 } 5738 5739 if (index == thisObj.$tracks.length - 1) { 5740 // This is the last track. 5741 if (!hasDefault) { 5742 if (hasTrackInDefLang) { 5743 thisObj.captionLang = thisObj.lang; 5744 trackFound = false; 5745 i = 0; 5746 while (i < thisObj.tracks.length && !trackFound) { 5747 if (thisObj.tracks[i]['language'] === thisObj.lang) { 5748 thisObj.tracks[i]['def'] = true; 5749 trackFound = true; 5750 } 5751 i++; 5752 } 5753 } 5754 else { 5755 // use the first track 5756 thisObj.tracks[0]['def'] = true; 5757 thisObj.captionLang = thisObj.tracks[0]['language']; 5758 } 5759 } 5760 // Remove 'default' attribute from all <track> elements 5761 // This data has already been saved to this.tracks 5762 // and some browsers will display the default captions, 5763 // despite all standard efforts to suppress them 5764 thisObj.$media.find('track').removeAttr('default'); 5765 } 5766 }); 5767 } 5768 if (!this.$tracks.length || !this.hasCaptionsTrack) { 5769 // this media has no track elements 5770 // if this is a youtube or vimeo player, check there for captions/subtitles 5771 if (this.player === 'youtube') { 5772 this.getYouTubeCaptionTracks(this.youTubeId).then(function() { 5773 if (thisObj.hasCaptions) { 5774 thisObj.usingYouTubeCaptions = true; 5775 if (thisObj.$captionsWrapper) { 5776 thisObj.$captionsWrapper.remove(); 5777 } 5778 } 5779 deferred.resolve(); 5780 }); 5781 } 5782 else if (this.player === 'vimeo') { 5783 this.getVimeoCaptionTracks().then(function() { 5784 if (thisObj.hasCaptions) { 5785 thisObj.usingVimeoCaptions = true; 5786 if (thisObj.$captionsWrapper) { 5787 thisObj.$captionsWrapper.remove(); 5788 } 5789 } 5790 deferred.resolve(); 5791 }); 5792 } 5793 else { 5794 // this is neither YouTube nor Vimeo 5795 // there just ain't no tracks (captions or otherwise) 5796 this.hasCaptions = false; 5797 if (thisObj.$captionsWrapper) { 5798 thisObj.$captionsWrapper.remove(); 5799 } 5800 deferred.resolve(); 5801 } 5802 } 5803 else { 5804 // there is at least one track with kind="captions" 5805 deferred.resolve(); 5806 5807 } 5808 return promise; 5809 5810 }; 5811 5812 AblePlayer.prototype.setupCaptions = function (track, cues) { 5813 5814 // Setup player for display of captions (one track at a time) 5815 var thisObj, captions, inserted, i, capLabel; 5816 5817 // Insert track into captions array 5818 // in its proper alphabetical sequence by label 5819 if (typeof cues === 'undefined') { 5820 cues = null; 5821 } 5822 5823 if (this.usingYouTubeCaptions || this.usingVimeoCaptions) { 5824 // this.captions has already been populated 5825 // For YouTube, this happens in youtube.js > getYouTubeCaptionTracks() 5826 // For VImeo, this happens in vimeo.js > getVimeoCaptionTracks() 5827 // So, nothing to do here... 5828 } 5829 else { 5830 5831 if (this.captions.length === 0) { // this is the first 5832 this.captions.push({ 5833 'language': track.language, 5834 'label': track.label, 5835 'def': track.def, 5836 'cues': cues 5837 }); 5838 } 5839 else { // there are already captions in the array 5840 inserted = false; 5841 for (i = 0; i < this.captions.length; i++) { 5842 capLabel = track.label; 5843 if (capLabel.toLowerCase() < this.captions[i].label.toLowerCase()) { 5844 // insert before track i 5845 this.captions.splice(i, 0, { 5846 'language': track.language, 5847 'label': track.label, 5848 'def': track.def, 5849 'cues': cues 5850 }); 5851 inserted = true; 5852 break; 5853 } 5854 } 5855 if (!inserted) { 5856 // just add track to the end 5857 this.captions.push({ 5858 'language': track.language, 5859 'label': track.label, 5860 'def': track.def, 5861 'cues': cues 5862 }); 5863 } 5864 } 5865 } 5866 5867 // there are captions available 5868 this.hasCaptions = true; 5869 this.currentCaption = -1; 5870 if (this.prefCaptions === 1) { 5871 this.captionsOn = true; 5872 } else if (this.prefCaptions === 0) { 5873 this.captionsOn = false;
5874 } else { 5875 // user has no prefs. Use default state. 5876 if (this.defaultStateCaptions === 1) { 5877 this.captionsOn = true; 5878 } else { 5879 this.captionsOn = false; 5880 } 5881 } 5882 if (this.mediaType === 'audio' && this.captionsOn) { 5883 this.$captionsContainer.removeClass('captions-off'); 5884 } 5885 5886 if (!this.$captionsWrapper || 5887 (this.$captionsWrapper && !($.contains(this.$ableDiv[0], this.$captionsWrapper[0])))) { 5888 // captionsWrapper either doesn't exist, or exists in an orphaned state 5889 // Either way, it needs to be rebuilt... 5890 this.$captionsDiv = $('<div>', { 5891 'class': 'able-captions', 5892 }); 5893 this.$captionsWrapper = $('<div>', { 5894 'class': 'able-captions-wrapper', 5895 'aria-hidden': 'true' 5896 }).hide(); 5897 if (this.prefCaptionsPosition === 'below') { 5898 this.$captionsWrapper.addClass('able-captions-below'); 5899 } else { 5900 this.$captionsWrapper.addClass('able-captions-overlay'); 5901 } 5902 this.$captionsWrapper.append(this.$captionsDiv); 5903 this.$captionsContainer.append(this.$captionsWrapper); 5904 } 5905 }; 5906 5907 AblePlayer.prototype.setupDescriptions = function (track, cues) { 5908 5909 // called via setupTracks() only if there is track with kind="descriptions" 5910 // prepares for delivery of text description , in case it's needed 5911 // whether and how it's delivered is controlled within description.js > initDescription() 5912 5913 this.hasClosedDesc = true; 5914 this.currentDescription = -1; 5915 this.descriptions.push({ 5916 cues: cues, 5917 language: track.language 5918 }); 5919 }; 5920 5921 AblePlayer.prototype.setupChapters = function (track, cues) { 5922 5923 // NOTE: WebVTT supports nested timestamps (to form an outline) 5924 // This is not currently supported. 5925 5926 this.hasChapters = true; 5927 this.chapters.push({ 5928 cues: cues, 5929 language: track.language 5930 }); 5931 }; 5932 5933 AblePlayer.prototype.setupMetadata = function (track, cues, trackDesc) { 5934 5935 if (this.metaType === 'text') { 5936 // Metadata is only supported if data-meta-div is provided 5937 // The player does not display metadata internally 5938 if (this.metaDiv) { 5939 if ($('#' + this.metaDiv)) { 5940 // container exists 5941 this.$metaDiv = $('#' + this.metaDiv); 5942 this.hasMeta = true; 5943 this.meta = cues; 5944 } 5945 } 5946 } else if (this.metaType === 'selector') { 5947 this.hasMeta = true; 5948 this.visibleSelectors = []; 5949 this.meta = cues; 5950 } 5951 }; 5952 5953 AblePlayer.prototype.loadTextObject = function (src) { 5954 5955 // TODO: Incorporate the following function, moved from setupTracks() 5956 // convert XMl/TTML captions file 5957 /* 5958 if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml'))) { 5959 trackContents = thisObj.ttml2webvtt(trackText); 5960 } 5961 */ 5962 var deferred, promise, thisObj, $tempDiv; 5963 5964 deferred = new $.Deferred(); 5965 promise = deferred.promise(); 5966 thisObj = this; 5967 5968 // create a temp div for holding data 5969 $tempDiv = $('<div>', { 5970 style: 'display:none' 5971 }); 5972 $tempDiv.load(src, function (trackText, status, req) { 5973 if (status === 'error') { 5974 if (thisObj.debug) { 5975 console.log('error reading file ' + src + ': ' + status); 5976 } 5977 deferred.reject(src); 5978 } else { 5979 deferred.resolve(src, trackText); 5980 } 5981 $tempDiv.remove(); 5982 }); 5983 return promise; 5984 }; 5985 5986})(jQuery); 5987 5988(function ($) { 5989 AblePlayer.prototype.initYouTubePlayer = function () { 5990 5991 var thisObj, deferred, promise, youTubeId, googleApiPromise, json; 5992 thisObj = this; 5993 5994 deferred = new $.Deferred(); 5995 promise = deferred.promise(); 5996 5997 this.youTubePlayerReady = false; 5998 5999 // if a described version is available && user prefers desription 6000 // init player using the described version 6001 if (this.youTubeDescId && this.prefDesc) { 6002 youTubeId = this.youTubeDescId; 6003 } 6004 else { 6005 youTubeId = this.youTubeId; 6006 } 6007 this.activeYouTubeId = youTubeId; 6008 if (AblePlayer.youTubeIframeAPIReady) { 6009 // Script already loaded and ready. 6010 thisObj.finalizeYoutubeInit().then(function() { 6011 deferred.resolve(); 6012 }); 6013 } 6014 else { 6015 // Has another player already started loading the script? If so, abort... 6016 if (!AblePlayer.loadingYouTubeIframeAPI) { 6017 $.getScript('https://www.youtube.com/iframe_api').fail(function () { 6018 deferred.fail(); 6019 }); 6020 } 6021 6022 // Otherwise, keeping waiting for script load event... 6023 $('body').on('youTubeIframeAPIReady', function () { 6024 thisObj.finalizeYoutubeInit().then(function() { 6025 deferred.resolve(); 6026 }); 6027 }); 6028 } 6029 return promise; 6030 }; 6031 6032 AblePlayer.prototype.finalizeYoutubeInit = function () { 6033 6034 // This is called once we're sure the Youtube iFrame API is loaded -- see above 6035 6036 var deferred, promise, thisObj, containerId, ccLoadPolicy, videoDimensions, autoplay; 6037 6038 deferred = new $.Deferred(); 6039 promise = deferred.promise(); 6040 6041 thisObj = this; 6042 6043 containerId = this.mediaId + '_youtube'; 6044
6045 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 6046 // NOTE: Tried the following in place of the above in January 2016 6047 // because in some cases two videos were being added to the DOM 6048 // However, once v2.2.23 was fairly stable, unable to reproduce that problem 6049 // so maybe it's not an issue. This is preserved here temporarily, just in case it's needed... 6050 // thisObj.$mediaContainer.html($('<div>').attr('id', containerId)); 6051 6052 // cc_load_policy: 6053 // 0 - show captions depending on user's preference on YouTube 6054 // 1 - show captions by default, even if the user has turned them off 6055 // IMPORTANT: This *must* be set to 1 or some browsers 6056 // fail to load any texttracks (observed in Chrome, not in Firefox) 6057 ccLoadPolicy = 1; 6058 6059 if (this.okToPlay) { 6060 autoplay = 1; 6061 } 6062 else { 6063 autoplay = 0; 6064 } 6065 6066 // Documentation https://developers.google.com/youtube/player_parameters 6067 6068 if (typeof this.captionLang == 'undefined') { 6069 // init using the default player lang 6070 this.captionLang = this.lang; 6071 } 6072 this.youTubePlayer = new YT.Player(containerId, { 6073 videoId: this.activeYouTubeId, 6074 host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com', 6075 playerVars: { 6076 autoplay: autoplay, 6077 enablejsapi: 1, 6078 disableKb: 1, // disable keyboard shortcuts, using our own 6079 playsinline: this.playsInline, 6080 start: this.startTime, 6081 controls: 0, // no controls, using our own 6082 cc_load_policy: ccLoadPolicy, 6083 cc_lang_pref: this.captionLang, // set the caption language 6084 hl: this.lang, // set the UI language to match Able Player 6085 modestbranding: 1, // no YouTube logo in controller 6086 rel: 0, // when video ends, show only related videos from same channel (1 shows any) 6087 iv_load_policy: 3 // do not show video annotations 6088 }, 6089 events: { 6090 onReady: function () { 6091 thisObj.youTubePlayerReady = true; 6092 if (!thisObj.playerWidth || !thisObj.playerHeight) { 6093 thisObj.getYouTubeDimensions(); 6094 } 6095 if (thisObj.playerWidth && thisObj.playerHeight) { 6096 thisObj.youTubePlayer.setSize(thisObj.playerWidth,thisObj.playerHeight); 6097 thisObj.$ableWrapper.css({ 6098 'width': thisObj.playerWidth + 'px' 6099 }); 6100 } 6101 if (thisObj.swappingSrc) { 6102 // swap is now complete 6103 thisObj.swappingSrc = false; 6104 thisObj.restoreFocus(); 6105 thisObj.cueingPlaylistItem = false; 6106 if (thisObj.playing || thisObj.okToPlay) { 6107 // resume playing 6108 thisObj.playMedia(); 6109 } 6110 } 6111 if (thisObj.userClickedPlaylist) { 6112 thisObj.userClickedPlaylist = false; // reset 6113 } 6114 if (thisObj.recreatingPlayer) { 6115 thisObj.recreatingPlayer = false; // reset 6116 } 6117 deferred.resolve(); 6118 }, 6119 onError: function (x) { 6120 deferred.fail(); 6121 }, 6122 onStateChange: function (x) { 6123 thisObj.getPlayerState().then(function(playerState) { 6124 // values of playerState: 'playing','paused','buffering','ended' 6125 if (playerState === 'playing') { 6126 thisObj.playing = true; 6127 thisObj.startedPlaying = true; 6128 thisObj.paused = false; 6129 } 6130 else if (playerState == 'ended') { 6131 thisObj.onMediaComplete(); 6132 } 6133 else { 6134 thisObj.playing = false; 6135 thisObj.paused = true; 6136 } 6137 if (thisObj.stoppingYouTube && playerState === 'paused') { 6138 if (typeof thisObj.$posterImg !== 'undefined') { 6139 thisObj.$posterImg.show(); 6140 } 6141 thisObj.stoppingYouTube = false; 6142 thisObj.seeking = false; 6143 thisObj.playing = false; 6144 thisObj.paused = true; 6145 } 6146 }); 6147 // If caption tracks are hosted locally, but are also available on YouTube, 6148 // we need to turn them off on YouTube or there will be redundant captions 6149 // This is the most reliable event on which to unload the caption module 6150 if (thisObj.player === 'youtube' && !thisObj.usingYouTubeCaptions) { 6151 if (thisObj.youTubePlayer.getOptions('captions')) { 6152 thisObj.youTubePlayer.unloadModule('captions'); 6153 } 6154 } 6155 }, 6156 onPlaybackQualityChange: function () {
6157 // do something 6158 }, 6159 } 6160 }); 6161 if (!this.hasPlaylist) { 6162 // remove the media element, since YouTube replaces that with its own element in an iframe 6163 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 6164 this.$media.remove(); 6165 } 6166 return promise; 6167 }; 6168 6169 AblePlayer.prototype.getYouTubeDimensions = function (youTubeContainerId) { 6170 6171 // The YouTube iframe API does not have a getSize() of equivalent method 6172 // so, need to get dimensions from YouTube's iframe 6173 6174 var $iframe, width, height; 6175 6176 $iframe = this.$ableWrapper.find('iframe'); 6177 if (typeof $iframe !== 'undefined') { 6178 if ($iframe.prop('width')) { 6179 width = $iframe.prop('width'); 6180 if ($iframe.prop('height')) { 6181 height = $iframe.prop('height'); 6182 this.resizePlayer(width,height); 6183 } 6184 } 6185 } 6186 }; 6187 6188 AblePlayer.prototype.getYouTubeCaptionTracks = function (youTubeId) { 6189 6190 // get data via YouTube IFrame Player API, and push data to this.tracks & this.captions 6191 // NOTE: Caption tracks are not available through the IFrame Player API 6192 // until AFTER the video has started playing. 6193 // Therefore, this function plays the video briefly in order to load the captions module 6194 // then stops the video and collects the data needed to build the cc menu 6195 // This is stupid, but seemingly unavoidable. 6196 // Caption tracks could be obtained through the YouTube Data API 6197 // but this required authors to have a Google API key, 6198 // which would complicate Able Player installation 6199 6200 var deferred = new $.Deferred(); 6201 var promise = deferred.promise(); 6202 6203 var thisObj, ytTracks, i, trackLang, trackLabel, isDefaultTrack; 6204 6205 thisObj = this; 6206
vendor: 4,768 bytes, lines 6207-6331
6207 if (!this.youTubePlayer.getOption('captions','tracklist')) { 6208 6209 // no tracks were found, probably because the captions module hasn't loaded 6210 // play video briefly (required in order to load the captions module) 6211 // and after the apiChange event is triggered, try again to retreive tracks 6212 this.youTubePlayer.addEventListener('onApiChange',function(x) { 6213 6214 // getDuration() also requires video to play briefly 6215 // so, let's set that while we're here 6216 thisObj.duration = thisObj.youTubePlayer.getDuration(); 6217 6218 if (thisObj.loadingYouTubeCaptions) { 6219 // loadingYouTubeCaptions is a stopgap in case onApiChange is called more than once 6220 ytTracks = thisObj.youTubePlayer.getOption('captions','tracklist'); 6221 if (!thisObj.okToPlay) { 6222 // Don't stopVideo() - that cancels loading 6223 // Just pause 6224 // No need to seekTo(0) - so little time has passed it isn't noticeable to the user 6225 thisObj.youTubePlayer.pauseVideo(); 6226 } 6227 if (ytTracks && ytTracks.length) { 6228 // Step through ytTracks and add them to global tracks array 6229 // Note: Unlike YouTube Data API, the IFrame Player API only returns 6230 // tracks that are published, and does NOT include ASR captions 6231 // So, no additional filtering is required 6232 for (i=0; i < ytTracks.length; i++) { 6233 trackLang = ytTracks[i].languageCode; 6234 trackLabel = ytTracks[i].languageName; // displayName and languageName seem to always have the same value 6235 isDefaultTrack = false; 6236 if (typeof thisObj.captionLang !== 'undefined') { 6237 if (trackLang === thisObj.captionLang) { 6238 isDefaultTrack = true; 6239 } 6240 } 6241 else if (typeof thisObj.lang !== 'undefined') { 6242 if (trackLang === thisObj.lang) { 6243 isDefaultTrack = true; 6244 } 6245 } 6246 thisObj.tracks.push({ 6247 'kind': 'captions', 6248 'language': trackLang, 6249 'label': trackLabel, 6250 'def': isDefaultTrack 6251 }); 6252 thisObj.captions.push({ 6253 'language': trackLang, 6254 'label': trackLabel, 6255 'def': isDefaultTrack, 6256 'cues': null 6257 }); 6258 } 6259 thisObj.hasCaptions = true; 6260 // setupPopups again with new captions array, replacing original 6261 thisObj.setupPopups('captions'); 6262 } 6263 else { 6264 // there are no YouTube captions 6265 thisObj.usingYouTubeCaptions = false; 6266 thisObj.hasCaptions = false; 6267 } 6268 thisObj.loadingYouTubeCaptions = false; 6269 if (thisObj.okToPlay) { 6270 thisObj.youTubePlayer.playVideo(); 6271 } 6272 } 6273 if (thisObj.captionLangPending) { 6274 // user selected a new caption language prior to playback starting 6275 // set it now 6276 thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLangPending}); 6277 thisObj.captionLangPending = null; 6278 } 6279 if (typeof thisObj.prefCaptionsSize !== 'undefined') { 6280 // set the default caption size 6281 // this doesn't work until the captions module is loaded 6282 thisObj.youTubePlayer.setOption('captions','fontSize',thisObj.translatePrefs('size',thisObj.prefCaptionsSize,'youtube')); 6283 } 6284 deferred.resolve(); 6285 }); 6286 // Trigger the above event listener by briefly playing the video 6287 this.loadingYouTubeCaptions = true; 6288 this.youTubePlayer.playVideo(); 6289 } 6290 return promise; 6291 }; 6292 6293 AblePlayer.prototype.getYouTubeTimedTextUrl = function (youTubeId, trackName, trackLang) { 6294 6295 // return URL for retrieving WebVTT source via YouTube's timedtext API 6296 // Note: This API seems to be undocumented, and could break anytime 6297 // UPDATE: Google removed this API on November 10, 2021 6298 // This function is no longer called, but is preserved here for reference 6299 var url = 'https://www.youtube.com/api/timedtext?fmt=vtt'; 6300 url += '&v=' + youTubeId; 6301 url += '&lang=' + trackLang; 6302 // if track has a value in the name field, it's *required* in the URL 6303 if (trackName !== '') { 6304 url += '&name=' + trackName; 6305 } 6306 return url; 6307 }; 6308 6309 AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) { 6310 6311 // return a URL for retrieving a YouTube poster image 6312 // supported values of width: 120, 320, 480, 640 6313 6314 var url = 'https://img.youtube.com/vi/' + youTubeId; 6315 if (width == '120') { 6316 // default (small) thumbnail, 120 x 90 6317 return url + '/default.jpg'; 6318 } 6319 else if (width == '320') { 6320 // medium quality thumbnail, 320 x 180 6321 return url + '/hqdefault.jpg'; 6322 } 6323 else if (width == '480') { 6324 // high quality thumbnail, 480 x 360 6325 return url + '/hqdefault.jpg'; 6326 } 6327 else if (width == '640') { 6328 // standard definition poster image, 640 x 480 6329 return url + '/sddefault.jpg'; 6330 } 6331 return false;
6332 }; 6333 6334 AblePlayer.prototype.getYouTubeId = function (url) { 6335 6336 // return a YouTube ID, extracted from a full YouTube URL 6337 // Supported URL patterns (with http or https): 6338 // https://youtu.be/xxx 6339 // https://www.youtube.com/watch?v=xxx 6340 // https://www.youtube.com/embed/xxx 6341 6342 // in all supported patterns, the id is the last 11 characters 6343 var idStartPos, id; 6344 6345 if (url.indexOf('youtu') !== -1) { 6346 // this is a full Youtube URL 6347 url = url.trim(); 6348 idStartPos = url.length - 11; 6349 id = url.substring(idStartPos); 6350 return id; 6351 } 6352 else { 6353 return url; 6354 } 6355}; 6356 6357})(jQuery); 6358 6359(function ($) { 6360 6361 6362 // Events: 6363 // startTracking(event, position) 6364 // tracking(event, position) 6365 // stopTracking(event, position) 6366 6367 window. AccessibleSlider = function(mediaType, div, orientation, length, min, max, bigInterval, label, className, trackingMedia, initialState) { 6368 6369 // mediaType is either 'audio' or 'video' 6370 // div is the host element around which the slider will be built 6371 // orientation is either 'horizontal' or 'vertical' 6372 // length is the width or height of the slider, depending on orientation 6373 // min is the low end of the slider scale 6374 // max is the high end of the slider scale 6375 // bigInterval is the number of steps supported by page up/page down (set to 0 if not supported) 6376 // (smallInterval, defined as nextStep below, is always set to 1) - this is the interval supported by arrow keys 6377 // label is used within an aria-label attribute to identify the slider to screen reader users 6378 // className is used as the root within class names (e.g., 'able-' + classname + '-head') 6379 // trackingMedia is true if this is a media timeline; otherwise false 6380 // initialState is either 'visible' or 'hidden' 6381 6382 var thisObj, coords; 6383 6384 thisObj = this; 6385 6386 // Initialize some variables. 6387 this.position = 0; // Note: position does not change while tracking. 6388 this.tracking = false; 6389 this.trackDevice = null; // 'mouse' or 'keyboard' 6390 this.keyTrackPosition = 0; 6391 this.lastTrackPosition = 0; 6392 this.nextStep = 1; 6393 this.inertiaCount = 0; 6394 6395 this.bodyDiv = $(div); 6396 6397 // Add divs for tracking amount of media loaded and played 6398 if (trackingMedia) { 6399 this.loadedDiv = $('<div></div>'); 6400 this.playedDiv = $('<div></div>'); 6401 } 6402 6403 // Add a seekhead 6404 this.seekHead = $('<div>',{ 6405 'aria-orientation': orientation, 6406 'class': 'able-' + className + '-head' 6407 }); 6408 6409 if (initialState === 'visible') { 6410 this.seekHead.attr('tabindex', '0'); 6411 } 6412 else { 6413 this.seekHead.attr('tabindex', '-1'); 6414 } 6415 // Since head is focusable, it gets the aria roles/titles. 6416 this.seekHead.attr({ 6417 'role': 'slider', 6418 'aria-label': label, 6419 'aria-valuemin': min, 6420 'aria-valuemax': max 6421 }); 6422 6423 this.timeTooltipTimeoutId = null; 6424 this.overTooltip = false; 6425 this.timeTooltip = $('<div>'); 6426 this.bodyDiv.append(this.timeTooltip); 6427 6428 this.timeTooltip.attr('role', 'tooltip'); 6429 this.timeTooltip.addClass('able-tooltip'); 6430 this.timeTooltip.on('mouseenter focus', function(){ 6431 thisObj.overTooltip = true; 6432 clearInterval(thisObj.timeTooltipTimeoutId); 6433 }); 6434 this.timeTooltip.on('mouseleave blur', function(){ 6435 thisObj.overTooltip = false; 6436 $(this).hide(); 6437 }); 6438 this.timeTooltip.hide(); 6439 6440 this.bodyDiv.append(this.loadedDiv); 6441 this.bodyDiv.append(this.playedDiv); 6442 this.bodyDiv.append(this.seekHead); 6443 6444 this.bodyDiv.wrap('<div></div>'); 6445 this.wrapperDiv = this.bodyDiv.parent(); 6446 6447 if (this.skin === 'legacy') { 6448 if (orientation === 'horizontal') { 6449 this.wrapperDiv.width(length); 6450 this.loadedDiv.width(0); 6451 } 6452 else { 6453 this.wrapperDiv.height(length); 6454 this.loadedDiv.height(0); 6455 } 6456 } 6457 this.wrapperDiv.addClass('able-' + className + '-wrapper'); 6458 6459 if (trackingMedia) { 6460 this.loadedDiv.addClass('able-' + className + '-loaded'); 6461 6462 this.playedDiv.width(0); 6463 this.playedDiv.addClass('able-' + className + '-played'); 6464 6465 // Set a default duration. User can call this dynamically if duration changes. 6466 this.setDuration(max); 6467 } 6468 6469 // handle seekHead events 6470 this.seekHead.on('mouseenter mouseleave mousemove mousedown mouseup focus blur touchstart touchmove touchend', function (e) { 6471 6472 coords = thisObj.pointerEventToXY(e); 6473 6474 if (e.type === 'mouseenter' || e.type === 'focus') { 6475 thisObj.overHead = true; 6476 } 6477 else if (e.type === 'mouseleave' || e.type === 'blur') { 6478 thisObj.overHead = false;
vendor: 1,931 bytes, lines 6479-6534
6479 if (!thisObj.overBody && thisObj.tracking && thisObj.trackDevice === 'mouse') { 6480 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 6481 } 6482 } 6483 else if (e.type === 'mousemove' || e.type === 'touchmove') { 6484 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 6485 thisObj.trackHeadAtPageX(coords.x); 6486 } 6487 } 6488 else if (e.type === 'mousedown' || e.type === 'touchstart') { 6489 thisObj.startTracking('mouse', thisObj.pageXToPosition(thisObj.seekHead.offset() + (thisObj.seekHead.width() / 2))); 6490 if (!thisObj.bodyDiv.is(':focus')) { 6491 thisObj.bodyDiv.focus(); 6492 } 6493 e.preventDefault(); 6494 } 6495 else if (e.type === 'mouseup' || e.type === 'touchend') { 6496 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 6497 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 6498 } 6499 } 6500 if (e.type !== 'mousemove' && e.type !== 'mousedown' && e.type !== 'mouseup' && e.type !== 'touchstart' && e.type !== 'touchend') { 6501 thisObj.refreshTooltip(); 6502 } 6503 }); 6504 6505 // handle bodyDiv events 6506 this.bodyDiv.on( 6507 'mouseenter mouseleave mousemove mousedown mouseup keydown keyup touchstart touchmove touchend', function (e) { 6508 6509 coords = thisObj.pointerEventToXY(e); 6510 6511 if (e.type === 'mouseenter') { 6512 thisObj.overBody = true; 6513 thisObj.overBodyMousePos = { 6514 x: coords.x, 6515 y: coords.y 6516 }; 6517 } 6518 else if (e.type === 'mouseleave') { 6519 thisObj.overBody = false; 6520 thisObj.overBodyMousePos = null; 6521 if (!thisObj.overHead && thisObj.tracking && thisObj.trackDevice === 'mouse') { 6522 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 6523 } 6524 } 6525 else if (e.type === 'mousemove' || e.type === 'touchmove') { 6526 thisObj.overBodyMousePos = { 6527 x: coords.x, 6528 y: coords.y 6529 }; 6530 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 6531 thisObj.trackHeadAtPageX(coords.x); 6532 } 6533 } 6534 else if (e.type === 'mousedown' || e.type === 'touchstart') {
6535 thisObj.startTracking('mouse', thisObj.pageXToPosition(coords.x)); 6536 thisObj.trackHeadAtPageX(coords.x); 6537 if (!thisObj.seekHead.is(':focus')) { 6538 thisObj.seekHead.focus(); 6539 } 6540 e.preventDefault(); 6541 } 6542 else if (e.type === 'mouseup' || e.type === 'touchend') { 6543 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 6544 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 6545 } 6546 } 6547 else if (e.type === 'keydown') { 6548 // Home 6549 if (e.which === 36) { 6550 thisObj.trackImmediatelyTo(0); 6551 } 6552 // End 6553 else if (e.which === 35) { 6554 thisObj.trackImmediatelyTo(thisObj.duration); 6555 } 6556 // Left arrow or down arrow 6557 else if (e.which === 37 || e.which === 40) { 6558 thisObj.arrowKeyDown(-1); 6559 } 6560 // Right arrow or up arrow 6561 else if (e.which === 39 || e.which === 38) { 6562 thisObj.arrowKeyDown(1); 6563 } 6564 // Page up 6565 else if (e.which === 33 && bigInterval > 0) { 6566 thisObj.arrowKeyDown(bigInterval); 6567 } 6568 // Page down 6569 else if (e.which === 34 && bigInterval > 0) { 6570 thisObj.arrowKeyDown(-bigInterval); 6571 } 6572 else { 6573 return; 6574 } 6575 e.preventDefault(); 6576 } 6577 else if (e.type === 'keyup') { 6578 if (e.which >= 33 && e.which <= 40) { 6579 if (thisObj.tracking && thisObj.trackDevice === 'keyboard') { 6580 thisObj.stopTracking(thisObj.keyTrackPosition); 6581 } 6582 e.preventDefault(); 6583 } 6584 } 6585 if (!thisObj.overTooltip && e.type !== 'mouseup' && e.type !== 'keydown' && e.type !== 'keydown') { 6586 thisObj.refreshTooltip(); 6587 } 6588 }); 6589 } 6590 6591 AccessibleSlider.prototype.arrowKeyDown = function (multiplier) { 6592 if (this.tracking && this.trackDevice === 'keyboard') { 6593 this.keyTrackPosition = this.boundPos(this.keyTrackPosition + (this.nextStep * multiplier)); 6594 this.inertiaCount += 1; 6595 if (this.inertiaCount === 20) { 6596 this.inertiaCount = 0; 6597 this.nextStep *= 2; 6598 } 6599 this.trackHeadAtPosition(this.keyTrackPosition); 6600 } 6601 else { 6602 this.nextStep = 1; 6603 this.inertiaCount = 0; 6604 this.keyTrackPosition = this.boundPos(this.position + (this.nextStep * multiplier)); 6605 this.startTracking('keyboard', this.keyTrackPosition); 6606 this.trackHeadAtPosition(this.keyTrackPosition); 6607 } 6608 }; 6609/* 6610 AccessibleSlider.prototype.pageUp = function (multiplier) { 6611 if (this.tracking && this.trackDevice === 'keyboard') { 6612 this.keyTrackPosition = this.boundPos(this.keyTrackPosition + (this.nextStep * multiplier)); 6613 this.inertiaCount += 1; 6614 if (this.inertiaCount === 20) { 6615 this.inertiaCount = 0; 6616 this.nextStep *= 2; 6617 } 6618 this.trackHeadAtPosition(this.keyTrackPosition); 6619 } 6620 else { 6621 this.nextStep = 1; 6622 this.inertiaCount = 0; 6623 this.keyTrackPosition = this.boundPos(this.position + (this.nextStep * multiplier)); 6624 this.startTracking('keyboard', this.keyTrackPosition); 6625 this.trackHeadAtPosition(this.keyTrackPosition); 6626 } 6627 }; 6628*/ 6629 AccessibleSlider.prototype.pageXToPosition = function (pageX) { 6630 var offset = pageX - this.bodyDiv.offset().left; 6631 var position = this.duration * (offset / this.bodyDiv.width()); 6632 return this.boundPos(position); 6633 }; 6634 6635 AccessibleSlider.prototype.boundPos = function (position) { 6636 return Math.max(0, Math.min(position, this.duration)); 6637 } 6638 6639 AccessibleSlider.prototype.setDuration = function (duration) { 6640 if (duration !== this.duration) { 6641 this.duration = duration; 6642 this.resetHeadLocation(); 6643 this.seekHead.attr('aria-valuemax', duration); 6644 } 6645 }; 6646 6647 AccessibleSlider.prototype.setWidth = function (width) { 6648 this.wrapperDiv.width(width); 6649 this.resizeDivs(); 6650 this.resetHeadLocation(); 6651 }; 6652 6653 AccessibleSlider.prototype.getWidth = function () { 6654 return this.wrapperDiv.width(); 6655 }; 6656 6657 AccessibleSlider.prototype.resizeDivs = function () { 6658 this.playedDiv.width(this.bodyDiv.width() * (this.position / this.duration)); 6659 this.loadedDiv.width(this.bodyDiv.width() * this.buffered); 6660 }; 6661 6662 // Stops tracking, sets the head location to the current position. 6663 AccessibleSlider.prototype.resetHeadLocation = function () { 6664 var ratio = this.position / this.duration; 6665 var center = this.bodyDiv.width() * ratio; 6666 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 6667 6668 if (this.tracking) { 6669 this.stopTracking(this.position); 6670 } 6671 }; 6672 6673 AccessibleSlider.prototype.setPosition = function (position, updateLive) { 6674 this.position = position; 6675 this.resetHeadLocation(); 6676 if (this.overHead) { 6677 this.refreshTooltip(); 6678 } 6679 this.resizeDivs(); 6680 this.updateAriaValues(position, updateLive); 6681 } 6682 6683 // TODO: Native HTML5 can have several buffered segments, and this actually happens quite often. Change this to display them all. 6684 AccessibleSlider.prototype.setBuffered = function (ratio) {
vendor: 5,641 bytes, lines 6685-6864
6685 if (!isNaN(ratio)) { 6686 this.buffered = ratio; 6687 this.redrawDivs; 6688 } 6689 } 6690 6691 AccessibleSlider.prototype.startTracking = function (device, position) { 6692 if (!this.tracking) { 6693 this.trackDevice = device; 6694 this.tracking = true; 6695 this.bodyDiv.trigger('startTracking', [position]); 6696 } 6697 }; 6698 6699 AccessibleSlider.prototype.stopTracking = function (position) { 6700 this.trackDevice = null; 6701 this.tracking = false; 6702 this.bodyDiv.trigger('stopTracking', [position]); 6703 this.setPosition(position, true); 6704 }; 6705 6706 AccessibleSlider.prototype.trackHeadAtPageX = function (pageX) { 6707 var position = this.pageXToPosition(pageX); 6708 var newLeft = pageX - this.bodyDiv.offset().left - (this.seekHead.width() / 2); 6709 newLeft = Math.max(0, Math.min(newLeft, this.bodyDiv.width() - this.seekHead.width())); 6710 this.lastTrackPosition = position; 6711 this.seekHead.css('left', newLeft); 6712 this.reportTrackAtPosition(position); 6713 }; 6714 6715 AccessibleSlider.prototype.trackHeadAtPosition = function (position) { 6716 var ratio = position / this.duration; 6717 var center = this.bodyDiv.width() * ratio; 6718 this.lastTrackPosition = position; 6719 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 6720 this.reportTrackAtPosition(position); 6721 }; 6722 6723 AccessibleSlider.prototype.reportTrackAtPosition = function (position) { 6724 this.bodyDiv.trigger('tracking', [position]); 6725 this.updateAriaValues(position, true); 6726 }; 6727 6728 AccessibleSlider.prototype.updateAriaValues = function (position, updateLive) { 6729 // TODO: Localize, move to another function. 6730 var pHours = Math.floor(position / 3600); 6731 var pMinutes = Math.floor((position % 3600) / 60); 6732 var pSeconds = Math.floor(position % 60); 6733 6734 var pHourWord = pHours === 1 ? 'hour' : 'hours'; 6735 var pMinuteWord = pMinutes === 1 ? 'minute' : 'minutes'; 6736 var pSecondWord = pSeconds === 1 ? 'second' : 'seconds'; 6737 6738 var descriptionText; 6739 if (pHours > 0) { 6740 descriptionText = pHours + 6741 ' ' + pHourWord + 6742 ', ' + pMinutes + 6743 ' ' + pMinuteWord + 6744 ', ' + pSeconds + 6745 ' ' + pSecondWord; 6746 } 6747 else if (pMinutes > 0) { 6748 descriptionText = pMinutes + 6749 ' ' + pMinuteWord + 6750 ', ' + pSeconds + 6751 ' ' + pSecondWord; 6752 } 6753 else { 6754 descriptionText = pSeconds + ' ' + pSecondWord; 6755 } 6756 6757 /* Comment to stop live region from generating or being used. */ 6758 if (!this.liveAriaRegion) { 6759 this.liveAriaRegion = $('<span>', { 6760 'class': 'able-offscreen', 6761 'aria-live': 'polite' 6762 }); 6763 this.wrapperDiv.append(this.liveAriaRegion); 6764 } 6765 if (updateLive && (this.liveAriaRegion.text() !== descriptionText)) { 6766 this.liveAriaRegion.text(descriptionText); 6767 } 6768 6769 // Uncomment the following lines to use aria values instead of separate live region. 6770 this.seekHead.attr('aria-valuetext', descriptionText); 6771 this.seekHead.attr('aria-valuenow', Math.floor(position).toString()); 6772 }; 6773 6774 AccessibleSlider.prototype.trackImmediatelyTo = function (position) { 6775 this.startTracking('keyboard', position); 6776 this.trackHeadAtPosition(position); 6777 this.keyTrackPosition = position; 6778 }; 6779 6780 AccessibleSlider.prototype.refreshTooltip = function () { 6781 if (this.overHead) { 6782 this.timeTooltip.show(); 6783 if (this.tracking) { 6784 this.timeTooltip.text(this.positionToStr(this.lastTrackPosition)); 6785 } 6786 else { 6787 this.timeTooltip.text(this.positionToStr(this.position)); 6788 } 6789 this.setTooltipPosition(this.seekHead.position().left + (this.seekHead.width() / 2)); 6790 } 6791 else if (this.overBody && this.overBodyMousePos) { 6792 this.timeTooltip.show(); 6793 this.timeTooltip.text(this.positionToStr(this.pageXToPosition(this.overBodyMousePos.x))); 6794 this.setTooltipPosition(this.overBodyMousePos.x - this.bodyDiv.offset().left); 6795 } 6796 else { 6797 6798 clearTimeout(this.timeTooltipTimeoutId); 6799 var _this = this; 6800 this.timeTooltipTimeoutId = setTimeout(function() { 6801 // give user a half second move cursor over tooltip 6802 _this.timeTooltip.hide(); 6803 }, 500); 6804 } 6805 }; 6806 6807 AccessibleSlider.prototype.hideSliderTooltips = function () { 6808 this.overHead = false; 6809 this.overBody = false; 6810 this.timeTooltip.hide(); 6811 }; 6812 6813 AccessibleSlider.prototype.setTooltipPosition = function (x) { 6814 this.timeTooltip.css({ 6815 left: x - (this.timeTooltip.width() / 2) - 10, 6816 bottom: this.seekHead.height() + 10 6817 }); 6818 }; 6819 6820 AccessibleSlider.prototype.positionToStr = function (seconds) { 6821 6822 // same logic as misc.js > formatSecondsAsColonTime() 6823 var dHours = Math.floor(seconds / 3600); 6824 var dMinutes = Math.floor(seconds / 60) % 60; 6825 var dSeconds = Math.floor(seconds % 60); 6826 if (dSeconds < 10) { 6827 dSeconds = '0' + dSeconds; 6828 } 6829 if (dHours > 0) { 6830 if (dMinutes < 10) { 6831 dMinutes = '0' + dMinutes; 6832 } 6833 return dHours + ':' + dMinutes + ':' + dSeconds; 6834 } 6835 else { 6836 return dMinutes + ':' + dSeconds; 6837 } 6838 }; 6839 6840 AccessibleSlider.prototype.pointerEventToXY = function(e) { 6841 6842 // returns array of coordinates x and y in response to both mouse and touch events 6843 // for mouse events, this comes from e.pageX and e.pageY 6844 // for touch events, it's a bit more complicated 6845 var out = {x:0, y:0}; 6846 if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') { 6847 var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]; 6848 out.x = touch.pageX; 6849 out.y = touch.pageY; 6850 } 6851 else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover'|| e.type=='mouseout' || e.type=='mouseenter' || e.type=='mouseleave') { 6852 out.x = e.pageX; 6853 out.y = e.pageY; 6854 } 6855 return out; 6856 }; 6857 6858})(jQuery); 6859 6860(function ($) { 6861 6862 AblePlayer.prototype.addVolumeSlider = function($div) { 6863 6864 // Prior to v4.4.64, we were using a custom-build vertical volunme slider
6865 // Changed to input type="range" because it's standard and gaining more widespread support 6866 // including screen reader support 6867 // TODO: Improve presentation of vertical slider. That requires some CSS finesse. 6868 6869 var thisObj, volumeSliderId, volumeHelpId, volumePct, tickLabelsId, $tickLabels, i, $tickOption, tickLabel; 6870 6871 thisObj = this; 6872 6873 // define a few variables 6874 volumeSliderId = this.mediaId + '-volume-slider'; 6875 volumeHelpId = this.mediaId + '-volume-help'; 6876 tickLabelsId = this.mediaId + '-volume-tick-labels'; 6877 6878 this.$volumeSlider = $('<div>',{ 6879 'id': volumeSliderId, 6880 'class': 'able-volume-slider', 6881 'aria-hidden': 'true' 6882 }).hide(); 6883 this.$volumeSliderTooltip = $('<div>',{ 6884 'class': 'able-tooltip', 6885 'role': 'tooltip' 6886 }).hide(); 6887 this.$volumeRange = $('<input>',{ 6888 'type': 'range', 6889 'min': '0', 6890 'max': '10', 6891 'step': '1', 6892 'orient': 'vertical', // non-standard, but required for Firefox 6893 'aria-label': this.tt.volumeUpDown, 6894 'value': this.volume 6895 // 'list': tickLabelsId // Uncomment this to use tickLabels (see note below) 6896 }); 6897 volumePct = parseInt(thisObj.volume) / 10 * 100; 6898 this.$volumeHelp = $('<div>',{ 6899 'id': volumeHelpId, 6900 'class': 'able-volume-help', 6901 'aria-live': 'polite' 6902 }).text(volumePct + '%'); 6903 this.$volumeButton.attr({ 6904 'aria-describedby': volumeHelpId 6905 }); 6906 $tickLabels = $('<datalist>',{ 6907 'id': tickLabelsId 6908 }); 6909 for (i = 0; i <= 10; i++) { 6910 if (i === 0) { 6911 tickLabel = this.tt.mute; 6912 } 6913 else { 6914 tickLabel = (i * 10) + '%'; 6915 } 6916 $tickOption = $('<option>',{ 6917 'value': i, 6918 'label': tickLabel 6919 }) 6920 $tickLabels.append($tickOption); 6921 } 6922 this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp); 6923 // To add $tickLabels, use the following line of code to replace the one above 6924 // and uncommnet the 'list' property in the definition of this.$volumeRange above 6925 // As of Nov 2022, this feature is not supported by any screen reader 6926 // this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp,$tickLabels); 6927 6928 $div.append(this.$volumeSlider); 6929 6930 // add event listeners 6931 this.$volumeRange.on('change',function (e) { 6932 thisObj.handleVolumeChange($(this).val()); 6933 }); 6934 6935 this.$volumeRange.on('input',function (e) { 6936 thisObj.handleVolumeChange($(this).val()); 6937 }); 6938 6939 this.$volumeRange.on('keydown',function (e) { 6940 6941 // Escape key or Enter key or Tab key 6942 if (e.which === 27 || e.which === 13 || e.which === 9) { 6943 // close popup 6944 if (thisObj.$volumeSlider.is(':visible')) { 6945 thisObj.closingVolume = true; // stopgap 6946 thisObj.hideVolumePopup(); 6947 } 6948 else { 6949 if (!thisObj.closingVolume) { 6950 thisObj.showVolumePopup(); 6951 } 6952 } 6953 } 6954 else { 6955 return; 6956 } 6957 }); 6958 }; 6959 6960 AblePlayer.prototype.refreshVolumeHelp = function(volume) { 6961 6962 // make adjustments based on current volume 6963 var volumePct; 6964 volumePct = (volume/10) * 100; 6965 6966 // Update help text 6967 if (this.$volumeHelp) { 6968 this.$volumeHelp.text(volumePct + '%'); 6969 } 6970 6971 // Update the default value of the volume slider input field 6972 // This doesn't seem to be necessary; browsers remember the previous setting during a session 6973 // but this is a fallback in case they don't 6974 this.$volumeRange.attr('value',volume); 6975 }; 6976 6977 AblePlayer.prototype.refreshVolumeButton = function(volume) { 6978 6979 var volumeName, volumePct, volumeLabel, volumeIconClass, volumeImg, newSvgData; 6980 6981 volumeName = this.getVolumeName(volume); 6982 volumePct = (volume/10) * 100; 6983 volumeLabel = this.tt.volume + ' ' + volumePct + '%'; 6984 6985 if (this.iconType === 'font') { 6986 volumeIconClass = 'icon-volume-' + volumeName; 6987 this.$volumeButton.find('span').first().removeClass().addClass(volumeIconClass); 6988 this.$volumeButton.find('span.able-clipped').text(volumeLabel); 6989 } 6990 else if (this.iconType === 'image') { 6991 volumeImg = this.imgPath + 'volume-' + volumeName + '.png'; 6992 this.$volumeButton.find('img').attr('src',volumeImg); 6993 } 6994 else if (this.iconType === 'svg') { 6995 if (volumeName !== 'mute') { 6996 volumeName = 'volume-' + volumeName; 6997 }
vendor: 4,996 bytes, lines 6998-7206
6998 newSvgData = this.getSvgData(volumeName); 6999 this.$volumeButton.find('svg').attr('viewBox',newSvgData[0]); 7000 this.$volumeButton.find('path').attr('d',newSvgData[1]); 7001 } 7002 }; 7003 7004 AblePlayer.prototype.handleVolumeButtonClick = function() { 7005 7006 if (this.$volumeSlider.is(':visible')) { 7007 this.hideVolumePopup(); 7008 } 7009 else { 7010 this.showVolumePopup(); 7011 } 7012 }; 7013 7014 AblePlayer.prototype.handleVolumeKeystroke = function(keycode) { 7015 7016 // keycode is an ASCII key code 49-57 (numeric keys 1-9), 7017 // keyboard shortcuts for changing volume 7018 7019 var volume; 7020 7021 if (keycode >= 49 && keycode <= 57) { 7022 volume = keycode - 48; 7023 } 7024 else { 7025 return false; 7026 } 7027 7028 if (this.isMuted() && volume > 0) { 7029 this.setMute(false); 7030 } 7031 else if (volume === 0) { 7032 this.setMute(true); 7033 } 7034 else { 7035 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 7036 this.refreshVolumeHelp(volume); 7037 this.refreshVolumeButton(volume); 7038 } 7039 }; 7040 7041 7042 AblePlayer.prototype.handleVolumeChange = function(volume) { 7043 7044 // handle volume change using the volume input slider 7045 7046 if (this.isMuted() && volume > 0) { 7047 this.setMute(false); 7048 } 7049 else if (volume === 0) { 7050 this.setMute(true); 7051 } 7052 else { 7053 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 7054 this.refreshVolumeHelp(volume); 7055 this.refreshVolumeButton(volume); 7056 } 7057 }; 7058 7059 AblePlayer.prototype.handleMute = function() { 7060 7061 if (this.isMuted()) { 7062 this.setMute(false); 7063 } 7064 else { 7065 this.setMute(true); 7066 } 7067 }; 7068 7069 AblePlayer.prototype.showVolumePopup = function() { 7070 7071 this.closePopups(); 7072 this.$tooltipDiv.hide(); 7073 this.$volumeSlider.show().attr('aria-hidden','false'); 7074 this.$volumeButton.attr('aria-expanded','true'); 7075 this.$volumeButton.focus(); // for screen reader expanded state to be read 7076 this.waitThenFocus(this.$volumeRange); 7077 }; 7078 7079 AblePlayer.prototype.hideVolumePopup = function() { 7080 7081 var thisObj = this; 7082 7083 this.$volumeSlider.hide().attr('aria-hidden','true'); 7084 this.$volumeButton.attr('aria-expanded','false').focus(); 7085 // wait a second before resetting stopgap var 7086 // otherwise the keypress used to close volume popup will trigger the volume button 7087 setTimeout(function() { 7088 thisObj.closingVolume = false; 7089 }, 1000); 7090 }; 7091 7092 AblePlayer.prototype.isMuted = function () { 7093 7094 if (this.player === 'html5') { 7095 return this.media.muted; 7096 } 7097 else if (this.player === 'youtube') { 7098 return this.youTubePlayer.isMuted(); 7099 } 7100 }; 7101 7102 AblePlayer.prototype.setMute = function(mute) { 7103 7104 // mute is either true (muting) or false (unmuting) 7105 if (mute) { 7106 // save current volume so it can be restored after unmute 7107 this.lastVolume = this.volume; 7108 this.volume = 0; 7109 } 7110 else { // restore to previous volume 7111 if (typeof this.lastVolume !== 'undefined') { 7112 this.volume = this.lastVolume; 7113 } 7114 } 7115 7116 if (this.player === 'html5') { 7117 this.media.muted = mute; 7118 } 7119 else if (this.player === 'youtube') { 7120 if (mute) { 7121 this.youTubePlayer.mute(); 7122 } 7123 else { 7124 this.youTubePlayer.unMute(); 7125 } 7126 } 7127 this.setVolume(this.volume); 7128 this.refreshVolumeHelp(this.volume); 7129 this.refreshVolumeButton(this.volume); 7130 }; 7131 7132 AblePlayer.prototype.setVolume = function (volume) { 7133 7134 // volume is 1 to 10 7135 // convert as needed depending on player 7136 7137 var newVolume; 7138 7139 if (this.player === 'html5') { 7140 // volume is 0 to 1 7141 newVolume = volume / 10; 7142 this.media.volume = newVolume; 7143 7144 if (this.hasSignLanguage && this.signVideo) { 7145 this.signVideo.volume = 0; // always mute 7146 } 7147 } 7148 else if (this.player === 'youtube') { 7149 // volume is 0 to 100 7150 newVolume = volume * 10; 7151 this.youTubePlayer.setVolume(newVolume); 7152 this.volume = volume; 7153 } 7154 else if (this.player === 'vimeo') { 7155 // volume is 0 to 1 7156 newVolume = volume / 10; 7157 this.vimeoPlayer.setVolume(newVolume).then(function() { 7158 // setVolume finished. 7159 // could do something here 7160 // successful completion also fires a 'volumechange' event (see event.js) 7161 }); 7162 } 7163 this.lastVolume = volume; 7164 }; 7165 7166 AblePlayer.prototype.getVolume = function (volume) { 7167 7168 // return volume using common audio control scale 1 to 10 7169 if (this.player === 'html5') { 7170 // uses 0 to 1 scale 7171 return this.media.volume * 10; 7172 } 7173 else if (this.player === 'youtube') { 7174 // uses 0 to 100 scale 7175 if (this.youTubePlayerReady) { 7176 return this.youTubePlayer.getVolume() / 10; 7177 } 7178 } 7179 if (this.player === 'vimeo') { 7180 // uses 0 to 1 scale 7181 // this.vimeoPlayer.getVolume() takes too long to resolve with a value 7182 // Just use variable that's already been defined (should be the same value anyway) 7183 return this.volume; 7184 } 7185 }; 7186 7187 AblePlayer.prototype.getVolumeName = function (volume) { 7188 7189 // returns 'mute','soft','medium', or 'loud' depending on volume level 7190 if (volume == 0) { 7191 return 'mute'; 7192 } 7193 else if (volume == 10) { 7194 return 'loud'; 7195 } 7196 else if (volume < 5) { 7197 return 'soft'; 7198 } 7199 else { 7200 return 'medium'; 7201 } 7202 }; 7203 7204})(jQuery); 7205 7206(function ($) {
7207 var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; 7208 7209 // Based on the incredible accessible modal dialog. 7210 window.AccessibleDialog = function(modalDiv, $returnElement, dialogRole, isModal, title, $descDiv, closeButtonLabel, width, fullscreen, escapeHook) { 7211 7212 this.title = title; 7213 this.closeButtonLabel = closeButtonLabel; 7214 this.focusedElementBeforeModal = $returnElement; 7215 this.escapeHook = escapeHook; 7216 this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString(); 7217 var thisObj = this; 7218 var modal = modalDiv; 7219 this.modal = modal; 7220 modal.css({ 7221 'width': width || '50%' 7222 }); 7223 modal.addClass('able-modal-dialog'); 7224 7225 if (!fullscreen) { 7226 var closeButton = $('<button>',{ 7227 'class': 'modalCloseButton', 7228 'title': thisObj.closeButtonLabel, 7229 'aria-label': thisObj.closeButtonLabel 7230 }).text('X'); 7231 closeButton.keydown(function (e) { 7232 // Space key down 7233 if (e.which === 32) { 7234 thisObj.hide(); 7235 } 7236 }).click(function () { 7237 thisObj.hide(); 7238 }); 7239 7240 var titleH1 = $('<h1></h1>'); 7241 titleH1.attr('id', 'modalTitle-' + this.baseId); 7242 titleH1.css('text-align', 'center'); 7243 titleH1.text(title); 7244 this.titleH1 = titleH1; 7245 7246 modal.attr({ 7247 'aria-labelledby': 'modalTitle-' + this.baseId, 7248 }); 7249 modal.prepend(titleH1); 7250 modal.prepend(closeButton); 7251 } 7252 7253 modal.attr({ 7254 'aria-hidden': 'true', 7255 'role': dialogRole, 7256 }); 7257 if (isModal) { 7258 modal.attr('aria-modal','true'); 7259 } 7260 7261 modal.keydown(function (e) { 7262 // Escape 7263 if (e.which === 27) { 7264 if (thisObj.escapeHook) { 7265 thisObj.escapeHook(e, this); 7266 } 7267 else { 7268 thisObj.hide(); 7269 e.preventDefault(); 7270 } 7271 } 7272 // Tab 7273 else if (e.which === 9) { 7274 // Manually loop tab navigation inside the modal. 7275 var parts = modal.find('*'); 7276 var focusable = parts.filter(focusableElementsSelector).filter(':visible'); 7277 7278 if (focusable.length === 0) { 7279 return; 7280 } 7281 7282 var focused = $(':focus'); 7283 var currentIndex = focusable.index(focused); 7284 if (e.shiftKey) { 7285 // If backwards from first element, go to last. 7286 if (currentIndex === 0) { 7287 focusable.get(focusable.length - 1).focus(); 7288 e.preventDefault(); 7289 } 7290 } 7291 else { 7292 if (currentIndex === focusable.length - 1) { 7293 focusable.get(0).focus(); 7294 e.preventDefault(); 7295 } 7296 } 7297 } 7298 e.stopPropagation(); 7299 }); 7300 7301 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('aria-hidden'); 7302 }; 7303 7304 AccessibleDialog.prototype.show = function () { 7305 if (!this.overlay) { 7306 // Generate overlay. 7307 var overlay = $('<div></div>').attr({ 7308 'class': 'able-modal-overlay', 7309 'tabindex': '-1' 7310 }); 7311 this.overlay = overlay; 7312 $('body').append(overlay); 7313 7314 // Keep from moving focus out of dialog when clicking outside of it. 7315 overlay.on('mousedown.accessibleModal', function (e) { 7316 e.preventDefault(); 7317 }); 7318 } 7319 7320 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'true'); 7321 7322 this.overlay.css('display', 'block'); 7323 this.modal.css('display', 'block'); 7324 this.modal.attr({ 7325 'aria-hidden': 'false', 7326 'tabindex': '-1' 7327 }); 7328 7329 var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible'); 7330 if (focusable.length === 0) { 7331 this.focusedElementBeforeModal.blur(); 7332 } 7333 var thisObj = this; 7334 setTimeout(function () { 7335 // set focus on the first focusable element 7336 thisObj.modal.find('button.modalCloseButton').first().focus(); 7337 }, 300); 7338 }; 7339 7340 AccessibleDialog.prototype.hide = function () { 7341 if (this.overlay) { 7342 this.overlay.css('display', 'none'); 7343 } 7344 this.modal.css('display', 'none'); 7345 this.modal.attr('aria-hidden', 'true'); 7346 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('aria-hidden'); 7347 7348 this.focusedElementBeforeModal.focus(); 7349 }; 7350 7351 AccessibleDialog.prototype.getInputs = function () { 7352 7353 // return an array of input elements within this dialog 7354 if (this.modal) { 7355 var inputs = this.modal.find('input');
7356 return inputs; 7357 } 7358 return false; 7359 }; 7360 7361})(jQuery); 7362 7363(function ($) { 7364 7365 AblePlayer.prototype.getNextHeadingLevel = function($element) { 7366 7367 // Finds the nearest heading in the ancestor tree 7368 // Loops over each parent of the current element until a heading is found 7369 // If multiple headings are found beneath a given parent, get the closest 7370 // Returns an integer (1-6) representing the next available heading level 7371 7372 var $parents, $foundHeadings, numHeadings, headingType, headingNumber; 7373 7374 $parents = $element.parents(); 7375 $parents.each(function(){ 7376 $foundHeadings = $(this).children(':header'); 7377 numHeadings = $foundHeadings.length; 7378 if (numHeadings) { 7379 headingType = $foundHeadings.eq(numHeadings-1).prop('tagName'); 7380 return false; 7381 } 7382 }); 7383 if (typeof headingType === 'undefined') { 7384 // page has no headings 7385 headingNumber = 1; 7386 } 7387 else { 7388 // Increment closest heading by one if less than 6. 7389 headingNumber = parseInt(headingType[1]); 7390 headingNumber += 1; 7391 if (headingNumber > 6) { 7392 headingNumber = 6; 7393 } 7394 } 7395 return headingNumber; 7396 }; 7397 7398 AblePlayer.prototype.countProperties = function(obj) { 7399 7400 // returns the number of properties in an object 7401 var count, prop; 7402 count = 0; 7403 for (prop in obj) { 7404 if (obj.hasOwnProperty(prop)) { 7405 ++count; 7406 } 7407 } 7408 return count; 7409 }; 7410 7411 AblePlayer.prototype.formatSecondsAsColonTime = function (seconds, showFullTime) { 7412 7413 // Takes seconds and converts to string of form hh:mm:ss 7414 // If showFullTime is true, shows 00 for hours if time is less than an hour 7415 // and show milliseconds (e.g., 00:00:04.123 as in Video Track Sorter) 7416 // Otherwise, omits empty hours and milliseconds (e.g., 00:04 as in timer on controller) 7417 7418 var dHours, dMinutes, dSeconds, 7419 parts, milliSeconds, numShort, i; 7420 7421 if (showFullTime) { 7422 // preserve milliseconds, if included in seconds 7423 parts = seconds.toString().split('.'); 7424 if (parts.length === 2) { 7425 milliSeconds = parts[1]; 7426 if (milliSeconds.length < 3) { 7427 numShort = 3 - milliSeconds.length; 7428 for (i=1; i <= numShort; i++) { 7429 milliSeconds += '0'; 7430 } 7431 } 7432 } 7433 else { 7434 milliSeconds = '000'; 7435 } 7436 } 7437 dHours = Math.floor(seconds / 3600); 7438 dMinutes = Math.floor(seconds / 60) % 60; 7439 dSeconds = Math.floor(seconds % 60); 7440 if (dSeconds < 10) { 7441 dSeconds = '0' + dSeconds; 7442 } 7443 if (dHours > 0) { 7444 if (dMinutes < 10) { 7445 dMinutes = '0' + dMinutes; 7446 } 7447 if (showFullTime) { 7448 return dHours + ':' + dMinutes + ':' + dSeconds + '.' + milliSeconds; 7449 } 7450 else { 7451 return dHours + ':' + dMinutes + ':' + dSeconds; 7452 } 7453 } 7454 else { 7455 if (showFullTime) { 7456 if (dHours < 1) { 7457 dHours = '00'; 7458 } 7459 else if (dHours < 10) { 7460 dHours = '0' + dHours; 7461 } 7462 if (dMinutes < 1) { 7463 dMinutes = '00'; 7464 } 7465 else if (dMinutes < 10) { 7466 dMinutes = '0' + dMinutes; 7467 } 7468 return dHours + ':' + dMinutes + ':' + dSeconds + '.' + milliSeconds; 7469 } 7470 else { 7471 return dMinutes + ':' + dSeconds; 7472 } 7473 } 7474 }; 7475 7476 AblePlayer.prototype.getSecondsFromColonTime = function (timeStr) { 7477 7478 // Converts string of form hh:mm:ss to seconds 7479 var timeParts, hours, minutes, seconds, newTime; 7480 7481 timeParts = timeStr.split(':'); 7482 if (timeParts.length === 3) { 7483 hours = parseInt(timeParts[0]); 7484 minutes = parseInt(timeParts[1]); 7485 seconds = parseFloat(timeParts[2]); 7486 return ((hours * 3600) + (minutes * 60) + (seconds)); 7487 } 7488 else if (timeParts.length === 2) { 7489 minutes = parseInt(timeParts[0]); 7490 seconds = parseFloat(timeParts[1]); 7491 return ((minutes * 60) + (seconds)); 7492 } 7493 else if (timeParts.length === 1) { 7494 seconds = parseFloat(timeParts[0]); 7495 return seconds; 7496 } 7497 }; 7498 7499 AblePlayer.prototype.capitalizeFirstLetter = function (string) { 7500 return string.charAt(0).toUpperCase() + string.slice(1); 7501 }; 7502 7503 AblePlayer.prototype.roundDown = function (value, decimals) { 7504 7505 // round value down to the nearest X decimal points 7506 // where X is the value of the decimals parameter 7507 return Number(Math.floor(value+'e'+decimals)+'e-'+decimals); 7508 }; 7509 7510 AblePlayer.prototype.hasAttr = function (object, attribute) { 7511 7512 // surprisingly, there is no hasAttr() function in Jquery as of 3.2.1 7513 // return true if object has attribute; otherwise false 7514 // selector is a Jquery object 7515 // attribute is a string 7516 7517 var attr = object.attr(attribute); 7518 7519 // For some browsers, `attr` is undefined; for others, 7520 // `attr` is false. Check for both. 7521 if (typeof attr !== typeof undefined && attr !== false) { 7522 return true; 7523 } 7524 else { 7525 return false;
7526 } 7527 }; 7528 7529 Number.isInteger = Number.isInteger || function(value) { 7530 7531 // polyfill for IE11, which doesn't otherwise support Number.isInteger 7532 // https://stackoverflow.com/a/31720368/744281 7533 return typeof value === "number" && isFinite(value) && Math.floor(value) === value; 7534 }; 7535 7536})(jQuery); 7537 7538(function ($) { 7539 AblePlayer.prototype.initDescription = function() { 7540 7541 // set default mode for delivering description (open vs closed) 7542 // based on availability and user preference 7543 7544 // called when player is being built, or when a user 7545 // toggles the Description button or changes a description-related preference 7546 7547 // The following variables are applicable to delivery of description: 7548 // defaultStateDescriptions == 'on' or 'off', defined by website owner (overridden by prefDesc) 7549 // prefDesc == 1 if user wants description (i.e., Description button is on); else 0 7550 // prefDescPause == 1 to pause video when description starts; else 0 7551 // prefDescVisible == 1 to visibly show text-based description area; else 0 7552 // prefDescMethod == either 'video' or 'text' (as of v4.0.10, prefDescMethod is always 'video') 7553 // descMethod is the format actually used ('video' or 'text'), regardless of user preference 7554 // hasOpenDesc == true if a described version of video is available via data-desc-src attribute 7555 // hasClosedDesc == true if a description text track is available 7556 // descOn == true if description of either type is on 7557 // readDescriptionsAloud == true if text description is to be announced audibly; otherwise false 7558 // descReader == either 'browser' or 'screenreader' 7559 7560 var deferred, promise, thisObj; 7561 7562 deferred = new $.Deferred(); 7563 promise = deferred.promise(); 7564 thisObj = this; 7565 7566 if (this.mediaType === 'audio') { 7567 deferred.resolve(); 7568 } 7569 7570 // check to see if there's an open-described version of this video 7571 // checks only the first source since if a described version is provided, 7572 // it must be provided for all sources 7573 this.descFile = this.$sources.first().attr('data-desc-src'); 7574 if (typeof this.descFile !== 'undefined') { 7575 this.hasOpenDesc = true; 7576 } 7577 else { 7578 // there's no open-described version via data-desc-src, 7579 // but what about data-youtube-desc-src or data-vimeo-desc-src? 7580 // if these exist, they would have been defined earlier 7581 if (this.youTubeDescId || this.vimeoDescId) { 7582 this.hasOpenDesc = true; 7583 } 7584 else { // there are no open-described versions from any source 7585 this.hasOpenDesc = false; 7586 } 7587 } 7588 7589 // Set this.descMethod based on media availability & user preferences 7590 if (this.hasOpenDesc && this.hasClosedDesc) { 7591 // both formats are available. User gets their preference. 7592 if (this.prefDescMethod) { 7593 this.descMethod = this.prefDescMethod; 7594 } 7595 else { 7596 // user has no preference. Video is default. 7597 this.descMethod = 'video'; 7598 } 7599 } 7600 else if (this.hasOpenDesc) { 7601 this.descMethod = 'video'; 7602 } 7603 else if (this.hasClosedDesc) { 7604 this.descMethod = 'text'; 7605 } 7606 else { 7607 // no description is available for this video 7608 this.descMethod = null; 7609 } 7610 7611 // Set the default state of descriptions 7612 if (this.descMethod) { 7613 if (this.prefDesc === 1) { 7614 this.descOn = true; 7615 } 7616 else if (this.prefDesc === 0) { 7617 this.descOn = false; 7618 } 7619 else { 7620 // user has no prefs. Use default state. 7621 if (this.defaultStateDescriptions === 1) { 7622 this.descOn = true; 7623 } 7624 else { 7625 this.descOn = false; 7626 } 7627 } 7628 } 7629 else { 7630 this.descOn = false; 7631 } 7632 if (typeof this.$descDiv === 'undefined' && this.hasClosedDesc && this.descMethod === 'text') { 7633 this.injectTextDescriptionArea(); 7634 } 7635 7636 if (this.descOn) { 7637 if (this.descMethod === 'video') { 7638 if (!this.usingDescribedVersion()) { 7639 // switched from non-described to described version 7640 this.swapDescription(); 7641 } 7642 } 7643 if (this.hasClosedDesc) { 7644 if (this.prefDescVisible) { 7645 // make description text visible 7646 if (typeof this.$descDiv !== 'undefined') { 7647 this.$descDiv.show(); 7648 this.$descDiv.removeClass('able-clipped'); 7649 } 7650 } 7651 else { 7652 // keep it visible to screen readers, but hide it visibly 7653 if (typeof this.$descDiv !== 'undefined') { 7654 this.$descDiv.addClass('able-clipped'); 7655 } 7656 } 7657 } 7658 } 7659 else { // description is off. 7660 if (this.descMethod === 'video') { // user has turned off described version of video 7661 if (this.usingDescribedVersion()) { 7662 // user was using the described verion. Swap for non-described version 7663 this.swapDescription(); 7664 } 7665 } 7666 else if (this.descMethod === 'text') { // user has turned off text description 7667 // hide description div from everyone, including screen reader users 7668 if (typeof this.$descDiv !== 'undefined') { 7669 this.$descDiv.hide(); 7670 this.$descDiv.removeClass('able-clipped'); 7671 } 7672 } 7673 } 7674 deferred.resolve(); 7675 return promise; 7676 }; 7677 7678 AblePlayer.prototype.usingDescribedVersion = function () { 7679 7680 // Returns true if currently using audio description, false otherwise. 7681 7682 if (this.player === 'youtube') { 7683 return (this.activeYouTubeId === this.youTubeDescId); 7684 } 7685 else if (this.player === 'vimeo') { 7686 return (this.activeVimeoId === this.vimeoDescId); 7687 } 7688 else { 7689 return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src')); 7690 } 7691 }; 7692 7693 AblePlayer.prototype.initSpeech = function (context) { 7694 7695 // Some browsers &/or operating systems require a user-initiated click 7696 // before this.synth.getVoices() will work. As of Nov 2022: 7697 // Chrome requires a click before synth.getVoices() will work 7698 // iOS requires a click before synth.speak() will work 7699 // A hack to address this: Listen for ANY click, then play an inaudible utterance 7700 // to intitiate speech synthesis 7701 // https://stackoverflow.com/questions/32193704/js-speech-synthesis-issue-on-ios 7702 // This function does that, and sets this.speechEnabled 7703 // It's called with either of these contexts: 7704 // 'init' - player is being initialized 7705 // 'play' - user has clicked play 7706 // 'prefs' - user has clicked prefs button 7707 // 'desc' - it's time to announce a description! 7708 7709 var thisObj = this; 7710 7711 if (this.speechEnabled === null) { 7712 7713 if (typeof this.synth !== 'undefined') { 7714 // cancel any previous synth instance and reinitialize 7715 this.synth.cancel(); 7716 } 7717 7718 if (window.speechSynthesis) { 7719 7720 // browser supports speech synthesis 7721 7722 this.synth = window.speechSynthesis; 7723 7724 if (context === 'init') { 7725 // handle a click on anything, in case the user 7726 // clicks something before they click 'play' or 'prefs' buttons 7727 // that would allow us to init speech before it's needed 7728 $(document).on('click',function() { 7729 var greeting = new SpeechSynthesisUtterance('Hi!'); 7730 greeting.volume = 0; // silent 7731 greeting.rate = 10; // fastest speed supported by the API 7732 thisObj.synth.speak(greeting); 7733 greeting.onstart = function(e) { 7734 // utterance has started 7735 $(document).off('click'); // unbind the click event listener 7736 } 7737 greeting.onend = function(e) { 7738 // should now be able to get browser voices 7739 // in browsers that require a click 7740 thisObj.getBrowserVoices(); 7741 if (thisObj.descVoices.length) { 7742 thisObj.speechEnabled = true; 7743 } 7744 }; 7745 }); 7746 7747 // go ahead and call get browser voices in case it might work, 7748 // for browsers that don't require a click 7749 this.getBrowserVoices(); 7750 if (this.descVoices.length) { 7751 this.speechEnabled = true; 7752 } 7753 } 7754 else { // context is either 'play' or 'prefs' or 'desc' 7755 var greeting = new SpeechSynthesisUtterance('Hi!'); 7756 greeting.volume = 0; // silent 7757 greeting.rate = 10; // fastest speed supported by the API 7758 thisObj.synth.speak(greeting); 7759 greeting.onstart = function(e) { 7760 // utterance has started 7761 $(document).off('click'); // unbind the click event listener 7762 }; 7763 greeting.onend = function(e) { 7764 // should now be able to get browser voices 7765 // in browsers that require a click 7766 thisObj.getBrowserVoices(); 7767 if (thisObj.descVoices.length) { 7768 thisObj.speechEnabled = true; 7769 } 7770 }; 7771 } 7772 } 7773 else { 7774 // browser does not support speech synthesis 7775 this.speechEnabled = false; 7776 } 7777 } 7778 }; 7779 7780 AblePlayer.prototype.getBrowserVoices = function () { 7781 7782 // define this.descVoices array 7783 // includes only languages that match the language of the captions or player 7784 7785 var voices, descLangs, voiceLang, preferredLang; 7786 7787 if (this.captionLang) { 7788 preferredLang = this.captionLang.substring(0,2).toLowerCase(); 7789 } 7790 else { 7791 preferredLang = this.lang.substring(0,2).toLowerCase(); 7792 } 7793 this.descVoices = []; 7794 voices = this.synth.getVoices(); 7795 descLangs = this.getDescriptionLangs(); 7796 if (voices.length > 0) { 7797 this.descVoices = []; 7798 // available languages are identified with local suffixes (e.g., en-US) 7799 for (var i=0; i<voices.length; i++) { 7800 // match only the first 2 characters of the lang code 7801 voiceLang = voices[i].lang.substring(0,2).toLowerCase(); 7802 if (voiceLang === preferredLang && (descLangs.indexOf(voiceLang) !== -1)) { 7803 // this voice matches preferredLang 7804 // AND there's a matching description track in this language 7805 // Add this voice to final array 7806 this.descVoices.push(voices[i]); 7807 } 7808 } 7809 if (!this.descVoices.length) { 7810 // no voices available in the default language(s) 7811 // just use all voices, regardless of language 7812 this.descVoices = voices; 7813 } 7814 } 7815 return false;
vendor: 12,063 bytes, lines 7816-8188
7816 }; 7817 7818 AblePlayer.prototype.getDescriptionLangs = function () { 7819 7820 // returns an array of languages (from srclang atttributes) 7821 // in which there are description tracks 7822 // use only first two characters of the lang code 7823 var descLangs = []; 7824 if (this.tracks) { 7825 for (var i=0; i < this.tracks.length; i++) { 7826 if (this.tracks[i].kind === 'descriptions') { 7827 descLangs.push(this.tracks[i].language.substring(0,2).toLowerCase()); 7828 } 7829 } 7830 } 7831 return descLangs; 7832 }; 7833 7834 AblePlayer.prototype.setDescriptionVoice = function () { 7835 7836 // set description voice on player init, or when user changes caption language 7837 // Voice is determined in the following order of precedence: 7838 // 1. User's preferred voice for this language, saved in a cookie 7839 // 2. The first available voice in the array of available voices for this browser in this language 7840 7841 var cookie, voices, prefDescVoice, descVoice, descLang, prefVoiceFound; 7842 cookie = this.getCookie(); 7843 if (typeof cookie.voices !== 'undefined') { 7844 prefDescVoice = this.getPrefDescVoice(); 7845 } 7846 else { 7847 prefDescVoice = null; 7848 } 7849 7850 this.getBrowserVoices(); 7851 this.rebuildDescPrefsForm(); 7852 7853 if (this.selectedDescriptions) { 7854 descLang = this.selectedDescriptions.language; 7855 } 7856 else if (this.captionLang) { 7857 descLang = this.captionLang; 7858 } 7859 else { 7860 descLang = this.lang; 7861 } 7862 7863 if (this.synth) { 7864 voices = this.synth.getVoices(); 7865 if (voices.length > 0) { 7866 if (prefDescVoice) { 7867 // select the language that matches prefDescVoice, if it's available 7868 prefVoiceFound = false; 7869 for (var i=0; i<voices.length; i++) { 7870 // first, be sure voice is the correct language 7871 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7872 if (voices[i].name === prefDescVoice) { 7873 descVoice = voices[i].name; 7874 prefVoiceFound = true; 7875 break; 7876 } 7877 } 7878 } 7879 } 7880 if (!prefVoiceFound) { 7881 // select the first language that matches the first 2 characters of the lang code 7882 for (var i=0; i<voices.length; i++) { 7883 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7884 descVoice = voices[i].name; 7885 break; 7886 } 7887 } 7888 } 7889 // make this the user's current preferred voice 7890 this.prefDescVoice = descVoice; 7891 this.prefDescVoiceLang = descLang; 7892 // select this voice in the Description Prefs dialog 7893 if (this.$voiceSelectField) { 7894 var selectedOption = this.$voiceSelectField.find('option[value="' + this.prefDescVoice + '"]'); 7895 this.$voiceSelectField.val(this.prefDescVoice); 7896 } 7897 this.updateCookie('voice'); 7898 } 7899 } 7900 }; 7901 7902 AblePlayer.prototype.swapDescription = function() { 7903 7904 // swap described and non-described source media, depending on which is playing 7905 // this function is only called in two circumstances: 7906 // 1. Swapping to described version when initializing player (based on user prefs & availability) 7907 // (playerCreated == false) 7908 // 2. User is toggling description 7909 // (playerCreated == true) 7910 7911 var thisObj, i, origSrc, descSrc, srcType, newSource; 7912 7913 thisObj = this; 7914 7915 // We are no longer loading the previous media source 7916 // Only now, as a new source is requested, is it safe to reset this var 7917 // It will be reset to true when media.load() is called 7918 this.loadingMedia = false; 7919 7920 // get element that has focus at the time swap is initiated 7921 // after player is rebuilt, focus will return to that same element 7922 // (if it exists) 7923 this.$focusedElement = $(':focus'); 7924 7925 // get current time of current source, and attempt to start new video at the same time 7926 // whether this is possible will be determined after the new media source has loaded 7927 // see onMediaNewSourceLoad() 7928 if (this.elapsed > 0) { 7929 this.swapTime = this.elapsed; 7930 } 7931 else { 7932 this.swapTime = 0; 7933 } 7934 if (this.duration > 0) { 7935 this.prevDuration = this.duration; 7936 } 7937 7938 // Capture current playback state, so media can resume after source is swapped 7939 if (!this.okToPlay) { 7940 this.okToPlay = this.playing; 7941 } 7942 7943 if (this.descOn) { 7944 // user has requested the described version 7945 this.showAlert(this.tt.alertDescribedVersion); 7946 } 7947 else { 7948 // user has requested the non-described version 7949 this.showAlert(this.tt.alertNonDescribedVersion); 7950 } 7951 7952 if (this.player === 'html5') { 7953 7954 this.swappingSrc = true; 7955 this.paused = true; 7956 7957 if (this.usingDescribedVersion()) { 7958 // the described version is currently playing. Swap to non-described 7959 for (i=0; i < this.$sources.length; i++) { 7960 // for all <source> elements, replace src with data-orig-src 7961 origSrc = this.$sources[i].getAttribute('data-orig-src'); 7962 srcType = this.$sources[i].getAttribute('type'); 7963 if (origSrc) { 7964 this.$sources[i].setAttribute('src',origSrc); 7965 } 7966 } 7967 } 7968 else { 7969 // the non-described version is currently playing. Swap to described. 7970 for (i=0; i < this.$sources.length; i++) { 7971 // for all <source> elements, replace src with data-desc-src (if one exists) 7972 // then store original source in a new data-orig-src attribute 7973 origSrc = this.$sources[i].getAttribute('src'); 7974 descSrc = this.$sources[i].getAttribute('data-desc-src'); 7975 srcType = this.$sources[i].getAttribute('type'); 7976 if (descSrc) { 7977 this.$sources[i].setAttribute('src',descSrc); 7978 this.$sources[i].setAttribute('data-orig-src',origSrc); 7979 } 7980 } 7981 } 7982 7983 if (this.recreatingPlayer) { 7984 // stopgap to prevent multiple firings of recreatePlayer() 7985 return; 7986 } 7987 if (this.playerCreated) { 7988 // delete old player, then recreate it with new source & tracks 7989 this.deletePlayer('swap-desc-html'); 7990 this.recreatePlayer().then(function() { 7991 if (!thisObj.loadingMedia) { 7992 thisObj.media.load(); 7993 thisObj.loadingMedia = true; 7994 } 7995 }); 7996 } 7997 else { 7998 // player is in the process of being created 7999 // no need to recreate it 8000 } 8001 } 8002 else if (this.player === 'youtube') { 8003 8004 if (this.usingDescribedVersion()) { 8005 // the described version is currently playing. Swap to non-described 8006 this.activeYouTubeId = this.youTubeId; 8007 } 8008 else { 8009 // the non-described version is currently playing. Swap to described. 8010 this.activeYouTubeId = this.youTubeDescId; 8011 } 8012 if (typeof this.youTubePlayer !== 'undefined') { 8013 thisObj.swappingSrc = true; 8014 if (thisObj.playing) { 8015 // loadVideoById() loads and immediately plays the new video at swapTime 8016 thisObj.youTubePlayer.loadVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 8017 } 8018 else { 8019 // cueVideoById() loads the new video and seeks to swapTime, but does not play 8020 thisObj.youTubePlayer.cueVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 8021 } 8022 } 8023 if (this.playerCreated) { 8024 this.deletePlayer('swap-desc-youtube'); 8025 } 8026 // player needs to be recreated with new source 8027 if (this.recreatingPlayer) { 8028 // stopgap to prevent multiple firings of recreatePlayer() 8029 return; 8030 } 8031 this.recreatePlayer().then(function() { 8032 // nothing to do here 8033 // next steps occur when youtube onReady event fires 8034 // see youtube.js > finalizeYoutubeInit() 8035 }); 8036 } 8037 else if (this.player === 'vimeo') { 8038 if (this.usingDescribedVersion()) { 8039 // the described version is currently playing. Swap to non-described 8040 this.activeVimeoId = this.vimeoId; 8041 this.showAlert(this.tt.alertNonDescribedVersion); 8042 } 8043 else { 8044 // the non-described version is currently playing. Swap to described. 8045 this.activeVimeoId = this.vimeoDescId; 8046 this.showAlert(this.tt.alertDescribedVersion); 8047 } 8048 if (this.playerCreated) { 8049 this.deletePlayer('swap-desc-vimeo'); 8050 } 8051 // player needs to be recreated with new source 8052 if (this.recreatingPlayer) { 8053 // stopgap to prevent multiple firings of recreatePlayer() 8054 return; 8055 } 8056 this.recreatePlayer().then(function() { 8057 // load the new video source 8058 thisObj.vimeoPlayer.loadVideo(thisObj.activeVimeoId).then(function() { 8059 if (thisObj.playing) { 8060 // video was playing when user requested an alternative version 8061 // seek to swapTime and continue playback (playback happens automatically) 8062 thisObj.vimeoPlayer.setCurrentTime(thisObj.swapTime); 8063 } 8064 else { 8065 // Vimeo autostarts immediately after video loads 8066 // The "Described" button should not trigger playback, so stop this before the user notices. 8067 thisObj.vimeoPlayer.pause(); 8068 } 8069 }); 8070 }); 8071 } 8072 }; 8073 8074 AblePlayer.prototype.showDescription = function(now) { 8075 8076 if (!this.hasClosedDesc || this.swappingSrc || !this.descOn || this.descMethod === 'video') { 8077 return; 8078 } 8079 8080 var thisObj, i, cues, d, thisDescription, descText, msg; 8081 thisObj = this; 8082 8083 var flattenComponentForDescription = function (component) { 8084 var result = []; 8085 if (component.type === 'string') { 8086 result.push(component.value); 8087 } 8088 else { 8089 for (var i = 0; i < component.children.length; i++) { 8090 result.push(flattenComponentForDescription(component.children[i])); 8091 } 8092 } 8093 return result.join(''); 8094 }; 8095 8096 if (this.selectedDescriptions) { 8097 cues = this.selectedDescriptions.cues; 8098 } 8099 else if (this.descriptions.length >= 1) { 8100 cues = this.descriptions[0].cues; 8101 } 8102 else { 8103 cues = []; 8104 } 8105 for (d = 0; d < cues.length; d++) { 8106 if ((cues[d].start <= now) && (cues[d].end > now)) { 8107 thisDescription = d; 8108 break; 8109 } 8110 } 8111 if (typeof thisDescription !== 'undefined') { 8112 if (this.currentDescription !== thisDescription) { 8113 // temporarily remove aria-live from $status in order to prevent description from being interrupted 8114 this.$status.removeAttr('aria-live'); 8115 descText = flattenComponentForDescription(cues[thisDescription].components); 8116 if (this.descReader === 'screenreader') { 8117 // load the new description into the container div for screen readers to read 8118 this.$descDiv.html(descText); 8119 } 8120 else if (this.speechEnabled) { 8121 // use browser's built-in speech synthesis 8122 this.announceDescriptionText('description',descText); 8123 if (this.prefDescVisible) { 8124 // write description to the screen for sighted users 8125 // but remove ARIA attributes since it isn't intended to be read by screen readers 8126 this.$descDiv.html(descText).removeAttr('aria-live aria-atomic'); 8127 } 8128 } 8129 else { 8130 // browser does not support speech synthesis 8131 // load the new description into the container div for screen readers to read 8132 this.$descDiv.html(descText); 8133 } 8134 if (this.prefDescPause && this.descMethod === 'text') { 8135 this.pauseMedia(); 8136 this.pausedForDescription = true; 8137 } 8138 this.currentDescription = thisDescription; 8139 } 8140 } 8141 else { 8142 this.$descDiv.html(''); 8143 this.currentDescription = -1; 8144 // restore aria-live to $status 8145 this.$status.attr('aria-live','polite'); 8146 } 8147 }; 8148 8149 AblePlayer.prototype.syncSpeechToPlaybackRate = function(rate) { 8150 8151 // called when user changed playback rate 8152 // adjust rate of audio description to match 8153 8154 var speechRate; 8155 8156 if (rate === 0.5) { 8157 speechRate = 0.7; // option 1 in prefs menu 8158 } 8159 else if (rate === 0.75) { 8160 speechRate = 0.8; // option 2 in prefs menu 8161 } 8162 else if (rate === 1.0) { 8163 speechRate = 1; // option 4 in prefs menu (normal speech, default) 8164 } 8165 else if (rate === 1.25) { 8166 speechRate = 1.1; // option 5 in prefs menu 8167 } 8168 else if (rate === 1.5) { 8169 speechRate = 1.2; // option 6 in prefs menu 8170 } 8171 else if (rate === 1.75) { 8172 speechRate = 1.5; // option 7 in prefs menu 8173 } 8174 else if (rate === 2.0) { 8175 speechRate = 2; // option 8 in prefs menu (fast) 8176 } 8177 else if (rate === 2.25) { 8178 speechRate = 2.5; // option 9 in prefs menu (very fast) 8179 } 8180 else if (rate >= 2.5) { 8181 speechRate = 3; // option 10 in prefs menu (super fast) 8182 } 8183 this.prefDescRate = speechRate; 8184 }; 8185 8186 AblePlayer.prototype.announceDescriptionText = function(context, text) { 8187 8188 // this function announces description text using speech synthesis
8189 // it's only called if already determined that browser supports speech synthesis 8190 // context is either: 8191 // 'description' - actual description text extracted from WebVTT file 8192 // 'sample' - called when user changes a setting in Description Prefs dialog 8193 8194 var thisObj, voiceName, i, voice, pitch, rate, volume, utterance, 8195 timeElapsed, secondsElapsed; 8196 8197 thisObj = this; 8198 8199 // As of Feb 2021, 8200 // 1. In some browsers (e.g., Chrome) window.speechSynthesis.getVoices() 8201 // returns 0 voices unless the request is triggered with a user click 8202 // Therefore, description may have failed to initialize when the page loaded 8203 // This function cannot have been called without a mouse click. 8204 // Therefore, this is a good time to check that, and try again if needed 8205 // 2. In some browsers, the window.speechSynthesis.speaking property fails to reset, 8206 // and onend event is never fired. This prevents new speech from being spoken. 8207 // window.speechSynthesis.cancel() also fails, so it's impossible to recover. 8208 // This only seems to happen with some voices. 8209 // Typically the first voice in the getVoices() array (index 0) is realiable 8210 // When speech synthesis gets wonky, this is a deep problem that impacts all browsers 8211 // and typically requires a computer reboot to make right again. 8212 // This has been observed frequently in macOS Big Sur, but also in Windows 10 8213 // To ignore user's voice preferences and always use the first voice, set the following var to true 8214 // This is for testing only; not recommended for production 8215 // unless the voice select field is also removed from the Prefs dialog 8216 var useFirstVoice = false; 8217 8218 if (!this.speechEnabled) { 8219 // voices array failed to load the first time. Try again 8220 this.initSpeech('desc'); 8221 } 8222 8223 if (context === 'sample') { 8224 // get settings from form 8225 voiceName = $('#' + this.mediaId + '_prefDescVoice').val(); 8226 pitch = $('#' + this.mediaId + '_prefDescPitch').val(); 8227 rate = $('#' + this.mediaId + '_prefDescRate').val(); 8228 volume = $('#' + this.mediaId + '_prefDescVolume').val(); 8229 } 8230 else { 8231 // get settings from global prefs 8232 voiceName = this.prefDescVoice; 8233 pitch = this.prefDescPitch; 8234 rate = this.prefDescRate; 8235 volume = this.prefDescVolume; 8236 } 8237 8238 // get the voice associated with the user's chosen voice name 8239 if (this.descVoices) { 8240 if (this.descVoices.length > 0) { 8241 if (useFirstVoice) { 8242 voice = this.descVoices[0]; 8243 } 8244 else if (voiceName) { 8245 // get the voice that matches user's preferred voiceName 8246 for (i = 0; i < this.descVoices.length; i++) { 8247 if (this.descVoices[i].name == voiceName) { 8248 voice = this.descVoices[i]; 8249 break; 8250 } 8251 } 8252 } 8253 if (typeof voice === 'undefined') { 8254 // no matching voice was found 8255 // use the first voice in the array 8256 voice = this.descVoices[0]; 8257 } 8258 utterance = new SpeechSynthesisUtterance(); 8259 utterance.voice = voice; 8260 utterance.voiceURI = 'native'; 8261 utterance.volume = volume; 8262 utterance.rate = rate; 8263 utterance.pitch = pitch; 8264 utterance.text = text; 8265 // TODO: Consider the best language for the utterance: 8266 // language of the web page? (this.lang) 8267 // language of the WebVTT description track? 8268 // language of the user's chosen voice? 8269 // If there's a mismatch between any of these, the description will likely be unintelligible 8270 utterance.lang = this.lang; 8271 utterance.onstart = function(e) { 8272 // utterance has started 8273 }; 8274 utterance.onpause = function(e) { 8275 // utterance has paused 8276 }; 8277 utterance.onend = function(e) { 8278 // utterance has ended 8279 this.speakingDescription = false; 8280 timeElapsed = e.elapsedTime; 8281 // As of Firefox 95, e.elapsedTime is expressed in seconds 8282 // Other browsers (tested in Chrome & Edge) express this in milliseconds 8283 // Assume no utterance will require over 100 seconds to express... 8284 if (timeElapsed > 100) { 8285 // time is likely expressed in milliseconds 8286 secondsElapsed = (e.elapsedTime/1000).toFixed(2); 8287 } 8288 else { 8289 // time is likely already expressed in seconds; just need to round it 8290 secondsElapsed = (e.elapsedTime).toFixed(2); 8291 } 8292 if (this.debug) { 8293 console.log('Finished speaking. That took ' + secondsElapsed + ' seconds.'); 8294 } 8295 if (context === 'description') { 8296 if (thisObj.prefDescPause) { 8297 if (thisObj.pausedForDescription) { 8298 thisObj.playMedia(); 8299 this.pausedForDescription = false;
8300 } 8301 } 8302 } 8303 }; 8304 utterance.onerror = function(e) { 8305 // handle error 8306 console.log('Web Speech API error',e); 8307 }; 8308 if (this.synth.paused) { 8309 this.synth.resume(); 8310 } 8311 this.synth.speak(utterance); 8312 this.speakingDescription = true; 8313 } 8314 } 8315 }; 8316 8317})(jQuery); 8318 8319(function ($) { 8320 8321 AblePlayer.prototype.isIOS = function(version) { 8322 8323 // return true if this is IOS 8324 // if version is provided check for a particular version 8325 8326 var userAgent, iOS; 8327 8328 userAgent = navigator.userAgent.toLowerCase(); 8329 iOS = /ipad|iphone|ipod/.exec(userAgent); 8330 if (iOS) { 8331 if (typeof version !== 'undefined') { 8332 if (userAgent.indexOf('os ' + version) !== -1) { 8333 // this is the target version of iOS 8334 return true; 8335 } 8336 else { 8337 return false; 8338 } 8339 } 8340 else { 8341 // no version was specified 8342 return true; 8343 } 8344 } 8345 else { 8346 // this is not IOS 8347 return false; 8348 } 8349 }; 8350 8351 AblePlayer.prototype.browserSupportsVolume = function() { 8352 8353 // To test whether the browser supports changing the volume, 8354 // create a new audio element and try setting the volume to something other than 1. 8355 // Then, retrieve the current setting to see if it preserved it. 8356 8357 // Unfortunately, this doesn't work in iOS. In 2022, our tests yield the same results as reported here: 8358 // https://stackoverflow.com/questions/72861253/how-do-i-detect-if-a-browser-does-not-support-changing-html-audio-volume 8359 8360 // So, unfortunately we have to resort to sniffing for iOS 8361 // before testing for support in other browsers 8362 var audio, testVolume; 8363 8364 if (this.isIOS()) { 8365 return false; 8366 } 8367 8368 testVolume = 0.9; // any value between 0.1 and 0.9 8369 audio = new Audio(); 8370 audio.volume = testVolume; 8371 if (audio.volume === testVolume) { 8372 return true; 8373 } 8374 else { 8375 return false; 8376 } 8377 }; 8378 8379 AblePlayer.prototype.nativeFullscreenSupported = function () { 8380 8381 return document.fullscreenEnabled || 8382 document.webkitFullscreenEnabled || 8383 document.mozFullscreenEnabled || 8384 document.msFullscreenEnabled; 8385 }; 8386 8387})(jQuery); 8388 8389(function ($) { 8390 AblePlayer.prototype.seekTo = function (newTime) { 8391 8392 var thisObj = this; 8393 8394 // define variables to be used for analytics 8395 // e.g., to measure the extent to which users seek back and forward 8396 this.seekFromTime = this.media.currentTime; 8397 this.seekToTime = newTime; 8398 8399 this.seeking = true; 8400 this.liveUpdatePending = true; 8401 8402 if (this.speakingDescription) { 8403 this.synth.cancel(); 8404 } 8405 8406 if (this.player === 'html5') { 8407 var seekable; 8408 8409 this.startTime = newTime; 8410 // Check HTML5 media "seekable" property to be sure media is seekable to startTime 8411 seekable = this.media.seekable; 8412 if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) { 8413 // ok to seek to startTime 8414 // canplaythrough will be triggered when seeking is complete 8415 // this.seeking will be set to false at that point 8416 this.media.currentTime = this.startTime; 8417 this.seekStatus = 'complete'; 8418 if (this.hasSignLanguage && this.signVideo) { 8419 // keep sign languge video in sync 8420 this.signVideo.currentTime = this.startTime; 8421 } 8422 } 8423 } 8424 else if (this.player === 'youtube') { 8425 this.youTubePlayer.seekTo(newTime,true); 8426 if (newTime > 0) { 8427 if (typeof this.$posterImg !== 'undefined') { 8428 this.$posterImg.hide(); 8429 } 8430 } 8431 } 8432 else if (this.player === 'vimeo') { 8433 this.vimeoPlayer.setCurrentTime(newTime).then(function() { 8434 // seek finished. 8435 // successful completion also fires a 'seeked' event (see event.js) 8436 thisObj.elapsed = newTime; 8437 thisObj.refreshControls('timeline'); 8438 }) 8439 } 8440 this.refreshControls('timeline'); 8441 }; 8442 8443 AblePlayer.prototype.getMediaTimes = function (duration, elapsed) { 8444 8445 // Returns an array with keys 'duration' and 'elapsed' 8446 // Vars passed to this function come courtesy of select Vimeo events 8447 // Use those if they're available. 8448 // Otherwise, will need to call the relevant media API 8449 // This function should only be called from onMediaUpdateTime() 8450 // If duration and elapsed are needed other times, use this.duration and this.elapsed 8451 8452 // both values are expressed in seconds, and all player APIs are similar: 8453 // they return a value that is rounded to the nearest second before playback begins, 8454 // then to the nearest thousandth of a second after playback begins 8455 // With HTML5 media API, some browsers are more precise (e.g., Firefox rounds to 6 decimal points) 8456 // but inconsistent (values with 9 decimal points have been sporadically observed in Safari) 8457 // For standardization, values are rounded to 6 decimal points before they're returned 8458 8459 var deferred, promise, thisObj, mediaTimes; 8460 mediaTimes = {}; 8461 8462 deferred = new $.Deferred(); 8463 promise = deferred.promise(); 8464 thisObj = this; 8465 if (typeof duration !== 'undefined' && typeof elapsed !== 'undefined') { 8466 mediaTimes['duration'] = duration; 8467 mediaTimes['elapsed'] = elapsed; 8468 deferred.resolve(mediaTimes); 8469 } 8470 else { 8471 this.getDuration().then(function(duration) { 8472 mediaTimes['duration'] = thisObj.roundDown(duration,6); 8473 thisObj.getElapsed().then(function(elapsed) {
vendor: 4,316 bytes, lines 8474-8640
8474 mediaTimes['elapsed'] = thisObj.roundDown(elapsed,6); 8475 deferred.resolve(mediaTimes); 8476 }); 8477 }); 8478 } 8479 return promise; 8480 }; 8481 8482 AblePlayer.prototype.getDuration = function () { 8483 8484 // returns duration of the current media, expressed in seconds 8485 // function is called by getMediaTimes, and return value is sanitized there 8486 var deferred, promise, thisObj; 8487 8488 deferred = new $.Deferred(); 8489 promise = deferred.promise(); 8490 thisObj = this; 8491 8492 if (this.player === 'vimeo') { 8493 if (this.vimeoPlayer) { 8494 this.vimeoPlayer.getDuration().then(function(duration) { 8495 if (duration === undefined || isNaN(duration) || duration === -1) { 8496 deferred.resolve(0); 8497 } 8498 else { 8499 deferred.resolve(duration); 8500 } 8501 }); 8502 } 8503 else { // vimeoPlayer hasn't been initialized yet. 8504 deferred.resolve(0); 8505 } 8506 } 8507 else { 8508 var duration; 8509 if (this.player === 'html5') { 8510 duration = this.media.duration; 8511 } 8512 else if (this.player === 'youtube') { 8513 if (this.youTubePlayerReady) { 8514 if (this.duration > 0) { 8515 // duration was already retrieved while checking for captions 8516 duration = this.duration; 8517 } 8518 else { 8519 duration = this.youTubePlayer.getDuration(); 8520 } 8521 } 8522 else { // the YouTube player hasn't initialized yet 8523 duration = 0; 8524 } 8525 } 8526 if (duration === undefined || isNaN(duration) || duration === -1) { 8527 deferred.resolve(0); 8528 } 8529 else { 8530 deferred.resolve(duration); 8531 } 8532 } 8533 return promise; 8534 }; 8535 8536 AblePlayer.prototype.getElapsed = function () { 8537 8538 // returns elapsed time of the current media, expressed in seconds 8539 // function is called by getMediaTimes, and return value is sanitized there 8540 8541 var deferred, promise, thisObj; 8542 8543 deferred = new $.Deferred(); 8544 promise = deferred.promise(); 8545 thisObj = this; 8546 8547 if (this.player === 'vimeo') { 8548 if (this.vimeoPlayer) { 8549 this.vimeoPlayer.getCurrentTime().then(function(elapsed) { 8550 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 8551 deferred.resolve(0); 8552 } 8553 else { 8554 deferred.resolve(elapsed); 8555 } 8556 }); 8557 } 8558 else { // vimeoPlayer hasn't been initialized yet. 8559 deferred.resolve(0); 8560 } 8561 } 8562 else { 8563 var elapsed; 8564 if (this.player === 'html5') { 8565 elapsed = this.media.currentTime; 8566 } 8567 else if (this.player === 'youtube') { 8568 if (this.youTubePlayerReady) { 8569 elapsed = this.youTubePlayer.getCurrentTime(); 8570 } 8571 else { // the YouTube player hasn't initialized yet 8572 elapsed = 0; 8573 } 8574 } 8575 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 8576 deferred.resolve(0); 8577 } 8578 else { 8579 deferred.resolve(elapsed); 8580 } 8581 } 8582 return promise; 8583 }; 8584 8585 AblePlayer.prototype.getPlayerState = function () { 8586 8587 // Returns one of the following states: 8588 // 'stopped' - Not yet played for the first time, or otherwise reset to unplayed. 8589 // 'ended' - Finished playing. 8590 // 'paused' - Not playing, but not stopped or ended. 8591 // 'buffering' - Momentarily paused to load, but will resume once data is loaded. 8592 // 'playing' - Currently playing. 8593 8594 // Commented out the following in 3.2.1 - not sure of its intended purpose 8595 // It can be useful to know player state even when swapping src 8596 // and the overhead is seemingly minimal 8597 // TODO - Investigate this further. Delete if it's not needed 8598 /* 8599 if (this.swappingSrc) { 8600 return; 8601 } 8602 */ 8603 var deferred, promise, thisObj, duration, elapsed; 8604 deferred = new $.Deferred(); 8605 promise = deferred.promise(); 8606 thisObj = this; 8607 8608 if (this.player === 'html5') { 8609 if (this.media.ended) { 8610 deferred.resolve('ended'); 8611 } 8612 else if (this.media.paused) { 8613 deferred.resolve('paused'); 8614 } 8615 else if (this.media.readyState !== 4) { 8616 deferred.resolve('buffering'); 8617 } 8618 else { 8619 deferred.resolve('playing'); 8620 } 8621 } 8622 else if (this.player === 'youtube' && this.youTubePlayerReady) { 8623 var state = this.youTubePlayer.getPlayerState(); 8624 if (state === -1 || state === 5) { 8625 deferred.resolve('stopped'); 8626 } 8627 else if (state === 0) { 8628 deferred.resolve('ended'); 8629 } 8630 else if (state === 1) { 8631 deferred.resolve('playing'); 8632 } 8633 else if (state === 2) { 8634 deferred.resolve('paused'); 8635 } 8636 else if (state === 3) { 8637 deferred.resolve('buffering'); 8638 } 8639 } 8640 else if (this.player === 'vimeo' && this.vimeoPlayer) {
8641 // curiously, Vimeo's API has no getPlaying(), getBuffering(), or getState() methods 8642 // so maybe if it's neither paused nor ended, it must be playing??? 8643 this.vimeoPlayer.getPaused().then(function(paused) { 8644 if (paused) { 8645 deferred.resolve('paused'); 8646 } 8647 else { 8648 thisObj.vimeoPlayer.getEnded().then(function(ended) { 8649 if (ended) { 8650 deferred.resolve('ended'); 8651 } 8652 else { 8653 deferred.resolve('playing'); 8654 } 8655 }); 8656 } 8657 }); 8658 } 8659 return promise; 8660 }; 8661 8662 AblePlayer.prototype.isPlaybackRateSupported = function () { 8663 8664 if (this.player === 'html5') { 8665 if (this.media.playbackRate) { 8666 return true; 8667 } 8668 else { 8669 return false; 8670 } 8671 } 8672 else if (this.player === 'youtube') { 8673 // Youtube supports varying playback rates per video. 8674 // Only expose controls if more than one playback rate is available. 8675 if (this.youTubePlayerReady) { 8676 if (this.youTubePlayer.getAvailablePlaybackRates().length > 1) { 8677 return true; 8678 } 8679 else { 8680 return false; 8681 } 8682 } 8683 else { 8684 return false; 8685 } 8686 } 8687 else if (this.player === 'vimeo') { 8688 // since this takes longer to determine, it was set previously in initVimeoPlayer() 8689 return this.vimeoSupportsPlaybackRateChange; 8690 } 8691 }; 8692 8693 AblePlayer.prototype.setPlaybackRate = function (rate) { 8694 8695 rate = Math.max(0.5, rate); 8696 8697 if (this.hasClosedDesc && this.descMethod === 'text') { 8698 // keep speech rate in sync with playback rate even if descOn is false 8699 this.syncSpeechToPlaybackRate(rate); 8700 } 8701 8702 if (this.player === 'html5') { 8703 this.media.playbackRate = rate; 8704 } 8705 else if (this.player === 'youtube') { 8706 this.youTubePlayer.setPlaybackRate(rate); 8707 } 8708 else if (this.player === 'vimeo') { 8709 this.vimeoPlayer.setPlaybackRate(rate); 8710 } 8711 if (this.hasSignLanguage && this.signVideo) { 8712 this.signVideo.playbackRate = rate; 8713 } 8714 this.playbackRate = rate; 8715 this.$speed.text(this.tt.speed + ': ' + rate.toFixed(2).toString() + 'x'); 8716 }; 8717 8718 AblePlayer.prototype.getPlaybackRate = function () { 8719 8720 if (this.player === 'html5') { 8721 return this.media.playbackRate; 8722 } 8723 else if (this.player === 'youtube') { 8724 if (this.youTubePlayerReady) { 8725 return this.youTubePlayer.getPlaybackRate(); 8726 } 8727 } 8728 }; 8729 8730 AblePlayer.prototype.isPaused = function () { 8731 8732 // Note there are three player states that count as paused in this sense, 8733 // and one of them is named 'paused'. 8734 // A better name would be 'isCurrentlyNotPlayingOrBuffering' 8735 8736 var state; 8737 8738 if (this.player === 'vimeo') { 8739 // just rely on value of this.playing 8740 if (this.playing) { 8741 return false; 8742 } 8743 else { 8744 return true; 8745 } 8746 } 8747 else { 8748 this.getPlayerState().then(function(state) { 8749 // if any of the following is true, consider the media 'paused' 8750 return state === 'paused' || state === 'stopped' || state === 'ended'; 8751 }); 8752 } 8753 }; 8754 8755 AblePlayer.prototype.pauseMedia = function () { 8756 8757 var thisObj = this; 8758 8759 if (this.player === 'html5') { 8760 this.media.pause(true); 8761 if (this.hasSignLanguage && this.signVideo) { 8762 this.signVideo.pause(true); 8763 } 8764 } 8765 else if (this.player === 'youtube') { 8766 this.youTubePlayer.pauseVideo(); 8767 } 8768 else if (this.player === 'vimeo') { 8769 this.vimeoPlayer.pause(); 8770 } 8771 }; 8772 8773 AblePlayer.prototype.playMedia = function () { 8774 8775 var thisObj = this; 8776 8777 if (this.player === 'html5') { 8778 this.media.play(true); 8779 if (this.hasSignLanguage && this.signVideo) { 8780 this.signVideo.play(true); 8781 } 8782 } 8783 else if (this.player === 'youtube') { 8784 8785 this.youTubePlayer.playVideo(); 8786 if (typeof this.$posterImg !== 'undefined') { 8787 this.$posterImg.hide(); 8788 } 8789 this.stoppingYouTube = false; 8790 } 8791 else if (this.player === 'vimeo') { 8792 this.vimeoPlayer.play(); 8793 } 8794 this.startedPlaying = true; 8795 if (this.hideControls) { 8796 // wait briefly after playback begins, then hide controls 8797 this.hidingControls = true; 8798 this.invokeHideControlsTimeout(); 8799 } 8800 }; 8801 8802 AblePlayer.prototype.fadeControls = function(direction) { 8803 8804 // Visibly fade controls without hiding them from screen reader users 8805 8806 // direction is either 'out' or 'in' 8807 8808 // TODO: This still needs work. 8809 // After the player fades, it's replaced by an empty space 8810 // Would be better if the video and captions expanded to fill the void 8811 // Attempted to fade out to 0 opacity, then move the playerDiv offscreen 8812 // and expand the mediaContainer to fill the vacated space 8813 // However, my attempts to do this have been choppy and buggy 8814 // Code is preserved below and commented out 8815
8816 var thisObj, mediaHeight, playerHeight, newMediaHeight; 8817 var thisObj = this; 8818 8819 if (direction == 'out') { 8820 // get the original height of two key components: 8821 mediaHeight = this.$mediaContainer.height(); 8822 playerHeight = this.$playerDiv.height(); 8823 newMediaHeight = mediaHeight + playerHeight; 8824 8825 // fade slowly to transparency 8826 this.$playerDiv.fadeTo(2000,0,function() { 8827 /* 8828 // when finished, position playerDiv offscreen 8829 // thisObj.$playerDiv.addClass('able-offscreen'); 8830 // Expand the height of mediaContainer to fill the void (needs work) 8831 thisObj.$mediaContainer.animate({ 8832 height: newMediaHeight 8833 },500); 8834 */ 8835 }); 8836 } 8837 else if (direction == 'in') { 8838 // restore captionsContainer to its original height (needs work) 8839 // this.$mediaContainer.removeAttr('style'); 8840 // fade relatively quickly back to its original position with full opacity 8841 // this.$playerDiv.removeClass('able-offscreen').fadeTo(100,1); 8842 this.$playerDiv.fadeTo(100,1); 8843 } 8844 }; 8845 8846 AblePlayer.prototype.invokeHideControlsTimeout = function () { 8847 8848 // invoke timeout for waiting a few seconds after a mouse move or key down 8849 // before hiding controls again 8850 var thisObj = this; 8851 this.hideControlsTimeout = window.setTimeout(function() { 8852 if (typeof thisObj.playing !== 'undefined' && thisObj.playing === true && thisObj.hideControls) { 8853 thisObj.fadeControls('out'); 8854 thisObj.controlsHidden = true; 8855 } 8856 },5000); 8857 this.hideControlsTimeoutStatus = 'active'; 8858 }; 8859 8860 AblePlayer.prototype.refreshControls = function(context, duration, elapsed) { 8861 8862 // context is one of the following: 8863 // 'init' - initial build (or subsequent change that requires full rebuild) 8864 // 'timeline' - a change may effect time-related controls 8865 // 'captions' - a change may effect caption-related controls 8866 // 'descriptions' - a change may effect description-related controls 8867 // 'transcript' - a change may effect the transcript window or button 8868 // 'fullscreen' - a change has been triggered by full screen toggle 8869 // 'playpause' - a change triggered by either a 'play' or 'pause' event 8870 8871 // NOTE: context is not currently supported. 8872 // The steps in this function have too many complex interdependencies 8873 // The gains in efficiency are offset by the possibility of introducing bugs 8874 // For now, executing everything 8875 context = 'init'; 8876 8877 // duration and elapsed are passed from callback functions of Vimeo API events 8878 // duration is expressed as sss.xxx 8879 // elapsed is expressed as sss.xxx 8880 8881 var thisObj, duration, elapsed, lastChapterIndex, displayElapsed, 8882 updateLive, textByState, timestamp, widthUsed, 8883 leftControls, rightControls, seekbarWidth, seekbarSpacer, captionsCount, 8884 buffered, newTop, statusBarHeight, speedHeight, statusBarWidthBreakpoint, 8885 newSvgData; 8886 8887 thisObj = this; 8888 if (this.swappingSrc) { 8889 if (this.playing) { 8890 // wait until new source has loaded before refreshing controls 8891 // can't wait if player is NOT playing because some critical events 8892 // won't fire until playback of new media starts 8893 return; 8894 } 8895 } 8896 8897 if (context === 'timeline' || context === 'init') { 8898 // all timeline-related functionality requires both duration and elapsed 8899 if (typeof this.duration === 'undefined') { 8900 // wait until duration is known before proceeding with refresh 8901 return; 8902 } 8903 if (this.useChapterTimes) { 8904 this.chapterDuration = this.getChapterDuration(); 8905 this.chapterElapsed = this.getChapterElapsed(); 8906 } 8907 8908 if (this.useFixedSeekInterval === false && this.seekIntervalCalculated === false && this.duration > 0) { 8909 // couldn't calculate seekInterval previously; try again. 8910 this.setSeekInterval(); 8911 } 8912 8913 if (this.seekBar) { 8914 if (this.useChapterTimes) { 8915 lastChapterIndex = this.selectedChapters.cues.length-1; 8916 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 8917 // this is the last chapter 8918 if (this.currentChapter.end !== this.duration) { 8919 // chapter ends before or after video ends 8920 // need to adjust seekbar duration to match video end 8921 this.seekBar.setDuration(this.duration - this.currentChapter.start); 8922 } 8923 else { 8924 this.seekBar.setDuration(this.chapterDuration); 8925 } 8926 } 8927 else { 8928 // this is not the last chapter 8929 this.seekBar.setDuration(this.chapterDuration); 8930 } 8931 } 8932 else { 8933 if (!(this.duration === undefined || isNaN(this.duration) || this.duration === -1)) { 8934 this.seekBar.setDuration(this.duration); 8935 } 8936 } 8937 if (!(this.seekBar.tracking)) { 8938 // Only update the aria live region if we have an update pending 8939 // (from a seek button control) or if the seekBar has focus. 8940 // We use document.activeElement instead of $(':focus') due to a strange bug: 8941 // When the seekHead element is focused, .is(':focus') is failing and $(':focus') is returning an undefined element. 8942 updateLive = this.liveUpdatePending || this.seekBar.seekHead.is($(document.activeElement)); 8943 this.liveUpdatePending = false;
vendor: 1,290 bytes, lines 8944-8983
8944 if (this.useChapterTimes) { 8945 this.seekBar.setPosition(this.chapterElapsed, updateLive); 8946 } 8947 else { 8948 this.seekBar.setPosition(this.elapsed, updateLive); 8949 } 8950 } 8951 8952 // When seeking, display the seek bar time instead of the actual elapsed time. 8953 if (this.seekBar.tracking) { 8954 displayElapsed = this.seekBar.lastTrackPosition; 8955 } 8956 else { 8957 if (this.useChapterTimes) { 8958 displayElapsed = this.chapterElapsed; 8959 } 8960 else { 8961 displayElapsed = this.elapsed; 8962 } 8963 } 8964 } 8965 // update elapsed & duration 8966 if (typeof this.$durationContainer !== 'undefined') { 8967 if (this.useChapterTimes) { 8968 this.$durationContainer.text(' / ' + this.formatSecondsAsColonTime(this.chapterDuration)); 8969 } 8970 else { 8971 this.$durationContainer.text(' / ' + this.formatSecondsAsColonTime(this.duration)); 8972 } 8973 } 8974 if (typeof this.$elapsedTimeContainer !== 'undefined') { 8975 this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed)); 8976 } 8977 8978 if (this.skin === 'legacy') { 8979 // Update seekbar width. // steve 8980 // To do this, we need to calculate the width of all buttons surrounding it. 8981 /* if (this.seekBar) { 8982 widthUsed = 0; 8983 leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-control
8983s'); 8984 rightControls = leftControls.next('div.able-right-controls'); 8985 leftControls.children().each(function () { 8986 if ($(this).attr('role')=='button') { 8987 widthUsed += $(this).outerWidth(true); // true = include margin 8988 } 8989 }); 8990 rightControls.children().each(function () { 8991 if ($(this).attr('role')=='button') { 8992 widthUsed += $(this).outerWidth(true); 8993 } 8994 }); 8995 if (this.fullscreen) { 8996 seekbarWidth = $(window).width() - widthUsed; 8997 } 8998 else { 8999 // seekbar is wide enough to fill the remaining space 9000 // include a 5px buffer to account for minor browser differences 9001 } 9002 // Sometimes some minor fluctuations based on browser weirdness, so set a threshold. 9003 if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) { 9004 this.seekBar.setWidth(seekbarWidth); 9005 } 9006 } */ 9007 } 9008 9009 // Update buffering progress. 9010 // TODO: Currently only using the first HTML5 buffered interval, 9011 // but this fails sometimes when buffering is split into two or more intervals. 9012 if (this.player === 'html5') { 9013 if (this.media.buffered.length > 0) { 9014 buffered = this.media.buffered.end(0); 9015 if (this.useChapterTimes) { 9016 if (buffered > this.chapterDuration) { 9017 buffered = this.chapterDuration; 9018 } 9019 if (this.seekBar) { 9020 this.seekBar.setBuffered(buffered / this.chapterDuration); 9021 } 9022 } 9023 else { 9024 if (this.seekBar) { 9025 if (!isNaN(buffered)) { 9026 this.seekBar.setBuffered(buffered / duration); 9027 } 9028 } 9029 } 9030 } 9031 } 9032 else if (this.player === 'youtube') { 9033 if (this.seekBar) { 9034 if (this.youTubePlayerReady) { 9035 this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction()); 9036 } 9037 } 9038 } 9039 else if (this.player === 'vimeo') { 9040 // TODO: Add support for Vimeo buffering update 9041 } 9042 } // end if context == 'timeline' or 'init' 9043 9044 if (context === 'descriptions' || context == 'init'){ 9045 9046 if (this.$descButton) { 9047 if (this.descOn) { 9048 this.$descButton.removeClass('buttonOff').attr('aria-label',this.tt.turnOffDescriptions); 9049 this.$descButton.find('span.able-clipped').text(this.tt.turnOffDescriptions); 9050 } 9051 else { 9052 this.$descButton.addClass('buttonOff').attr('aria-label',this.tt.turnOnDescriptions); 9053 this.$descButton.find('span.able-clipped').text(this.tt.turnOnDescriptions); 9054 } 9055 } 9056 } 9057 9058 if (context === 'captions' || context == 'init'){ 9059 9060 if (this.$ccButton) { 9061 9062 captionsCount = this.captions.length; 9063 9064 // Button has a different title depending on the number of captions. 9065 // If only one caption track, this is "Show captions" and "Hide captions" 9066 // Otherwise, it is just always "Captions" 9067 if (!this.captionsOn) { 9068 this.$ccButton.addClass('buttonOff'); 9069 this.$ccButton.attr('aria-pressed', 'false') 9070 if (captionsCount === 1) { 9071 this.$ccButton.attr('aria-label',this.tt.showCaptions); 9072 this.$ccButton.find('span.able-clipped').text(this.tt.showCaptions); 9073 } 9074 } 9075 else { 9076 this.$ccButton.removeClass('buttonOff'); 9077 this.$ccButton.attr('aria-pressed', 'true') 9078 if (captionsCount === 1) { 9079 this.$ccButton.attr('aria-label',this.tt.hideCaptions); 9080 this.$ccButton.find('span.able-clipped').text(this.tt.hideCaptions); 9081 } 9082 } 9083 9084 if (captionsCount > 1) { 9085 this.$ccButton.attr({ 9086 'aria-label': this.tt.captions, 9087 'aria-haspopup': 'true', 9088 'aria-controls': this.mediaId + '-captions-menu' 9089 }); 9090 this.$ccButton.find('span.able-clipped').text(this.tt.captions); 9091 } 9092 } 9093 } 9094 9095 if (context === 'fullscreen' || context == 'init'){ 9096 if (this.$fullscreenButton) { 9097 if (!this.fullscreen) { 9098 this.$fullscreenButton.attr('aria-label', this.tt.enterFullscreen); 9099 if (this.iconType === 'font') { 9100 this.$fullscreenButton.find('span').first().removeClass('icon-fullscreen-collapse').addClass('icon-fullscreen-expand'); 9101 this.$fullscreenButton.find('span.able-clipped').text(this.tt.enterFullscreen); 9102 } 9103 else if (this.iconType === 'svg') {
9104 newSvgData = this.getSvgData('fullscreen-expand'); 9105 this.$fullscreenButton.find('svg').attr('viewBox',newSvgData[0]); 9106 this.$fullscreenButton.find('path').attr('d',newSvgData[1]); 9107 this.$fullscreenButton.find('span.able-clipped').text(this.tt.enterFullscreen); 9108 } 9109 else { 9110 this.$fullscreenButton.find('img').attr('src',this.fullscreenExpandButtonImg); 9111 } 9112 } 9113 else { 9114 this.$fullscreenButton.attr('aria-label',this.tt.exitFullscreen); 9115 if (this.iconType === 'font') { 9116 this.$fullscreenButton.find('span').first().removeClass('icon-fullscreen-expand').addClass('icon-fullscreen-collapse'); 9117 this.$fullscreenButton.find('span.able-clipped').text(this.tt.exitFullscreen); 9118 } 9119 else if (this.iconType === 'svg') { 9120 newSvgData = this.getSvgData('fullscreen-collapse'); 9121 this.$fullscreenButton.find('svg').attr('viewBox',newSvgData[0]); 9122 this.$fullscreenButton.find('path').attr('d',newSvgData[1]); 9123 this.$fullscreenButton.find('span.able-clipped').text(this.tt.exitFullscreen); 9124 } 9125 else { 9126 this.$fullscreenButton.find('img').attr('src',this.fullscreenCollapseButtonImg); 9127 } 9128 } 9129 } 9130 } 9131 if (context === 'playpause' || context == 'init'){ 9132 if (typeof this.$bigPlayButton !== 'undefined' && typeof this.seekBar !== 'undefined') { 9133 // Choose show/hide for big play button and adjust position. 9134 if (this.paused && !this.seekBar.tracking) { 9135 if (!this.hideBigPlayButton) { 9136 this.$bigPlayButton.show(); 9137 this.$bigPlayButton.attr('aria-hidden', 'false'); 9138 9139 } 9140 if (this.fullscreen) { 9141 this.$bigPlayButton.width($(window).width()); 9142 this.$bigPlayButton.height($(window).height()); 9143 } 9144 else { 9145 this.$bigPlayButton.width(this.$mediaContainer.width()); 9146 this.$bigPlayButton.height(this.$mediaContainer.height()); 9147 } 9148 } 9149 else { 9150 this.$bigPlayButton.hide(); 9151 this.$bigPlayButton.attr('aria-hidden', 'true'); 9152 } 9153 } 9154 } 9155 9156 if (context === 'transcript' || context == 'init'){ 9157 9158 if (this.transcriptType) { 9159 // Sync checkbox and autoScrollTranscript with user preference 9160 if (this.prefAutoScrollTranscript === 1) { 9161 this.autoScrollTranscript = true; 9162 this.$autoScrollTranscriptCheckbox.prop('checked',true); 9163 } 9164 else { 9165 this.autoScrollTranscript = false; 9166 this.$autoScrollTranscriptCheckbox.prop('checked',false); 9167 } 9168 9169 // If transcript locked, scroll transcript to current highlight location. 9170 if (this.autoScrollTranscript && this.currentHighlight) { 9171 newTop = Math.floor(this.$transcriptDiv.scrollTop() + 9172 $(this.currentHighlight).position().top - 9173 (this.$transcriptDiv.height() / 2) + 9174 ($(this.currentHighlight).height() / 2)); 9175 if (newTop !== Math.floor(this.$transcriptDiv.scrollTop())) { 9176 // Set a flag to ignore the coming scroll event. 9177 // there's no other way I know of to differentiate programmatic and user-initiated scroll events. 9178 this.scrollingTranscript = true; 9179 // only scroll once after moving a highlight 9180 if (this.movingHighlight) { 9181 this.$transcriptDiv.scrollTop(newTop); 9182 this.movingHighlight = false; 9183 } 9184 } 9185 } 9186 } 9187 } 9188 9189 if (context === 'init') { 9190 9191 if (this.$chaptersButton) { 9192 this.$chaptersButton.attr({ 9193 'aria-label': this.tt.chapters, 9194 'aria-haspopup': 'true',
9195 'aria-controls': this.mediaId + '-chapters-menu' 9196 }); 9197 } 9198 } 9199 9200 if (context === 'timeline' || context === 'playpause' || context === 'init') { 9201 9202 // update status 9203 textByState = { 9204 'stopped': this.tt.statusStopped, 9205 'paused': this.tt.statusPaused, 9206 'playing': this.tt.statusPlaying, 9207 'buffering': this.tt.statusBuffering, 9208 'ended': this.tt.statusEnd 9209 }; 9210 9211 if (this.stoppingYouTube) { 9212 // stoppingYouTube is true temporarily while video is paused and seeking to 0 9213 // See notes in handleRestart() 9214 // this.stoppingYouTube will be reset when seek to 0 is finished (in event.js > onMediaUpdateTime()) 9215 if (this.$status.text() !== this.tt.statusStopped) { 9216 this.$status.text(this.tt.statusStopped); 9217 } 9218 if (this.$playpauseButton.find('span').first().hasClass('icon-pause')) { 9219 if (this.iconType === 'font') { 9220 this.$playpauseButton.find('span').first().removeClass('icon-pause').addClass('icon-play'); 9221 this.$playpauseButton.find('span.able-clipped').text(this.tt.play); 9222 } 9223 else if (this.iconType === 'svg') { 9224 newSvgData = this.getSvgData('play'); 9225 this.$playpauseButton.find('svg').attr('viewBox',newSvgData[0]); 9226 this.$playpauseButton.find('path').attr('d',newSvgData[1]); 9227 this.$playpauseButton.find('span.able-clipped').text(this.tt.play); 9228 } 9229 else { 9230 this.$playpauseButton.find('img').attr('src',this.playButtonImg); 9231 } 9232 } 9233 } 9234 else { 9235 if (typeof this.$status !== 'undefined' && typeof this.seekBar !== 'undefined') { 9236 // Update the text only if it's changed since it has role="alert"; 9237 // also don't update while tracking, since this may Pause/Play the player but we don't want to send a Pause/Play update. 9238 this.getPlayerState().then(function(currentState) { 9239 if (thisObj.$status.text() !== textByState[currentState] && !thisObj.seekBar.tracking) { 9240 // Debounce updates; only update after status has stayed steadily different for a while 9241 // "A while" is defined differently depending on context 9242 if (thisObj.swappingSrc) { 9243 // this is where most of the chatter occurs (e.g., playing, paused, buffering, playing), 9244 // so set a longer wait time before writing a status message 9245 if (!thisObj.debouncingStatus) { 9246 thisObj.statusMessageThreshold = 2000; // in ms (2 seconds) 9247 } 9248 } 9249 else { 9250 // for all other contexts (e.g., users clicks Play/Pause) 9251 // user should receive more rapid feedback 9252 if (!thisObj.debouncingStatus) { 9253 thisObj.statusMessageThreshold = 250; // in ms 9254 } 9255 } 9256 timestamp = (new Date()).getTime(); 9257 if (!thisObj.statusDebounceStart) { 9258 thisObj.statusDebounceStart = timestamp; 9259 // Call refreshControls() again after allotted time has passed 9260 thisObj.debouncingStatus = true; 9261 thisObj.statusTimeout = setTimeout(function () { 9262 thisObj.debouncingStatus = false; 9263 thisObj.refreshControls(context); 9264 }, thisObj.statusMessageThreshold); 9265 } 9266 else if ((timestamp - thisObj.statusDebounceStart) > thisObj.statusMessageThreshold) { 9267 thisObj.$status.text(textByState[currentState]); 9268 thisObj.statusDebounceStart = null; 9269 clearTimeout(thisObj.statusTimeout); 9270 thisObj.statusTimeout = null; 9271 } 9272 } 9273 else { 9274 thisObj.statusDebounceStart = null; 9275 thisObj.debouncingStatus = false; 9276 clearTimeout(thisObj.statusTimeout); 9277 thisObj.statusTimeout = null; 9278 } 9279 // Don't change play/pause button display while using the seek bar (or if YouTube stopped) 9280 if (!thisObj.seekBar.tracking && !thisObj.stoppingYouTube) { 9281 if (currentState === 'paused' || currentState === 'stopped' || currentState === 'ended') { 9282 thisObj.$playpauseButton.attr('aria-label',thisObj.tt.play); 9283 9284 if (thisObj.iconType === 'font') { 9285 thisObj.$playpauseButton.find('span').first().removeClass('icon-pause').addClass('icon-play'); 9286 thisObj.$playpauseButton.find('span.able-clipped').text(thisObj.tt.play); 9287 } 9288 else if (thisObj.iconType === 'svg') { 9289 newSvgData = thisObj.getSvgData('play'); 9290 thisObj.$playpauseButton.find('svg').attr('viewBox',newSvgData[0]); 9291 thisObj.$playpauseButton.find('path').attr('d',newSvgData[1]); 9292 thisObj.$playpauseButton.find('span.able-clipped').text(thisObj.tt.play); 9293 } 9294 else { 9295 thisObj.$playpauseButton.find('img').attr('src',thisObj.playButtonImg); 9296 } 9297 } 9298 else { 9299 thisObj.$playpauseButton.attr('aria-label',thisObj.tt.pause); 9300 9301 if (thisObj.iconType === 'font') { 9302 thisObj.$playpauseButton.find('span').first().removeClass('icon-play').addClass('icon-pause'); 9303 thisObj.$playpauseButton.find('span.able-clipped').text(thisObj.tt.pause); 9304 } 9305 else if (thisObj.iconType === 'svg') { 9306 newSvgData = thisObj.getSvgData('pause'); 9307 thisObj.$playpauseButton.find('svg').attr('viewBox',newSvgData[0]); 9308 thisObj.$playpauseButton.find('path').attr('d',newSvgData[1]); 9309 thisObj.$playpauseButton.find('span.able-clipped').text(thisObj.tt.pause); 9310 } 9311 else { 9312 thisObj.$playpauseButton.find('img').attr('src',thisObj.pauseButtonImg); 9313 } 9314 } 9315 } 9316 }); 9317 } 9318 } 9319 } 9320 9321 // Show/hide status bar content conditionally 9322 if (!this.fullscreen) { 9323 statusBarWidthBreakpoint = 300; 9324 statusBarHeight = this.$statusBarDiv.height(); 9325 speedHeight = this.$statusBarDiv.find('span.able-speed').height(); 9326 if (speedHeight > (statusBarHeight + 5)) { 9327 // speed bar is wrapping (happens often in German player) 9328 this.$statusBarDiv.find('span.able-speed').hide(); 9329 this.hidingSpeed = true; 9330 } 9331 else { 9332 if (this.hidingSpeed) { 9333 this.$statusBarDiv.find('span.able-speed').show(); 9334 this.hidingSpeed = false;
9335 } 9336 if (this.$statusBarDiv.width() < statusBarWidthBreakpoint) { 9337 // Player is too small for a speed span 9338 this.$statusBarDiv.find('span.able-speed').hide(); 9339 this.hidingSpeed = true; 9340 } 9341 else { 9342 if (this.hidingSpeed) { 9343 this.$statusBarDiv.find('span.able-speed').show(); 9344 this.hidingSpeed = false; 9345 } 9346 } 9347 } 9348 } 9349 9350 }; 9351 9352 AblePlayer.prototype.getHiddenWidth = function($el) { 9353 9354 // jQuery returns for width() if element is hidden 9355 // this function is a workaround 9356 9357 // save a reference to a cloned element that can be measured 9358 var $hiddenElement = $el.clone().appendTo('body'); 9359 9360 // calculate the width of the clone 9361 var width = $hiddenElement.outerWidth(); 9362 9363 // remove the clone from the DOM 9364 $hiddenElement.remove(); 9365 9366 return width; 9367 }; 9368 9369 AblePlayer.prototype.handlePlay = function(e) { 9370 9371 if (this.paused) { 9372 // user clicked play 9373 this.okToPlay = true; 9374 this.playMedia(); 9375 if (this.synth.paused) { 9376 // media was paused while description was speaking 9377 // resume utterance 9378 this.synth.resume(); 9379 } 9380 } 9381 else { 9382 // user clicked pause 9383 this.okToPlay = false; 9384 this.pauseMedia(); 9385 if (this.speakingDescription) { 9386 // pause the current utterance 9387 // it will resume when the user presses play 9388 this.synth.pause(); 9389 } 9390 } 9391 if (this.speechEnabled === null) { 9392 this.initSpeech('play'); 9393 } 9394 }; 9395 9396 AblePlayer.prototype.handleRestart = function() { 9397 9398 if (this.speakingDescription) { 9399 // cancel audio description 9400 this.synth.cancel(); 9401 } 9402 this.seekTo(0); 9403 }; 9404 9405 AblePlayer.prototype.handlePrevTrack = function() { 9406 9407 if (this.playlistIndex === 0) { 9408 // currently on the first track 9409 // wrap to bottom and play the last track 9410 this.playlistIndex = this.$playlist.length - 1; 9411 } 9412 else { 9413 this.playlistIndex--; 9414 } 9415 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 9416 this.cuePlaylistItem(this.playlistIndex); 9417 }; 9418 9419 AblePlayer.prototype.handleNextTrack = function() { 9420 9421 if (this.playlistIndex === this.$playlist.length - 1) { 9422 // currently on the last track 9423 // wrap to top and play the forst track 9424 this.playlistIndex = 0; 9425 } 9426 else { 9427 this.playlistIndex++; 9428 } 9429 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 9430 this.cuePlaylistItem(this.playlistIndex); 9431 }; 9432 9433 AblePlayer.prototype.handleRewind = function() { 9434 9435 var targetTime; 9436 9437 targetTime = this.elapsed - this.seekInterval; 9438 if (this.useChapterTimes) { 9439 if (targetTime < this.currentChapter.start) { 9440 targetTime = this.currentChapter.start; 9441 } 9442 } 9443 else { 9444 if (targetTime < 0) { 9445 targetTime = 0; 9446 } 9447 } 9448 this.seekTo(targetTime); 9449 }; 9450 9451 AblePlayer.prototype.handleFastForward = function() { 9452 9453 var targetTime, lastChapterIndex; 9454 9455 lastChapterIndex = this.chapters.length-1; 9456 targetTime = this.elapsed + this.seekInterval; 9457 if (this.useChapterTimes) { 9458 if (this.chapters[lastChapterIndex] == this.currentChapter) { 9459 // this is the last chapter 9460 if (targetTime > this.duration || targetTime > this.currentChapter.end) { 9461 // targetTime would exceed the end of the video (or chapter) 9462 // scrub to end of whichever is earliest 9463 targetTime = Math.min(this.duration, this.currentChapter.end); 9464 } 9465 else if (this.duration % targetTime < this.seekInterval) { 9466 // nothing left but pocket change after seeking to targetTime 9467 // go ahead and seek to end of video (or chapter), whichever is earliest 9468 targetTime = Math.min(this.duration, this.currentChapter.end); 9469 } 9470 } 9471 else { 9472 // this is not the last chapter 9473 if (targetTime > this.currentChapter.end) { 9474 // targetTime would exceed the end of the chapter 9475 // scrub exactly to end of chapter 9476 targetTime = this.currentChapter.end; 9477 } 9478 } 9479 } 9480 else { 9481 // not using chapter times 9482 if (targetTime > this.duration) { 9483 targetTime = this.duration; 9484 } 9485 } 9486 this.seekTo(targetTime); 9487 }; 9488 9489 AblePlayer.prototype.handleRateIncrease = function() { 9490 this.changeRate(1); 9491 }; 9492 9493 AblePlayer.prototype.handleRateDecrease = function() { 9494 this.changeRate(-1); 9495 }; 9496 9497 // Increases or decreases playback rate, where dir is 1 or -1 indication direction. 9498 AblePlayer.prototype.changeRate = function (dir) { 9499 9500 var rates, currentRate, index, newRate, vimeoMin, vimeoMax; 9501 9502 if (this.player === 'html5') { 9503 this.setPlaybackRate(this.getPlaybackRate() + (0.25 * dir)); 9504 } 9505 else if (this.player === 'youtube') { 9506 if (this.youTubePlayerReady) { 9507 rates = this.youTubePlayer.getAvailablePlaybackRates(); 9508 currentRate = this.getPlaybackRate(); 9509 index = rates.indexOf(currentRate); 9510 if (index === -1) { 9511 console.log('ERROR: Youtube returning unknown playback rate ' + currentRate.toString()); 9512 } 9513 else { 9514 index += dir; 9515 // Can only increase or decrease rate if there's another rate available. 9516 if (index < rates.length && index >= 0) { 9517 this.setPlaybackRate(rates[index]); 9518 } 9519 } 9520 } 9521 } 9522 else if (this.player === 'vimeo') { 9523 // range is 0.5 to 2 9524 // increase/decrease in inrements of 0.5 9525 vimeoMin = 0.5; 9526 vimeoMax = 2; 9527 if (dir === 1) { 9528 if (this.vimeoPlaybackRate + 0.5 <= vimeoMax) { 9529 newRate = this.vimeoPlaybackRate + 0.5; 9530 } 9531 else { 9532 newRate = vimeoMax; 9533 } 9534 } 9535 else if (dir === -1) { 9536 if (this.vimeoPlaybackRate - 0.5 >= vimeoMin) { 9537 newRate = this.vimeoPlaybackRate - 0.5; 9538 } 9539 else { 9540 newRate = vimeoMin; 9541 } 9542 } 9543 this.setPlaybackRate(newRate); 9544 } 9545 }; 9546 9547 AblePlayer.prototype.handleCaptionToggle = function() { 9548 9549 var thisObj = this; 9550 var captions; 9551 if (this.hidingPopup) { 9552 // stopgap to prevent spacebar in Firefox from reopening popup 9553 // immediately after closing it 9554 this.hidingPopup = false;
9555 return false; 9556 } 9557 if (this.captions.length) { 9558 captions = this.captions; 9559 } 9560 else { 9561 captions = []; 9562 } 9563 if (captions.length === 1) { 9564 // When there's only one set of captions, just do an on/off toggle. 9565 if (this.captionsOn === true) { 9566 // turn them off 9567 this.captionsOn = false; 9568 this.prefCaptions = 0; 9569 this.$ccButton.attr('aria-pressed', 'false'); 9570 this.updateCookie('prefCaptions'); 9571 if (this.usingYouTubeCaptions) { 9572 this.youTubePlayer.unloadModule('captions'); 9573 } 9574 else if (this.usingVimeoCaptions) { 9575 this.vimeoPlayer.disableTextTrack(); 9576 } 9577 else { 9578 this.$captionsWrapper.hide(); 9579 } 9580 } 9581 else { 9582 // captions are off. Turn them on. 9583 this.captionsOn = true; 9584 this.prefCaptions = 1; 9585 this.$ccButton.attr('aria-pressed', 'true'); 9586 this.updateCookie('prefCaptions'); 9587 if (this.usingYouTubeCaptions) { 9588 this.youTubePlayer.loadModule('captions'); 9589 } 9590 else if (this.usingVimeoCaptions) { 9591 this.vimeoPlayer.enableTextTrack(this.captionLang).then(function(track) { 9592 // track.language = the iso code for the language 9593 // track.kind = 'captions' or 'subtitles' 9594 // track.label = the human-readable label 9595 }).catch(function(error) { 9596 switch (error.name) { 9597 case 'InvalidTrackLanguageError': 9598 // no track was available with the specified language 9599 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 9600 break; 9601 case 'InvalidTrackError': 9602 // no track was available with the specified language and kind 9603 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 9604 break; 9605 default: 9606 // some other error occurred 9607 console.log('Error loading ' + track.label + ' ' + track.kind + ' track'); 9608 break; 9609 } 9610 }); 9611 } 9612 else { 9613 this.$captionsWrapper.show(); 9614 } 9615 for (var i=0; i<captions.length; i++) { 9616 if (captions[i].def === true) { // this is the default language 9617 this.selectedCaptions = captions[i]; 9618 } 9619 } 9620 this.selectedCaptions = this.captions[0]; 9621 if (this.descriptions.length >= 0) { 9622 this.selectedDescriptions = this.descriptions[0]; 9623 } 9624 } 9625 this.refreshControls('captions'); 9626 } 9627 else { 9628 // there is more than one caption track. 9629 // clicking on a track is handled via caption.js > getCaptionClickFunction() 9630 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 9631 this.captionsPopup.hide(); 9632 this.hidingPopup = false; 9633 this.$ccButton.attr('aria-expanded', 'false') 9634 this.waitThenFocus(this.$ccButton); 9635 } 9636 else { 9637 this.closePopups(); 9638 if (this.captionsPopup) { 9639 this.captionsPopup.show(); 9640 this.$ccButton.attr('aria-expanded','true'); 9641 9642 // Gives time to "register" expanded ccButton 9643 setTimeout(function() { 9644 thisObj.captionsPopup.css('top', thisObj.$ccButton.position().top - thisObj.captionsPopup.outerHeight()); 9645 thisObj.captionsPopup.css('left', thisObj.$ccButton.position().left) 9646 // Place focus on the first button (even if another button is checked) 9647 thisObj.captionsPopup.find('li').removeClass('able-focus'); 9648 thisObj.captionsPopup.find('li').first().focus().addClass('able-focus'); 9649 }, 50); 9650 } 9651 } 9652 } 9653 }; 9654 9655 /** 9656 * Gives enough time for DOM changes to take effect before adjusting focus. 9657 * Helpful for allowing screen reading of elements whose state is intermittently changed. 9658 * 9659 * @param {*} $el element to focus on 9660 * @param {*} timeout optional wait time in milliseconds before focus 9661 */ 9662 AblePlayer.prototype.waitThenFocus = function($el, timeout) { 9663 9664 // Default wait time of 50 ms 9665 var _timeout = (timeout === undefined || timeout === null) ? 50 : timeout; 9666 9667 setTimeout(function() { 9668 $el.focus(); 9669 }, _timeout); 9670 } 9671 9672 AblePlayer.prototype.handleChapters = function () { 9673 if (this.hidingPopup) { 9674 // stopgap to prevent spacebar in Firefox from reopening popup 9675 // immediately after closing it 9676 this.hidingPopup = false; 9677 return false;
9678 } 9679 if (this.chaptersPopup.is(':visible')) { 9680 this.chaptersPopup.hide(); 9681 this.hidingPopup = false; 9682 this.$chaptersButton.attr('aria-expanded','false').focus(); 9683 } 9684 else { 9685 this.closePopups(); 9686 this.chaptersPopup.show(); 9687 this.$chaptersButton.attr('aria-expanded','true'); 9688 this.chaptersPopup.css('top', this.$chaptersButton.position().top - this.chaptersPopup.outerHeight()); 9689 this.chaptersPopup.css('left', this.$chaptersButton.position().left) 9690 9691 // Highlight the current chapter, if any chapters are checked 9692 // Otherwise, place focus on the first chapter 9693 this.chaptersPopup.find('li').removeClass('able-focus'); 9694 if (this.chaptersPopup.find('li[aria-checked="true"]').length) { 9695 this.chaptersPopup.find('li[aria-checked="true"]').focus().addClass('able-focus'); 9696 } 9697 else { 9698 this.chaptersPopup.find('li').first().addClass('able-focus').attr('aria-checked','true').focus(); 9699 } 9700 } 9701 }; 9702 9703 AblePlayer.prototype.handleDescriptionToggle = function() { 9704 9705 this.descOn = !this.descOn; 9706 this.prefDesc = + this.descOn; // convert boolean to integer 9707 this.updateCookie('prefDesc'); 9708 if (typeof this.$descDiv !== 'undefined') { 9709 if (!this.$descDiv.is(':hidden')) { 9710 this.$descDiv.hide(); 9711 } 9712 // NOTE: now showing $descDiv here if previously hidden 9713 // that's handled elsewhere, dependent on whether there's text to show 9714 } 9715 this.initDescription(); 9716 this.refreshControls('descriptions'); 9717 }; 9718 9719 AblePlayer.prototype.handlePrefsClick = function(pref) { 9720 9721 // NOTE: the prefs menu is positioned near the right edge of the player 9722 // This assumes the Prefs button is also positioned in that vicinity 9723 // (last or second-last button the right) 9724 9725 // NOTE: If previously unable to fully populate the Description dialog 9726 // because the Web Speech API failed to getVoices() 9727 // now is a good time to try again 9728 // so the Description dialog can be rebuilt before the user requests it 9729 9730 var thisObj, prefsButtonPosition, prefsMenuRight, prefsMenuLeft; 9731 9732 thisObj = this; 9733 9734 if (this.speechEnabled === null) { 9735 this.initSpeech('prefs'); 9736 } 9737 if (this.hidingPopup) { 9738 // stopgap to prevent spacebar in Firefox from reopening popup 9739 // immediately after closing it 9740 this.hidingPopup = false; 9741 return false; 9742 } 9743 if (this.prefsPopup.is(':visible')) { 9744 this.prefsPopup.hide(); 9745 this.$prefsButton.attr('aria-expanded','false'); 9746 // restore each menu item to original hidden state 9747 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 9748 if (!this.showingPrefsDialog) { 9749 this.$prefsButton.focus(); 9750 } 9751 // wait briefly, then reset hidingPopup 9752 setTimeout(function() { 9753 thisObj.hidingPopup = false; 9754 },100); 9755 } 9756 else { 9757 this.closePopups(); 9758 this.prefsPopup.show(); 9759 this.$prefsButton.attr('aria-expanded','true'); 9760 this.$prefsButton.focus(); // focus first on prefs button to announce expanded state 9761 // give time for focus on button then adjust popup settings and focus 9762 setTimeout(function() { 9763 prefsButtonPosition = thisObj.$prefsButton.position(); 9764 prefsMenuRight = thisObj.$ableDiv.width() - 5; 9765 prefsMenuLeft = prefsMenuRight - thisObj.prefsPopup.width(); 9766 thisObj.prefsPopup.css('top', prefsButtonPosition.top - thisObj.prefsPopup.outerHeight()); 9767 thisObj.prefsPopup.css('left', prefsMenuLeft); 9768 // remove prior focus and set focus on first item; also change tabindex from -1 to 0 9769 thisObj.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','0'); 9770 thisObj.prefsPopup.find('li').first().focus().addClass('able-focus'); 9771 }, 50); 9772 } 9773 }; 9774 9775 AblePlayer.prototype.handleHelpClick = function() { 9776 this.setFullscreen(false); 9777 this.helpDialog.show(); 9778 }; 9779 9780 AblePlayer.prototype.handleTranscriptToggle = function () { 9781 9782 var thisObj = this; 9783 9784 if (this.$transcriptDiv.is(':visible')) { 9785 this.$transcriptArea.hide(); 9786 this.$transcriptButton.addClass('buttonOff').attr('aria-label',this.tt.showTranscript); 9787 9788 this.$transcriptButton.find('span.able-clipped').text(this.tt.showTranscript); 9789 this.prefTranscript = 0; 9790 this.$transcriptButton.focus().addClass('able-focus'); 9791 // wait briefly before resetting stopgap var 9792 // otherwise the keypress used to select 'Close' will trigger the transcript button 9793 // Benchmark tests: If this is gonna happen, it typically happens in around 3ms; max 12ms 9794 // Setting timeout to 100ms is a virtual guarantee of proper functionality 9795 setTimeout(function() { 9796 thisObj.closingTranscript = false;
9797 }, 100); 9798 } 9799 else { 9800 9801 this.positionDraggableWindow('transcript'); 9802 this.$transcriptArea.show(); 9803 // showing transcriptArea has a cascading effect of showing all content *within* transcriptArea 9804 // need to re-hide the popup menu 9805 this.$transcriptPopup.hide(); 9806 this.$transcriptButton.removeClass('buttonOff').attr('aria-label',this.tt.hideTranscript); 9807 this.$transcriptButton.find('span.able-clipped').text(this.tt.hideTranscript); 9808 this.prefTranscript = 1; 9809 // move focus to first focusable element (window options button) 9810 this.focusNotClick = true; 9811 this.$transcriptArea.find('button').first().focus(); 9812 // wait briefly before resetting stopgap var 9813 setTimeout(function() { 9814 thisObj.focusNotClick = false; 9815 }, 100); 9816 } 9817 this.updateCookie('prefTranscript'); 9818 }; 9819 9820 AblePlayer.prototype.handleSignToggle = function () { 9821 9822 var thisObj = this; 9823 9824 if (this.$signWindow.is(':visible')) { 9825 this.$signWindow.hide(); 9826 this.$signButton.addClass('buttonOff').attr('aria-label',this.tt.showSign); 9827 this.$signButton.find('span.able-clipped').text(this.tt.showSign); 9828 this.prefSign = 0; 9829 this.$signButton.focus().addClass('able-focus'); 9830 // wait briefly before resetting stopgap var 9831 // otherwise the keypress used to select 'Close' will trigger the transcript button 9832 setTimeout(function() { 9833 thisObj.closingSign = false; 9834 }, 100); 9835 } 9836 else { 9837 this.positionDraggableWindow('sign'); 9838 this.$signWindow.show(); 9839 // showing signWindow has a cascading effect of showing all content *within* signWindow 9840 // need to re-hide the popup menu 9841 this.$signPopup.hide(); 9842 this.$signButton.removeClass('buttonOff').attr('aria-label',this.tt.hideSign); 9843 this.$signButton.find('span.able-clipped').text(this.tt.hideSign); 9844 this.prefSign = 1; 9845 this.focusNotClick = true; 9846 this.$signWindow.find('button').first().focus(); 9847 // wait briefly before resetting stopgap var 9848 // otherwise the keypress used to select 'Close' will trigger the transcript button 9849 setTimeout(function() { 9850 thisObj.focusNotClick = false; 9851 }, 100); 9852 } 9853 this.updateCookie('prefSign'); 9854 }; 9855 9856 AblePlayer.prototype.isFullscreen = function () { 9857 9858 // NOTE: This has been largely replaced as of 3.2.5 with a Boolean this.fullscreen, 9859 // which is defined in setFullscreen() 9860 // This function returns true if *any* element is fullscreen 9861 // but doesn't tell us whether a particular element is in fullscreen 9862 // (e.g., if there are multiple players on the page) 9863 // The Boolean this.fullscreen is defined separately for each player instance 9864 9865 if (this.nativeFullscreenSupported()) { 9866 return (document.fullscreenElement || 9867 document.webkitFullscreenElement || 9868 document.webkitCurrentFullscreenElement || 9869 document.mozFullscreenElement || 9870 document.msFullscreenElement) ? true : false; 9871 } 9872 else { 9873 return this.modalFullscreenActive ? true : false; 9874 } 9875 } 9876 9877 AblePlayer.prototype.setFullscreen = function (fullscreen) { 9878 9879 if (this.fullscreen == fullscreen) { 9880 // replace isFullscreen() with a Boolean. see function for explanation 9881 return; 9882 } 9883 var thisObj = this; 9884 var $el = this.$ableWrapper; 9885 var el = $el[0]; 9886 9887 if (this.nativeFullscreenSupported()) { 9888 // Note: many varying names for options for browser compatibility. 9889 if (fullscreen) { 9890 // Initialize fullscreen 9891 9892 if (el.requestFullscreen) { 9893 el.requestFullscreen(); 9894 } 9895 else if (el.webkitRequestFullscreen) { 9896 el.webkitRequestFullscreen(); 9897 } 9898 else if (el.mozRequestFullscreen) { 9899 el.mozRequestFullscreen(); 9900 } 9901 else if (el.msRequestFullscreen) { 9902 el.msRequestFullscreen(); 9903 } 9904 this.fullscreen = true; 9905 } 9906 else { 9907 // Exit fullscreen 9908 this.restoringAfterFullscreen = true; 9909 if (document.exitFullscreen) { 9910 document.exitFullscreen(); 9911 } 9912 else if (document.webkitExitFullscreen) { 9913 document.webkitExitFullscreen(); 9914 } 9915 else if (document.webkitCancelFullscreen) { 9916 document.webkitCancelFullscreen(); 9917 } 9918 else if (document.mozCancelFullscreen) { 9919 document.mozCancelFullscreen(); 9920 } 9921 else if (document.msExitFullscreen) { 9922 document.msExitFullscreen(); 9923 } 9924 this.fullscreen = false;
9925 } 9926 } 9927 else { 9928 // Non-native fullscreen support through modal dialog. 9929 // Create dialog on first run through. 9930 if (!this.fullscreenDialog) { 9931 var $dialogDiv = $('<div>'); 9932 // create a hidden alert, communicated to screen readers via aria-describedby 9933 var $fsDialogAlert = $('<p>',{ 9934 'class': 'able-screenreader-alert' 9935 }).text(this.tt.fullscreen); // In English: "Full screen"; TODO: Add alert text that is more descriptive 9936 $dialogDiv.append($fsDialogAlert); 9937 // now render this as a dialog 9938 this.fullscreenDialog = new AccessibleDialog($dialogDiv, this.$fullscreenButton, 'dialog', true, 'Fullscreen video player', $fsDialogAlert, this.tt.exitFullscreen, '100%', true, function () { thisObj.handleFullscreenToggle() }); 9939 $('body').append($dialogDiv); 9940 } 9941 9942 // Track whether paused/playing before moving element; moving the element can stop playback. 9943 var wasPaused = this.paused; 9944 9945 if (fullscreen) { 9946 this.modalFullscreenActive = true; 9947 this.fullscreenDialog.show(); 9948 9949 // Move player element into fullscreen dialog, then show. 9950 // Put a placeholder element where player was. 9951 this.$modalFullscreenPlaceholder = $('<div class="placeholder">'); 9952 this.$modalFullscreenPlaceholder.insertAfter($el); 9953 $el.appendTo(this.fullscreenDialog.modal); 9954 9955 // Column left css is 50% by default; set to 100% for full screen. 9956 if ($el === this.$ableColumnLeft) { 9957 $el.width('100%'); 9958 } 9959 var newHeight = $(window).height() - this.$playerDiv.height(); 9960 if (typeof this.$descDiv !== 'undefined') { 9961 if (!this.$descDiv.is(':hidden')) { 9962 newHeight -= this.$descDiv.height(); 9963 } 9964 } 9965 } 9966 else { 9967 this.modalFullscreenActive = false; 9968 if ($el === this.$ableColumnLeft) { 9969 $el.width('50%'); 9970 } 9971 $el.insertAfter(this.$modalFullscreenPlaceholder); 9972 this.$modalFullscreenPlaceholder.remove(); 9973 this.fullscreenDialog.hide(); 9974 } 9975 9976 // Resume playback if moving stopped it. 9977 if (!wasPaused && this.paused) { 9978 this.playMedia(); 9979 } 9980 } 9981 // add event handlers for changes in fullscreen mode. 9982 // Browsers natively trigger this event with the Escape key, 9983 // in addition to clicking the exit fullscreen button 9984 $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', function(e) { 9985 // NOTE: e.type = the specific event that fired (in case needing to control for browser-specific idiosyncrasies) 9986 if (!thisObj.fullscreen) { 9987 // user has just exited full screen 9988 thisObj.restoringAfterFullscreen = true; 9989 } 9990 else if (!thisObj.clickedFullscreenButton) { 9991 // user triggered fullscreenchange without clicking fullscreen button 9992 thisObj.fullscreen = false; 9993 thisObj.restoringAfterFullscreen = true; 9994 } 9995 thisObj.resizePlayer(); 9996 thisObj.refreshControls('fullscreen'); 9997 9998 // NOTE: The fullscreenchange (or browser-equivalent) event is triggered twice 9999 // when exiting fullscreen via the "Exit fullscreen" button (only once if using Escape) 10000 // Not sure why, but consequently we need to be sure thisObj.clickedFullscreenButton 10001 // continues to be true through both events 10002 // Could use a counter variable to control that (reset to false after the 2nd trigger) 10003 // However, since I don't know why it's happening, and whether it's 100% reliable 10004 // resetting clickedFullscreenButton after a timeout seems to be better approach 10005 setTimeout(function() { 10006 thisObj.clickedFullscreenButton = false; 10007 thisObj.restoringAfterFullscreen = false; 10008 },1000); 10009 }); 10010 }; 10011 10012 AblePlayer.prototype.handleFullscreenToggle = function () { 10013 10014 var stillPaused = this.paused; 10015 this.setFullscreen(!this.fullscreen); 10016 if (stillPaused) { 10017 this.pauseMedia(); // when toggling fullscreen and media is just paused, keep media paused. 10018 } 10019 else if (!stillPaused) { 10020 this.playMedia(); // when toggling fullscreen and media is playing, continue playing. 10021 } 10022 // automatically hide controller in fullscreen mode 10023 // then reset back to original setting after exiting fullscreen mode 10024 if (this.fullscreen) { 10025 this.hideControls = true; 10026 if (this.playing) { 10027 // go ahead and hide the controls 10028 this.fadeControls('out'); 10029 this.controlsHidden = true; 10030 } 10031 } 10032 else { 10033 // exit fullscreen mode 10034 this.hideControls = this.hideControlsOriginal; 10035 if (!this.hideControls) { // do not hide controls 10036 if (this.controlsHidden) { 10037 this.fadeControls('in'); 10038 this.controlsHidden = false;
10039 } 10040 // if there's an active timeout to fade controls out again, clear it 10041 if (this.hideControlsTimeoutStatus === 'active') { 10042 window.clearTimeout(this.hideControlsTimeout); 10043 this.hideControlsTimeoutStatus = 'clear'; 10044 } 10045 } 10046 } 10047 // don't resizePlayer yet; that will be called in response to the window resize event 10048 // this.resizePlayer(); 10049 }; 10050 10051 AblePlayer.prototype.handleTranscriptLockToggle = function (val) { 10052 10053 this.autoScrollTranscript = val; // val is boolean 10054 this.prefAutoScrollTranscript = +val; // convert boolean to numeric 1 or 0 for cookie 10055 this.updateCookie('prefAutoScrollTranscript'); 10056 this.refreshControls('transcript'); 10057 }; 10058 10059 10060 AblePlayer.prototype.showTooltip = function($tooltip) { 10061 10062 if (($tooltip).is(':animated')) { 10063 $tooltip.stop(true,true).show(); 10064 } 10065 else { 10066 $tooltip.stop().show(); 10067 } 10068 }; 10069 10070 AblePlayer.prototype.showAlert = function( msg, location ) { 10071 10072 // location is either of the following: 10073 // 'main' (default) 10074 // 'screenreader (visibly hidden) 10075 // 'sign' (sign language window) 10076 // 'transcript' (trasncript window) 10077 var thisObj, $alertBox, $parentWindow, alertLeft, alertTop; 10078 10079 thisObj = this; 10080 10081 if (location === 'transcript') { 10082 $alertBox = this.$transcriptAlert; 10083 $parentWindow = this.$transcriptArea; 10084 } 10085 else if (location === 'sign') { 10086 $alertBox = this.$signAlert; 10087 $parentWindow = this.$signWindow; 10088 } 10089 else if (location === 'screenreader') { 10090 $alertBox = this.$srAlertBox; 10091 } 10092 else { 10093 $alertBox = this.$alertBox; 10094 } 10095 $alertBox.text(msg).show(); 10096 if (location == 'transcript' || location === 'sign') { 10097 if ($parentWindow.width() > $alertBox.width()) { 10098 alertLeft = $parentWindow.width() / 2 - $alertBox.width() / 2; 10099 } 10100 else { 10101 // alert box is wider than its container. Position it far left and let it wrap 10102 alertLeft = 10; 10103 } 10104 if (location === 'sign') { 10105 // position alert in the lower third of the sign window (to avoid covering the signer) 10106 alertTop = ($parentWindow.height() / 3) * 2; 10107 } 10108 else if (location === 'transcript') { 10109 // position alert just beneath the toolbar to avoid getting lost among transcript text 10110 alertTop = this.$transcriptToolbar.height() + 30; 10111 } 10112 $alertBox.css({ 10113 top: alertTop + 'px', 10114 left: alertLeft + 'px' 10115 }); 10116 } 10117 else if (location !== 'screenreader') { 10118 // The original formula incorporated offset() into the calculation 10119 // but at some point this began resulting in an alert that's off-centered 10120 // Changed in v2.2.17, but here's the original for reference in case needed: 10121 // left: this.$playerDiv.offset().left + (this.$playerDiv.width() / 2) - ($alertBox.width() / 2) 10122 $alertBox.css({ 10123 left: (this.$playerDiv.width() / 2) - ($alertBox.width() / 2) 10124 }); 10125 } 10126 if (location !== 'screenreader') { 10127 setTimeout(function () { 10128 $alertBox.fadeOut(300); 10129 }, 3000); 10130 } 10131 }; 10132 10133 AblePlayer.prototype.showedAlert = function (which) { 10134 10135 // returns true if the target alert has already been shown 10136 // useful for throttling alerts that only need to be shown once 10137 // e.g., move alerts with instructions for dragging a window 10138 10139 if (which === 'transcript') { 10140 if (this.showedTranscriptAlert) { 10141 return true; 10142 } 10143 else { 10144 return false; 10145 } 10146 } 10147 else if (which === 'sign') { 10148 if (this.showedSignAlert) { 10149 return true; 10150 } 10151 else { 10152 return false; 10153 } 10154 } 10155 return false; 10156 } 10157 10158 // Resizes all relevant player attributes. 10159 AblePlayer.prototype.resizePlayer = function (width, height) { 10160 10161 var captionSizeOkMin, captionSizeOkMax, captionSize, newCaptionSize, newLineHeight; 10162 10163 var newWidth, newHeight, $iframe, alertTop; 10164 10165 if (this.mediaType === 'audio') { 10166 return; 10167 } 10168 10169 if (typeof width !== 'undefined' && typeof height !== 'undefined') { 10170 // this is being called the first time a player is initialized 10171 // width and height were collected from the HTML, YouTube, or Vimeo media API 10172 // so are reflective of the actual size of the media 10173 // use these values to calculate aspectRatio 10174 this.aspectRatio = height / width; 10175 if (this.playerWidth) { 10176 // default width is already defined via a width or data-width attribute. Use that. 10177 newWidth = this.playerWidth;
vendor: 2,991 bytes, lines 10178-10278
10178 if (this.playerHeight) { 10179 newHeight = this.playerHeight; 10180 } 10181 else { 10182 newHeight = Math.round(newWidth * this.aspectRatio); 10183 this.playerHeight = newHeight; 10184 } 10185 } 10186 else { 10187 // playerWidth was not defined via HTML attributes 10188 if (this.player === 'html5') { 10189 newWidth = $(window).width(); 10190 } 10191 else { 10192 newWidth = this.$ableWrapper.width(); 10193 } 10194 newHeight = Math.round(newWidth * this.aspectRatio); 10195 } 10196 } 10197 else if (this.fullscreen) { 10198 this.$ableWrapper.addClass('fullscreen'); 10199 newWidth = $(window).width(); 10200 // the 5 pixel buffer is arbitrary, but results in a better fit for all browsers 10201 newHeight = $(window).height() - this.$playerDiv.outerHeight() - 5; 10202 this.positionCaptions('overlay'); 10203 } 10204 else { // not fullscreen, and not first time initializing player 10205 this.$ableWrapper.removeClass('fullscreen'); 10206 if (this.player === 'html5') { 10207 if (this.playerWidth) { 10208 newWidth = this.playerWidth; 10209 } 10210 else { 10211 // use full size of window 10212 // player will be downsized to fit container if CSS requires it 10213 newWidth = $(window).width(); 10214 } 10215 } 10216 else { 10217 newWidth = this.$ableWrapper.width(); 10218 } 10219 newHeight = Math.round(newWidth * this.aspectRatio); 10220 this.positionCaptions(this.prefCaptionsPosition); 10221 } 10222 if (this.debug) { 10223 console.log('resizePlayer to ' + newWidth + 'x' + newHeight); 10224 } 10225 // Now size the player with newWidth and newHeight 10226 if (this.player === 'youtube' || this.player === 'vimeo') { 10227 $iframe = this.$ableWrapper.find('iframe'); 10228 if (this.player === 'youtube' && this.youTubePlayer) { 10229 // alternatively, YouTube API offers a method for setting the video size 10230 // this adds width and height attributes to the iframe 10231 // but might have other effects, so best to do it this way 10232 this.youTubePlayer.setSize(newWidth,newHeight); 10233 } 10234 else { 10235 // Vimeo API does not have a method for changing size of player 10236 // Therefore, need to change iframe attributes directly 10237 $iframe.attr({ 10238 'width': newWidth, 10239 'height': newHeight 10240 }); 10241 } 10242 if (this.playerWidth && this.playerHeight) { 10243 if (this.fullscreen) { 10244 // remove constraints 10245 $iframe.css({ 10246 'max-width': '', 10247 'max-height': '' 10248 }); 10249 } 10250 else { 10251 // use CSS on iframe to enforce explicitly defined size constraints 10252 $iframe.css({ 10253 'max-width': this.playerWidth + 'px', 10254 'max-height': this.playerHeight + 'px' 10255 }); 10256 } 10257 } 10258 } 10259 else if (this.player === 'html5') { 10260 if (this.fullscreen) { 10261 this.$media.attr({ 10262 'width': newWidth, 10263 'height': newHeight 10264 }); 10265 this.$ableWrapper.css({ 10266 'width': newWidth, 10267 'height': newHeight 10268 }); 10269 } 10270 else { 10271 // No constraints. Let CSS handle the positioning. 10272 this.$media.removeAttr('width height'); 10273 this.$ableWrapper.css({ 10274 'width': newWidth + 'px', 10275 'height': 'auto' 10276 }); 10277 } 10278 }
10279 // Resize captions 10280 if (typeof this.$captionsDiv !== 'undefined') { 10281 10282 // Font-size is too small in full screen view 10283 // use viewport units (vw) instead 10284 // % units work fine if not fullscreen 10285 // prefCaptionSize is expressed as a percentage 10286 captionSize = parseInt(this.prefCaptionsSize,10); 10287 if (this.fullscreen) { 10288 captionSize = (captionSize / 100) + 'vw'; 10289 } 10290 else { 10291 captionSize = captionSize + '%'; 10292 } 10293 this.$captionsDiv.css({ 10294 'font-size': captionSize 10295 }); 10296 } 10297 10298 // Reposition alert message (video player only) 10299 // just below the vertical center of the mediaContainer 10300 // hopefully above captions, but not too far from the controller bar 10301 if (this.mediaType === 'video') { 10302 alertTop = Math.round(this.$mediaContainer.height() / 3) * 2; 10303 this.$alertBox.css({ 10304 top: alertTop + 'px' 10305 }); 10306 } 10307 10308 this.refreshControls(); 10309 }; 10310 10311 AblePlayer.prototype.retrieveOffscreenWindow = function( which, width, height ) { 10312 10313 // check to be sure popup windows ('transcript' or 'sign') are positioned on-screen 10314 // (they sometimes disappear off-screen when entering fullscreen mode) 10315 // if off-screen, recalculate so they are back on screen 10316 10317 var window, windowPos, windowTop, windowLeft, windowRight, windowWidth, windowBottom, windowHeight; 10318 10319 if (which == 'transcript') { 10320 window = this.$transcriptArea; 10321 } 10322 else if (which == 'sign') { 10323 window = this.$signWindow; 10324 } 10325 windowWidth = window.width(); 10326 windowHeight = window.height(); 10327 windowPos = window.position(); 10328 windowTop = windowPos.top; 10329 windowLeft = windowPos.left; 10330 windowRight = windowLeft + windowWidth; 10331 windowBottom = windowTop + windowHeight; 10332 10333 if (windowTop < 0) { // off-screen to the top 10334 windowTop = 10; 10335 window.css('top',windowTop); 10336 } 10337 if (windowLeft < 0) { // off-screen to the left 10338 windowLeft = 10; 10339 window.css('left',windowLeft); 10340 } 10341 if (windowRight > width) { // off-screen to the right 10342 windowLeft = (width - 20) - windowWidth; 10343 window.css('left',windowLeft); 10344 } 10345 if (windowBottom > height) { // off-screen to the bottom 10346 windowTop = (height - 10) - windowHeight; 10347 window.css('top',windowTop); 10348 } 10349 }; 10350 10351 AblePlayer.prototype.getHighestZIndex = function() { 10352 10353 // returns the highest z-index on page 10354 // used to ensure dialogs (or potentially other windows) are on top 10355 10356 var max, $elements, z; 10357 max = 0; 10358 10359 // exclude the Able Player dialogs and windows 10360 $elements = $('body *').not('.able-modal-dialog,.able-modal-dialog *,.able-modal-overlay,.able-modal-overlay *,.able-sign-window,.able-transcript-area'); 10361 10362 $elements.each(function(){ 10363 z = $(this).css('z-index'); 10364 if (Number.isInteger(+z)) { // work only with integer values, not 'auto' 10365 if (parseInt(z) > max) { 10366 max = parseInt(z); 10367 } 10368 } 10369 }); 10370 return max; 10371 }; 10372 10373 AblePlayer.prototype.updateZIndex = function(which) { 10374 10375 // update z-index of 'transcript' or 'sign', relative to each other 10376 // direction is always 'up' (i.e., move window to top) 10377 // windows come to the top when the user clicks on them 10378 var defHighZ, defLowZ, highestZ, transcriptZ, signZ, newHighZ, newLowZ; 10379 10380 // set the default z-indexes, as defined in ableplayer.css 10381 defHighZ = 8000; // by default, assigned to the sign window 10382 defLowZ = 7000; // by default, assigned to the transcript area 10383 highestZ = this.getHighestZIndex(); // highest z-index on the page, excluding Able Player windows & modals 10384 10385 // NOTE: Although highestZ is collected here, it currently isn't used. 10386 // If something on the page has a higher z-index than the transcript or sign window, do we care? 10387 // Excluding it here assumes "No". Our immediate concern is with the relationship between our own components. 10388 // If we elevate our z-indexes so our content is on top, we run the risk of starting a z-index war. 10389 10390 if (typeof this.$transcriptArea === 'undefined' || typeof this.$signWindow === 'undefined' ) { 10391 // at least one of the windows doesn't exist, so there's no conflict 10392 // since z-index may have been stored to a cookie on another page, need to restore default 10393 if (typeof this.$transcriptArea !== 'undefined') { 10394 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 10395 if (transcriptZ > defLowZ) { 10396 // restore to the default 10397 this.$transcriptArea.css('z-index',defLowZ); 10398 } 10399 } 10400 else if (typeof this.$signWindow !== 'undefined') { 10401 signZ = parseInt(this.$signWindow.css('z-index')); 10402 if (signZ > defHighZ) { 10403 // restore to the default 10404 this.$signWindow.css('z-index',defHighZ); 10405 } 10406 } 10407 return false;
vendor: 7,469 bytes, lines 10408-10630
10408 } 10409 10410 // both windows exist 10411 10412 // get current values 10413 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 10414 signZ = parseInt(this.$signWindow.css('z-index')); 10415 10416 if (transcriptZ === signZ) { 10417 // the two windows are equal; restore defaults (the target window will be on top) 10418 newHighZ = defHighZ; 10419 newLowZ = defLowZ; 10420 } 10421 else if (transcriptZ > signZ) { 10422 if (which === 'transcript') { 10423 // transcript is already on top; nothing to do 10424 return false; 10425 } 10426 else { 10427 // swap z's 10428 newHighZ = transcriptZ; 10429 newLowZ = signZ; 10430 } 10431 } 10432 else { // signZ is greater 10433 if (which === 'sign') { 10434 // sign is already on top; nothing to do 10435 return false; 10436 } 10437 else { 10438 newHighZ = signZ; 10439 newLowZ = transcriptZ; 10440 } 10441 } 10442 // now assign the new values 10443 if (which === 'transcript') { 10444 this.$transcriptArea.css('z-index',newHighZ); 10445 this.$signWindow.css('z-index',newLowZ); 10446 } 10447 else if (which === 'sign') { 10448 this.$signWindow.css('z-index',newHighZ); 10449 this.$transcriptArea.css('z-index',newLowZ); 10450 } 10451 }; 10452 10453 AblePlayer.prototype.syncTrackLanguages = function (source, language) { 10454 10455 // this function is called when the player is built (source == 'init') 10456 // and again when user changes the language of either 'captions' or 'transcript' 10457 // It syncs the languages of chapters, descriptions, and metadata tracks 10458 // NOTE: Caption and transcript languages are somewhat independent from one another 10459 // If a user changes the caption language, the transcript follows 10460 // However, if a user changes the transcript language, this only affects the transcript 10461 // This was a group decision based on the belief that users may want a transcript 10462 // that is in a different language than the captions 10463 10464 var i, captions, descriptions, chapters, meta, langHasChanged; 10465 10466 // Captions 10467 for (i = 0; i < this.captions.length; i++) { 10468 if (this.captions[i].language === language) { 10469 captions = this.captions[i]; 10470 } 10471 } 10472 // Chapters 10473 for (i = 0; i < this.chapters.length; i++) { 10474 if (this.chapters[i].language === language) { 10475 chapters = this.chapters[i]; 10476 } 10477 } 10478 // Descriptions 10479 for (i = 0; i < this.descriptions.length; i++) { 10480 if (this.descriptions[i].language === language) { 10481 descriptions = this.descriptions[i]; 10482 } 10483 } 10484 // Metadata 10485 for (i = 0; i < this.meta.length; i++) { 10486 if (this.meta[i].language === language) { 10487 meta = this.meta[i]; 10488 } 10489 } 10490 // regardless of source... 10491 this.transcriptLang = language; 10492 if (source === 'init' || source === 'captions') { 10493 this.captionLang = language; 10494 this.selectedCaptions = captions; 10495 this.selectedChapters = chapters; 10496 this.selectedDescriptions = descriptions; 10497 this.selectedMeta = meta; 10498 this.transcriptCaptions = captions; 10499 this.transcriptChapters = chapters; 10500 this.transcriptDescriptions = descriptions; 10501 this.updateChaptersList(); 10502 // the following was commented out in Oct/Nov 2018. 10503 // chapters popup is setup automatically when setupPopups() is called later with no param 10504 // not sure why it was included here. 10505 // this.setupPopups('chapters'); 10506 } 10507 else if (source === 'transcript') { 10508 this.transcriptCaptions = captions; 10509 this.transcriptChapters = chapters; 10510 this.transcriptDescriptions = descriptions; 10511 } 10512 if (this.selectedDescriptions) { 10513 // updating description voice to match new description language 10514 this.setDescriptionVoice(); 10515 if (this.$sampleDescDiv) { 10516 if (this.sampleText) { 10517 for (i = 0; i < this.sampleText.length; i++) { 10518 if (this.sampleText[i].lang === this.selectedDescriptions.language) { 10519 this.currentSampleText = this.sampleText[i]['text']; 10520 this.$sampleDescDiv.html(this.currentSampleText); 10521 } 10522 } 10523 } 10524 } 10525 } 10526 this.updateTranscript(); 10527 }; 10528 10529})(jQuery); 10530 10531(function ($) { 10532 AblePlayer.prototype.updateCaption = function (time) { 10533 10534 if (!this.usingYouTubeCaptions && !this.usingVimeoCaptions && 10535 (typeof this.$captionsWrapper !== 'undefined')) { 10536 if (this.captionsOn) { 10537 this.$captionsWrapper.show(); 10538 if (typeof time !== 'undefined') { 10539 this.showCaptions(time); 10540 } 10541 } 10542 else if (this.$captionsWrapper) { 10543 this.$captionsWrapper.hide(); 10544 this.prefCaptions = 0; 10545 } 10546 } 10547 }; 10548 10549 AblePlayer.prototype.updateCaptionsMenu = function (lang) { 10550 10551 // uncheck all previous menu items 10552 this.captionsPopup.find('li').attr('aria-checked','false'); 10553 if (typeof lang === 'undefined') { 10554 // check the last menu item (captions off) 10555 this.captionsPopup.find('li').last().attr('aria-checked','true'); 10556 } 10557 else { 10558 // check the newly selected lang 10559 this.captionsPopup.find('li[lang=' + lang + ']').attr('aria-checked','true'); 10560 } 10561 }; 10562 10563 AblePlayer.prototype.getCaptionClickFunction = function (track) { 10564 10565 // Returns the function used when a caption is clicked in the captions menu. 10566 // Not called if user clicks "Captions off". Instead, that triggers getCaptionOffFunction() 10567 10568 var thisObj = this; 10569 return function () { 10570 10571 thisObj.selectedCaptions = track; 10572 thisObj.captionLang = track.language; 10573 thisObj.currentCaption = -1; 10574 if (thisObj.usingYouTubeCaptions) { 10575 if (thisObj.captionsOn) { 10576 // Two things must be true in order for setOption() to work: 10577 // The YouTube caption module must be loaded 10578 // and the video must have started playing 10579 if (thisObj.youTubePlayer.getOptions('captions') && thisObj.startedPlaying) { 10580 thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLang}); 10581 } 10582 else { 10583 // the two conditions were not met 10584 // try again to set the language after onApiChange event is triggered 10585 // meanwhile, the following variable will hold the value 10586 thisObj.captionLangPending = thisObj.captionLang; 10587 } 10588 } 10589 else { 10590 if (thisObj.youTubePlayer.getOptions('captions')) { 10591 thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLang}); 10592 } 10593 else { 10594 thisObj.youTubePlayer.loadModule('captions'); 10595 thisObj.captionLangPending = thisObj.captionLang; 10596 } 10597 } 10598 } 10599 else if (thisObj.usingVimeoCaptions) { 10600 thisObj.vimeoPlayer.enableTextTrack(thisObj.captionLang).then(function(track) { 10601 // track.language = the iso code for the language 10602 // track.kind = 'captions' or 'subtitles' 10603 // track.label = the human-readable label 10604 }).catch(function(error) { 10605 switch (error.name) { 10606 case 'InvalidTrackLanguageError': 10607 // no track was available with the specified language 10608 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 10609 break; 10610 case 'InvalidTrackError': 10611 // no track was available with the specified language and kind 10612 console.log('No ' + track.kind + ' track is available in the specified language (' + track.label + ')'); 10613 break; 10614 default: 10615 // some other error occurred 10616 console.log('Error loading ' + track.label + ' ' + track.kind + ' track'); 10617 break; 10618 } 10619 }); 10620 } 10621 else { // using local track elements for captions/subtitles 10622 thisObj.syncTrackLanguages('captions',thisObj.captionLang); 10623 if (!thisObj.swappingSrc) { 10624 thisObj.updateCaption(thisObj.elapsed); 10625 thisObj.showDescription(thisObj.elapsed); 10626 } 10627 } 10628 thisObj.captionsOn = true; 10629 // stopgap to prevent spacebar in Firefox from reopening popup 10630 // immediately after closing it (used in handleCaptionToggle(
vendor: 5,595 bytes, lines 10630-10814
10630)) 10631 thisObj.hidingPopup = true; 10632 thisObj.captionsPopup.hide(); 10633 thisObj.$ccButton.attr('aria-expanded', 'false'); 10634 if (thisObj.mediaType === 'audio') { 10635 thisObj.$captionsContainer.removeClass('captions-off'); 10636 } 10637 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 10638 // e.g., if user triggered button with Enter or mouse click, not spacebar 10639 setTimeout(function() { 10640 thisObj.hidingPopup = false; 10641 }, 100); 10642 thisObj.updateCaptionsMenu(thisObj.captionLang); 10643 thisObj.waitThenFocus(thisObj.$ccButton); 10644 10645 // save preference to cookie 10646 thisObj.prefCaptions = 1; 10647 thisObj.updateCookie('prefCaptions'); 10648 thisObj.refreshControls('captions'); 10649 } 10650 }; 10651 10652 // Returns the function used when the "Captions Off" button is clicked in the captions tooltip. 10653 AblePlayer.prototype.getCaptionOffFunction = function () { 10654 10655 var thisObj = this; 10656 return function () { 10657 10658 if (thisObj.player == 'youtube') { 10659 thisObj.youTubePlayer.unloadModule('captions'); 10660 } 10661 else if (thisObj.usingVimeoCaptions) { 10662 thisObj.vimeoPlayer.disableTextTrack(); 10663 } 10664 thisObj.captionsOn = false; 10665 thisObj.currentCaption = -1; 10666 10667 if (thisObj.mediaType === 'audio') { 10668 thisObj.$captionsContainer.addClass('captions-off'); 10669 } 10670 10671 // stopgap to prevent spacebar in Firefox from reopening popup 10672 // immediately after closing it (used in handleCaptionToggle()) 10673 thisObj.hidingPopup = true; 10674 thisObj.captionsPopup.hide(); 10675 thisObj.$ccButton.attr('aria-expanded', 'false'); 10676 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 10677 // e.g., if user triggered button with Enter or mouse click, not spacebar 10678 setTimeout(function() { 10679 thisObj.hidingPopup = false; 10680 }, 100); 10681 thisObj.updateCaptionsMenu(); 10682 thisObj.waitThenFocus(thisObj.$ccButton); 10683 10684 // save preference to cookie 10685 thisObj.prefCaptions = 0; 10686 thisObj.updateCookie('prefCaptions'); 10687 if (!this.swappingSrc) { 10688 thisObj.refreshControls('captions'); 10689 thisObj.updateCaption(); 10690 } 10691 } 10692 }; 10693 10694 AblePlayer.prototype.showCaptions = function(now) { 10695 10696 var c, thisCaption, captionText; 10697 var cues; 10698 if (this.selectedCaptions.cues.length) { 10699 cues = this.selectedCaptions.cues; 10700 } 10701 else if (this.captions.length >= 1) { 10702 cues = this.captions[0].cues; 10703 } 10704 else { 10705 cues = []; 10706 } 10707 for (c = 0; c < cues.length; c++) { 10708 if ((cues[c].start <= now) && (cues[c].end > now)) { 10709 thisCaption = c; 10710 break; 10711 } 10712 } 10713 if (typeof thisCaption !== 'undefined') { 10714 if (this.currentCaption !== thisCaption) { 10715 // it's time to load the new caption into the container div 10716 captionText = this.flattenCueForCaption(cues[thisCaption]).replace('\n', '<br>'); 10717 this.$captionsDiv.html(captionText); 10718 this.currentCaption = thisCaption; 10719 if (captionText.length === 0) { 10720 // hide captionsDiv; otherwise background-color is visible due to padding 10721 this.$captionsDiv.css('display','none'); 10722 } 10723 else { 10724 this.$captionsDiv.css('display','inline-block'); 10725 } 10726 } 10727 } 10728 else { 10729 this.$captionsDiv.html('').css('display','none'); 10730 this.currentCaption = -1; 10731 } 10732 }; 10733 10734 AblePlayer.prototype.flattenCueForCaption = function (cue) { 10735 10736 // Takes a cue and returns the caption text to display 10737 // Also used for chapters 10738 10739 // Support for 'i' and 'b' tags added in 2.3.66 10740 // TODO: Add support for 'c' (class) and 'ruby' 10741 10742 // c (class): <c.myClass1.myClass2>Some text</c> 10743 // Classes can be used to modify other tags too (e.g., <v.loud>) 10744 // If <c> tag, should be rendered as a <span> 10745 10746 // ruby: http://www.w3schools.com/tags/tag_ruby.asp 10747 10748 // WebVTT also supports 'u' (underline) 10749 // I see no reason to support that in Able Player. 10750 // If it's available authors are likely to use it incorrectly 10751 // where <i> or <b> should be used instead 10752 // Here are the rare use cases where an underline is appropriate on the web: 10753 // http://html5doctor.com/u-element/ 10754 10755 var result = []; 10756 10757 var flattenComponent = function (component) { 10758 var result = [], ii; 10759 if (component.type === 'string') { 10760 result.push(component.value); 10761 } 10762 else if (component.type === 'v') { 10763 result.push('(' + component.value + ')'); 10764 for (ii = 0; ii < component.children.length; ii++) { 10765 result.push(flattenComponent(component.children[ii])); 10766 } 10767 } 10768 else if (component.type === 'i') { 10769 result.push('<em>'); 10770 for (ii = 0; ii < component.children.length; ii++) { 10771 result.push(flattenComponent(component.children[ii])); 10772 } 10773 result.push('</em>'); 10774 } 10775 else if (component.type === 'b') { 10776 result.push('<strong>'); 10777 for (ii = 0; ii < component.children.length; ii++) { 10778 result.push(flattenComponent(component.children[ii])); 10779 } 10780 result.push('</strong>'); 10781 } 10782 else { 10783 for (ii = 0; ii < component.children.length; ii++) { 10784 result.push(flattenComponent(component.children[ii])); 10785 } 10786 } 10787 return result.join(''); 10788 }; 10789 10790 if (typeof cue.components !== 'undefined') { 10791 for (var ii = 0; ii < cue.components.children.length; ii++) { 10792 result.push(flattenComponent(cue.components.children[ii])); 10793 } 10794 } 10795 return result.join(''); 10796 }; 10797 10798 AblePlayer.prototype.getCaptionsOptions = function(pref) { 10799 10800 var options = []; 10801 10802 switch (pref) { 10803 10804 case 'prefCaptionsFont': 10805 options[0] = ['serif',this.tt.serif]; 10806 options[1] = ['sans-serif',this.tt.sans]; 10807 options[2] = ['cursive',this.tt.cursive]; 10808 options[3] = ['fantasy',this.tt.fantasy]; 10809 options[4] = ['monospace',this.tt.monospace]; 10810 break; 10811 10812 case 'prefCaptionsColor': 10813 case 'prefCaptionsBGColor': 10814 // HTML color values must be in English
10815 options[0] = ['white',this.tt.white]; 10816 options[1] = ['yellow',this.tt.yellow]; 10817 options[2] = ['green',this.tt.green]; 10818 options[3] = ['cyan',this.tt.cyan]; 10819 options[4] = ['blue',this.tt.blue]; 10820 options[5] = ['magenta',this.tt.magenta]; 10821 options[6] = ['red',this.tt.red]; 10822 options[7] = ['black',this.tt.black]; 10823 break; 10824 10825 case 'prefCaptionsSize': 10826 options[0] = '75%'; 10827 options[1] = '100%'; 10828 options[2] = '125%'; 10829 options[3] = '150%'; 10830 options[4] = '200%'; 10831 break; 10832 10833 case 'prefCaptionsOpacity': 10834 options[0] = '0%'; 10835 options[1] = '25%'; 10836 options[2] = '50%'; 10837 options[3] = '75%'; 10838 options[4] = '100%'; 10839 break; 10840 10841 case 'prefCaptionsStyle': 10842 options[0] = this.tt.captionsStylePopOn; 10843 options[1] = this.tt.captionsStyleRollUp; 10844 break; 10845 10846 case 'prefCaptionsPosition': 10847 options[0] = 'overlay'; 10848 options[1] = 'below'; 10849 break; 10850 10851 } 10852 return options; 10853 }; 10854 10855 AblePlayer.prototype.translatePrefs = function(pref, value, outputFormat) { 10856 10857 // translate current value of pref to a value supported by outputformat 10858 if (outputFormat == 'youtube') { 10859 if (pref === 'size') { 10860 // YouTube font sizes are a range from -1 to 3 (0 = default) 10861 switch (value) { 10862 case '75%': 10863 return -1; 10864 case '100%': 10865 return 0; 10866 case '125%': 10867 return 1; 10868 case '150%': 10869 return 2; 10870 case '200%': 10871 return 3; 10872 } 10873 } 10874 } 10875 return false; 10876 } 10877 10878 AblePlayer.prototype.stylizeCaptions = function($element, pref) { 10879 10880 // $element is the jQuery element containing the captions 10881 // this function handles stylizing of the sample caption text in the Prefs dialog 10882 // plus the actual production captions 10883 // TODO: consider applying the same user prefs to visible text-based description
10884 var property, newValue, opacity, lineHeight; 10885 10886 if (typeof $element !== 'undefined') { 10887 if (pref == 'prefCaptionsPosition') { 10888 this.positionCaptions(); 10889 } 10890 else if (typeof pref !== 'undefined') { 10891 // just change the one property that user just changed 10892 if (pref === 'prefCaptionsFont') { 10893 property = 'font-family'; 10894 } 10895 else if (pref === 'prefCaptionsSize') { 10896 property = 'font-size'; 10897 } 10898 else if (pref === 'prefCaptionsColor') { 10899 property = 'color'; 10900 } 10901 else if (pref === 'prefCaptionsBGColor') { 10902 property = 'background-color'; 10903 } 10904 else if (pref === 'prefCaptionsOpacity') { 10905 property = 'opacity'; 10906 } 10907 if (pref === 'prefCaptionsOpacity') { 10908 newValue = parseFloat($('#' + this.mediaId + '_' + pref).val()) / 100.0; 10909 } 10910 else { 10911 newValue = $('#' + this.mediaId + '_' + pref).val(); 10912 } 10913 $element.css(property, newValue); 10914 } 10915 else { // no property was specified, update all styles with current saved prefs 10916 opacity = parseFloat(this.prefCaptionsOpacity) / 100.0; 10917 $element.css({ 10918 'font-family': this.prefCaptionsFont, 10919 'color': this.prefCaptionsColor, 10920 'background-color': this.prefCaptionsBGColor, 10921 'opacity': opacity 10922 }); 10923 if ($element === this.$captionsDiv) { 10924 if (typeof this.$captionsDiv !== 'undefined') { 10925 this.$captionsDiv.css({ 10926 'font-size': this.prefCaptionsSize 10927 }); 10928 } 10929 } 10930 if (this.prefCaptionsPosition === 'below') { 10931 // also need to add the background color to the wrapper div 10932 if (typeof this.$captionsWrapper !== 'undefined') { 10933 this.$captionsWrapper.css({ 10934 'background-color': this.prefCaptionsBGColor, 10935 'opacity': '1' 10936 }); 10937 } 10938 } 10939 else if (this.prefCaptionsPosition === 'overlay') { 10940 // no background color for overlay wrapper, captions are displayed in-line 10941 if (typeof this.$captionsWrapper !== 'undefined') { 10942 this.$captionsWrapper.css({ 10943 'background-color': 'transparent', 10944 'opacity': '' 10945 }); 10946 } 10947 } 10948 this.positionCaptions(); 10949 } 10950 } 10951 }; 10952 AblePlayer.prototype.positionCaptions = function(position) { 10953 10954 // set caption position to either 'overlay' or 'below' 10955 // if position parameter was passed to this function, use that 10956 // otherwise use user preference 10957 if (typeof position === 'undefined') { 10958 position = this.prefCaptionsPosition; 10959 } 10960 if (typeof this.$captionsWrapper !== 'undefined') { 10961 10962 if (position == 'below') { 10963 this.$captionsWrapper.removeClass('able-captions-overlay').addClass('able-captions-below'); 10964 // also need to update in-line styles 10965 this.$captionsWrapper.css({ 10966 'background-color': this.prefCaptionsBGColor, 10967 'opacity': '1' 10968 }); 10969 } 10970 else { 10971 this.$captionsWrapper.removeClass('able-captions-below').addClass('able-captions-overlay'); 10972 this.$captionsWrapper.css({ 10973 'background-color': 'transparent', 10974 'opacity': '' 10975 }); 10976 } 10977 } 10978 }; 10979 10980})(jQuery); 10981 10982(function ($) { 10983 10984 AblePlayer.prototype.populateChaptersDiv = function() { 10985 10986 var headingLevel, headingType, headingId, $chaptersHeading, 10987 $chaptersList; 10988 10989 if ($('#' + this.chaptersDivLocation)) { 10990 10991 this.$chaptersDiv = $('#' + this.chaptersDivLocation); 10992 this.$chaptersDiv.addClass('able-chapters-div'); 10993 10994 // empty content from previous build before starting fresh 10995 this.$chaptersDiv.empty(); 10996 10997 // add optional header 10998 if (this.chaptersTitle) { 10999 headingLevel = this.getNextHeadingLevel(this.$chaptersDiv); 11000 headingType = 'h' + headingLevel.toString();
vendor: 10,563 bytes, lines 11001-11344
11001 headingId = this.mediaId + '-chapters-heading'; 11002 $chaptersHeading = $('<' + headingType + '>', { 11003 'class': 'able-chapters-heading', 11004 'id': headingId 11005 }).text(this.chaptersTitle); 11006 this.$chaptersDiv.append($chaptersHeading); 11007 } 11008 11009 this.$chaptersNav = $('<nav>'); 11010 if (this.chaptersTitle) { 11011 this.$chaptersNav.attr('aria-labelledby',headingId); 11012 } 11013 else { 11014 this.$chaptersNav.attr('aria-label',this.tt.chapters); 11015 } 11016 this.$chaptersDiv.append(this.$chaptersNav); 11017 11018 // populate this.$chaptersNav with a list of chapters 11019 this.updateChaptersList(); 11020 } 11021 }; 11022 11023 AblePlayer.prototype.updateChaptersList = function() { 11024 11025 var thisObj, cues, $chaptersList, c, thisChapter, 11026 $chapterItem, $chapterButton, buttonId, hasDefault, 11027 getClickFunction, $clickedItem, $chaptersList, thisChapterIndex; 11028 11029 thisObj = this; 11030 11031 // TODO: Update this so it can change the chapters popup menu 11032 // currently it only works if chapters are in an external container 11033 if (!this.$chaptersNav) { 11034 return false; 11035 } 11036 11037 if (typeof this.useChapterTimes === 'undefined') { 11038 if (this.seekbarScope === 'chapter' && this.selectedChapters.cues.length) { 11039 this.useChapterTimes = true; 11040 } 11041 else { 11042 this.useChapterTimes = false; 11043 } 11044 } 11045 if (this.useChapterTimes) { 11046 cues = this.selectedChapters.cues; 11047 } 11048 else if (this.chapters.length >= 1) { 11049 cues = this.chapters[0].cues; 11050 } 11051 else { 11052 cues = []; 11053 } 11054 if (cues.length > 0) { 11055 $chaptersList = $('<ul>'); 11056 for (c = 0; c < cues.length; c++) { 11057 thisChapter = c; 11058 $chapterItem = $('<li></li>'); 11059 $chapterButton = $('<button>',{ 11060 'type': 'button', 11061 'val': thisChapter 11062 }).text(this.flattenCueForCaption(cues[thisChapter])); 11063 11064 // add event listeners 11065 getClickFunction = function (time) { 11066 return function () { 11067 thisObj.seekTrigger = 'chapter'; 11068 $clickedItem = $(this).closest('li'); 11069 $chaptersList = $(this).closest('ul').find('li'); 11070 thisChapterIndex = $chaptersList.index($clickedItem); 11071 $chaptersList.removeClass('able-current-chapter') 11072 .children('button').removeAttr('aria-current'); 11073 $clickedItem.addClass('able-current-chapter') 11074 .children('button').attr('aria-current','true'); 11075 // Need to updateChapter before seeking to it 11076 // Otherwise seekBar is redrawn with wrong chapterDuration and/or chapterTime 11077 thisObj.updateChapter(time); 11078 thisObj.seekTo(time); 11079 } 11080 }; 11081 $chapterButton.on('click',getClickFunction(cues[thisChapter].start)); // works with Enter too 11082 $chapterButton.on('focus',function() { 11083 $(this).closest('ul').find('li').removeClass('able-focus'); 11084 $(this).closest('li').addClass('able-focus'); 11085 }); 11086 $chapterItem.on('hover',function() { 11087 $(this).closest('ul').find('li').removeClass('able-focus'); 11088 $(this).addClass('able-focus'); 11089 }); 11090 $chapterItem.on('mouseleave',function() { 11091 $(this).removeClass('able-focus'); 11092 }); 11093 $chapterButton.on('blur',function() { 11094 $(this).closest('li').removeClass('able-focus'); 11095 }); 11096 11097 // put it all together 11098 $chapterItem.append($chapterButton); 11099 $chaptersList.append($chapterItem); 11100 if (this.defaultChapter === cues[thisChapter].id) { 11101 $chapterButton.attr('aria-current','true').parent('li').addClass('able-current-chapter'); 11102 this.currentChapter = cues[thisChapter]; 11103 hasDefault = true; 11104 } 11105 } 11106 if (!hasDefault) { 11107 // select the first chapter 11108 this.currentChapter = cues[0]; 11109 $chaptersList.find('button').first().attr('aria-current','true') 11110 .parent('li').addClass('able-current-chapter'); 11111 } 11112 this.$chaptersNav.html($chaptersList); 11113 } 11114 return false; 11115 }; 11116 11117 AblePlayer.prototype.seekToChapter = function(chapterId) { 11118 11119 // step through chapters looking for matching ID 11120 var i=0; 11121 while (i < this.selectedChapters.cues.length) { 11122 if (this.selectedChapters.cues[i].id == chapterId) { 11123 // found the target chapter! Seek to it 11124 this.seekTo(this.selectedChapters.cues[i].start); 11125 this.updateChapter(this.selectedChapters.cues[i].start); 11126 break; 11127 } 11128 i++; 11129 } 11130 }; 11131 11132 AblePlayer.prototype.updateChapter = function (now) { 11133 11134 // as time-synced chapters change during playback, track changes in current chapter 11135 if (typeof this.selectedChapters === 'undefined') { 11136 return; 11137 } 11138 11139 var chapters, i, thisChapterIndex, chapterLabel; 11140 11141 chapters = this.selectedChapters.cues; 11142 for (i = 0; i < chapters.length; i++) { 11143 if ((chapters[i].start <= now) && (chapters[i].end > now)) { 11144 thisChapterIndex = i; 11145 break; 11146 } 11147 } 11148 if (typeof thisChapterIndex !== 'undefined') { 11149 if (this.currentChapter !== chapters[thisChapterIndex]) { 11150 // this is a new chapter 11151 this.currentChapter = chapters[thisChapterIndex]; 11152 if (this.useChapterTimes) { 11153 this.chapterDuration = this.getChapterDuration(); 11154 this.seekIntervalCalculated = false; // will be recalculated in setSeekInterval() 11155 } 11156 if (typeof this.$chaptersDiv !== 'undefined') { 11157 // chapters are listed in an external container 11158 this.$chaptersDiv.find('ul').find('li') 11159 .removeClass('able-current-chapter') 11160 .children('button').removeAttr('aria-current'); 11161 this.$chaptersDiv.find('ul').find('li').eq(thisChapterIndex) 11162 .addClass('able-current-chapter') 11163 .children('button').attr('aria-current','true'); 11164 } 11165 } 11166 } 11167 }; 11168 11169 AblePlayer.prototype.getChapterDuration = function () { 11170 11171 // called if this.seekbarScope === 'chapter' 11172 // get duration of the current chapter 11173 11174 var lastChapterIndex, chapterEnd; 11175 11176 if (typeof this.currentChapter === 'undefined') { 11177 return 0; 11178 } 11179 if (typeof this.duration === 'undefined') { 11180 return 0; 11181 } 11182 lastChapterIndex = this.selectedChapters.cues.length-1; 11183 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 11184 // this is the last chapter 11185 if (this.currentChapter.end !== this.duration) { 11186 // chapter ends before or after video ends, adjust chapter end to match video end 11187 chapterEnd = this.duration; 11188 this.currentChapter.end = this.duration; 11189 } 11190 else { 11191 chapterEnd = this.currentChapter.end; 11192 } 11193 } 11194 else { // this is not the last chapter 11195 chapterEnd = this.currentChapter.end; 11196 } 11197 return chapterEnd - this.currentChapter.start; 11198 }; 11199 11200 AblePlayer.prototype.getChapterElapsed = function () { 11201 // called if this.seekbarScope === 'chapter' 11202 // get current elapsed time, relative to the current chapter duration 11203 11204 if (typeof this.currentChapter === 'undefined') { 11205 return 0; 11206 } 11207 11208 if (this.elapsed > this.currentChapter.start) { 11209 return this.elapsed - this.currentChapter.start; 11210 } 11211 else { 11212 return 0; 11213 } 11214 }; 11215 11216 AblePlayer.prototype.convertChapterTimeToVideoTime = function (chapterTime) { 11217 11218 // chapterTime is the time within the current chapter 11219 // return the same time, relative to the entire video 11220 if (typeof this.currentChapter !== 'undefined') { 11221 var newTime = this.currentChapter.start + chapterTime; 11222 if (newTime > this.currentChapter.end) { 11223 return this.currentChapter.end; 11224 } 11225 else { 11226 return newTime; 11227 } 11228 } 11229 else { 11230 return chapterTime; 11231 } 11232 }; 11233 11234 AblePlayer.prototype.getChapterClickFunction = function (time) { 11235 11236 // Returns the function used when a chapter is clicked in the chapters menu. 11237 var thisObj = this; 11238 return function () { 11239 thisObj.seekTrigger = 'chapter'; 11240 thisObj.seekTo(time); 11241 // stopgap to prevent spacebar in Firefox from reopening popup 11242 // immediately after closing it (used in handleChapters()) 11243 thisObj.hidingPopup = true; 11244 thisObj.chaptersPopup.hide(); 11245 // Ensure stopgap gets cancelled if handleChapters() isn't called 11246 // e.g., if user triggered button with Enter or mouse click, not spacebar 11247 setTimeout(function() { 11248 thisObj.hidingPopup = false; 11249 }, 100); 11250 thisObj.$chaptersButton.focus(); 11251 } 11252 }; 11253 11254})(jQuery); 11255 11256(function ($) { 11257 AblePlayer.prototype.updateMeta = function (time) { 11258 if (this.hasMeta) { 11259 if (this.metaType === 'text') { 11260 this.$metaDiv.show(); 11261 this.showMeta(time || this.elapsed); 11262 } 11263 else { 11264 this.showMeta(time || this.elapsed); 11265 } 11266 } 11267 }; 11268 11269 AblePlayer.prototype.showMeta = function(now) { 11270 var tempSelectors, m, thisMeta, 11271 cues, cueText, cueLines, i, line, 11272 showDuration, focusTarget; 11273 11274 tempSelectors = []; 11275 if (this.meta.length >= 1) { 11276 cues = this.meta; 11277 } 11278 else { 11279 cues = []; 11280 } 11281 for (m = 0; m < cues.length; m++) { 11282 if ((cues[m].start <= now) && (cues[m].end > now)) { 11283 thisMeta = m; 11284 break; 11285 } 11286 } 11287 if (typeof thisMeta !== 'undefined') { 11288 if (this.currentMeta !== thisMeta) { 11289 if (this.metaType === 'text') { 11290 // it's time to load the new metadata cue into the container div 11291 this.$metaDiv.html(this.flattenCueForMeta(cues[thisMeta]).replace('\n', '<br>')); 11292 } 11293 else if (this.metaType === 'selector') { 11294 // it's time to show content referenced by the designated selector(s) 11295 cueText = this.flattenCueForMeta(cues[thisMeta]); 11296 cueLines = cueText.split('\n'); 11297 for (i=0; i<cueLines.length; i++) { 11298 line = $.trim(cueLines[i]); 11299 if (line.toLowerCase().trim() === 'pause') { 11300 // don't show big play button when pausing via metadata 11301 this.hideBigPlayButton = true; 11302 this.pauseMedia(); 11303 } 11304 else if (line.toLowerCase().substring(0,6) == 'focus:') { 11305 focusTarget = line.substring(6).trim(); 11306 if ($(focusTarget).length) { 11307 $(focusTarget).focus(); 11308 } 11309 } 11310 else { 11311 if ($(line).length) { 11312 // selector exists 11313 this.currentMeta = thisMeta; 11314 showDuration = parseInt($(line).attr('data-duration')); 11315 if (typeof showDuration !== 'undefined' && !isNaN(showDuration)) { 11316 $(line).show().delay(showDuration).fadeOut(); 11317 } 11318 else { 11319 // no duration specified. Just show the element until end time specified in VTT file 11320 $(line).show(); 11321 } 11322 // add to array of visible selectors so it can be hidden at end time 11323 this.visibleSelectors.push(line); 11324 tempSelectors.push(line); 11325 11326 } 11327 } 11328 } 11329 // now step through this.visibleSelectors and remove anything that's stale 11330 if (this.visibleSelectors && this.visibleSelectors.length) { 11331 if (this.visibleSelectors.length !== tempSelectors.length) { 11332 for (i=this.visibleSelectors.length-1; i>=0; i--) { 11333 if ($.inArray(this.visibleSelectors[i],tempSelectors) == -1) { 11334 $(this.visibleSelectors[i]).hide(); 11335 this.visibleSelectors.splice(i,1); 11336 } 11337 } 11338 } 11339 } 11340 11341 } 11342 } 11343 } 11344 else {
11345 // there is currently no metadata. Empty stale content 11346 if (typeof this.$metaDiv !== 'undefined') { 11347 this.$metaDiv.html(''); 11348 } 11349 if (this.visibleSelectors && this.visibleSelectors.length) { 11350 for (i=0; i<this.visibleSelectors.length; i++) { 11351 $(this.visibleSelectors[i]).hide(); 11352 } 11353 // reset array 11354 this.visibleSelectors = []; 11355 } 11356 this.currentMeta = -1; 11357 } 11358 }; 11359 11360 // Takes a cue and returns the metadata text to display for it. 11361 AblePlayer.prototype.flattenCueForMeta = function (cue) { 11362 var result = []; 11363 11364 var flattenComponent = function (component) { 11365 var result = [], ii; 11366 if (component.type === 'string') { 11367 result.push(component.value); 11368 } 11369 else if (component.type === 'v') { 11370 result.push('[' + component.value + ']'); 11371 for (ii = 0; ii < component.children.length; ii++) { 11372 result.push(flattenComponent(component.children[ii])); 11373 } 11374 } 11375 else { 11376 for (ii = 0; ii < component.children.length; ii++) { 11377 result.push(flattenComponent(component.children[ii])); 11378 } 11379 } 11380 return result.join(''); 11381 } 11382 11383 for (var ii = 0; ii < cue.components.children.length; ii++) { 11384 result.push(flattenComponent(cue.components.children[ii])); 11385 } 11386 11387 return result.join(''); 11388 }; 11389 11390})(jQuery); 11391 11392(function ($) { 11393 11394 AblePlayer.prototype.setupTranscript = function() { 11395 11396 var deferred = new $.Deferred(); 11397 var promise = deferred.promise(); 11398 11399 if (this.usingYouTubeCaptions || this.usingVimeoCaptions) { 11400 // a transcript is not possible 11401 this.transcriptType = null; 11402 deferred.resolve(); 11403 } 11404 else { 11405 if (!this.transcriptType) { 11406 // previously set transcriptType to null since there are no <track> elements 11407 // check again to see if captions have been collected from other sources (e.g., YouTube) 11408 11409 if (this.captions.length) { 11410 // captions are possible! Use the default type (popup) 11411 // if other types ('external' and 'manual') were desired, transcriptType would not be null here 11412 this.transcriptType = 'popup'; 11413 } 11414 } 11415 if (this.transcriptType) { 11416 if (this.transcriptType === 'popup' || this.transcriptType === 'external') { 11417 this.injectTranscriptArea(); 11418 deferred.resolve(); 11419 } 11420 else if (this.transcriptType === 'manual') { 11421 this.setupManualTranscript(); 11422 deferred.resolve(); 11423 } 11424 } 11425 else { 11426 // there is no transcript 11427 deferred.resolve(); 11428 } 11429 } 11430 return promise; 11431 }; 11432 11433 AblePlayer.prototype.injectTranscriptArea = function() { 11434 11435 var thisObj, $autoScrollLabel, $languageSelectWrapper, $languageSelectLabel, i, $option; 11436 11437 thisObj = this; 11438 this.$transcriptArea = $('<div>', { 11439 'class': 'able-transcript-area', 11440 'role': 'dialog', 11441 'aria-label': this.tt.transcriptTitle 11442 }); 11443 11444 this.$transcriptToolbar = $('<div>', { 11445 'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls' 11446 }); 11447 11448 this.$transcriptDiv = $('<div>', { 11449 'class' : 'able-transcript' 11450 }); 11451 11452 // Transcript toolbar content 11453 11454 // Add auto Scroll checkbox 11455 this.$autoScrollTranscriptCheckbox = $('<input>', { 11456 'id': 'autoscroll-transcript-checkbox-' + this.mediaId, 11457 'type': 'checkbox' 11458 }); 11459 $autoScrollLabel = $('<label>', { 11460 'for': 'autoscroll-transcript-checkbox-' + this.mediaId 11461 }).text(this.tt.autoScroll); 11462 this.$transcriptToolbar.append($autoScrollLabel,this.$autoScrollTranscriptCheckbox); 11463 11464 // Add field for selecting a transcript language 11465 // Only necessary if there is more than one language 11466 if (this.captions.length > 1) { 11467 $languageSelectWrapper = $('<div>',{ 11468 'class': 'transcript-language-select-wrapper' 11469 }); 11470 $languageSelectLabel = $('<label>',{ 11471 'for': 'transcript-language-select-' + this.mediaId 11472 }).text(this.tt.language); 11473 this.$transcriptLanguageSelect = $('<select>',{ 11474 'id': 'transcript-language-select-' + this.mediaId 11475 }); 11476 for (i=0; i < this.captions.length; i++) { 11477 $option = $('<option></option>',{ 11478 value: this.captions[i]['language'], 11479 lang: this.captions[i]['language'] 11480 }).text(this.captions[i]['label']); 11481 if (this.captions[i]['def']) { 11482 $option.prop('selected',true); 11483 } 11484 this.$transcriptLanguageSelect.append($option); 11485 } 11486 } 11487 if ($languageSelectWrapper) { 11488 $languageSelectWrapper.append($languageSelectLabel,this.$transcriptLanguageSelect); 11489 this.$transcriptToolbar.append($languageSelectWrapper); 11490 } 11491 this.$transcriptArea.append(this.$transcriptToolbar, this.$transcriptDiv); 11492 11493 // If client has provided separate transcript location, put it there. 11494 // Otherwise append it to the body 11495 if (this.transcriptDivLocation) { 11496 $('#' + this.transcriptDivLocation).append(this.$transcriptArea); 11497 } 11498 else { 11499 this.$ableWrapper.append(this.$transcriptArea); 11500 } 11501 11502 // make it draggable (popup only; NOT external transcript) 11503 if (!this.transcriptDivLocation) { 11504 this.initDragDrop('transcript'); 11505 if (this.prefTranscript === 1) { 11506 // transcript is on. Go ahead and position it 11507 this.positionDraggableWindow('transcript',this.getDefaultWidth('transcript')); 11508 } 11509 } 11510 11511 // If client has provided separate transcript location, override user's preference for hiding transcript 11512 if (!this.prefTranscript && !this.transcriptDivLocation) { 11513 this.$transcriptArea.hide(); 11514 } 11515 }; 11516 11517 AblePlayer.prototype.addTranscriptAreaEvents = function() { 11518 11519 var thisObj = this; 11520 11521 this.$autoScrollTranscriptCheckbox.click(function () { 11522 thisObj.handleTranscriptLockToggle(thisObj.$autoScrollTranscriptCheckbox.prop('checked')); 11523 }); 11524 11525 this.$transcriptDiv.on('mousewheel DOMMouseScroll click scroll', function (e) { 11526 // Propagation is stopped in transcript click handler, so clicks are on the scrollbar 11527 // or outside of a clickable span. 11528 if (!thisObj.scrollingTranscript) { 11529 thisObj.autoScrollTranscript = false;
vendor: 5,404 bytes, lines 11530-11690
11530 thisObj.refreshControls('transcript'); 11531 } 11532 thisObj.scrollingTranscript = false; 11533 }); 11534 11535 if (typeof this.$transcriptLanguageSelect !== 'undefined') { 11536 11537 this.$transcriptLanguageSelect.on('click mousedown',function (e) { 11538 // execute default behavior 11539 // prevent propagation of mouse event to toolbar or window 11540 e.stopPropagation(); 11541 }); 11542 11543 this.$transcriptLanguageSelect.on('change',function () { 11544 11545 var language = thisObj.$transcriptLanguageSelect.val(); 11546 11547 thisObj.syncTrackLanguages('transcript',language); 11548 }); 11549 } 11550 }; 11551 11552 AblePlayer.prototype.transcriptSrcHasRequiredParts = function() { 11553 11554 // check the external transcript to be sure it has all required components 11555 // return true or false 11556 // in the process, define all the needed variables and properties 11557 11558 if ($('#' + this.transcriptSrc).length) { 11559 this.$transcriptArea = $('#' + this.transcriptSrc); 11560 if (this.$transcriptArea.find('.able-window-toolbar').length) { 11561 this.$transcriptToolbar = this.$transcriptArea.find('.able-window-toolbar').eq(0); 11562 if (this.$transcriptArea.find('.able-transcript').length) { 11563 this.$transcriptDiv = this.$transcriptArea.find('.able-transcript').eq(0); 11564 if (this.$transcriptArea.find('.able-transcript-seekpoint').length) { 11565 this.$transcriptSeekpoints = this.$transcriptArea.find('.able-transcript-seekpoint'); 11566 return true; 11567 } 11568 } 11569 } 11570 } 11571 return false; 11572 } 11573 11574 AblePlayer.prototype.setupManualTranscript = function() { 11575 11576 var $autoScrollInput, $autoScrollLabel; 11577 11578 $autoScrollInput = $('<input>', { 11579 'id': 'autoscroll-transcript-checkbox-' + this.mediaId, 11580 'type': 'checkbox' 11581 }); 11582 $autoScrollLabel = $('<label>', { 11583 'for': 'autoscroll-transcript-checkbox-' + this.mediaId 11584 }).text(this.tt.autoScroll); 11585 11586 // Add an auto-scroll checkbox to the toolbar. 11587 this.$autoScrollTranscriptCheckbox = $autoScrollInput; 11588 this.$transcriptToolbar.append($autoScrollLabel, this.$autoScrollTranscriptCheckbox); 11589 11590 }; 11591 11592 AblePlayer.prototype.updateTranscript = function() { 11593 11594 if (!this.transcriptType) { 11595 return; 11596 } 11597 if (this.playerCreated && !this.$transcriptArea) { 11598 return; 11599 } 11600 if (this.transcriptType === 'external' || this.transcriptType === 'popup') { 11601 11602 var chapters, captions, descriptions; 11603 11604 // Language of transcript might be different than language of captions 11605 // But both are in sync by default 11606 if (this.transcriptLang) { 11607 captions = this.transcriptCaptions.cues; 11608 } 11609 else { 11610 if (this.transcriptCaptions) { 11611 this.transcriptLang = this.transcriptCaptions.language; 11612 captions = this.transcriptCaptions.cues; 11613 } 11614 else if (this.selectedCaptions) { 11615 this.transcriptLang = this.captionLang; 11616 captions = this.selectedCaptions.cues; 11617 } 11618 } 11619 11620 // setup chapters 11621 if (this.transcriptChapters) { 11622 chapters = this.transcriptChapters.cues; 11623 } 11624 else if (this.chapters.length > 0) { 11625 // Try and match the caption language. 11626 if (this.transcriptLang) { 11627 for (var i = 0; i < this.chapters.length; i++) { 11628 if (this.chapters[i].language === this.transcriptLang) { 11629 chapters = this.chapters[i].cues; 11630 } 11631 } 11632 } 11633 if (typeof chapters === 'undefined') { 11634 chapters = this.chapters[0].cues || []; 11635 } 11636 } 11637 11638 // setup descriptions 11639 if (this.transcriptDescriptions) { 11640 descriptions = this.transcriptDescriptions.cues; 11641 } 11642 else if (this.descriptions.length > 0) { 11643 // Try and match the caption language. 11644 if (this.transcriptLang) { 11645 for (var i = 0; i < this.descriptions.length; i++) { 11646 if (this.descriptions[i].language === this.transcriptLang) { 11647 descriptions = this.descriptions[i].cues; 11648 } 11649 } 11650 } 11651 if (!descriptions) { 11652 descriptions = this.descriptions[0].cues || []; 11653 } 11654 } 11655 11656 var div = this.generateTranscript(chapters || [], captions || [], descriptions || []); 11657 this.$transcriptDiv.html(div); 11658 // reset transcript selected <option> to this.transcriptLang 11659 if (this.$transcriptLanguageSelect) { 11660 this.$transcriptLanguageSelect.find('option:selected').prop('selected',false); 11661 this.$transcriptLanguageSelect.find('option[lang=' + this.transcriptLang + ']').prop('selected',true); 11662 } 11663 } 11664 11665 var thisObj = this; 11666 11667 // Make transcript tabbable if preference is turned on. 11668 if (this.prefTabbable === 1) { 11669 this.$transcriptDiv.find('span.able-transcript-seekpoint').attr('tabindex','0'); 11670 } 11671 11672 // handle clicks on text within transcript 11673 // Note: This event listeners handles clicks only, not keydown events 11674 // Pressing Enter on an element that is not natively clickable does NOT trigger click() 11675 // Keydown events are handled elsehwere, both globally (ableplayer-base.js) and locally (event.js) 11676 if (this.$transcriptArea.length > 0) { 11677 this.$transcriptArea.find('span.able-transcript-seekpoint').click(function(e) { 11678 thisObj.seekTrigger = 'transcript'; 11679 var spanStart = parseFloat($(this).attr('data-start')); 11680 // Add a tiny amount so that we're inside the span. 11681 spanStart += .01; 11682 // Each click within the transcript triggers two click events (not sure why) 11683 // this.seekingFromTranscript is a stopgab to prevent two calls to SeekTo() 11684 if (!thisObj.seekingFromTranscript) { 11685 thisObj.seekingFromTranscript = true; 11686 thisObj.seekTo(spanStart); 11687 } 11688 else { 11689 // don't seek a second time, but do reset var 11690 thisObj.seekingFromTranscript = false;
11691 } 11692 }); 11693 } 11694 }; 11695 11696 AblePlayer.prototype.highlightTranscript = function (currentTime) { 11697 11698 // Show highlight in transcript marking current caption. 11699 11700 if (!this.transcriptType) { 11701 return; 11702 } 11703 11704 var start, end, isChapterHeading; 11705 var thisObj = this; 11706 11707 currentTime = parseFloat(currentTime); 11708 11709 // Highlight the current transcript item. 11710 this.$transcriptArea.find('span.able-transcript-seekpoint').each(function() { 11711 start = parseFloat($(this).attr('data-start')); 11712 end = parseFloat($(this).attr('data-end')); 11713 // be sure this isn't a chapter (don't highlight chapter headings) 11714 if ($(this).parent().hasClass('able-transcript-chapter-heading')) { 11715 isChapterHeading = true; 11716 } 11717 else { 11718 isChapterHeading = false; 11719 } 11720 11721 if (currentTime >= start && currentTime <= end && !isChapterHeading) { 11722 11723 // If this item isn't already highlighted, it should be 11724 if (!($(this).hasClass('able-highlight'))) { 11725 // remove all previous highlights before adding one to current span 11726 thisObj.$transcriptArea.find('.able-highlight').removeClass('able-highlight'); 11727 $(this).addClass('able-highlight'); 11728 thisObj.movingHighlight = true; 11729 } 11730 return false; 11731 } 11732 }); 11733 thisObj.currentHighlight = thisObj.$transcriptArea.find('.able-highlight'); 11734 if (thisObj.currentHighlight.length === 0) { 11735 // Nothing highlighted. 11736 thisObj.currentHighlight = null; 11737 } 11738 }; 11739 11740 AblePlayer.prototype.generateTranscript = function(chapters, captions, descriptions) { 11741 11742 var thisObj = this; 11743 11744 var $main = $('<div class="able-transcript-container"></div>'); 11745 var transcriptTitle; 11746 11747 // set language for transcript container 11748 $main.attr('lang', this.transcriptLang); 11749 11750 if (typeof this.transcriptTitle !== 'undefined') { 11751 transcriptTitle = this.transcriptTitle; 11752 } 11753 else if (this.lyricsMode) { 11754 transcriptTitle = this.tt.lyricsTitle; 11755 } 11756 else { 11757 transcriptTitle = this.tt.transcriptTitle; 11758 } 11759 11760 if (!this.transcriptDivLocation) { 11761 // only add an HTML heading to internal transcript 11762 // external transcript is expected to have its own heading 11763 var headingNumber = this.playerHeadingLevel; 11764 headingNumber += 1; 11765 var chapterHeadingNumber = headingNumber + 1; 11766 11767 if (headingNumber <= 6) { 11768 var transcriptHeading = 'h' + headingNumber.toString(); 11769 } 11770 else { 11771 var transcriptHeading = 'div'; 11772 } 11773 var $transcriptHeadingTag = $('<' + transcriptHeading + '>'); 11774 $transcriptHeadingTag.addClass('able-transcript-heading'); 11775 if (headingNumber > 6) { 11776 $transcriptHeadingTag.attr({ 11777 'role': 'heading', 11778 'aria-level': headingNumber 11779 }); 11780 } 11781 $transcriptHeadingTag.text(transcriptTitle); 11782 11783 // set language of transcript heading to language of player 11784 // this is independent of language of transcript 11785 $transcriptHeadingTag.attr('lang', this.lang); 11786 11787 $main.append($transcriptHeadingTag); 11788 } 11789 11790 var nextChapter = 0; 11791 var nextCap = 0; 11792 var nextDesc = 0; 11793 11794 var addChapter = function(div, chap) { 11795 11796 if (chapterHeadingNumber <= 6) { 11797 var chapterHeading = 'h' + chapterHeadingNumber.toString(); 11798 } 11799 else { 11800 var chapterHeading = 'div'; 11801 } 11802 11803 var $chapterHeadingTag = $('<' + chapterHeading + '>',{ 11804 'class': 'able-transcript-chapter-heading' 11805 }); 11806 if (chapterHeadingNumber > 6) { 11807 $chapterHeadingTag.attr({ 11808 'role': 'heading', 11809 'aria-level': chapterHeadingNumber 11810 }); 11811 } 11812 11813 var flattenComponentForChapter = function(comp) { 11814 11815 var result = []; 11816 if (comp.type === 'string') { 11817 result.push(comp.value); 11818 } 11819 else { 11820 for (var i = 0; i < comp.children.length; i++) { 11821 result = result.concat(flattenComponentForChapter(comp.children[i])); 11822 } 11823 } 11824 return result; 11825 } 11826 11827 var $chapSpan = $('<span>',{ 11828 'class': 'able-transcript-seekpoint' 11829 }); 11830 for (var i = 0; i < chap.components.children.length; i++) { 11831 var results = flattenComponentForChapter(chap.components.children[i]); 11832 for (var jj = 0; jj < results.length; jj++) { 11833 $chapSpan.append(results[jj]); 11834 } 11835 } 11836 $chapSpan.attr('data-start', chap.start.toString()); 11837 $chapSpan.attr('data-end', chap.end.toString()); 11838 $chapterHeadingTag.append($chapSpan); 11839 11840 div.append($chapterHeadingTag); 11841 }; 11842 11843 var addDescription = function(div, desc) {
11844 var $descDiv = $('<div>', { 11845 'class': 'able-transcript-desc' 11846 }); 11847 var $descHiddenSpan = $('<span>',{ 11848 'class': 'able-hidden' 11849 }); 11850 $descHiddenSpan.attr('lang', thisObj.lang); 11851 $descHiddenSpan.text(thisObj.tt.prefHeadingDescription + ': '); 11852 $descDiv.append($descHiddenSpan); 11853 11854 var flattenComponentForDescription = function(comp) { 11855 11856 var result = []; 11857 if (comp.type === 'string') { 11858 result.push(comp.value); 11859 } 11860 else { 11861 for (var i = 0; i < comp.children.length; i++) { 11862 result = result.concat(flattenComponentForDescription(comp.children[i])); 11863 } 11864 } 11865 return result; 11866 } 11867 11868 var $descSpan = $('<span>',{ 11869 'class': 'able-transcript-seekpoint' 11870 }); 11871 for (var i = 0; i < desc.components.children.length; i++) { 11872 var results = flattenComponentForDescription(desc.components.children[i]); 11873 for (var jj = 0; jj < results.length; jj++) { 11874 $descSpan.append(results[jj]); 11875 } 11876 } 11877 $descSpan.attr('data-start', desc.start.toString()); 11878 $descSpan.attr('data-end', desc.end.toString()); 11879 $descDiv.append($descSpan); 11880 11881 div.append($descDiv); 11882 }; 11883 11884 var addCaption = function(div, cap) { 11885 11886 var $capSpan = $('<span>',{ 11887 'class': 'able-transcript-seekpoint able-transcript-caption' 11888 }); 11889 11890 var flattenComponentForCaption = function(comp) { 11891 11892 var result = []; 11893 11894 var parts = 0; 11895 11896 var flattenString = function (str) { 11897 11898 parts++; 11899 11900 var flatStr; 11901 var result = []; 11902 if (str === '') { 11903 return result; 11904 } 11905 11906 var openBracket = str.indexOf('['); 11907 var closeBracket = str.indexOf(']'); 11908 var openParen = str.indexOf('('); 11909 var closeParen = str.indexOf(')'); 11910 11911 var hasBrackets = openBracket !== -1 && closeBracket !== -1; 11912 var hasParens = openParen !== -1 && closeParen !== -1; 11913 11914 if (hasParens || hasBrackets) { 11915 if (parts > 1) { 11916 // force a line break between sections that contain parens or brackets 11917 var silentSpanBreak = '<br/>'; 11918 } 11919 else { 11920 var silentSpanBreak = ''; 11921 } 11922 var silentSpanOpen = silentSpanBreak + '<span class="able-unspoken">'; 11923 var silentSpanClose = '</span>'; 11924 if (hasParens && hasBrackets) { 11925 // string has both! 11926 if (openBracket < openParen) { 11927 // brackets come first. Parse parens separately 11928 hasParens = false; 11929 } 11930 else { 11931 // parens come first. Parse brackets separately 11932 hasBrackets = false; 11933 } 11934 } 11935 } 11936 if (hasParens) { 11937 flatStr = str.substring(0, openParen); 11938 flatStr += silentSpanOpen; 11939 flatStr += str.substring(openParen, closeParen + 1); 11940 flatStr += silentSpanClose; 11941 flatStr += flattenString(str.substring(closeParen + 1)); 11942 result.push(flatStr); 11943 } 11944 else if (hasBrackets) { 11945 flatStr = str.substring(0, openBracket); 11946 flatStr += silentSpanOpen; 11947 flatStr += str.substring(openBracket, closeBracket + 1); 11948 flatStr += silentSpanClose; 11949 flatStr += flattenString(str.substring(closeBracket + 1)); 11950 result.push(flatStr); 11951 } 11952 else { 11953 result.push(str); 11954 } 11955 return result; 11956 }; 11957 11958 if (comp.type === 'string') { 11959 result = result.concat(flattenString(comp.value)); 11960 } 11961 else if (comp.type === 'v') { 11962 var $vSpan = $('<span>',{ 11963 'class': 'able-unspoken' 11964 }); 11965 $vSpan.text('(' + comp.value + ')'); 11966 result.push($vSpan); 11967 for (var i = 0; i < comp.children.length; i++) { 11968 var subResults = flattenComponentForCaption(comp.children[i]); 11969 for (var jj = 0; jj < subResults.length; jj++) { 11970 result.push(subResults[jj]); 11971 } 11972 } 11973 } 11974 else if (comp.type === 'b' || comp.type === 'i') { 11975 if (comp.type === 'b') { 11976 var $tag = $('<strong>'); 11977 } 11978 else if (comp.type === 'i') { 11979 var $tag = $('<em>'); 11980 } 11981 for (var i = 0; i < comp.children.length; i++) { 11982 var subResults = flattenComponentForCaption(comp.children[i]); 11983 for (var jj = 0; jj < subResults.length; jj++) { 11984 $tag.append(subResults[jj]); 11985 } 11986 } 11987 if (comp.type === 'b' || comp.type == 'i') { 11988 result.push($tag,' '); 11989 } 11990 } 11991 else { 11992 for (var i = 0; i < comp.children.length; i++) { 11993 result = result.concat(flattenComponentForCaption(comp.children[i])); 11994 } 11995 } 11996 return result; 11997 }; 11998 11999 for (var i = 0; i < cap.components.children.length; i++) { 12000 var results = flattenComponentForCaption(cap.components.children[i]); 12001 for (var jj = 0; jj < results.length; jj++) { 12002 var result = results[jj]; 12003 if (typeof result === 'string') { 12004 if (thisObj.lyricsMode) { 12005 // add <br> BETWEEN each caption and WITHIN each caption (if payload includes "\n") 12006 result = result.replace('\n','<br>') + '<br>'; 12007 } 12008 else { 12009 // just add a space between captions 12010 result += ' '; 12011 } 12012 } 12013 $capSpan.append(result); 12014 } 12015 } 12016 $capSpan.attr('data-start', cap.start.toString()); 12017 $capSpan.attr('data-end', cap.end.toString());
12018 div.append($capSpan); 12019 div.append(' \n'); 12020 }; 12021 12022 // keep looping as long as any one of the three arrays has content 12023 while ((nextChapter < chapters.length) || (nextDesc < descriptions.length) || (nextCap < captions.length)) { 12024 12025 if ((nextChapter < chapters.length) && (nextDesc < descriptions.length) && (nextCap < captions.length)) { 12026 // they all three have content 12027 var firstStart = Math.min(chapters[nextChapter].start,descriptions[nextDesc].start,captions[nextCap].start); 12028 } 12029 else if ((nextChapter < chapters.length) && (nextDesc < descriptions.length)) { 12030 // chapters & descriptions have content 12031 var firstStart = Math.min(chapters[nextChapter].start,descriptions[nextDesc].start); 12032 } 12033 else if ((nextChapter < chapters.length) && (nextCap < captions.length)) { 12034 // chapters & captions have content 12035 var firstStart = Math.min(chapters[nextChapter].start,captions[nextCap].start); 12036 } 12037 else if ((nextDesc < descriptions.length) && (nextCap < captions.length)) { 12038 // descriptions & captions have content 12039 var firstStart = Math.min(descriptions[nextDesc].start,captions[nextCap].start); 12040 } 12041 else { 12042 var firstStart = null; 12043 } 12044 if (firstStart !== null) { 12045 if (typeof chapters[nextChapter] !== 'undefined' && chapters[nextChapter].start === firstStart) { 12046 addChapter($main, chapters[nextChapter]); 12047 nextChapter += 1; 12048 } 12049 else if (typeof descriptions[nextDesc] !== 'undefined' && descriptions[nextDesc].start === firstStart) { 12050 addDescription($main, descriptions[nextDesc]); 12051 nextDesc += 1; 12052 } 12053 else { 12054 addCaption($main, captions[nextCap]); 12055 nextCap += 1; 12056 } 12057 } 12058 else { 12059 if (nextChapter < chapters.length) { 12060 addChapter($main, chapters[nextChapter]); 12061 nextChapter += 1; 12062 } 12063 else if (nextDesc < descriptions.length) { 12064 addDescription($main, descriptions[nextDesc]); 12065 nextDesc += 1; 12066 } 12067 else if (nextCap < captions.length) { 12068 addCaption($main, captions[nextCap]); 12069 nextCap += 1; 12070 } 12071 } 12072 }
12073 // organize transcript into blocks using [] and () as starting points 12074 var $components = $main.children(); 12075 var spanCount = 0; 12076 var openBlock = true; 12077 $components.each(function() { 12078 if ($(this).hasClass('able-transcript-caption')) { 12079 if ($(this).text().indexOf('[') !== -1 || $(this).text().indexOf('(') !== -1) { 12080 // this caption includes a bracket or parenth. Start a new block 12081 // close the previous block first 12082 if (spanCount > 0) { 12083 $main.find('.able-block-temp').removeClass('able-block-temp').wrapAll('<div class="able-transcript-block"></div>'); 12084 spanCount = 0; 12085 } 12086 } 12087 $(this).addClass('able-block-temp'); 12088 spanCount++; 12089 } 12090 else { 12091 // this is not a caption. Close the caption block 12092 if (spanCount > 0) { 12093 $main.find('.able-block-temp').removeClass('able-block-temp').wrapAll('<div class="able-transcript-block"></div>'); 12094 spanCount = 0; 12095 } 12096 } 12097 }); 12098 return $main; 12099 }; 12100 12101})(jQuery); 12102 12103(function ($) { 12104 AblePlayer.prototype.showSearchResults = function() { 12105 12106 // search VTT file for all instances of searchTerms 12107 // Currently just supports search terms separated with one or more spaces 12108 12109 // TODO: Add support for more robust search syntax: 12110 // Search terms wrapped in quotation marks ("") must occur exactly as they appear in the quotes 12111 // Search terms with an attached minus sign (e.g., -term) are to be excluded from results 12112 // Boolean AND/OR operators 12113 // ALSO: Add localization support 12114 12115 var thisObj = this; 12116 if (this.searchDiv && this.searchString) { 12117 if ($('#' + this.SearchDiv)) { 12118 var searchStringHtml = '<p>' + this.tt.resultsSummary1 + ' '; 12119 searchStringHtml += '<span id="able-search-term-echo">' + this.searchString + '</span>'; 12120 searchStringHtml += '</p>'; 12121 var resultsArray = this.searchFor(this.searchString, this.searchIgnoreCaps); 12122 if (resultsArray.length > 0) { 12123 var $resultsSummary = $('<p>',{ 12124 'class': 'able-search-results-summary' 12125 }); 12126 var resultsSummaryText = this.tt.resultsSummary2; 12127 resultsSummaryText += ' <strong>' + resultsArray.length + '</strong> '; 12128 resultsSummaryText += this.tt.resultsSummary3 + ' '; 12129 resultsSummaryText += this.tt.resultsSummary4; 12130 $resultsSummary.html(resultsSummaryText); 12131 var $resultsList = $('<ul>'); 12132 for (var i = 0; i < resultsArray.length; i++) { 12133 var resultId = 'aria-search-result-' + i; 12134 var $resultsItem = $('<li>',{}); 12135 var itemStartTime = this.secondsToTime(resultsArray[i]['start']); 12136 var itemLabel = this.tt.searchButtonLabel + ' ' + itemStartTime['title']; 12137 var itemStartSpan = $('<button>',{ 12138 'class': 'able-search-results-time', 12139 'data-start': resultsArray[i]['start'], 12140 'title': itemLabel, 12141 'aria-label': itemLabel, 12142 'aria-describedby': resultId 12143 }); 12144 itemStartSpan.text(itemStartTime['value']); 12145 // add a listener for clisk on itemStart 12146 itemStartSpan.on('click',function(e) { 12147 thisObj.seekTrigger = 'search'; 12148 var spanStart = parseFloat($(this).attr('data-start')); 12149 // Add a tiny amount so that we're inside the span. 12150 spanStart += .01; 12151 thisObj.seeking = true; 12152 thisObj.seekTo(spanStart); 12153 }); 12154 12155 var itemText = $('<span>',{ 12156 'class': 'able-search-result-text', 12157 'id': resultId 12158 }) 12159 itemText.html('...' + resultsArray[i]['caption'] + '...'); 12160 $resultsItem.append(itemStartSpan, itemText); 12161 $resultsList.append($resultsItem); 12162 } 12163 $('#' + this.searchDiv).html(searchStringHtml).append($resultsSummary,$resultsList); 12164 } 12165 else { 12166 var noResults = $('<p>').text(this.tt.noResultsFound); 12167 $('#' + this.searchDiv).html(searchStringHtml).append(noResults); 12168 } 12169 } 12170 } 12171 }; 12172 12173 AblePlayer.prototype.searchFor = function(searchString, ignoreCaps) { 12174 12175 // return chronological array of caption cues that match searchTerms 12176 var captionLang, captions, results, caption, c, i, j; 12177 results = []; 12178 // split searchTerms into an array 12179 var searchTerms = searchString.split(' '); 12180 if (this.captions.length > 0) { 12181 // Get caption track that matches this.searchLang 12182 for (i=0; i < this.captions.length; i++) { 12183 if (this.captions[i].language === this.searchLang) { 12184 captionLang = this.searchLang; 12185 captions = this.captions[i].cues; 12186 } 12187 } 12188 if (captions.length > 0) { 12189 c = 0; 12190 for (i = 0; i < captions.length; i++) { 12191 if ($.inArray(captions[i].components.children[0]['type'], ['string','i','b','u','v','c']) !== -1) { 12192 caption = this.flattenCueForCaption(captions[i]); 12193 var captionNormalized = ignoreCaps ? caption.toLowerCase() : caption; 12194 for (j = 0; j < searchTerms.length; j++) { 12195 var searchTermNormalized = ignoreCaps ? searchTerms[j].toLowerCase() : searchTerms[j]; 12196 if (captionNormalized.indexOf(searchTermNormalized) !== -1) { 12197 results[c] = []; 12198 results[c]['start'] = captions[i].start; 12199 results[c]['lang'] = captionLang; 12200 results[c]['caption'] = this.highlightSearchTerm(searchTerms,caption); 12201 c++; 12202 break; 12203 } 12204 } 12205 } 12206 } 12207 } 12208 } 12209 return results; 12210 }; 12211 12212 AblePlayer.prototype.highlightSearchTerm = function(searchTerms, resultString) { 12213 // highlight ALL found searchTerms in the current resultString 12214 // Need to step through the remaining terms to see if they're present as well
12215 searchTerms.forEach(function(searchTerm) { 12216 var reg = new RegExp(searchTerm, 'gi'); 12217 resultString = resultString.replace(reg, '<span class="able-search-term">$&</span>'); 12218 }); 12219 return resultString; 12220 }; 12221 12222 AblePlayer.prototype.secondsToTime = function(totalSeconds) { 12223 12224 // return an array of totalSeconds converted into two formats 12225 // time['value'] = HH:MM:SS with hours dropped if there are none 12226 // time['title'] = a speakable rendering, so speech rec users can easily speak the link 12227 12228 // first, round down to nearest second 12229 var totalSeconds = Math.floor(totalSeconds); 12230 12231 var hours = parseInt( totalSeconds / 3600 , 10) % 24; 12232 var minutes = parseInt( totalSeconds / 60 , 10) % 60; 12233 var seconds = totalSeconds % 60; 12234 var value = ''; 12235 var title = ''; 12236 if (hours > 0) { 12237 value += hours + ':'; 12238 if (hours == 1) { 12239 title += '1 ' + this.tt.hour + ' '; 12240 } 12241 else { 12242 title += hours + ' ' + this.tt.hours + ' '; 12243 } 12244 } 12245 if (minutes < 10) { 12246 value += '0' + minutes + ':'; 12247 if (minutes > 0) { 12248 if (minutes == 1) { 12249 title += '1 ' + this.tt.minute + ' '; 12250 } 12251 else { 12252 title += minutes + ' ' + this.tt.minutes + ' '; 12253 } 12254 } 12255 } 12256 else { 12257 value += minutes + ':'; 12258 title += minutes + ' ' + this.tt.minutes + ' '; 12259 } 12260 if (seconds < 10) { 12261 value += '0' + seconds; 12262 if (seconds > 0) { 12263 if (seconds == 1) { 12264 title += '1 ' + this.tt.second + ' '; 12265 } 12266 else { 12267 title += seconds + ' ' + this.tt.seconds + ' '; 12268 } 12269 } 12270 } 12271 else { 12272 value += seconds; 12273 title += seconds + ' ' + this.tt.seconds + ' '; 12274 } 12275 var time = []; 12276 time['value'] = value; 12277 time['title'] = title; 12278 return time; 12279 }; 12280})(jQuery); 12281 12282(function ($) { 12283 // Media events 12284 AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) { 12285 12286 12287 // duration and elapsed are passed from callback functions of Vimeo API events 12288 // duration is expressed as sss.xxx 12289 // elapsed is expressed as sss.xxx 12290 var thisObj = this; 12291 this.getMediaTimes(duration,elapsed).then(function(mediaTimes) { 12292 thisObj.duration = mediaTimes['duration']; 12293 thisObj.elapsed = mediaTimes['elapsed']; 12294 if (thisObj.duration > 0) { 12295 // do all the usual time-sync stuff during playback 12296 if (thisObj.prefHighlight === 1) { 12297 thisObj.highlightTranscript(thisObj.elapsed); 12298 } 12299 thisObj.updateCaption(thisObj.elapsed); 12300 thisObj.showDescription(thisObj.elapsed); 12301 thisObj.updateChapter(thisObj.elapsed); 12302 thisObj.updateMeta(thisObj.elapsed); 12303 thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed); 12304 } 12305 }); 12306 }; 12307 12308 AblePlayer.prototype.onMediaPause = function () { 12309 12310 if (this.controlsHidden) { 12311 this.fadeControls('in'); 12312 this.controlsHidden = false; 12313 } 12314 if (this.hideControlsTimeoutStatus === 'active') { 12315 window.clearTimeout(this.hideControlsTimeout); 12316 this.hideControlsTimeoutStatus = 'clear'; 12317 12318 } 12319 this.refreshControls('playpause'); 12320 }; 12321 12322 AblePlayer.prototype.onMediaComplete = function () { 12323 // if there's a playlist, advance to next item and start playing 12324 if (this.hasPlaylist && !this.cueingPlaylistItem) { 12325 if (this.playlistIndex === (this.$playlist.length - 1)) { 12326 // this is the last track in the playlist 12327 if (this.loop) { 12328 this.playlistIndex = 0; 12329 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 12330 this.cuePlaylistItem(0); 12331 } 12332 else { 12333 this.playing = false; 12334 this.paused = true; 12335 } 12336 } 12337 else { 12338 // this is not the last track. Play the next one. 12339 this.playlistIndex++; 12340 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 12341 this.cuePlaylistItem(this.playlistIndex) 12342 } 12343 } 12344 this.refreshControls('init'); 12345 }; 12346 12347 AblePlayer.prototype.onMediaNewSourceLoad = function () { 12348 12349 var loadIsComplete = false; 12350 12351 if (this.cueingPlaylistItem) { 12352 // this variable was set in order to address bugs caused by multiple firings of media 'end' event 12353 // safe to reset now 12354 this.cueingPlaylistItem = false; 12355 } 12356 if (this.recreatingPlayer) { 12357 // same as above; different bugs 12358 this.recreatingPlayer = false; 12359 } 12360 if (this.playbackRate) { 12361 // user has set playbackRate on a previous src or track 12362 // use that setting on the new src or track too 12363 this.setPlaybackRate(this.playbackRate); 12364 } 12365 if (this.userClickedPlaylist) { 12366 if (!this.startedPlaying || this.okToPlay) { 12367 // start playing; no further user action is required 12368 this.playMedia(); 12369 loadIsComplete = true; 12370 } 12371 } 12372 else if (this.seekTrigger == 'restart' || 12373 this.seekTrigger == 'chapter' || 12374 this.seekTrigger == 'transcript' || 12375 this.seekTrigger == 'search' 12376 ) { 12377 // by clicking on any of these elements, user is likely intending to play 12378 // Not included: elements where user might click multiple times in succession 12379 // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play 12380 this.playMedia(); 12381 loadIsComplete = true; 12382 } 12383 else if (this.swappingSrc) { 12384 // new source file has just been loaded 12385 if (this.hasPlaylist) { 12386 // a new source file from the playlist has just been loaded 12387 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 12388 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 12389 this.playMedia(); 12390 loadIsComplete = true; 12391 } 12392 } 12393 else if (this.swapTime > 0) { 12394 if (this.seekStatus === 'complete') { 12395 if (this.okToPlay) { 12396 // should be able to resume playback 12397 this.playMedia(); 12398 } 12399 loadIsComplete = true; 12400 } 12401 else if (this.seekStatus === 'seeking') { 12402 } 12403 else { 12404 if (this.swapTime === this.elapsed) { 12405 // seek is finished! 12406 this.seekStatus = 'complete'; 12407 if (this.okToPlay) { 12408 // should be able to resume playback 12409 this.playMedia(); 12410 } 12411 loadIsComplete = true; 12412 } 12413 else { 12414 // seeking hasn't started yet 12415 // first, determine whether it's possible 12416 if (this.hasDescTracks) { 12417 // do nothing. Unable to seek ahead if there are descTracks 12418 loadIsComplete = true; 12419 } 12420 else if (this.durationsAreCloseEnough(this.duration,this.prevDuration)) { 12421 // durations of two sources are close enough to making seek ahead in new source ok 12422 this.seekStatus = 'seeking'; 12423 this.seekTo(this.swapTime); 12424 } 12425 else {
12426 // durations of two sources are too dissimilar to support seeking ahead to swapTime. 12427 loadIsComplete = true; 12428 } 12429 } 12430 } 12431 } 12432 else { 12433 // swapTime is 0. No seeking required. 12434 if (this.playing) { 12435 this.playMedia(); 12436 // swap is complete. Reset vars. 12437 loadIsComplete = true; 12438 } 12439 } 12440 } 12441 else if (!this.startedPlaying) { 12442 if (this.startTime > 0) { 12443 if (this.seeking) { 12444 // a seek has already been initiated 12445 // since canplaythrough has been triggered, the seek is complete 12446 this.seeking = false; 12447 if (this.okToPlay) { 12448 this.playMedia(); 12449 } 12450 loadIsComplete = true; 12451 } 12452 else { 12453 // haven't started seeking yet 12454 this.seekTo(this.startTime); 12455 } 12456 } 12457 else if (this.defaultChapter && typeof this.selectedChapters !== 'undefined') { 12458 this.seekToChapter(this.defaultChapter); 12459 } 12460 else { 12461 // there is no startTime, therefore no seeking required 12462 if (this.okToPlay) { 12463 this.playMedia(); 12464 } 12465 loadIsComplete = true; 12466 } 12467 } 12468 else if (this.hasPlaylist) { 12469 // new source media is part of a playlist, but user didn't click on it 12470 // (and somehow, swappingSrc is false) 12471 // this may happen when the previous track ends and next track loads 12472 // this same code is called above when swappingSrc is true 12473 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 12474 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 12475 this.playMedia(); 12476 loadIsComplete = true; 12477 } 12478 } 12479 else { 12480 // None of the above. 12481 // User is likely seeking to a new time, but not loading a new media source 12482 // need to reset vars 12483 loadIsComplete = true; 12484 } 12485 if (loadIsComplete) { 12486 // reset vars 12487 this.swappingSrc = false; 12488 this.seekStatus = null; 12489 this.swapTime = 0; 12490 this.seekTrigger = null; 12491 this.seekingFromTranscript = false; 12492 this.userClickedPlaylist = false; 12493 this.okToPlay = false; 12494 } 12495 this.refreshControls('init'); 12496 if (this.$focusedElement) { 12497 this.restoreFocus(); 12498 this.$focusedElement = null; 12499 } 12500 }; 12501 12502 AblePlayer.prototype.durationsAreCloseEnough = function(d1,d2) { 12503 12504 // Compare the durations of two media sources to determine whether it's ok to seek ahead after swapping src 12505 // The durations may not be exact, but they might be "close enough" 12506 // returns true if "close enough", otherwise false 12507 12508 var tolerance, diff; 12509 12510 tolerance = 1; // number of seconds between rounded durations that is considered "close enough" 12511 12512 diff = Math.abs(Math.round(d1) - Math.round(d2)); 12513 12514 if (diff <= tolerance) { 12515 return true; 12516 } 12517 else { 12518 return false; 12519 } 12520 }; 12521 12522 AblePlayer.prototype.restoreFocus = function() { 12523 12524 // function called after player has been rebuilt (during media swap) 12525 // the original focusedElement no longer exists, 12526 // but this function finds a match in the new player 12527 // and places focus there 12528 12529 var classList; 12530 12531 if (this.$focusedElement) { 12532 12533 if ((this.$focusedElement).attr('role') === 'button') { 12534 classList = this.$focusedElement.attr("class").split(/\s+/); 12535 $.each(classList, function(index, item) { 12536 if (item.substring(0,20) === 'able-button-handler-') { 12537 $('div.able-controller div.' + item).focus(); 12538 } 12539 }); 12540 } 12541 } 12542 12543 }; 12544 12545 AblePlayer.prototype.addSeekbarListeners = function () { 12546 12547 var thisObj = this; 12548 12549 // Handle seek bar events. 12550 this.seekBar.bodyDiv.on('startTracking', function (e) { 12551 thisObj.pausedBeforeTracking = thisObj.paused; 12552 thisObj.pauseMedia(); 12553 }).on('tracking', function (e, position) { 12554 // Scrub transcript, captions, and metadata. 12555 thisObj.highlightTranscript(position); 12556 thisObj.updateCaption(position); 12557 thisObj.showDescription(position); 12558 thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position)); 12559 thisObj.updateMeta(position); 12560 thisObj.refreshControls('init'); 12561 }).on('stopTracking', function (e, position) { 12562 if (thisObj.useChapterTimes) { 12563 thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position)); 12564 } 12565 else { 12566 thisObj.seekTo(position); 12567 } 12568 if (!thisObj.pausedBeforeTracking) { 12569 setTimeout(function () { 12570 thisObj.playMedia(); 12571 }, 200); 12572 } 12573 }); 12574 }; 12575 12576 AblePlayer.prototype.onClickPlayerButton = function (el) { 12577 12578 var whichButton, prefsPopup; 12579 12580 whichButton = this.getButtonNameFromClass($(el).attr('class')); 12581 12582 if (whichButton === 'play') { 12583 this.clickedPlay = true; 12584 this.handlePlay(); 12585 } 12586 else if (whichButton === 'restart') { 12587 this.seekTrigger = 'restart'; 12588 this.handleRestart(); 12589 } 12590 else if (whichButton === 'previous') { 12591 this.userClickedPlaylist = true; 12592 this.okToPlay = true; 12593 this.seekTrigger = 'previous'; 12594 this.buttonWithFocus = 'previous'; 12595 this.handlePrevTrack(); 12596 } 12597 else if (whichButton === 'next') { 12598 this.userClickedPlaylist = true; 12599 this.okToPlay = true; 12600 this.seekTrigger = 'next'; 12601 this.buttonWithFocus = 'next'; 12602 this.handleNextTrack(); 12603 } 12604 else if (whichButton === 'rewind') { 12605 this.seekTrigger = 'rewind'; 12606 this.handleRewind(); 12607 } 12608 else if (whichButton === 'forward') { 12609 this.seekTrigger = 'forward'; 12610 this.handleFastForward(); 12611 } 12612 else if (whichButton === 'mute') { 12613 this.handleMute(); 12614 } 12615 else if (whichButton === 'volume') { 12616 this.handleVolumeButtonClick(); 12617 } 12618 else if (whichButton === 'faster') { 12619 this.handleRateIncrease(); 12620 } 12621 else if (whichButton === 'slower') { 12622 this.handleRateDecrease(); 12623 } 12624 else if (whichButton === 'captions') {
12625 this.handleCaptionToggle(); 12626 } 12627 else if (whichButton === 'chapters') { 12628 this.handleChapters(); 12629 } 12630 else if (whichButton === 'descriptions') { 12631 this.handleDescriptionToggle(); 12632 } 12633 else if (whichButton === 'sign') { 12634 if (!this.closingSign) { 12635 this.handleSignToggle(); 12636 } 12637 } 12638 else if (whichButton === 'preferences') { 12639 if ($(el).attr('data-prefs-popup') === 'menu') { 12640 this.handlePrefsClick(); 12641 } 12642 else { 12643 this.showingPrefsDialog = true; // stopgap 12644 this.closePopups(); 12645 prefsPopup = $(el).attr('data-prefs-popup'); 12646 if (prefsPopup === 'keyboard') { 12647 this.keyboardPrefsDialog.show(); 12648 } 12649 else if (prefsPopup === 'captions') { 12650 this.captionPrefsDialog.show(); 12651 } 12652 else if (prefsPopup === 'descriptions') { 12653 this.descPrefsDialog.show(); 12654 } 12655 else if (prefsPopup === 'transcript') { 12656 this.transcriptPrefsDialog.show(); 12657 } 12658 this.showingPrefsDialog = false; 12659 } 12660 } 12661 else if (whichButton === 'help') { 12662 this.handleHelpClick(); 12663 } 12664 else if (whichButton === 'transcript') { 12665 if (!this.closingTranscript) { 12666 this.handleTranscriptToggle(); 12667 } 12668 } 12669 else if (whichButton === 'fullscreen') { 12670 this.clickedFullscreenButton = true; 12671 this.handleFullscreenToggle(); 12672 } 12673 }; 12674 12675 AblePlayer.prototype.getButtonNameFromClass = function (classString) { 12676 12677 // player control buttons all have class="able-button-handler-x" where x is the identifier 12678 // buttons might also have other classes assigned though 12679 12680 var classes, i; 12681 12682 classes = classString.split(' '); 12683 for (i = 0; i < classes.length; i++) { 12684 if (classes[i].substring(0,20) === 'able-button-handler-') { 12685 return classes[i].substring(20); 12686 } 12687 } 12688 return classString; 12689 } 12690 12691 AblePlayer.prototype.okToHandleKeyPress = function () { 12692 12693 // returns true unless user's focus is on a UI element 12694 // that is likely to need supported keystrokes, including space 12695 12696 var activeElement = AblePlayer.getActiveDOMElement(); 12697 12698 if ($(activeElement).prop('tagName') === 'INPUT') { 12699 return false; 12700 } 12701 else { 12702 return true; 12703 } 12704 }; 12705 12706 AblePlayer.prototype.onPlayerKeyPress = function (e) { 12707 12708 // handle keystrokes (using DHTML Style Guide recommended key combinations) 12709 // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer 12710 // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences 12711 // NOTE #1: Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down 12712 // The rest are reasonable best choices 12713 // NOTE #2: If there are multiple players on a single page, keystroke handlers 12714 // are only bound to the FIRST player 12715 // NOTE #3: The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions 12716 // including removal of the "media player" design pattern. There's an issue about that: 12717 // https://github.com/w3c/aria-practices/issues/27 12718 12719 var which, $thisElement; 12720 12721 // Convert to lower case. 12722 which = e.which; 12723 if (which >= 65 && which <= 90) { 12724 which += 32; 12725 } 12726 $thisElement = $(document.activeElement); 12727 12728 if (which === 27) { // escape 12729 if (this.$transcriptArea && $.contains(this.$transcriptArea[0],$thisElement[0]) && !this.hidingPopup) { 12730 // This element is part of transcript area. 12731 this.handleTranscriptToggle(); 12732 return false; 12733 } 12734 } 12735 if (!this.okToHandleKeyPress()) { 12736 return false; 12737 } 12738 12739 // Only use keypress to control player if focus is NOT on a form field or contenteditable element 12740 // (or a textarea element with player in stenoMode) 12741 if (!( 12742 $(':focus').is('[contenteditable]') || 12743 $(':focus').is('input') || 12744 ($(':focus').is('textarea') && !this.stenoMode) || 12745 $(':focus').is('select') || 12746 e.target.hasAttribute('contenteditable') || 12747 e.target.tagName === 'INPUT' || 12748 (e.target.tagName === 'TEXTAREA' && !this.stenoMode) || 12749 e.target.tagName === 'SELECT' 12750 )){ 12751 if (which === 27) { // escape 12752 this.closePopups(); 12753 this.$tooltipDiv.hide(); 12754 this.seekBar.hideSliderTooltips(); 12755 } 12756 else if (which === 32) { // spacebar = play/pause 12757 // disable spacebar support for play/pause toggle as of 4.2.10 12758 // spacebar should not be handled everywhere on the page, since users use that to scroll the page 12759 // when the player has focus, most controls are buttons so spacebar should be used to trigger the buttons 12760 if ($thisElement.attr('role') === 'button') { 12761 // register a click on this element 12762 e.preventDefault(); 12763 $thisElement.click(); 12764 } 12765 } 12766 else if (which === 112) { // p = play/pause 12767 if (this.usingModifierKeys(e)) { 12768 e.preventDefault(); 12769 this.handlePlay(); 12770 } 12771 } 12772 else if (which === 115) { // s = stop (now restart) 12773 if (this.usingModifierKeys(e)) { 12774 e.preventDefault(); 12775 this.handleRestart(); 12776 } 12777 } 12778 else if (which === 109) { // m = mute 12779 if (this.usingModifierKeys(e)) { 12780 e.preventDefault(); 12781 this.handleMute(); 12782 } 12783 } 12784 else if (which === 118) { // v = volume 12785 if (this.usingModifierKeys(e)) { 12786 e.preventDefault(); 12787 this.handleVolumeButtonClick(); 12788 } 12789 } 12790 else if (which >= 49 && which <= 57) { // set volume 1-9 12791 if (this.usingModifierKeys(e)) { 12792 e.preventDefault(); 12793 this.handleVolumeKeystroke(which); 12794 } 12795 } 12796 else if (which === 99) { // c = caption toggle 12797 if (this.usingModifierKeys(e)) { 12798 e.preventDefault();
12799 this.handleCaptionToggle(); 12800 } 12801 } 12802 else if (which === 100) { // d = description 12803 if (this.usingModifierKeys(e)) { 12804 e.preventDefault(); 12805 this.handleDescriptionToggle(); 12806 } 12807 } 12808 else if (which === 102) { // f = forward 12809 if (this.usingModifierKeys(e)) { 12810 e.preventDefault(); 12811 this.handleFastForward(); 12812 } 12813 } 12814 else if (which === 114) { // r = rewind 12815 if (this.usingModifierKeys(e)) { 12816 e.preventDefault(); 12817 this.handleRewind(); 12818 } 12819 } 12820 else if (which === 98) { // b = back (previous track) 12821 if (this.usingModifierKeys(e)) { 12822 e.preventDefault(); 12823 this.handlePrevTrack(); 12824 } 12825 } 12826 else if (which === 110) { // n = next track 12827 if (this.usingModifierKeys(e)) { 12828 e.preventDefault(); 12829 this.handleNextTrack(); 12830 } 12831 } 12832 else if (which === 101) { // e = preferences 12833 if (this.usingModifierKeys(e)) { 12834 e.preventDefault(); 12835 this.handlePrefsClick(); 12836 } 12837 } 12838 else if (which === 13) { // Enter 12839 if ($thisElement.attr('role') === 'button' || $thisElement.prop('tagName') === 'SPAN') { 12840 // register a click on this element 12841 // if it's a transcript span the transcript span click handler will take over 12842 $thisElement.click(); 12843 } 12844 else if ($thisElement.prop('tagName') === 'LI') { 12845 $thisElement.click(); 12846 } 12847 } 12848 } 12849 }; 12850 12851 AblePlayer.prototype.addHtml5MediaListeners = function () { 12852 12853 var thisObj = this; 12854 12855 // NOTE: iOS and some browsers do not support autoplay 12856 // and no events are triggered until media begins to play 12857 // Able Player gets around this by automatically loading media in some circumstances 12858 // (see initialize.js > initPlayer() for details) 12859 12860 this.$media 12861 .on('emptied',function() { 12862 // do something 12863 }) 12864 .on('loadedmetadata',function() { 12865 // should be able to get duration now 12866 thisObj.duration = thisObj.media.duration; 12867 var x = 50.5; 12868 var y = 51.9; 12869 var diff = Math.abs(Math.round(x)-Math.round(y)); 12870 }) 12871 .on('canplay',function() { 12872 // previously handled seeking to startTime here 12873 // but it's probably safer to wait for canplaythrough 12874 // so we know player can seek ahead to anything 12875 }) 12876 .on('canplaythrough',function() { 12877 // previously onMediaNewSourceLoad() was called on 'loadedmetadata' 12878 // but that proved to be too soon for some of this functionality. 12879 // TODO: Monitor this. If moving it here causes performance issues, 12880 // consider moving some or all of this functionality to 'canplay' 12881 thisObj.onMediaNewSourceLoad(); 12882 }) 12883 .on('play',function() { 12884 // both 'play' and 'playing' seem to be fired in all browsers (including IE11) 12885 // therefore, doing nothing here & doing everything when 'playing' is triggered 12886 thisObj.refreshControls('playpause'); 12887 }) 12888 .on('playing',function() { 12889 thisObj.playing = true; 12890 thisObj.paused = false; 12891 thisObj.swappingSrc = false; 12892 thisObj.refreshControls('playpause'); 12893 }) 12894 .on('ended',function() { 12895 thisObj.playing = false; 12896 thisObj.paused = true; 12897 thisObj.onMediaComplete(); 12898 }) 12899 .on('progress', function() { 12900 thisObj.refreshControls('timeline'); 12901 }) 12902 .on('waiting',function() { 12903 // do something 12904 // previously called refreshControls() here but this event probably doesn't warrant a refresh 12905 }) 12906 .on('durationchange',function() { 12907 // Display new duration. 12908 thisObj.refreshControls('timeline'); 12909 }) 12910 .on('timeupdate',function() { 12911 thisObj.onMediaUpdateTime(); // includes a call to refreshControls() 12912 }) 12913 .on('pause',function() { 12914 if (!thisObj.clickedPlay) { 12915 // 'pause' was triggered automatically, not initiated by user 12916 // this happens in some browsers when swapping source 12917 // (e.g., between tracks in a playlist or swapping description) 12918 if (thisObj.hasPlaylist || thisObj.swappingSrc) { 12919 // do NOT set playing to false. 12920 // doing so prevents continual playback after new track is loaded 12921 } 12922 else { 12923 thisObj.playing = false; 12924 thisObj.paused = true; 12925 } 12926 } 12927 else { 12928 thisObj.playing = false;
vendor: 4,697 bytes, lines 12929-13054
12929 thisObj.paused = true; 12930 } 12931 thisObj.clickedPlay = false; // done with this variable 12932 thisObj.onMediaPause(); // includes a call to refreshControls() 12933 }) 12934 .on('ratechange',function() { 12935 // do something 12936 }) 12937 .on('volumechange',function() { 12938 thisObj.volume = thisObj.getVolume(); 12939 }) 12940 .on('error',function() { 12941 if (thisObj.debug) { 12942 switch (thisObj.media.error.code) { 12943 case 1: 12944 console.log('HTML5 Media Error: MEDIA_ERR_ABORTED'); 12945 break; 12946 case 2: 12947 console.log('HTML5 Media Error: MEDIA_ERR_NETWORK '); 12948 break; 12949 case 3: 12950 console.log('HTML5 Media Error: MEDIA_ERR_DECODE '); 12951 break; 12952 case 4: 12953 console.log('HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED '); 12954 break; 12955 } 12956 } 12957 }); 12958 }; 12959 12960 AblePlayer.prototype.addVimeoListeners = function () { 12961 12962 var thisObj = this; 12963 12964 // Vimeo doesn't seem to support chaining of on() functions 12965 // so each event listener must be attached separately 12966 this.vimeoPlayer.on('loaded', function(vimeoId) { 12967 // Triggered when a new video is loaded in the player 12968 thisObj.onMediaNewSourceLoad(); 12969 }); 12970 this.vimeoPlayer.on('play', function(data) { 12971 // Triggered when the video plays 12972 thisObj.playing = true; 12973 thisObj.startedPlaying = true; 12974 thisObj.paused = false; 12975 thisObj.refreshControls('playpause'); 12976 }); 12977 this.vimeoPlayer.on('ended', function(data) { 12978 // Triggered any time the video playback reaches the end. 12979 // Note: when loop is turned on, the ended event will not fire. 12980 thisObj.playing = false; 12981 thisObj.paused = true; 12982 thisObj.onMediaComplete(); 12983 }); 12984 this.vimeoPlayer.on('bufferstart', function() { 12985 // Triggered when buffering starts in the player. 12986 // This is also triggered during preload and while seeking. 12987 // There is no associated data with this event. 12988 }); 12989 this.vimeoPlayer.on('bufferend', function() { 12990 // Triggered when buffering ends in the player. 12991 // This is also triggered at the end of preload and seeking. 12992 // There is no associated data with this event. 12993 }); 12994 this.vimeoPlayer.on('progress', function(data) { 12995 // Triggered as the video is loaded. 12996 // Reports back the amount of the video that has been buffered (NOT the amount played) 12997 // Data has keys duration, percent, and seconds 12998 }); 12999 this.vimeoPlayer.on('seeking', function(data) { 13000 // Triggered when the player starts seeking to a specific time. 13001 // A timeupdate event will also be fired at the same time. 13002 }); 13003 this.vimeoPlayer.on('seeked', function(data) { 13004 // Triggered when the player seeks to a specific time. 13005 // A timeupdate event will also be fired at the same time. 13006 }); 13007 this.vimeoPlayer.on('timeupdate',function(data) { 13008 // Triggered as the currentTime of the video updates. 13009 // It generally fires every 250ms, but it may vary depending on the browser. 13010 thisObj.onMediaUpdateTime(data['duration'], data['seconds']); 13011 }); 13012 this.vimeoPlayer.on('pause',function(data) { 13013 // Triggered when the video pauses 13014 if (!thisObj.clickedPlay) { 13015 // 'pause' was triggered automatically, not initiated by user 13016 // this happens in some browsers (not Chrome, as of 70.x) 13017 // when swapping source (e.g., between tracks in a playlist, or swapping description) 13018 if (thisObj.hasPlaylist || thisObj.swappingSrc) { 13019 // do NOT set playing to false. 13020 // doing so prevents continual playback after new track is loaded 13021 } 13022 else { 13023 thisObj.playing = false; 13024 thisObj.paused = true; 13025 } 13026 } 13027 else { 13028 thisObj.playing = false; 13029 thisObj.paused = true; 13030 } 13031 thisObj.clickedPlay = false; // done with this variable 13032 thisObj.onMediaPause(); 13033 thisObj.refreshControls('playpause'); 13034 }); 13035 this.vimeoPlayer.on('playbackratechange',function(data) { 13036 // Triggered when the playback rate of the video in the player changes. 13037 // The ability to change rate can be disabled by the creator 13038 // and the event will not fire for those videos. 13039 // data contains one key: 'playbackRate' 13040 thisObj.vimeoPlaybackRate = data['playbackRate']; 13041 }); 13042 this.vimeoPlayer.on('texttrackchange', function(data) { 13043 // Triggered when the active text track (captions/subtitles) changes. 13044 // The values will be null if text tracks are turned off. 13045 // data contains three keys: kind, label, language 13046 }); 13047 this.vimeoPlayer.on('volumechange',function(data) { 13048 // Triggered when the volume in the player changes. 13049 // Some devices do not support setting the volume of the video 13050 // independently from the system volume, 13051 // so this event will never fire on those devices. 13052 thisObj.volume = data['volume'] * 10; 13053 }); 13054 this.vimeoPlayer.on('error',function(data) {
13055 // do something with the available data 13056 // data contains three keys: message, method, name 13057 // message: A user-friendly error message 13058 // method: The Vimeo API method call that triggered the error 13059 // name: Name of the error (not necesssarily user-friendly) 13060 }); 13061 }; 13062 13063 AblePlayer.prototype.addEventListeners = function () { 13064 13065 var thisObj, whichButton, thisElement; 13066 13067 // Save the current object context in thisObj for use with inner functions. 13068 thisObj = this; 13069 13070 // Appropriately resize media player for full screen. 13071 $(window).on('resize',function () { 13072 thisObj.resizePlayer(); 13073 }); 13074 13075 // Refresh player if it changes from hidden to visible 13076 // There is no event triggered by a change in visibility 13077 // but MutationObserver works in most browsers (but NOT in IE 10 or earlier) 13078 // http://caniuse.com/#feat=mutationobserver 13079 if (window.MutationObserver) { 13080 var target = this.$ableDiv[0]; 13081 var observer = new MutationObserver(function(mutations) { 13082 mutations.forEach(function(mutation) { 13083 if (mutation.type === 'attributes' && mutation.attributeName === 'style') { 13084 // the player's style attribute has changed. Check to see if it's visible 13085 if (thisObj.$ableDiv.is(':visible')) { 13086 thisObj.refreshControls('init'); 13087 } 13088 } 13089 }); 13090 }); 13091 var config = { attributes: true, childList: true, characterData: true }; 13092 observer.observe(target, config); 13093 } 13094 else { 13095 // browser doesn't support MutationObserver 13096 // TODO: Figure out an alternative solution for this rare use case in older browsers 13097 // See example in buildplayer.js > useSvg() 13098 } 13099 if (typeof this.seekBar !== 'undefined') { 13100 this.addSeekbarListeners(); 13101 } 13102 else { 13103 // wait a bit and try again 13104 // TODO: Should set this up to keep trying repeatedly. 13105 // Seekbar listeners are critical. 13106 setTimeout(function() { 13107 if (typeof thisObj.seekBar !== 'undefined') { 13108 thisObj.addSeekbarListeners(); 13109 } 13110 },2000); 13111 } 13112 13113 // handle clicks on player buttons 13114 this.$controllerDiv.find('div[role="button"]').on('click',function(e){ 13115 e.stopPropagation(); 13116 thisObj.onClickPlayerButton(this); 13117 }); 13118 13119 // handle clicks (left only) anywhere on the page. If any popups are open, close them. 13120 $(document).on('click',function(e) { 13121 13122 if (e.button !== 0) { // not a left click 13123 return false; 13124 } 13125 if ($('.able-popup:visible').length || $('.able-volume-popup:visible')) { 13126 // at least one popup is visible 13127 thisObj.closePopups(); 13128 } 13129 if (e.target.tagName === 'VIDEO') { 13130 // user clicked the video (not an element that sits on top of the video) 13131 // handle this as a play/pause toggle click 13132 thisObj.clickedPlay = true; 13133 } 13134 }); 13135 13136 // handle mouse movement over player; make controls visible again if hidden 13137 this.$ableDiv.on('mousemove',function() { 13138 if (thisObj.controlsHidden) { 13139 thisObj.fadeControls('in'); 13140 thisObj.controlsHidden = false; 13141 // if there's already an active timeout, clear it and start timer again 13142 if (thisObj.hideControlsTimeoutStatus === 'active') { 13143 window.clearTimeout(thisObj.hideControlsTimeout); 13144 thisObj.hideControlsTimeoutStatus = 'clear'; 13145 } 13146 if (thisObj.hideControls) { 13147 // after showing controls, hide them again after a brief timeout 13148 thisObj.invokeHideControlsTimeout(); 13149 } 13150 } 13151 else { 13152 // if there's already an active timeout, clear it and start timer again 13153 if (thisObj.hideControlsTimeoutStatus === 'active') { 13154 window.clearTimeout(thisObj.hideControlsTimeout); 13155 thisObj.hideControlsTimeoutStatus = 'clear'; 13156 if (thisObj.hideControls) { 13157 thisObj.invokeHideControlsTimeout(); 13158 } 13159 } 13160 } 13161 }); 13162 13163 // if user presses a key from anywhere on the page, show player controls 13164 $(document).keydown(function(e) { 13165 if (thisObj.controlsHidden) { 13166 thisObj.fadeControls('in'); 13167 thisObj.controlsHidden = false; 13168 if (thisObj.hideControlsTimeoutStatus === 'active') { 13169 window.clearTimeout(thisObj.hideControlsTimeout); 13170 thisObj.hideControlsTimeoutStatus = 'clear'; 13171 } 13172 if (thisObj.hideControls) { 13173 // after showing controls, hide them again after a brief timeout 13174 thisObj.invokeHideControlsTimeout(); 13175 } 13176 } 13177 else { 13178 // controls are visible 13179 // if there's already an active timeout, clear it and start timer again 13180 if (thisObj.hideControlsTimeoutStatus === 'active') { 13181 window.clearTimeout(thisObj.hideControlsTimeout); 13182 thisObj.hideControlsTimeoutStatus = 'clear'; 13183 13184 if (thisObj.hideControls) { 13185 thisObj.invokeHideControlsTimeout(); 13186 } 13187 } 13188 } 13189 }); 13190 13191 // handle local keydown events if this isn't the only player on the page; 13192 // otherwise these are dispatched by global handler (see ableplayer-base,js) 13193 this.$ableDiv.keydown(function (e) { 13194 if (AblePlayer.nextIndex > 1) { 13195 thisObj.onPlayerKeyPress(e); 13196 } 13197 }); 13198 13199 // If stenoMode is enabled in an iframe, handle keydown events from the iframe 13200 if (this.stenoMode && (typeof this.stenoFrameContents !== 'undefined')) { 13201 this.stenoFrameContents.on('keydown',function(e) { 13202 thisObj.onPlayerKeyPress(e); 13203 }); 13204 }; 13205 13206 // transcript is not a child of this.$ableDiv 13207 // therefore, must be added separately 13208 if (this.$transcriptArea) { 13209 this.$transcriptArea.on('keydown',function (e) { 13210 if (AblePlayer.nextIndex > 1) { 13211 thisObj.onPlayerKeyPress(e); 13212 } 13213 }); 13214 } 13215 13216 // handle clicks on playlist items 13217 if (this.$playlist) { 13218 this.$playlist.click(function(e) { 13219 if (!thisObj.userClickedPlaylist) { 13220 // stopgap in case multiple clicks are fired on the same playlist item 13221 thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered 13222 thisObj.playlistIndex = $(this).index(); 13223 thisObj.cuePlaylistItem(thisObj.playlistIndex); 13224 } 13225 }); 13226 } 13227 13228 // Also play/pause when clicking on the media. 13229 this.$media.click(function () { 13230 thisObj.handlePlay(); 13231 }); 13232 13233 // add listeners for media events 13234 if (this.player === 'html5') { 13235 this.addHtml5MediaListeners(); 13236 } 13237 else if (this.player === 'vimeo') { 13238 this.addVimeoListeners(); 13239 } 13240 else if (this.player === 'youtube') { 13241 // Youtube doesn't give us time update events, so we just periodically generate them ourselves 13242 setInterval(function () { 13243 thisObj.onMediaUpdateTime(); 13244 }, 300); 13245 } 13246 }; 13247})(jQuery); 13248 13249(function ($) { 13250 13251 AblePlayer.prototype.initDragDrop = function ( which ) { 13252 13253 // supported values of which: 'sign', 'transcript' 13254 13255 // NOTE: "Drag and Drop" for Able Player is a metaphor only!!! 13256 // HTML5 Drag & Drop API enables moving elements to new locations in the DOM 13257 // Thats not our purpose; we're simply changing the visible position on-screen
vendor: 4,298 bytes, lines 13258-13397
13258 // Therefore, the drag & drop interface was overhauled in v2.3.41 to simple 13259 // use mouse (and keyboard) events to change CSS positioning properties 13260 13261 // There are nevertheless lessons to be learned from Drag & Drop about accessibility: 13262 // http://dev.opera.com/articles/accessible-drag-and-drop/ 13263 13264 var thisObj, $window, $toolbar, windowName, $resizeHandle, $resizeSvg, 13265 i, x1, y1, x2, y2, $resizeLine, resizeZIndex; 13266 13267 thisObj = this; 13268 13269 if (which === 'transcript') { 13270 $window = this.$transcriptArea; 13271 windowName = 'transcript-window'; 13272 $toolbar = this.$transcriptToolbar; 13273 } 13274 else if (which === 'sign') { 13275 $window = this.$signWindow; 13276 windowName = 'sign-window'; 13277 $toolbar = this.$signToolbar; 13278 } 13279 13280 // add class to trigger change in cursor on hover 13281 $toolbar.addClass('able-draggable'); 13282 13283 // add resize handle selector to bottom right corner 13284 $resizeHandle = $('<div>',{ 13285 'class': 'able-resizable' 13286 }); 13287 13288 // fill it with three parallel diagonal lines 13289 $resizeSvg = $('<svg>').attr({ 13290 'width': '100%', 13291 'height': '100%', 13292 'viewBox': '0 0 100 100', 13293 'preserveAspectRatio': 'none' 13294 }); 13295 for (i=1; i<=3; i++) { 13296 if (i === 1) { 13297 x1 = '100'; 13298 y1 = '0'; 13299 x2 = '0'; 13300 y2 = '100'; 13301 } 13302 else if (i === 2) { 13303 x1 = '33'; 13304 y1 = '100'; 13305 x2 = '100'; 13306 y2 = '33'; 13307 } 13308 else if (i === 3) { 13309 x1 = '67'; 13310 y1 = '100'; 13311 x2 = '100'; 13312 y2 = '67'; 13313 } 13314 $resizeLine = $('<line>').attr({ 13315 'x1': x1, 13316 'y1': y1, 13317 'x2': x2, 13318 'y2': y2, 13319 'vector-effect': 'non-scaling-stroke' 13320 }) 13321 $resizeSvg.append($resizeLine); 13322 } 13323 $resizeHandle.html($resizeSvg); 13324 13325 // assign z-index that's slightly higher than parent window 13326 resizeZIndex = parseInt($window.css('z-index')) + 100; 13327 $resizeHandle.css('z-index',resizeZIndex); 13328 $window.append($resizeHandle); 13329 13330 // Final step: Need to refresh the DOM in order for browser to process & display the SVG 13331 $resizeHandle.html($resizeHandle.html()); 13332 13333 // add event listener to toolbar to start and end drag 13334 // other event listeners will be added when drag starts 13335 $toolbar.on('mousedown mouseup touchstart touchend', function(e) { 13336 e.stopPropagation(); 13337 if (e.type === 'mousedown' || e.type === 'touchstart') { 13338 if (!thisObj.windowMenuClickRegistered) { 13339 thisObj.windowMenuClickRegistered = true; 13340 thisObj.startMouseX = e.pageX; 13341 thisObj.startMouseY = e.pageY; 13342 thisObj.dragDevice = 'mouse'; // ok to use this even if device is a touchpad 13343 thisObj.startDrag(which, $window); 13344 } 13345 } 13346 else if (e.type === 'mouseup' || e.type === 'touchend') { 13347 if (thisObj.dragging && thisObj.dragDevice === 'mouse') { 13348 thisObj.endDrag(which); 13349 } 13350 } 13351 return false; 13352 }); 13353 13354 // add event listeners for resizing 13355 $resizeHandle.on('mousedown mouseup touchstart touchend', function(e) { 13356 e.stopPropagation(); 13357 if (e.type === 'mousedown' || e.type === 'touchstart') { 13358 if (!thisObj.windowMenuClickRegistered) { 13359 thisObj.windowMenuClickRegistered = true; 13360 thisObj.startMouseX = e.pageX; 13361 thisObj.startMouseY = e.pageY; 13362 thisObj.startResize(which, $window); 13363 } 13364 } 13365 else if (e.type === 'mouseup' || e.type === 'touchend') { 13366 if (thisObj.resizing) { 13367 thisObj.endResize(which); 13368 } 13369 } 13370 return false; 13371 }); 13372 13373 // whenever a window is clicked, bring it to the foreground 13374 $window.on('click', function() { 13375 13376 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 13377 thisObj.updateZIndex(which); 13378 } 13379 thisObj.finishingDrag = false; 13380 }); 13381 13382 this.addWindowMenu(which,$window,windowName); 13383 }; 13384 13385 AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) { 13386 13387 var thisObj, $windowAlert, menuId, $newButton, $buttonIcon, buttonImgSrc, $buttonImg, 13388 $buttonLabel, tooltipId, $tooltip, $popup, 13389 label, position, buttonHeight, buttonWidth, tooltipY, tooltipX, tooltipStyle, tooltip, 13390 $optionList, menuBaseId, options, i, $optionItem, option, menuId; 13391 13392 thisObj = this; 13393 13394 // Add a Boolean that will be set to true temporarily if window button or a menu item is clicked 13395 // This will prevent the click event from also triggering a mousedown event on the toolbar 13396 // (which would unexpectedly send the window into drag mode) 13397 this.windowMenuClickRegistered = false;
13398 13399 // Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag 13400 // this will prevent the click event from being triggered 13401 this.finishingDrag = false; 13402 13403 // create an alert div and add it to window 13404 $windowAlert = $('<div role="alert"></div>'); 13405 $windowAlert.addClass('able-alert'); 13406 $windowAlert.hide(); 13407 $windowAlert.appendTo(this.$activeWindow); 13408 $windowAlert.css({ 13409 top: $window.offset().top 13410 }); 13411 13412 // add button to draggable window which triggers a popup menu 13413 // for now, re-use preferences icon for this purpose 13414 menuId = this.mediaId + '-' + windowName + '-menu'; 13415 $newButton = $('<button>',{ 13416 'type': 'button', 13417 'tabindex': '0', 13418 'aria-label': this.tt.windowButtonLabel, 13419 'aria-haspopup': 'true', 13420 'aria-controls': menuId, 13421 'aria-expanded': 'false', 13422 'class': 'able-button-handler-preferences' 13423 }); 13424 if (this.iconType === 'font') { 13425 $buttonIcon = $('<span>',{ 13426 'class': 'icon-preferences', 13427 'aria-hidden': 'true' 13428 }); 13429 $newButton.append($buttonIcon); 13430 } 13431 else { 13432 // use image 13433 buttonImgSrc = this.rootPath + 'button-icons/' + this.toolbarIconColor + '/preferences.png'; 13434 $buttonImg = $('<img>',{ 13435 'src': buttonImgSrc, 13436 'alt': '', 13437 'role': 'presentation' 13438 }); 13439 $newButton.append($buttonImg); 13440 } 13441 13442 // add the visibly-hidden label for screen readers that don't support aria-label on the button 13443 $buttonLabel = $('<span>',{ 13444 'class': 'able-clipped' 13445 }).text(this.tt.windowButtonLabel); 13446 $newButton.append($buttonLabel); 13447 13448 // add a tooltip that displays aria-label on mouseenter or focus 13449 tooltipId = this.mediaId + '-' + windowName + '-tooltip'; 13450 $tooltip = $('<div>',{ 13451 'class' : 'able-tooltip', 13452 'id' : tooltipId 13453 }).hide(); 13454 $newButton.on('mouseenter focus',function(e) { 13455 var label = $(this).attr('aria-label'); 13456 // get position of this button 13457 var position = $(this).position(); 13458 var buttonHeight = $(this).height(); 13459 var buttonWidth = $(this).width(); 13460 var tooltipY = position.top - buttonHeight - 5; 13461 var tooltipX = 0; 13462 var tooltipStyle = { 13463 left: '', 13464 right: tooltipX + 'px', 13465 top: tooltipY + 'px' 13466 }; 13467 var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label).css(tooltipStyle); 13468 thisObj.showTooltip(tooltip); 13469 $(this).on('mouseleave blur',function() { 13470 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 13471 }); 13472 }); 13473 13474 // setup popup menu 13475 $popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window' 13476 // define vars and assemble all the parts 13477 if (which === 'transcript') { 13478 this.$transcriptAlert = $windowAlert; 13479 this.$transcriptPopupButton = $newButton; 13480 this.$transcriptPopup = $popup; 13481 this.$transcriptToolbar.prepend($windowAlert,$newButton,$tooltip,$popup); 13482 } 13483 else if (which === 'sign') { 13484 this.$signAlert = $windowAlert; 13485 this.$signPopupButton = $newButton; 13486 this.$signPopup = $popup; 13487 this.$signToolbar.append($windowAlert,$newButton,$tooltip,$popup); 13488 } 13489 13490 // handle button click 13491 $newButton.on('click mousedown keydown',function(e) { 13492 13493 if (thisObj.focusNotClick) { 13494 return false; 13495 } 13496 if (thisObj.dragging) { 13497 thisObj.dragKeys(which, e); 13498 return false; 13499 } 13500 e.stopPropagation(); 13501 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 13502 // don't set windowMenuClickRegistered yet; that happens in handler function 13503 thisObj.handleWindowButtonClick(which, e); 13504 } 13505 thisObj.finishingDrag = false; 13506 }); 13507 13508 this.addResizeDialog(which, $window); 13509 }; 13510 13511 AblePlayer.prototype.addResizeDialog = function (which, $window) { 13512 13513 var thisObj, $windowPopup, $windowButton, 13514 widthId, heightId, startingWidth, startingHeight, aspectRatio, 13515 $resizeForm, $resizeWrapper, 13516 $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel, 13517 $resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel, 13518 tempWidth, tempHeight, 13519 $saveButton, $cancelButton, newWidth, newHeight, resizeDialog; 13520 13521 thisObj = this; 13522 13523 if (which === 'transcript') { 13524 $windowPopup = this.$transcriptPopup; 13525 $windowButton = this.$transcriptPopupButton; 13526 } 13527 else if (which === 'sign') { 13528 $windowPopup = this.$signPopup; 13529 $windowButton = this.$signPopupButton; 13530 } 13531 13532 widthId = this.mediaId + '-resize-' + which + '-width'; 13533 heightId = this.mediaId + '-resize-' + which + '-height'; 13534 startingWidth = $window.width(); 13535 startingHeight = $window.height(); 13536 aspectRatio = startingWidth / startingHeight; 13537 13538 $resizeForm = $('<div></div>',{ 13539 'class' : 'able-resize-form' 13540 }); 13541
13542 // inner container for all content, will be assigned to modal div's aria-describedby 13543 $resizeWrapper = $('<div></div>'); 13544 13545 // width field 13546 $resizeWidthDiv = $('<div></div>'); 13547 $resizeWidthInput = $('<input>',{ 13548 'type': 'text', 13549 'id': widthId, 13550 'value': startingWidth 13551 }); 13552 $resizeWidthLabel = $('<label>',{ 13553 'for': widthId 13554 }).text(this.tt.width); 13555 13556 // height field 13557 $resizeHeightDiv = $('<div></div>'); 13558 $resizeHeightInput = $('<input>',{ 13559 'type': 'text', 13560 'id': heightId, 13561 'value': startingHeight 13562 }); 13563 $resizeHeightLabel = $('<label>',{ 13564 'for': heightId 13565 }).text(this.tt.height); 13566 13567 if (which === 'sign') { 13568 // make height a read-only field 13569 // and calculate its value based on width to preserve aspect ratio 13570 $resizeHeightInput.prop('readonly',true); 13571 $resizeWidthInput.on('input',function() { 13572 tempWidth = $(this).val(); 13573 tempHeight = Math.round(tempWidth/aspectRatio, 0); 13574 $resizeHeightInput.val(tempHeight); 13575 }) 13576 } 13577 13578 // Add save and cancel buttons. 13579 $saveButton = $('<button class="modal-button">' + this.tt.save + '</button>'); 13580 $cancelButton = $('<button class="modal-button">' + this.tt.cancel + '</button>'); 13581 $saveButton.on('click',function () { 13582 newWidth = $('#' + widthId).val(); 13583 newHeight = $('#' + heightId).val(); 13584 if (newWidth !== startingWidth || newHeight !== startingHeight) { 13585 thisObj.resizeObject(which,newWidth,newHeight); 13586 thisObj.updateCookie(which); 13587 } 13588 resizeDialog.hide(); 13589 $windowPopup.hide(); 13590 $windowButton.focus(); 13591 }); 13592 $cancelButton.on('click',function () { 13593 resizeDialog.hide(); 13594 $windowPopup.hide(); 13595 $windowButton.focus(); 13596 }); 13597 13598 // Now assemble all the parts 13599 $resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput); 13600 $resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput); 13601 $resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv); 13602 $resizeForm.append($resizeWrapper,'<hr>',$saveButton,$cancelButton); 13603 13604 // must be appended to the BODY! 13605 // otherwise when aria-hidden="true" is applied to all background content 13606 // that will include an ancestor of the dialog, 13607 // which will render the dialog unreadable by screen readers 13608 $('body').append($resizeForm); 13609 resizeDialog = new AccessibleDialog($resizeForm, $windowButton, 'dialog', true, this.tt.windowResizeHeading, $resizeWrapper, this.tt.closeButtonLabel, '20em'); 13610 if (which === 'transcript') { 13611 this.transcriptResizeDialog = resizeDialog; 13612 } 13613 else if (which === 'sign') { 13614 this.signResizeDialog = resizeDialog; 13615 } 13616 }; 13617 13618 AblePlayer.prototype.handleWindowButtonClick = function (which, e) { 13619 13620 var thisObj, $windowPopup, $windowButton, $toolbar, popupTop; 13621 13622 thisObj = this; 13623 13624 if (this.focusNotClick) { 13625 // transcript or sign window has just opened, 13626 // and focus moved to the window button 13627 // ignore the keystroke that triggered the popup 13628 return false; 13629 } 13630 13631 if (which === 'transcript') { 13632 $windowPopup = this.$transcriptPopup; 13633 $windowButton = this.$transcriptPopupButton; 13634 $toolbar = this.$transcriptToolbar; 13635 } 13636 else if (which === 'sign') { 13637 $windowPopup = this.$signPopup; 13638 $windowButton = this.$signPopupButton; 13639 $toolbar = this.$signToolbar; 13640 } 13641 13642 if (e.type === 'keydown') { 13643 // user pressed a key 13644 if (e.which === 32 || e.which === 13) { 13645 // this was Enter or space 13646 this.windowMenuClickRegistered = true; 13647 } 13648 else if (e.which === 27) { // escape 13649 if ($windowPopup.is(':visible')) { 13650 // close the popup menu 13651 $windowPopup.hide('fast', function() { 13652 // also reset the Boolean 13653 thisObj.windowMenuClickRegistered = false; 13654 // also restore menu items to their original state 13655 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 13656 // also return focus to window options button 13657 $windowButton.focus(); 13658 }); 13659 } 13660 else { 13661 // popup isn't open. Close the window 13662 if (which === 'sign') { 13663 this.handleSignToggle(); 13664 } 13665 else if (which === 'transcript') { 13666 this.handleTranscriptToggle(); 13667 } 13668 } 13669 } 13670 else { 13671 return false; 13672 } 13673 } 13674 else { 13675 // this was a mouse event 13676 this.windowMenuClickRegistered = true; 13677 } 13678 13679 if ($windowPopup.is(':visible')) { 13680 $windowPopup.hide(200,'',function() { 13681 thisObj.windowMenuClickRegistered = false; // reset 13682 }); 13683 $windowPopup.find('li').removeClass('able-focus'); 13684 $windowButton.attr('aria-expanded','false').focus(); 13685 } 13686 else { 13687 // first, be sure window is on top 13688 this.updateZIndex(which); 13689 popupTop = $windowButton.position().top + $windowButton.outerHeight(); 13690 $windowPopup.css('top', popupTop); 13691 $windowPopup.show(200,'',function() { 13692 $windowButton.attr('aria-expanded','true'); 13693 $(this).find('li').first().focus().addClass('able-focus'); 13694 thisObj.windowMenuClickRegistered = false; // reset 13695 }); 13696 } 13697 }; 13698 13699 AblePlayer.prototype.handleMenuChoice = function (which, choice, e) { 13700 13701 var thisObj, $window, $windowPopup, $windowButton, resizeDialog, width, height, $thisRadio; 13702 13703 thisObj = this; 13704 if (which === 'transcript') { 13705 $window = this.$transcriptArea; 13706 $windowPopup = this.$transcriptPopup; 13707 $windowButton = this.$transcriptPopupButton; 13708 resizeDialog = this.transcriptResizeDialog; 13709 } 13710 else if (which === 'sign') { 13711 $window = this.$signWindow; 13712 $windowPopup = this.$signPopup; 13713 $windowButton = this.$signPopupButton; 13714 resizeDialog = this.signResizeDialog; 13715 } 13716 this.$activeWindow = $window; 13717 13718 if (e.type === 'keydown') { 13719 if (e.which === 27) { // escape 13720 // hide the popup menu 13721 $windowPopup.hide('fast', function() { 13722 // also reset the Boolean 13723 thisObj.windowMenuClickRegistered = false;
13724 // also restore menu items to their original state 13725 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 13726 $windowButton.attr('aria-expanded','false'); 13727 // also return focus to window options button 13728 $windowButton.focus(); 13729 }); 13730 return false; 13731 } 13732 else { 13733 // all other keys will be handled by upstream functions 13734 if (choice !== 'close') { 13735 this.$activeWindow = $window; 13736 } 13737 return false; 13738 } 13739 } 13740 13741 // hide the popup menu 13742 $windowPopup.hide('fast', function() { 13743 // also reset the boolean 13744 thisObj.windowMenuClickRegistered = false; 13745 // also restore menu items to their original state 13746 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 13747 $windowButton.attr('aria-expanded','false'); 13748 }); 13749 if (choice !== 'close') { 13750 $windowButton.focus(); 13751 } 13752 if (choice === 'move') { 13753 13754 // temporarily add role="application" to activeWindow 13755 // otherwise, screen readers incercept arrow keys and moving window will not work 13756 this.$activeWindow.attr('role','application'); 13757 13758 if (!this.showedAlert(which)) { 13759 this.showAlert(this.tt.windowMoveAlert,which); 13760 if (which === 'transcript') { 13761 this.showedTranscriptAlert = true; 13762 } 13763 else if (which === 'sign') { 13764 this.showedSignAlert = true; 13765 } 13766 } 13767 if (e.type === 'keydown') { 13768 this.dragDevice = 'keyboard'; 13769 } 13770 else { 13771 this.dragDevice = 'mouse'; 13772 } 13773 this.startDrag(which, $window); 13774 $windowPopup.hide().parent().focus(); 13775 } 13776 else if (choice == 'resize') { 13777 // resize through the menu uses a form, not drag 13778 var resizeFields = resizeDialog.getInputs(); 13779 if (resizeFields) { 13780 // reset width and height values in form 13781 resizeFields[0].value = $window.width(); 13782 resizeFields[1].value = $window.height(); 13783 } 13784 resizeDialog.show(); 13785 } 13786 else if (choice == 'close') { 13787 // close window, place focus on corresponding button on controller bar 13788 if (which === 'transcript') { 13789 this.closingTranscript = true; // stopgrap to prevent double-firing of keypress 13790 this.handleTranscriptToggle(); 13791 } 13792 else if (which === 'sign') { 13793 this.closingSign = true; // stopgrap to prevent double-firing of keypress 13794 this.handleSignToggle(); 13795 } 13796 } 13797 }; 13798 13799 AblePlayer.prototype.startDrag = function(which, $element) { 13800 13801 var thisObj, $windowPopup, zIndex, startPos, newX, newY; 13802 13803 thisObj = this; 13804 13805 if (!this.$activeWindow) { 13806 this.$activeWindow = $element; 13807 } 13808 this.dragging = true; 13809 13810 if (which === 'transcript') { 13811 $windowPopup = this.$transcriptPopup; 13812 } 13813 else if (which === 'sign') { 13814 $windowPopup = this.$signPopup; 13815 } 13816 13817 if (!this.showedAlert(which)) { 13818 this.showAlert(this.tt.windowMoveAlert,which); 13819 if (which === 'transcript') { 13820 this.showedTranscriptAlert = true; 13821 } 13822 else if (which === 'sign') { 13823 this.showedSignAlert = true; 13824 } 13825 } 13826 13827 // if window's popup menu is open, close it 13828 if ($windowPopup.is(':visible')) { 13829 $windowPopup.hide(); 13830 } 13831 13832 // be sure this window is on top 13833 this.updateZIndex(which); 13834 13835 // get starting position of element 13836 startPos = this.$activeWindow.position(); 13837 this.dragStartX = startPos.left; 13838 this.dragStartY = startPos.top; 13839 13840 if (typeof this.startMouseX === 'undefined') { 13841 this.dragDevice = 'keyboard'; 13842 this.dragKeyX = this.dragStartX; 13843 this.dragKeyY = this.dragStartY; 13844 // add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd() 13845 this.startingDrag = true; 13846 } 13847 else { 13848 this.dragDevice = 'mouse'; 13849 // get offset between mouse position and top left corner of draggable element 13850 this.dragOffsetX = this.startMouseX - this.dragStartX; 13851 this.dragOffsetY = this.startMouseY - this.dragStartY; 13852 } 13853 13854 // prepare element for dragging 13855 this.$activeWindow.addClass('able-drag').css({ 13856 'position': 'absolute', 13857 'top': this.dragStartY + 'px', 13858 'left': this.dragStartX + 'px' 13859 }).focus(); 13860 13861 // add device-specific event listeners 13862 if (this.dragDevice === 'mouse') { // might also be a touchpad 13863 $(document).on('mousemove touchmove',function(e) { 13864 if (thisObj.dragging) { 13865 // calculate new top left based on current mouse position - offset 13866 newX = e.pageX - thisObj.dragOffsetX; 13867 newY = e.pageY - thisObj.dragOffsetY; 13868 thisObj.resetDraggedObject( newX, newY ); 13869 } 13870 }); 13871 } 13872 else if (this.dragDevice === 'keyboard') { 13873 this.$activeWindow.on('keydown',function(e) { 13874 if (thisObj.dragging) { 13875 thisObj.dragKeys(which, e); 13876 } 13877 }); 13878 } 13879 return false;
13880 }; 13881 13882 AblePlayer.prototype.dragKeys = function(which, e) { 13883 13884 var key, keySpeed; 13885 13886 var thisObj = this; 13887 13888 // stopgap to prevent firing on initial Enter or space 13889 // that selected "Move" from menu 13890 if (this.startingDrag) { 13891 this.startingDrag = false; 13892 return false; 13893 } 13894 key = e.which; 13895 keySpeed = 10; // pixels per keypress event 13896 13897 switch (key) { 13898 case 37: // left 13899 case 63234: 13900 this.dragKeyX -= keySpeed; 13901 break; 13902 case 38: // up 13903 case 63232: 13904 this.dragKeyY -= keySpeed; 13905 break; 13906 case 39: // right 13907 case 63235: 13908 this.dragKeyX += keySpeed; 13909 break; 13910 case 40: // down 13911 case 63233: 13912 this.dragKeyY += keySpeed; 13913 break; 13914 case 13: // enter 13915 case 27: // escape 13916 this.endDrag(which); 13917 return false; 13918 default: 13919 return false; 13920 } 13921 this.resetDraggedObject(this.dragKeyX,this.dragKeyY); 13922 if (e.preventDefault) { 13923 e.preventDefault(); 13924 } 13925 return false; 13926 }; 13927 13928 AblePlayer.prototype.resetDraggedObject = function ( x, y) { 13929 13930 this.$activeWindow.css({ 13931 'left': x + 'px', 13932 'top': y + 'px' 13933 }); 13934 }, 13935 13936 AblePlayer.prototype.resizeObject = function ( which, width, height ) { 13937 13938 var innerHeight; 13939 13940 // which is either 'transcript' or 'sign' 13941 this.$activeWindow.css({ 13942 'width': width + 'px', 13943 'height': height + 'px' 13944 }); 13945 13946 if (which === 'transcript') { 13947 // $activeWindow is the outer $transcriptArea 13948 // but the inner able-transcript also needs to be resized proporitionally 13949 // (it's 50px less than its outer container) 13950 innerHeight = height - 50; 13951 this.$transcriptDiv.css('height', innerHeight + 'px'); 13952 } 13953 }; 13954 13955 AblePlayer.prototype.endDrag = function(which) { 13956 13957 var thisObj, $window, $windowPopup, $windowButton; 13958 thisObj = this; 13959 13960 if (which === 'transcript') { 13961 $windowPopup = this.$transcriptPopup; 13962 $windowButton = this.$transcriptPopupButton; 13963 } 13964 else if (which === 'sign') { 13965 $windowPopup = this.$signPopup; 13966 $windowButton = this.$signPopupButton; 13967 } 13968 13969 $(document).off('mousemove mouseup touchmove touchup'); 13970 this.$activeWindow.off('keydown').removeClass('able-drag'); 13971 // restore activeWindow role from 'application' to 'dialog' 13972 this.$activeWindow.attr('role','dialog'); 13973 this.$activeWindow = null; 13974 13975 if (this.dragDevice === 'keyboard') { 13976 $windowButton.focus(); 13977 } 13978 this.dragging = false; 13979 13980 // save final position of dragged element 13981 this.updateCookie(which); 13982 13983 // reset starting mouse positions 13984 this.startMouseX = undefined; 13985 this.startMouseY = undefined; 13986 13987 // Boolean to stop stray events from firing 13988 this.windowMenuClickRegistered = false; 13989 this.finishingDrag = true; // will be reset after window click event 13990 // finishingDrag should e reset after window click event, 13991 // which is triggered automatically after mouseup 13992 // However, in case that's not reliable in some browsers 13993 // need to ensure this gets cancelled 13994 setTimeout(function() { 13995 thisObj.finishingDrag = false; 13996 }, 100); 13997 }; 13998 13999 AblePlayer.prototype.isCloseToCorner = function($window, mouseX, mouseY) { 14000 14001 // return true if mouse is close to bottom right corner (resize target) 14002 var tolerance, position, top, left, width, height, bottom, right; 14003 14004 tolerance = 10; // number of pixels in both directions considered "close enough" 14005 14006 // first, get position of element 14007 position = $window.offset(); 14008 top = position.top; 14009 left = position.left; 14010 width = $window.width(); 14011 height = $window.height(); 14012 bottom = top + height; 14013 right = left + width; 14014 if ((Math.abs(bottom-mouseY) <= tolerance) && (Math.abs(right-mouseX) <= tolerance)) { 14015 return true; 14016 } 14017 return false; 14018 }; 14019 14020 AblePlayer.prototype.startResize = function(which, $element) { 14021 14022 var thisObj, $windowPopup, zIndex, startPos, newWidth, newHeight; 14023 14024 thisObj = this; 14025 this.$activeWindow = $element; 14026 this.resizing = true; 14027 14028 if (which === 'transcript') { 14029 $windowPopup = this.$transcriptPopup; 14030 } 14031 else if (which === 'sign') { 14032 $windowPopup = this.$signPopup; 14033 } 14034 14035 // if window's popup menu is open, close it & place focus on button (???) 14036 if ($windowPopup.is(':visible')) { 14037 $windowPopup.hide().parent().focus(); 14038 } 14039 14040 // get starting width and height 14041 startPos = this.$activeWindow.position(); 14042 this.dragKeyX = this.dragStartX; 14043 this.dragKeyY = this.dragStartY; 14044 this.dragStartWidth = this.$activeWindow.width(); 14045 this.dragStartHeight = this.$activeWindow.height(); 14046 14047 // add event listeners 14048 $(document).on('mousemove touchmove',function(e) { 14049 if (thisObj.resizing) { 14050 // calculate new width and height based on changes to mouse position 14051 newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX); 14052 newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY); 14053 thisObj.resizeObject( which, newWidth, newHeight ); 14054 } 14055 }); 14056 return false;
14057 }; 14058 14059 AblePlayer.prototype.endResize = function(which) { 14060 14061 var $window, $windowPopup, $windowButton; 14062 14063 if (which === 'transcript') { 14064 $windowPopup = this.$transcriptPopup; 14065 $windowButton = this.$transcriptPopupButton; 14066 } 14067 else if (which === 'sign') { 14068 $windowPopup = this.$signPopup; 14069 $windowButton = this.$signPopupButton; 14070 } 14071 14072 $(document).off('mousemove mouseup touchmove touchup'); 14073 this.$activeWindow.off('keydown'); 14074 $windowButton.show().focus(); 14075 this.resizing = false; 14076 this.$activeWindow.removeClass('able-resize'); 14077 14078 // save final width and height of dragged element 14079 this.updateCookie(which); 14080 14081 // Booleans for preventing stray events 14082 this.windowMenuClickRegistered = false; 14083 this.finishingDrag = true; 14084 14085 // finishingDrag should e reset after window click event, 14086 // which is triggered automatically after mouseup 14087 // However, in case that's not reliable in some browsers 14088 // need to ensure this gets cancelled 14089 setTimeout(function() { 14090 this.finishingDrag = false; 14091 }, 100); 14092 }; 14093 14094})(jQuery); 14095 14096(function ($) { 14097 AblePlayer.prototype.initSignLanguage = function() { 14098 14099 // Sign language is only currently supported in HTML5 player, not YouTube or Vimeo 14100 if (this.player === 'html5') { 14101 // check to see if there's a sign language video accompanying this video 14102 // check only the first source 14103 // If sign language is provided, it must be provided for all sources 14104 this.signFile = this.$sources.first().attr('data-sign-src'); 14105 if (this.signFile) { 14106 if (this.isIOS()) { 14107 // IOS does not allow multiple videos to play simultaneously 14108 // Therefore, sign language as rendered by Able Player unfortunately won't work 14109 this.hasSignLanguage = false; 14110 if (this.debug) { 14111 console.log('Sign language has been disabled due to IOS restrictions'); 14112 } 14113 } 14114 else { 14115 if (this.debug) { 14116 console.log('This video has an accompanying sign language video: ' + this.signFile); 14117 } 14118 this.hasSignLanguage = true; 14119 this.injectSignPlayerCode(); 14120 } 14121 } 14122 else { 14123 this.hasSignLanguage = false; 14124 } 14125 } 14126 else { 14127 this.hasSignLanguage = false; 14128 } 14129 }; 14130 14131 AblePlayer.prototype.injectSignPlayerCode = function() { 14132 14133 // create and inject surrounding HTML structure 14134 14135 var thisObj, signVideoId, signVideoWidth, i, signSrc, srcType, $signSource; 14136 14137 thisObj = this; 14138 14139 signVideoWidth = this.getDefaultWidth('sign'); 14140 14141 signVideoId = this.mediaId + '-sign'; 14142 this.$signVideo = $('<video>',{ 14143 'id' : signVideoId, 14144 'tabindex' : '-1' 14145 }); 14146 this.signVideo = this.$signVideo[0]; 14147 // for each original <source>, add a <source> to the sign <video> 14148 for (i=0; i < this.$sources.length; i++) { 14149 signSrc = this.$sources[i].getAttribute('data-sign-src'); 14150 srcType = this.$sources[i].getAttribute('type'); 14151 if (signSrc) { 14152 $signSource = $('<source>',{ 14153 'src' : signSrc, 14154 'type' : srcType 14155 }); 14156 this.$signVideo.append($signSource); 14157 } 14158 else { 14159 // source is missing a sign language version 14160 // can't include sign language 14161 this.hasSignLanguage = false; 14162 break; 14163 } 14164 } 14165 14166 this.$signWindow = $('<div>',{ 14167 'class' : 'able-sign-window', 14168 'role': 'dialog', 14169 'aria-label': this.tt.sign 14170 }); 14171 this.$signToolbar = $('<div>',{ 14172 'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls' 14173 }); 14174 14175 this.$signWindow.append(this.$signToolbar, this.$signVideo); 14176 14177 this.$ableWrapper.append(this.$signWindow); 14178 14179 // make it draggable 14180 this.initDragDrop('sign'); 14181 14182 if (this.prefSign === 1) { 14183 // sign window is on. Go ahead and position it and show it 14184 this.positionDraggableWindow('sign',this.getDefaultWidth('sign')); 14185 } 14186 else { 14187 this.$signWindow.hide(); 14188 } 14189 }; 14190 14191})(jQuery); 14192 14193(function ($
vendor: 18,947 bytes, lines 14193-15169
14193) { 14194 // Look up ISO 639-1 language codes to be used as subtitle labels 14195 // In some instances "name" has been trunctation for readability 14196 // Sources: 14197 // http://stackoverflow.com/questions/3217492/list-of-language-codes-in-yaml-or-json/4900304#4900304 14198 // https://www.venea.net/web/culture_code 14199 14200 var isoLangs = { 14201 "ab":{ 14202 "name":"Abkhaz", 14203 "nativeName":"аҧÑÑа" 14204 }, 14205 "aa":{ 14206 "name":"Afar", 14207 "nativeName":"Afaraf" 14208 }, 14209 "af":{ 14210 "name":"Afrikaans", 14211 "nativeName":"Afrikaans" 14212 }, 14213 "ak":{ 14214 "name":"Akan", 14215 "nativeName":"Akan" 14216 }, 14217 "sq":{ 14218 "name":"Albanian", 14219 "nativeName":"Shqip" 14220 }, 14221 "am":{ 14222 "name":"Amharic", 14223 "nativeName":"á ááá" 14224 }, 14225 "ar":{ 14226 "name":"Arabic", 14227 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ©" 14228 }, 14229 "an":{ 14230 "name":"Aragonese", 14231 "nativeName":"Aragonés" 14232 }, 14233 "hy":{ 14234 "name":"Armenian", 14235 "nativeName":"ÕÕ¡ÕµÕ¥ÖÕ¥Õ¶" 14236 }, 14237 "as":{ 14238 "name":"Assamese", 14239 "nativeName":"ঠসমà§à¦¯à¦¼à¦¾" 14240 }, 14241 "av":{ 14242 "name":"Avaric", 14243 "nativeName":"Ð°Ð²Ð°Ñ Ð¼Ð°ÑÓ, магÓаÑÑл маÑÓ" 14244 }, 14245 "ae":{ 14246 "name":"Avestan", 14247 "nativeName":"avesta" 14248 }, 14249 "ay":{ 14250 "name":"Aymara", 14251 "nativeName":"aymar aru" 14252 }, 14253 "az":{ 14254 "name":"Azerbaijani", 14255 "nativeName":"azÉrbaycan dili" 14256 }, 14257 "bm":{ 14258 "name":"Bambara", 14259 "nativeName":"bamanankan" 14260 }, 14261 "ba":{ 14262 "name":"Bashkir", 14263 "nativeName":"баÑҡоÑÑ Ñеле" 14264 }, 14265 "eu":{ 14266 "name":"Basque", 14267 "nativeName":"euskara, euskera" 14268 }, 14269 "be":{ 14270 "name":"Belarusian", 14271 "nativeName":"ÐелаÑÑÑкаÑ" 14272 }, 14273 "bn":{ 14274 "name":"Bengali", 14275 "nativeName":"বাà¦à¦²à¦¾" 14276 }, 14277 "bh":{ 14278 "name":"Bihari", 14279 "nativeName":"à¤à¥à¤à¤ªà¥à¤°à¥" 14280 }, 14281 "bi":{ 14282 "name":"Bislama", 14283 "nativeName":"Bislama" 14284 }, 14285 "bs":{ 14286 "name":"Bosnian", 14287 "nativeName":"bosanski jezik" 14288 }, 14289 "br":{ 14290 "name":"Breton", 14291 "nativeName":"brezhoneg" 14292 }, 14293 "bg":{ 14294 "name":"Bulgarian", 14295 "nativeName":"бÑлгаÑÑки език" 14296 }, 14297 "my":{ 14298 "name":"Burmese", 14299 "nativeName":"ááá¬á á¬" 14300 }, 14301 "ca":{ 14302 "name":"Catalan", 14303 "nativeName":"Català " 14304 }, 14305 "ch":{ 14306 "name":"Chamorro", 14307 "nativeName":"Chamoru" 14308 }, 14309 "ce":{ 14310 "name":"Chechen", 14311 "nativeName":"Ð½Ð¾Ñ Ñийн моÑÑ" 14312 }, 14313 "ny":{ 14314 "name":"Chichewa", 14315 "nativeName":"chiCheŵa, chinyanja" 14316 }, 14317 "zh":{ 14318 "name":"Chinese", 14319 "nativeName":"䏿 (ZhÅngwén), æ±è¯, æ¼¢èª" 14320 }, 14321 "cv":{ 14322 "name":"Chuvash", 14323 "nativeName":"ÑÓÐ²Ð°Ñ ÑÓÐ»Ñ Ð¸" 14324 }, 14325 "kw":{ 14326 "name":"Cornish", 14327 "nativeName":"Kernewek" 14328 }, 14329 "co":{ 14330 "name":"Corsican", 14331 "nativeName":"corsu, lingua corsa" 14332 }, 14333 "cr":{ 14334 "name":"Cree", 14335 "nativeName":"áá¦ááááá£" 14336 }, 14337 "hr":{ 14338 "name":"Croatian", 14339 "nativeName":"hrvatski" 14340 }, 14341 "cs":{ 14342 "name":"Czech", 14343 "nativeName":"Äesky, ÄeÅ¡tina" 14344 }, 14345 "da":{ 14346 "name":"Danish", 14347 "nativeName":"dansk" 14348 }, 14349 "dv":{ 14350 "name":"Divehi", 14351 "nativeName":"ÞÞ¨ÞÞ¬ÞÞ¨" 14352 }, 14353 "nl":{ 14354 "name":"Dutch", 14355 "nativeName":"Nederlands, Vlaams" 14356 }, 14357 "en":{ 14358 "name":"English", 14359 "nativeName":"English" 14360 }, 14361 "eo":{ 14362 "name":"Esperanto", 14363 "nativeName":"Esperanto" 14364 }, 14365 "et":{ 14366 "name":"Estonian", 14367 "nativeName":"eesti, eesti keel" 14368 }, 14369 "ee":{ 14370 "name":"Ewe", 14371 "nativeName":"EÊegbe" 14372 }, 14373 "fo":{ 14374 "name":"Faroese", 14375 "nativeName":"føroyskt" 14376 }, 14377 "fj":{ 14378 "name":"Fijian", 14379 "nativeName":"vosa Vakaviti" 14380 }, 14381 "fi":{ 14382 "name":"Finnish", 14383 "nativeName":"suomi, suomen kieli" 14384 }, 14385 "fr":{ 14386 "name":"French", 14387 "nativeName":"français, langue française" 14388 }, 14389 "ff":{ 14390 "name":"Fula", 14391 "nativeName":"Fulfulde, Pulaar, Pular" 14392 }, 14393 "gl":{ 14394 "name":"Galician", 14395 "nativeName":"Galego" 14396 }, 14397 "ka":{ 14398 "name":"Georgian", 14399 "nativeName":"á¥áá áá£áá" 14400 }, 14401 "de":{ 14402 "name":"German", 14403 "nativeName":"Deutsch" 14404 }, 14405 "el":{ 14406 "name":"Greek", 14407 "nativeName":"Îλληνικά" 14408 }, 14409 "gn":{ 14410 "name":"GuaranÃ", 14411 "nativeName":"Avañeẽ" 14412 }, 14413 "gu":{ 14414 "name":"Gujarati", 14415 "nativeName":"àªà«àªàª°àª¾àª¤à«" 14416 }, 14417 "ht":{ 14418 "name":"Haitian", 14419 "nativeName":"Kreyòl ayisyen" 14420 }, 14421 "ha":{ 14422 "name":"Hausa", 14423 "nativeName":"Hausa, ÙÙÙÙØ³Ù" 14424 }, 14425 "he":{ 14426 "name":"Hebrew", 14427 "nativeName":"×¢×ר×ת" 14428 }, 14429 "hz":{ 14430 "name":"Herero", 14431 "nativeName":"Otjiherero" 14432 }, 14433 "hi":{ 14434 "name":"Hindi", 14435 "nativeName":"हिनà¥à¤¦à¥, हिà¤à¤¦à¥" 14436 }, 14437 "ho":{ 14438 "name":"Hiri Motu", 14439 "nativeName":"Hiri Motu" 14440 }, 14441 "hu":{ 14442 "name":"Hungarian", 14443 "nativeName":"Magyar" 14444 }, 14445 "ia":{ 14446 "name":"Interlingua", 14447 "nativeName":"Interlingua" 14448 }, 14449 "id":{ 14450 "name":"Indonesian", 14451 "nativeName":"Bahasa Indonesia" 14452 }, 14453 "ie":{ 14454 "name":"Interlingue", 14455 "nativeName":"Originally called Occidental; then Interlingue after WWII" 14456 }, 14457 "ga":{ 14458 "name":"Irish", 14459 "nativeName":"Gaeilge" 14460 }, 14461 "ig":{ 14462 "name":"Igbo", 14463 "nativeName":"Asụsụ Igbo" 14464 }, 14465 "ik":{ 14466 "name":"Inupiaq", 14467 "nativeName":"Iñupiaq, Iñupiatun" 14468 }, 14469 "io":{ 14470 "name":"Ido", 14471 "nativeName":"Ido" 14472 }, 14473 "is":{ 14474 "name":"Icelandic", 14475 "nativeName":"Ãslenska" 14476 }, 14477 "it":{ 14478 "name":"Italian", 14479 "nativeName":"Italiano" 14480 }, 14481 "iu":{ 14482 "name":"Inuktitut", 14483 "nativeName":"áááááá¦" 14484 }, 14485 "ja":{ 14486 "name":"Japanese", 14487 "nativeName":"æ¥æ¬èª (ã«ã»ããï¼ã«ã£ã½ãã)" 14488 }, 14489 "jv":{ 14490 "name":"Javanese", 14491 "nativeName":"basa Jawa" 14492 }, 14493 "kl":{ 14494 "name":"Kalaallisut", 14495 "nativeName":"kalaallisut, kalaallit oqaasii" 14496 }, 14497 "kn":{ 14498 "name":"Kannada", 14499 "nativeName":"à²à²¨à³à²¨à²¡" 14500 }, 14501 "kr":{ 14502 "name":"Kanuri", 14503 "nativeName":"Kanuri" 14504 }, 14505 "ks":{ 14506 "name":"Kashmiri", 14507 "nativeName":"à¤à¤¶à¥à¤®à¥à¤°à¥, ÙØ´Ù ÙØ±Ùâ" 14508 }, 14509 "kk":{ 14510 "name":"Kazakh", 14511 "nativeName":"ÒÐ°Ð·Ð°Ò ÑÑлÑ" 14512 }, 14513 "km":{ 14514 "name":"Khmer", 14515 "nativeName":"áá¶áá¶ááááá" 14516 }, 14517 "ki":{ 14518 "name":"Kikuyu", 14519 "nativeName":"GÄ©kÅ©yÅ©" 14520 }, 14521 "rw":{ 14522 "name":"Kinyarwanda", 14523 "nativeName":"Ikinyarwanda" 14524 }, 14525 "ky":{ 14526 "name":"Kyrgyz", 14527 "nativeName":"кÑÑгÑз Ñили" 14528 }, 14529 "kv":{ 14530 "name":"Komi", 14531 "nativeName":"коми кÑв" 14532 }, 14533 "kg":{ 14534 "name":"Kongo", 14535 "nativeName":"KiKongo" 14536 }, 14537 "ko":{ 14538 "name":"Korean", 14539 "nativeName":"íêµì´ (éåèª), ì¡°ì ë§ (æé®®èª)" 14540 }, 14541 "ku":{ 14542 "name":"Kurdish", 14543 "nativeName":"Kurdî, ÙÙØ±Ø¯Ûâ" 14544 }, 14545 "kj":{ 14546 "name":"Kuanyama", 14547 "nativeName":"Kuanyama" 14548 }, 14549 "la":{ 14550 "name":"Latin", 14551 "nativeName":"latine, lingua latina" 14552 }, 14553 "lb":{ 14554 "name":"Luxembourgish", 14555 "nativeName":"Lëtzebuergesch" 14556 }, 14557 "lg":{ 14558 "name":"Luganda", 14559 "nativeName":"Luganda" 14560 }, 14561 "li":{ 14562 "name":"Limburgish", 14563 "nativeName":"Limburgs" 14564 }, 14565 "ln":{ 14566 "name":"Lingala", 14567 "nativeName":"Lingála" 14568 }, 14569 "lo":{ 14570 "name":"Lao", 14571 "nativeName":"àºàº²àºªàº²àº¥àº²àº§" 14572 }, 14573 "lt":{ 14574 "name":"Lithuanian", 14575 "nativeName":"lietuvių kalba" 14576 }, 14577 "lu":{ 14578 "name":"Luba-Katanga", 14579 "nativeName":"" 14580 }, 14581 "lv":{ 14582 "name":"Latvian", 14583 "nativeName":"latvieÅ¡u valoda" 14584 }, 14585 "gv":{ 14586 "name":"Manx", 14587 "nativeName":"Gaelg, Gailck" 14588 }, 14589 "mk":{ 14590 "name":"Macedonian", 14591 "nativeName":"македонÑки Ñазик" 14592 }, 14593 "mg":{ 14594 "name":"Malagasy", 14595 "nativeName":"Malagasy fiteny" 14596 }, 14597 "ms":{ 14598 "name":"Malay", 14599 "nativeName":"bahasa Melayu, Ø¨ÙØ§Ø³ Ù ÙØ§ÙÙâ" 14600 }, 14601 "ml":{ 14602 "name":"Malayalam", 14603 "nativeName":"മലയാളà´" 14604 }, 14605 "mt":{ 14606 "name":"Maltese", 14607 "nativeName":"Malti" 14608 }, 14609 "mi":{ 14610 "name":"MÄori", 14611 "nativeName":"te reo MÄori" 14612 }, 14613 "mr":{ 14614 "name":"Marathi", 14615 "nativeName":"मराठà¥" 14616 }, 14617 "mh":{ 14618 "name":"Marshallese", 14619 "nativeName":"Kajin M̧ajeļ" 14620 }, 14621 "mn":{ 14622 "name":"Mongolian", 14623 "nativeName":"монгол" 14624 }, 14625 "na":{ 14626 "name":"Nauru", 14627 "nativeName":"EkakairÅ© Naoero" 14628 }, 14629 "nv":{ 14630 "name":"Navajo", 14631 "nativeName":"Diné bizaad, DinékʼehǰÃ" 14632 }, 14633 "nb":{ 14634 "name":"Norwegian BokmÃ¥l", 14635 "nativeName":"Norsk bokmÃ¥l" 14636 }, 14637 "nd":{ 14638 "name":"North Ndebele", 14639 "nativeName":"isiNdebele" 14640 }, 14641 "ne":{ 14642 "name":"Nepali", 14643 "nativeName":"नà¥à¤ªà¤¾à¤²à¥" 14644 }, 14645 "ng":{ 14646 "name":"Ndonga", 14647 "nativeName":"Owambo" 14648 }, 14649 "nn":{ 14650 "name":"Norwegian Nynorsk", 14651 "nativeName":"Norsk nynorsk" 14652 }, 14653 "no":{ 14654 "name":"Norwegian", 14655 "nativeName":"Norsk" 14656 }, 14657 "ii":{ 14658 "name":"Nuosu", 14659 "nativeName":"êê ê¿ Nuosuhxop" 14660 }, 14661 "nr":{ 14662 "name":"South Ndebele", 14663 "nativeName":"isiNdebele" 14664 }, 14665 "oc":{ 14666 "name":"Occitan", 14667 "nativeName":"Occitan" 14668 }, 14669 "oj":{ 14670 "name":"Ojibwe", 14671 "nativeName":"ááááá¯á§áá" 14672 }, 14673 "cu":{ 14674 "name":"Church Slavonic", 14675 "nativeName":"ѩзÑÐºÑ ÑловѣнÑÑкÑ" 14676 }, 14677 "om":{ 14678 "name":"Oromo", 14679 "nativeName":"Afaan Oromoo" 14680 }, 14681 "or":{ 14682 "name":"Oriya", 14683 "nativeName":"à¬à¬¡à¬¼à¬¿à¬" 14684 }, 14685 "os":{ 14686 "name":"Ossetian", 14687 "nativeName":"иÑон æвзаг" 14688 }, 14689 "pa":{ 14690 "name":"Punjabi", 14691 "nativeName":"ਪੰà¨à¨¾à¨¬à©, Ù¾ÙØ¬Ø§Ø¨Ûâ" 14692 }, 14693 "pi":{ 14694 "name":"PÄli", 14695 "nativeName":"पाऴि" 14696 }, 14697 "fa":{ 14698 "name":"Persian", 14699 "nativeName":"ÙØ§Ø±Ø³Û" 14700 }, 14701 "pl":{ 14702 "name":"Polish", 14703 "nativeName":"polski" 14704 }, 14705 "ps":{ 14706 "name":"Pashto", 14707 "nativeName":"Ù¾ÚØªÙ" 14708 }, 14709 "pt":{ 14710 "name":"Portuguese", 14711 "nativeName":"Português" 14712 }, 14713 "qu":{ 14714 "name":"Quechua", 14715 "nativeName":"Runa Simi, Kichwa" 14716 }, 14717 "rm":{ 14718 "name":"Romansh", 14719 "nativeName":"rumantsch grischun" 14720 }, 14721 "rn":{ 14722 "name":"Kirundi", 14723 "nativeName":"kiRundi" 14724 }, 14725 "ro":{ 14726 "name":"Romanian", 14727 "nativeName":"românÄ" 14728 }, 14729 "ru":{ 14730 "name":"Russian", 14731 "nativeName":"ÑÑÑÑкий ÑзÑк" 14732 }, 14733 "sa":{ 14734 "name":"Sanskrit", 14735 "nativeName":"सà¤à¤¸à¥à¤à¥à¤¤à¤®à¥" 14736 }, 14737 "sc":{ 14738 "name":"Sardinian", 14739 "nativeName":"sardu" 14740 }, 14741 "sd":{ 14742 "name":"Sindhi", 14743 "nativeName":"सिनà¥à¤§à¥, سÙÚÙØ Ø³ÙØ¯Ú¾Ûâ" 14744 }, 14745 "se":{ 14746 "name":"Northern Sami", 14747 "nativeName":"Davvisámegiella" 14748 }, 14749 "sm":{ 14750 "name":"Samoan", 14751 "nativeName":"gagana faa Samoa" 14752 }, 14753 "sg":{ 14754 "name":"Sango", 14755 "nativeName":"yângâ tî sängö" 14756 }, 14757 "sr":{ 14758 "name":"Serbian", 14759 "nativeName":"ÑÑпÑки Ñезик" 14760 }, 14761 "gd":{ 14762 "name":"Gaelic", 14763 "nativeName":"Gà idhlig" 14764 }, 14765 "sn":{ 14766 "name":"Shona", 14767 "nativeName":"chiShona" 14768 }, 14769 "si":{ 14770 "name":"Sinhalese", 14771 "nativeName":"à·à·à¶à·à¶½" 14772 }, 14773 "sk":{ 14774 "name":"Slovak", 14775 "nativeName":"slovenÄina" 14776 }, 14777 "sl":{ 14778 "name":"Slovene", 14779 "nativeName":"slovenÅ¡Äina" 14780 }, 14781 "so":{ 14782 "name":"Somali", 14783 "nativeName":"Soomaaliga, af Soomaali" 14784 }, 14785 "st":{ 14786 "name":"Southern Sotho", 14787 "nativeName":"Sesotho" 14788 }, 14789 "es":{ 14790 "name":"Spanish", 14791 "nativeName":"español, castellano" 14792 }, 14793 "su":{ 14794 "name":"Sundanese", 14795 "nativeName":"Basa Sunda" 14796 }, 14797 "sw":{ 14798 "name":"Swahili", 14799 "nativeName":"Kiswahili" 14800 }, 14801 "ss":{ 14802 "name":"Swati", 14803 "nativeName":"SiSwati" 14804 }, 14805 "sv":{ 14806 "name":"Swedish", 14807 "nativeName":"svenska" 14808 }, 14809 "ta":{ 14810 "name":"Tamil", 14811 "nativeName":"தமிழà¯" 14812 }, 14813 "te":{ 14814 "name":"Telugu", 14815 "nativeName":"à°¤à±à°²à±à°à±" 14816 }, 14817 "tg":{ 14818 "name":"Tajik", 14819 "nativeName":"Ñоҷикӣ, toÄikÄ«, تاجÛÚ©Ûâ" 14820 }, 14821 "th":{ 14822 "name":"Thai", 14823 "nativeName":"à¹à¸à¸¢" 14824 }, 14825 "ti":{ 14826 "name":"Tigrinya", 14827 "nativeName":"áµááá" 14828 }, 14829 "bo":{ 14830 "name":"Tibetan", 14831 "nativeName":"à½à½¼à½à¼à½¡à½²à½" 14832 }, 14833 "tk":{ 14834 "name":"Turkmen", 14835 "nativeName":"Türkmen, ТүÑкмен" 14836 }, 14837 "tl":{ 14838 "name":"Tagalog", 14839 "nativeName":"Wikang Tagalog, áááá á áááááá" 14840 }, 14841 "tn":{ 14842 "name":"Tswana", 14843 "nativeName":"Setswana" 14844 }, 14845 "to":{ 14846 "name":"Tonga", 14847 "nativeName":"faka Tonga" 14848 }, 14849 "tr":{ 14850 "name":"Turkish", 14851 "nativeName":"Türkçe" 14852 }, 14853 "ts":{ 14854 "name":"Tsonga", 14855 "nativeName":"Xitsonga" 14856 }, 14857 "tt":{ 14858 "name":"Tatar", 14859 "nativeName":"ÑаÑаÑÑа, tatarça, ØªØ§ØªØ§Ø±ÚØ§â" 14860 }, 14861 "tw":{ 14862 "name":"Twi", 14863 "nativeName":"Twi" 14864 }, 14865 "ty":{ 14866 "name":"Tahitian", 14867 "nativeName":"Reo Tahiti" 14868 }, 14869 "ug":{ 14870 "name":"Uyghur", 14871 "nativeName":"UyÆ£urqÉ, ئÛÙØºÛرÚÛâ" 14872 }, 14873 "uk":{ 14874 "name":"Ukrainian", 14875 "nativeName":"ÑкÑаÑнÑÑка" 14876 }, 14877 "ur":{ 14878 "name":"Urdu", 14879 "nativeName":"اردÙ" 14880 }, 14881 "uz":{ 14882 "name":"Uzbek", 14883 "nativeName":"zbek, Ðзбек, Ø£ÛØ²Ø¨ÛÙâ" 14884 }, 14885 "ve":{ 14886 "name":"Venda", 14887 "nativeName":"Tshivená¸a" 14888 }, 14889 "vi":{ 14890 "name":"Vietnamese", 14891 "nativeName":"Tiếng Viá»t" 14892 }, 14893 "vo":{ 14894 "name":"Volapük", 14895 "nativeName":"Volapük" 14896 }, 14897 "wa":{ 14898 "name":"Walloon", 14899 "nativeName":"Walon" 14900 }, 14901 "cy":{ 14902 "name":"Welsh", 14903 "nativeName":"Cymraeg" 14904 }, 14905 "wo":{ 14906 "name":"Wolof", 14907 "nativeName":"Wollof" 14908 }, 14909 "fy":{ 14910 "name":"Western Frisian", 14911 "nativeName":"Frysk" 14912 }, 14913 "xh":{ 14914 "name":"Xhosa", 14915 "nativeName":"isiXhosa" 14916 }, 14917 "yi":{ 14918 "name":"Yiddish", 14919 "nativeName":"××Ö´××ש" 14920 }, 14921 "yo":{ 14922 "name":"Yoruba", 14923 "nativeName":"Yorùbá" 14924 }, 14925 "za":{ 14926 "name":"Zhuang", 14927 "nativeName":"Saɯ cueÅÆ , Saw cuengh" 14928 }, 14929 "ar-dz":{ 14930 "name":"Arabic (Algeria)", 14931 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¬Ø²Ø§Ø¦Ø±)" 14932 }, 14933 "ar-bh":{ 14934 "name":"Arabic (Bahrain)", 14935 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¨ØØ±ÙÙ)" 14936 }, 14937 "ar-eg":{ 14938 "name":"Arabic (Egypt)", 14939 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (٠صر)" 14940 }, 14941 "ar-iq":{ 14942 "name":"Arabic (Iraq)", 14943 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¹Ø±Ø§Ù)" 14944 }, 14945 "ar-jo":{ 14946 "name":"Arabic (Jordan)", 14947 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ£Ø±Ø¯Ù)" 14948 }, 14949 "ar-kw":{ 14950 "name":"Arabic (Kuwait)", 14951 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙÙØª)" 14952 }, 14953 "ar-lb":{ 14954 "name":"Arabic (Lebanon)", 14955 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ¨ÙاÙ)" 14956 }, 14957 "ar-ly":{ 14958 "name":"Arabic (Libya)", 14959 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙÙØ¨Ùا)" 14960 }, 14961 "ar-ma":{ 14962 "name":"Arabic (Morocco)", 14963 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© اÙÙ ØºØ±Ø¨ÙØ©)" 14964 }, 14965 "ar-om":{ 14966 "name":"Arabic (Oman)", 14967 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ع٠اÙ)" 14968 }, 14969 "ar-qa":{ 14970 "name":"Arabic (Qatar)", 14971 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ·Ø±)" 14972 }, 14973 "ar-sa":{ 14974 "name":"Arabic (Saudi Arabia)", 14975 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© Ø§ÙØ¹Ø±Ø¨ÙØ© Ø§ÙØ³Ø¹ÙØ¯ÙØ©)" 14976 }, 14977 "ar-sy":{ 14978 "name":"Arabic (Syria)", 14979 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø³ÙØ±Ùا)" 14980 }, 14981 "ar-tn":{ 14982 "name":"Arabic (Tunisia)", 14983 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (تÙÙØ³)" 14984 }, 14985 "ar-ae":{ 14986 "name":"Arabic (U.A.E.)", 14987 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¥Ù ارات Ø§ÙØ¹Ø±Ø¨ÙØ© اÙÙ ØªØØ¯Ø©)" 14988 }, 14989 "ar-ye":{ 14990 "name":"Arabic (Yemen)", 14991 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙ Ù)" 14992 }, 14993 "de-at":{ 14994 "name":"German (Austria)", 14995 "nativeName":"Deutsch (Ãsterreich)" 14996 }, 14997 "de-li":{ 14998 "name":"German (Liechtenstein)", 14999 "nativeName":"Deutsch (Liechtenstein)" 15000 }, 15001 "de-lu":{ 15002 "name":"German (Luxembourg)", 15003 "nativeName":"Deutsch (Luxemburg)" 15004 }, 15005 "de-ch":{ 15006 "name":"German (Switzerland)", 15007 "nativeName":"Deutsch (Schweiz)" 15008 }, 15009 "en-au":{ 15010 "name":"English (Australia)", 15011 "nativeName":"English (Australia)" 15012 }, 15013 "en-bz":{ 15014 "name":"English (Belize)", 15015 "nativeName":"English (Belize)" 15016 }, 15017 "en-ca":{ 15018 "name":"English (Canada)", 15019 "nativeName":"English (Canada)" 15020 }, 15021 "en-ie":{ 15022 "name":"English (Ireland)", 15023 "nativeName":"English (Ireland)" 15024 }, 15025 "en-jm":{ 15026 "name":"English (Jamaica)", 15027 "nativeName":"English (Jamaica)" 15028 }, 15029 "en-nz":{ 15030 "name":"English (New Zealand)", 15031 "nativeName":"" 15032 }, 15033 "en-za":{ 15034 "name":"English (South Africa)", 15035 "nativeName":"English (South Africa)" 15036 }, 15037 "en-tt":{ 15038 "name":"English (Trinidad)", 15039 "nativeName":"English (Trinidad y Tobago)" 15040 }, 15041 "en-gb":{ 15042 "name":"English (United Kingdom)", 15043 "nativeName":"English (United Kingdom)" 15044 }, 15045 "en-us":{ 15046 "name":"English (United States)", 15047 "nativeName":"English (United States)" 15048 }, 15049 "es-ar":{ 15050 "name":"Spanish (Argentina)", 15051 "nativeName":"Español (Argentina)" 15052 }, 15053 "es-bo":{ 15054 "name":"Spanish (Bolivia)", 15055 "nativeName":"Español (Bolivia)" 15056 }, 15057 "es-cl":{ 15058 "name":"Spanish (Chile)", 15059 "nativeName":"Español (Chile)" 15060 }, 15061 "es-co":{ 15062 "name":"Spanish (Colombia)", 15063 "nativeName":"Español (Colombia)" 15064 }, 15065 "es-cr":{ 15066 "name":"Spanish (Costa Rica)", 15067 "nativeName":"Español (Costa Rica)" 15068 }, 15069 "es-do":{ 15070 "name":"Spanish (Dominican Republic)", 15071 "nativeName":"Español (República Dominicana)" 15072 }, 15073 "es-ec":{ 15074 "name":"Spanish (Ecuador)", 15075 "nativeName":"Español (Ecuador)" 15076 }, 15077 "es-sv":{ 15078 "name":"Spanish (El Salvador)", 15079 "nativeName":"Español (El Salvador)" 15080 }, 15081 "es-gt":{ 15082 "name":"Spanish (Guatemala)", 15083 "nativeName":"Español (Guatemala)" 15084 }, 15085 "es-hn":{ 15086 "name":"Spanish (Honduras)", 15087 "nativeName":"Español (Honduras)" 15088 }, 15089 "es-mx":{ 15090 "name":"Spanish (Mexico)", 15091 "nativeName":"Español (México)" 15092 }, 15093 "es-ni":{ 15094 "name":"Spanish (Nicaragua)", 15095 "nativeName":"Español (Nicaragua)" 15096 }, 15097 "es-pa":{ 15098 "name":"Spanish (Panama)", 15099 "nativeName":"Español (Panamá)" 15100 }, 15101 "es-py":{ 15102 "name":"Spanish (Paraguay)", 15103 "nativeName":"Español (Paraguay)" 15104 }, 15105 "es-pe":{ 15106 "name":"Spanish (Peru)", 15107 "nativeName":"Español (Perú)" 15108 }, 15109 "es-pr":{ 15110 "name":"Spanish (Puerto Rico)", 15111 "nativeName":"Español (Puerto Rico)" 15112 }, 15113 "es-uy":{ 15114 "name":"Spanish (Uruguay)", 15115 "nativeName":"Español (Uruguay)" 15116 }, 15117 "es-ve":{ 15118 "name":"Spanish (Venezuela)", 15119 "nativeName":"Español (Venezuela)" 15120 }, 15121 "fr-be":{ 15122 "name":"French (Belgium)", 15123 "nativeName":"français (Belgique)" 15124 }, 15125 "fr-ca":{ 15126 "name":"French (Canada)", 15127 "nativeName":"français (Canada)" 15128 }, 15129 "fr-lu":{ 15130 "name":"French (Luxembourg)", 15131 "nativeName":"français (Luxembourg)" 15132 }, 15133 "fr-ch":{ 15134 "name":"French (Switzerland)", 15135 "nativeName":"français (Suisse)" 15136 }, 15137 "it-ch":{ 15138 "name":"Italian (Switzerland)", 15139 "nativeName":"italiano (Svizzera)" 15140 }, 15141 "nl-be":{ 15142 "name":"Dutch (Belgium)", 15143 "nativeName":"Nederlands (België)" 15144 }, 15145 "pt-br":{ 15146 "name":"Portuguese (Brazil)", 15147 "nativeName":"Português (Brasil)" 15148 }, 15149 "sv-fi":{ 15150 "name":"Swedish (Finland)", 15151 "nativeName":"svenska (Finland)" 15152 }, 15153 "zh-hk":{ 15154 "name":"Chinese (Hong Kong)", 15155 "nativeName":"䏿(馿¸¯ç¹å«è¡æ¿å)" 15156 }, 15157 "zh-cn":{ 15158 "name":"Chinese (PRC)", 15159 "nativeName":"䏿(ä¸å人æ°å ±åå½)" 15160 }, 15161 "zh-sg":{ 15162 "name":"Chinese (Singapore)", 15163 "nativeName":"䏿(æ°å å¡)" 15164 }, 15165 "zh-tw":{ 15166 "name":"Chinese Traditional (Taiwan)", 15167 "nativeName":"䏿ï¼å°ç£ï¼" 15168 } 15169 }
15169 15170 15171 AblePlayer.prototype.getLanguageName = function (key,whichName) { 15172 15173 // return language name associated with lang code "key" 15174 // whichName is either "English" or "local" (i.e., native name) 15175 15176 var lang, code, subTag; 15177 lang = isoLangs[key.toLowerCase()]; 15178 if (lang) { 15179 if (whichName === 'local') { 15180 return lang.nativeName; 15181 } 15182 else { 15183 return lang.name; 15184 } 15185 } 15186 else if (key.includes('-')) { 15187 code = key.substring(0,2); 15188 subTag = key.substring(3); 15189 lang = isoLangs[code.toLowerCase()]; 15190 if (lang) { 15191 if (whichName === 'local') { 15192 return lang.nativeName + ' (' + subTag + ')'; 15193 } 15194 else { 15195 return lang.name + ' (' + subTag + ')'; 15196 } 15197 } 15198 } 15199 // if all else has failed, use the key as the label 15200 return key; 15201 }; 15202 15203})(jQuery); 15204(function ($) { 15205 AblePlayer.prototype.getSupportedLangs = function() { 15206 // returns an array of languages for which AblePlayer has translation tables 15207 var langs = ['ca','cs','da','de','en','es','fr','he','id','it','ja','nb','nl','pt','pt-br','sv','tr','zh-tw']; 15208 return langs; 15209 }; 15210 15211 AblePlayer.prototype.getTranslationText = function() { 15212 15213 // determine language, then get labels and prompts from corresponding translation var 15214 15215 var deferred, thisObj, supportedLangs, docLang, msg, translationFile, collapsedLang, i, 15216 similarLangFound; 15217 deferred = $.Deferred(); 15218 thisObj = this; 15219 15220 supportedLangs = this.getSupportedLangs(); // returns an array 15221 15222 if (this.lang) { // a data-lang attribute is included on the media element 15223 if ($.inArray(this.lang,supportedLangs) === -1) { 15224 // the specified language is not supported 15225 if (this.lang.indexOf('-') == 2) { 15226 // this is a localized lang attribute (e.g., fr-CA) 15227 // try the parent language, given the first two characters 15228 if ($.inArray(this.lang.substring(0,2),supportedLangs) !== -1) { 15229 // parent lang is supported. Use that. 15230 this.lang = this.lang.substring(0,2); 15231 } 15232 else { 15233 // the parent language is not supported either 15234 // unable to use the specified language 15235 this.lang = null; 15236 } 15237 } 15238 else { 15239 // this is not a localized language. 15240 // but maybe there's a similar localized language supported 15241 // that has the same parent? 15242 similarLangFound = false; 15243 i = 0; 15244 while (i < supportedLangs.length) { 15245 if (supportedLangs[i].substring(0,2) == this.lang) { 15246 this.lang = supportedLangs[i]; 15247 similarLangFound = true; 15248 } 15249 i++; 15250 } 15251 if (!similarLangFound) { 15252 // language requested via data-lang is not supported 15253 this.lang = null; 15254 } 15255 } 15256 } 15257 } 15258 15259 if (!this.lang) { 15260 // try the language of the web page, if specified 15261 if ($('body').attr('lang')) { 15262 docLang = $('body').attr('lang').toLowerCase(); 15263 } 15264 else if ($('html').attr('lang')) { 15265 docLang = $('html').attr('lang').toLowerCase(); 15266 } 15267 else { 15268 docLang = null; 15269 } 15270 if (docLang) { 15271 if ($.inArray(docLang,supportedLangs) !== -1) { 15272 // the document language is supported 15273 this.lang = docLang; 15274 } 15275 else { 15276 // the document language is not supported 15277 if (docLang.indexOf('-') == 2) { 15278 // this is a localized lang attribute (e.g., fr-CA) 15279 // try the parent language, given the first two characters 15280 if ($.inArray(docLang.substring(0,2),supportedLangs) !== -1) { 15281 // the parent language is supported. use that. 15282 this.lang = docLang.substring(0,2); 15283 } 15284 } 15285 } 15286 } 15287 } 15288 15289 if (!this.lang) { 15290 // No supported language has been specified by any means 15291 // Fallback to English 15292 this.lang = 'en'; 15293 } 15294 15295 if (!this.searchLang) { 15296 this.searchLang = this.lang; 15297 } 15298 translationFile = this.rootPath + 'translations/' + this.lang + '.js'; 15299 $.getJSON(translationFile, function(data) { 15300 // success! 15301 thisObj.tt = data; 15302 deferred.resolve(); 15303 }) 15304 .fail(function() { 15305 console.log( "Critical Error: Unable to load translation file:",translationFile); 15306 thisObj.provideFallback(); 15307 deferred.fail(); 15308 }) 15309 return deferred.promise(); 15310 }; 15311 15312 AblePlayer.prototype.getSampleDescriptionText = function() { 15313 15314 // Create an array of sample description text in all languages 15315 // This needs to be readily available for testing different voices 15316 // in the Description Preferences dialog 15317 var thisObj, supportedLangs, i, thisLang, translationFile, thisText, translation; 15318
15319 supportedLangs = this.getSupportedLangs(); 15320 15321 thisObj = this; 15322 15323 this.sampleText = []; 15324 for (i=0; i < supportedLangs.length; i++) { 15325 translationFile = this.rootPath + 'translations/' + supportedLangs[i] + '.js'; 15326 $.getJSON(translationFile, thisLang, (function(thisLang) { 15327 return function(data) { 15328 thisText = data.sampleDescriptionText; 15329 translation = {'lang':thisLang, 'text': thisText}; 15330 thisObj.sampleText.push(translation); 15331 }; 15332 }(supportedLangs[i])) // pass lang to callback function 15333 ); 15334 } 15335 }; 15336 15337})(jQuery); 15338 15339(function($) { 15340 AblePlayer.prototype.computeEndTime = function(startTime, durationTime) { 15341 var SECONDS = 0; 15342 var MINUTES = 1; 15343 var HOURS = 2; 15344 15345 var startParts = startTime 15346 .split(':') 15347 .reverse() 15348 .map(function(value) { 15349 return parseFloat(value); 15350 }); 15351 15352 var durationParts = durationTime 15353 .split(':') 15354 .reverse() 15355 .map(function(value) { 15356 return parseFloat(value); 15357 }); 15358 15359 var endTime = startParts 15360 .reduce(function(acc, val, index) { 15361 var sum = val + durationParts[index]; 15362 15363 if (index === SECONDS) { 15364 if (sum > 60) { 15365 durationParts[index + 1] += 1; 15366 sum -= 60; 15367 } 15368 15369 sum = sum.toFixed(3); 15370 } 15371 15372 if (index === MINUTES) { 15373 if (sum > 60) { 15374 durationParts[index + 1] += 1; 15375 sum -= 60; 15376 } 15377 } 15378 15379 if (sum < 10) { 15380 sum = '0' + sum; 15381 } 15382 15383 acc.push(sum); 15384 15385 return acc; 15386 }, []) 15387 .reverse() 15388 .join(':'); 15389 15390 return endTime; 15391 }; 15392 15393 AblePlayer.prototype.ttml2webvtt = function(contents) { 15394 var thisObj = this; 15395 15396 var xml = thisObj.convert.xml2json(contents, { 15397 ignoreComment: true, 15398 alwaysChildren: true, 15399 compact: true, 15400 spaces: 2 15401 }); 15402 15403 var vttHeader = 'WEBVTT\n\n\n'; 15404 var captions = JSON.parse(xml).tt.body.div.p; 15405 15406 var vttCaptions = captions.reduce(function(acc, value, index) { 15407 var text = value._text; 15408 var isArray = Array.isArray(text); 15409 var attributes = value._attributes; 15410 var endTime = thisObj.computeEndTime(attributes.begin, attributes.dur); 15411 15412 var caption = 15413 thisObj.computeEndTime(attributes.begin, '00:00:0') + 15414 ' --> ' + 15415 thisObj.computeEndTime(attributes.begin, attributes.dur) + 15416 '\n' + 15417 (isArray ? text.join('\n') : text) + 15418 '\n\n'; 15419 15420 return acc + caption; 15421 }, vttHeader); 15422 15423 return vttCaptions; 15424 }; 15425})(jQuery); 15426 15427/*! Copyright (c) 2014 - Paul Tavares - purtuga - @paul_tavares - MIT License */ 15428;(function($){ 15429 15430 /** 15431 * Delays the execution of a function until an expression returns true. 15432 * The expression is checked every 100 milliseconds for as many tries 15433 * as defined in in the attempts option 15434 * 15435 * @param {Object} options 15436 * @param {Function} options.when 15437 * Function to execute on every interval. 15438 * Must return true (boolean) in order for 15439 * options.do to be executed. 15440 * @param {Function} [options.exec] 15441 * Function to be executed once options.when() 15442 * returns true. 15443 * @param {Interger} [options.interval=100] 15444 * How long to wait in-between tries. 15445 * @param {Interger} [options.attempts=100] 15446 * How many tries to use before its considered 15447 * a failure. 15448 * @param {Interger} [options.delayed=0] 15449 * Number of miliseconds to wait before execution 15450 is started. Default is imediately. 15451 * 15452 * @return {jQuery.Promise} 15453 * 15454 * @example 15455 * 15456 * $.doWhen({ 15457 * when: function(){ 15458 * return false; 15459 * }, 15460 * exec: function(){ 15461 * alert("never called given false response on when param!"); 15462 * } 15463 * }) 15464 * .fail(function(){ 15465 * alert('ALERT: FAILED CONDITION'); 15466 * }) 15467 * .then(function(){ 15468 * alert("resolved."); 15469 * }); 15470 * 15471 */ 15472 $.doWhen = function(options) { 15473 15474 return $.Deferred(function(dfd){ 15475 15476 var opt = $.extend({}, { 15477 when: null, 15478 exec: function(){}, 15479 interval: 100, 15480 attempts: 100, 15481 delayed: 0 15482 }, 15483 options, 15484 { 15485 checkId: null 15486 }), 15487 startChecking = function(){ 15488 15489 // Check condition now and if true, then resolve object 15490 if (opt.when() === true) { 15491 15492 opt.exec.call(dfd.promise()); 15493 dfd.resolve(); 15494 return; 15495 15496 } 15497 15498 // apply minimal UI and hide the overlay 15499 opt.checkId = setInterval(function(){ 15500 15501 if (opt.attempts === 0) { 15502 15503 clearInterval(opt.checkId); 15504 dfd.reject(); 15505 15506 } else { 15507 15508 --opt.attempts; 15509 15510 if (opt.when() === true) { 15511 15512 opt.attempts = 0; 15513 clearInterval(opt.checkId); 15514 opt.exec.call(dfd.promise()); 15515 dfd.resolve(); 15516 15517 } 15518 15519 } 15520 15521 }, opt.interval); 15522 15523 }; 15524 15525 if (opt.delayed > 0) { 15526 15527 setTimeout(startChecking, Number(opt.delayed)); 15528 15529 } else { 15530 15531 startChecking(); 15532 15533 } 15534 15535 }).promise(); 15536 15537 }; 15538 15539})(jQuery); 15540/* Video Transcript Sorter (VTS) 15541 * Used to synchronize time stamps from WebVTT resources 15542 * so they appear in the proper sequence within an auto-generated interactive transcript 15543*/ 15544 15545(function ($) {
15546 AblePlayer.prototype.injectVTS = function() { 15547 15548 // To add a transcript sorter to a web page: 15549 // Add <div id="able-vts"></div> to the web page 15550 15551 // Define all variables 15552 var thisObj, tracks, $heading; 15553 var $instructions, $p1, $p2, $ul, $li1, $li2, $li3; 15554 var $fieldset, $legend, i, $radioDiv, radioId, $label, $radio; 15555 var $saveButton, $savedTable; 15556 15557 thisObj = this; 15558 15559 if ($('#able-vts').length) { 15560 // Page includes a container for a VTS instance 15561 15562 // Are they qualifying tracks? 15563 if (this.vtsTracks.length) { 15564 // Yes - there are! 15565 15566 // Build an array of unique languages 15567 this.langs = []; 15568 this.getAllLangs(this.vtsTracks); 15569 15570 // Set the default VTS language 15571 this.vtsLang = this.lang; 15572 15573 // Inject a heading 15574 $heading = $('<h2>').text('Video Transcript Sorter'); // TODO: Localize; intelligently assign proper heading level 15575 $('#able-vts').append($heading); 15576 15577 // Inject an empty div for writing messages
vendor: 34,209 bytes, lines 15578-16599
15578 this.$vtsAlert = $('<div>',{ 15579 'id': 'able-vts-alert', 15580 'aria-live': 'polite', 15581 'aria-atomic': 'true' 15582 }) 15583 $('#able-vts').append(this.$vtsAlert); 15584 15585 // Inject instructions (TODO: Localize) 15586 $instructions = $('<div>',{ 15587 'id': 'able-vts-instructions' 15588 }); 15589 $p1 = $('<p>').text('Use the Video Transcript Sorter to perform any of the following tasks:'); 15590 $ul = $('<ul>'); 15591 $li1 = $('<li>').text('Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player\'s auto-generated transcript.'); 15592 $li2 = $('<li>').text('Modify content or start/end times (all are directly editable within the table).'); 15593 $li3 = $('<li>').text('Insert new content, such as chapters or descriptions.'); 15594 $p2 = $('<p>').text('When finished editing, click the "Save Changes" button. This will auto-generate new content for all relevant timed text files (chapters, descriptions, captions, and/or subtitles), which can be copied and pasted into separate WebVTT files for use by Able Player.'); 15595 $ul.append($li1,$li2,$li3); 15596 $instructions.append($p1,$ul,$p2); 15597 $('#able-vts').append($instructions); 15598 15599 // Inject a fieldset with radio buttons for each language 15600 $fieldset = $('<fieldset>'); 15601 $legend = $('<legend>').text('Select a language'); // TODO: Localize this 15602 $fieldset.append($legend) 15603 for (i in this.langs) { 15604 radioId = 'vts-lang-radio-' + this.langs[i]; 15605 $radioDiv = $('<div>',{ 15606 // uncomment the following if label is native name 15607 // 'lang': this.langs[i] 15608 }); 15609 $radio = $('<input>', { 15610 'type': 'radio', 15611 'name': 'vts-lang', 15612 'id': radioId, 15613 'value': this.langs[i] 15614 }).on('click',function() { 15615 thisObj.vtsLang = $(this).val(); 15616 thisObj.showVtsAlert('Loading ' + thisObj.getLanguageName(thisObj.vtsLang) + ' tracks'); 15617 thisObj.injectVtsTable('update',thisObj.vtsLang); 15618 }); 15619 if (this.langs[i] == this.lang) { 15620 // this is the default language. 15621 $radio.prop('checked',true); 15622 } 15623 $label = $('<label>', { 15624 'for': radioId 15625 // Two options for label: 15626 // getLanguageName() - with second parameter "local" would return native name, otherwise returns English; 15627 // TODO: if using this be sure to add lang attr to <div> (see above) 15628 }).text(this.getLanguageName(this.langs[i])); 15629 $radioDiv.append($radio,$label); 15630 $fieldset.append($radioDiv); 15631 } 15632 $('#able-vts').append($fieldset); 15633 15634 // Inject a 'Save Changes' button 15635 $saveButton = $('<button>',{ 15636 'type': 'button', 15637 'id': 'able-vts-save', 15638 'value': 'save' 15639 }).text('Save Changes'); // TODO: Localize this 15640 $('#able-vts').append($saveButton); 15641 15642 // Inject a table with one row for each cue in the default language 15643 this.injectVtsTable('add',this.vtsLang); 15644 15645 // TODO: Add drag/drop functionality for mousers 15646 15647 // Add event listeners for contenteditable cells 15648 var kindOptions, beforeEditing, editedCell, editedContent, i, closestKind; 15649 kindOptions = ['captions','chapters','descriptions','subtitles']; 15650 $('td[contenteditable="true"]').on('focus',function() { 15651 beforeEditing = $(this).text(); 15652 }).on('blur',function() { 15653 if (beforeEditing != $(this).text()) { 15654 editedCell = $(this).index(); 15655 editedContent = $(this).text(); 15656 if (editedCell === 1) { 15657 // do some simple spelling auto-correct 15658 if ($.inArray(editedContent,kindOptions) === -1) { 15659 // whatever user typed is not a valid kind 15660 // assume they correctly typed the first character 15661 if (editedContent.substring(0,1) === 's') { 15662 $(this).text('subtitles'); 15663 } 15664 else if (editedContent.substring(0,1) === 'd') { 15665 $(this).text('descriptions'); 15666 } 15667 else if (editedContent.substring(0,2) === 'ch') { 15668 $(this).text('chapters'); 15669 } 15670 else { 15671 // whatever else they types, assume 'captions' 15672 $(this).text('captions'); 15673 } 15674 } 15675 } 15676 else if (editedCell === 2 || editedCell === 3) { 15677 // start or end time 15678 // ensure proper formatting (with 3 decimal places) 15679 $(this).text(thisObj.formatTimestamp(editedContent)); 15680 } 15681 } 15682 }).on('keydown',function(e) { 15683 // don't allow keystrokes to trigger Able Player (or other) functions 15684 // while user is editing 15685 e.stopPropagation(); 15686 }); 15687 15688 // handle click on the Save button 15689 15690 // handle click on the Save button 15691 $('#able-vts-save').on('click',function(e) { 15692 e.stopPropagation(); 15693 if ($(this).attr('value') == 'save') { 15694 // replace table with WebVTT output in textarea fields (for copying/pasting) 15695 $(this).attr('value','cancel').text('Return to Editor'); // TODO: Localize this 15696 $savedTable = $('#able-vts table'); 15697 $('#able-vts-instructions').hide(); 15698 $('#able-vts > fieldset').hide(); 15699 $('#able-vts table').remove(); 15700 $('#able-vts-icon-credit').remove(); 15701 thisObj.parseVtsOutput($savedTable); 15702 } 15703 else { 15704 // cancel saving, and restore the table using edited content 15705 $(this).attr('value','save').text('Save Changes'); // TODO: Localize this 15706 $('#able-vts-output').remove(); 15707 $('#able-vts-instructions').show(); 15708 $('#able-vts > fieldset').show(); 15709 $('#able-vts').append($savedTable); 15710 $('#able-vts').append(thisObj.getIconCredit()); 15711 thisObj.showVtsAlert('Cancelling saving. Any edits you made have been restored in the VTS table.'); // TODO: Localize this 15712 } 15713 }); 15714 } 15715 } 15716 }; 15717 15718 AblePlayer.prototype.setupVtsTracks = function(kind, lang, trackDesc, label, src, contents) { 15719 15720 // TODO: Add support for trackDesc 15721 // (to destinguish between tracks for the decribed vs non-described versions) 15722 15723 // Called from tracks.js 15724 var srcFile, vtsCues; 15725 15726 srcFile = this.getFilenameFromPath(src); 15727 vtsCues = this.parseVtsTracks(contents); 15728 15729 this.vtsTracks.push({ 15730 'kind': kind, 15731 'language': lang, 15732 'label': label, 15733 'srcFile': srcFile, 15734 'cues': vtsCues 15735 }); 15736 }; 15737 15738 AblePlayer.prototype.getFilenameFromPath = function(path) { 15739 15740 var lastSlash; 15741 15742 lastSlash = path.lastIndexOf('/'); 15743 if (lastSlash === -1) { 15744 // there are no slashes in path. 15745 return path; 15746 } 15747 else { 15748 return path.substring(lastSlash+1); 15749 } 15750 }; 15751 15752 AblePlayer.prototype.getFilenameFromTracks = function(kind,lang) { 15753 15754 for (var i=0; i<this.vtsTracks.length; i++) { 15755 if (this.vtsTracks[i].kind === kind && this.vtsTracks[i].language === lang) { 15756 // this is a matching track 15757 // srcFile has already been converted to filename from path before saving to vtsTracks 15758 return this.vtsTracks[i].srcFile; 15759 } 15760 } 15761 // no matching track found 15762 return false; 15763 }; 15764 15765 AblePlayer.prototype.parseVtsTracks = function(contents) { 15766 15767 var rows, timeParts, cues, i, j, thisRow, nextRow, content, blankRow; 15768 rows = contents.split("\n"); 15769 cues = []; 15770 i = 0; 15771 while (i < rows.length) { 15772 thisRow = rows[i]; 15773 if (thisRow.indexOf(' --> ') !== -1) { 15774 // this is probably a time row 15775 timeParts = thisRow.trim().split(' '); 15776 if (this.isValidTimestamp(timeParts[0]) && this.isValidTimestamp(timeParts[2])) { 15777 // both timestamps are valid. This is definitely a time row 15778 content = ''; 15779 j = i+1; 15780 blankRow = false; 15781 while (j < rows.length && !blankRow) { 15782 nextRow = rows[j].trim(); 15783 if (nextRow.length > 0) { 15784 if (content.length > 0) { 15785 // add back the EOL between rows of content 15786 content += "\n" + nextRow; 15787 } 15788 else { 15789 // this is the first row of content. No need for an EOL 15790 content += nextRow; 15791 } 15792 } 15793 else { 15794 blankRow = true; 15795 } 15796 j++; 15797 } 15798 cues.push({ 15799 'start': timeParts[0], 15800 'end': timeParts[2], 15801 'content': content 15802 }); 15803 i = j; //skip ahead 15804 } 15805 } 15806 else { 15807 i++; 15808 } 15809 } 15810 return cues; 15811 }; 15812 15813 AblePlayer.prototype.isValidTimestamp = function(timestamp) { 15814 15815 // return true if timestamp contains only numbers or expected punctuation 15816 if (/^[0-9:,.]*$/.test(timestamp)) { 15817 return true; 15818 } 15819 else { 15820 return false; 15821 } 15822 }; 15823 15824 AblePlayer.prototype.formatTimestamp = function(timestamp) { 15825 15826 // timestamp is a string in the form "HH:MM:SS.xxx" 15827 // Take some simple steps to ensure edited timestamp values still adhere to expected format 15828 15829 var firstPart, lastPart; 15830 15831 firstPart = timestamp.substring(0,timestamp.lastIndexOf('.')+1); 15832 lastPart = timestamp.substring(timestamp.lastIndexOf('.')+1); 15833 15834 // TODO: Be sure each component within firstPart has only exactly two digits 15835 // Probably can't justify doing this automatically 15836 // If users enters '5' for minutes, that could be either '05' or '50' 15837 // This should trigger an error and prompt the user to correct the value before proceeding 15838 15839 // Be sure lastPart has exactly three digits 15840 if (lastPart.length > 3) { 15841 // chop off any extra digits 15842 lastPart = lastPart.substring(0,3); 15843 } 15844 else if (lastPart.length < 3) { 15845 // add trailing zeros 15846 while (lastPart.length < 3) { 15847 lastPart += '0'; 15848 } 15849 } 15850 return firstPart + lastPart; 15851 }; 15852 15853 15854 AblePlayer.prototype.injectVtsTable = function(action,lang) { 15855 15856 // action is either 'add' (for a new table) or 'update' (if user has selected a new lang) 15857 15858 var $table, headers, i, $tr, $th, $td, rows, rowNum, rowId; 15859 15860 if (action === 'update') { 15861 // remove existing table 15862 $('#able-vts table').remove(); 15863 $('#able-vts-icon-credit').remove(); 15864 } 15865 15866 $table = $('<table>',{ 15867 'lang': lang 15868 }); 15869 $tr = $('<tr>',{ 15870 'lang': 'en' // TEMP, until header row is localized 15871 }); 15872 headers = ['Row #','Kind','Start','End','Content','Actions']; // TODO: Localize this 15873 for (i=0; i < headers.length; i++) { 15874 $th = $('<th>', { 15875 'scope': 'col' 15876 }).text(headers[i]); 15877 if (headers[i] === 'Actions') { 15878 $th.addClass('actions'); 15879 } 15880 $tr.append($th); 15881 } 15882 $table.append($tr); 15883 15884 // Get all rows (sorted by start time), and inject them into table 15885 rows = this.getAllRows(lang); 15886 for (i=0; i < rows.length; i++) { 15887 rowNum = i + 1; 15888 rowId = 'able-vts-row-' + rowNum; 15889 $tr = $('<tr>',{ 15890 'id': rowId, 15891 'class': 'kind-' + rows[i].kind 15892 }); 15893 // Row # 15894 $td = $('<td>').text(rowNum); 15895 $tr.append($td); 15896 15897 // Kind 15898 $td = $('<td>',{ 15899 'contenteditable': 'true' 15900 }).text(rows[i].kind); 15901 $tr.append($td); 15902 15903 // Start 15904 $td = $('<td>',{ 15905 'contenteditable': 'true' 15906 }).text(rows[i].start); 15907 $tr.append($td); 15908 15909 // End 15910 $td = $('<td>',{ 15911 'contenteditable': 'true' 15912 }).text(rows[i].end); 15913 $tr.append($td); 15914 15915 // Content 15916 $td = $('<td>',{ 15917 'contenteditable': 'true' 15918 }).text(rows[i].content); // TODO: Preserve tags 15919 $tr.append($td); 15920 15921 // Actions 15922 $td = this.addVtsActionButtons(rowNum,rows.length); 15923 $tr.append($td); 15924 15925 $table.append($tr); 15926 } 15927 $('#able-vts').append($table); 15928 15929 // Add credit for action button SVG icons 15930 $('#able-vts').append(this.getIconCredit()); 15931 15932 }; 15933 15934 AblePlayer.prototype.addVtsActionButtons = function(rowNum,numRows) { 15935 15936 // rowNum is the number of the current table row (starting with 1) 15937 // numRows is the total number of rows (excluding the header row) 15938 // TODO: Position buttons so they're vertically aligned, even if missing an Up or Down button 15939 var thisObj, $td, buttons, i, button, $button, $svg, $g, pathString, pathString2, $path, $path2; 15940 thisObj = this; 15941 $td = $('<td>'); 15942 buttons = ['up','down','insert','delete']; 15943 15944 for (i=0; i < buttons.length; i++) { 15945 button = buttons[i]; 15946 if (button === 'up') { 15947 if (rowNum > 1) { 15948 $button = $('<button>',{ 15949 'id': 'able-vts-button-up-' + rowNum, 15950 'title': 'Move up', 15951 'aria-label': 'Move Row ' + rowNum + ' up' 15952 }).on('click', function(el) { 15953 thisObj.onClickVtsActionButton(el.currentTarget); 15954 }); 15955 $svg = $('<svg>',{ 15956 'focusable': 'false', 15957 'aria-hidden': 'true', 15958 'x': '0px', 15959 'y': '0px', 15960 'width': '254.296px', 15961 'height': '254.296px', 15962 'viewBox': '0 0 254.296 254.296', 15963 'style': 'enable-background:new 0 0 254.296 254.296' 15964 }); 15965 pathString = 'M249.628,176.101L138.421,52.88c-6.198-6.929-16.241-6.929-22.407,0l-0.381,0.636L4.648,176.101' 15966 + 'c-6.198,6.897-6.198,18.052,0,24.981l0.191,0.159c2.892,3.305,6.865,5.371,11.346,5.371h221.937c4.577,0,8.613-2.161,11.41-5.594' 15967 + 'l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z'; 15968 $path = $('<path>',{ 15969 'd': pathString 15970 }); 15971 $g = $('<g>').append($path); 15972 $svg.append($g); 15973 $button.append($svg); 15974 // Refresh button in the DOM in order for browser to process & display the SVG 15975 $button.html($button.html()); 15976 $td.append($button); 15977 } 15978 } 15979 else if (button === 'down') { 15980 if (rowNum < numRows) { 15981 $button = $('<button>',{ 15982 'id': 'able-vts-button-down-' + rowNum, 15983 'title': 'Move down', 15984 'aria-label': 'Move Row ' + rowNum + ' down' 15985 }).on('click', function(el) { 15986 thisObj.onClickVtsActionButton(el.currentTarget); 15987 }); 15988 $svg = $('<svg>',{ 15989 'focusable': 'false', 15990 'aria-hidden': 'true', 15991 'x': '0px', 15992 'y': '0px', 15993 'width': '292.362px', 15994 'height': '292.362px', 15995 'viewBox': '0 0 292.362 292.362', 15996 'style': 'enable-background:new 0 0 292.362 292.362' 15997 }); 15998 pathString = 'M286.935,69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952,0-9.233,1.807-12.85,5.424' 15999 + 'C1.807,72.998,0,77.279,0,82.228c0,4.948,1.807,9.229,5.424,12.847l127.907,127.907c3.621,3.617,7.902,5.428,12.85,5.428' 16000 + 's9.233-1.811,12.847-5.428L286.935,95.074c3.613-3.617,5.427-7.898,5.427-12.847C292.362,77.279,290.548,72.998,286.935,69.377z'; 16001 $path = $('<path>',{ 16002 'd': pathString 16003 }); 16004 $g = $('<g>').append($path); 16005 $svg.append($g); 16006 $button.append($svg); 16007 // Refresh button in the DOM in order for browser to process & display the SVG 16008 $button.html($button.html()); 16009 $td.append($button); 16010 } 16011 } 16012 else if (button === 'insert') { 16013 // Add Insert button to all rows 16014 $button = $('<button>',{ 16015 'id': 'able-vts-button-insert-' + rowNum, 16016 'title': 'Insert row below', 16017 'aria-label': 'Insert row before Row ' + rowNum 16018 }).on('click', function(el) { 16019 thisObj.onClickVtsActionButton(el.currentTarget); 16020 }); 16021 $svg = $('<svg>',{ 16022 'focusable': 'false', 16023 'aria-hidden': 'true', 16024 'x': '0px', 16025 'y': '0px', 16026 'width': '401.994px', 16027 'height': '401.994px', 16028 'viewBox': '0 0 401.994 401.994', 16029 'style': 'enable-background:new 0 0 401.994 401.994' 16030 }); 16031 pathString = 'M394,154.175c-5.331-5.33-11.806-7.994-19.417-7.994H255.811V27.406c0-7.611-2.666-14.084-7.994-19.414' 16032 + 'C242.488,2.666,236.02,0,228.398,0h-54.812c-7.612,0-14.084,2.663-19.414,7.993c-5.33,5.33-7.994,11.803-7.994,19.414v118.775' 16033 + 'H27.407c-7.611,0-14.084,2.664-19.414,7.994S0,165.973,0,173.589v54.819c0,7.618,2.662,14.086,7.992,19.411' 16034 + 'c5.33,5.332,11.803,7.994,19.414,7.994h118.771V374.59c0,7.611,2.664,14.089,7.994,19.417c5.33,5.325,11.802,7.987,19.414,7.987' 16035 + 'h54.816c7.617,0,14.086-2.662,19.417-7.987c5.332-5.331,7.994-11.806,7.994-19.417V255.813h118.77' 16036 + 'c7.618,0,14.089-2.662,19.417-7.994c5.329-5.325,7.994-11.793,7.994-19.411v-54.819C401.991,165.973,399.332,159.502,394,154.175z'; 16037 $path = $('<path>',{ 16038 'd': pathString 16039 }); 16040 $g = $('<g>').append($path); 16041 $svg.append($g); 16042 $button.append($svg); 16043 // Refresh button in the DOM in order for browser to process & display the SVG 16044 $button.html($button.html()); 16045 $td.append($button); 16046 } 16047 else if (button === 'delete') { 16048 // Add Delete button to all rows 16049 $button = $('<button>',{ 16050 'id': 'able-vts-button-delete-' + rowNum, 16051 'title': 'Delete row ', 16052 'aria-label': 'Delete Row ' + rowNum 16053 }).on('click', function(el) { 16054 thisObj.onClickVtsActionButton(el.currentTarget); 16055 }); 16056 $svg = $('<svg>',{ 16057 'focusable': 'false', 16058 'aria-hidden': 'true', 16059 'x': '0px', 16060 'y': '0px', 16061 'width': '508.52px', 16062 'height': '508.52px', 16063 'viewBox': '0 0 508.52 508.52', 16064 'style': 'enable-background:new 0 0 508.52 508.52' 16065 }); 16066 pathString = 'M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347' 16067 + 'c-17.544,0-31.782,14.239-31.782,31.782h-63.565c-17.544,0-31.782,14.239-31.782,31.782h349.607' 16068 + 'C429.063,46.021,414.825,31.782,397.281,31.782z'; 16069 $path = $('<path>',{ 16070 'd': pathString 16071 }); 16072 pathString2 = 'M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042' 16073 + 'c17.544,0,31.782-14.239,31.782-31.782V95.347H79.456V476.737z M333.716,174.804c0-8.772,7.151-15.891,15.891-15.891' 16074 + 'c8.74,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.151,15.891-15.891,15.891c-8.74,0-15.891-7.151-15.891-15.891V174.804z' 16075 + 'M238.369,174.804c0-8.772,7.119-15.891,15.891-15.891c8.74,0,15.891,7.119,15.891,15.891v254.26' 16076 + 'c0,8.74-7.151,15.891-15.891,15.891c-8.772,0-15.891-7.151-15.891-15.891V174.804z M143.021,174.804' 16077 + 'c0-8.772,7.119-15.891,15.891-15.891c8.772,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.119,15.891-15.891,15.891' 16078 + 'c-8.772,0-15.891-7.151-15.891-15.891V174.804z'; 16079 $path2 = $('<path>',{ 16080 'd': pathString2 16081 }); 16082 16083 $g = $('<g>').append($path,$path2); 16084 $svg.append($g); 16085 $button.append($svg); 16086 // Refresh button in the DOM in order for browser to process & display the SVG 16087 $button.html($button.html()); 16088 $td.append($button); 16089 } 16090 } 16091 return $td; 16092 }; 16093 16094 AblePlayer.prototype.updateVtsActionButtons = function($buttons,nextRowNum) { 16095 16096 // TODO: Add some filters to this function to add or delete 'Up' and 'Down' buttons 16097 // if row is moved to/from the first/last rows 16098 var i, $thisButton, id, label, newId, newLabel; 16099 for (i=0; i < $buttons.length; i++) { 16100 $thisButton = $buttons.eq(i); 16101 id = $thisButton.attr('id'); 16102 label = $thisButton.attr('aria-label'); 16103 // replace the integer (id) within each of the above strings 16104 newId = id.replace(/[0-9]+/g, nextRowNum); 16105 newLabel = label.replace(/[0-9]+/g, nextRowNum); 16106 $thisButton.attr('id',newId); 16107 $thisButton.attr('aria-label',newLabel); 16108 } 16109 } 16110 16111 AblePlayer.prototype.getIconCredit = function() { 16112 16113 var credit; 16114 credit = '<div id="able-vts-icon-credit">' 16115 + 'Action buttons made by <a href="https://www.flaticon.com/authors/elegant-themes">Elegant Themes</a> ' 16116 + 'from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> ' 16117 + 'are licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" ' 16118 + 'target="_blank">CC 3.0 BY</a>' 16119 + '</div>'; 16120 return credit; 16121 }; 16122 16123 AblePlayer.prototype.getAllLangs = function(tracks) { 16124 16125 // update this.langs with any unique languages found in tracks 16126 var i; 16127 for (i in tracks) { 16128 if (tracks[i].hasOwnProperty('language')) { 16129 if ($.inArray(tracks[i].language,this.langs) === -1) { 16130 // this language is not already in the langs array. Add it. 16131 this.langs[this.langs.length] = tracks[i].language; 16132 } 16133 } 16134 } 16135 }; 16136 16137 AblePlayer.prototype.getAllRows = function(lang) { 16138 16139 // returns an array of data to be displayed in VTS table 16140 // includes all cues for tracks of any type with matching lang 16141 // cues are sorted by start time 16142 var i, track, c, cues; 16143 cues = []; 16144 for (i=0; i < this.vtsTracks.length; i++) { 16145 track = this.vtsTracks[i]; 16146 if (track.language == lang) { 16147 // this track matches the language. Add its cues to array 16148 for (c in track.cues) { 16149 cues.push({ 16150 'kind': track.kind, 16151 'lang': lang, 16152 'id': track.cues[c].id, 16153 'start': track.cues[c].start, 16154 'end': track.cues[c].end, 16155 'content': track.cues[c].content 16156 }); 16157 } 16158 } 16159 } 16160 // Now sort cues by start time 16161 cues.sort(function(a,b) { 16162 return a.start > b.start ? 1 : -1; 16163 }); 16164 return cues; 16165 }; 16166 16167 16168 AblePlayer.prototype.onClickVtsActionButton = function(el) { 16169 16170 // handle click on up, down, insert, or delete button 16171 var idParts, action, rowNum; 16172 idParts = $(el).attr('id').split('-'); 16173 action = idParts[3]; 16174 rowNum = idParts[4]; 16175 if (action == 'up') { 16176 // move the row up 16177 this.moveRow(rowNum,'up'); 16178 } 16179 else if (action == 'down') { 16180 // move the row down 16181 this.moveRow(rowNum,'down'); 16182 } 16183 else if (action == 'insert') { 16184 // insert a row below 16185 this.insertRow(rowNum); 16186 } 16187 else if (action == 'delete') { 16188 // delete the row 16189 this.deleteRow(rowNum); 16190 } 16191 }; 16192 16193 AblePlayer.prototype.insertRow = function(rowNum) { 16194 16195 // Insert empty row below rowNum 16196 var $table, $rows, numRows, newRowNum, newRowId, newTimes, $tr, $td; 16197 var $select, options, i, $option, newKind, newClass, $parentRow; 16198 var i, nextRowNum, $buttons; 16199 16200 $table = $('#able-vts table'); 16201 $rows = $table.find('tr'); 16202 16203 numRows = $rows.length - 1; // exclude header row 16204 16205 newRowNum = parseInt(rowNum) + 1; 16206 newRowId = 'able-vts-row-' + newRowNum; 16207 16208 // Create an empty row 16209 $tr = $('<tr>',{ 16210 'id': newRowId 16211 }); 16212 16213 // Row # 16214 $td = $('<td>').text(newRowNum); 16215 $tr.append($td); 16216 16217 // Kind (add a select field for chosing a kind) 16218 newKind = null; 16219 $select = $('<select>',{ 16220 'id': 'able-vts-kind-' + newRowNum, 16221 'aria-label': 'What kind of track is this?', 16222 'placeholder': 'Select a kind' 16223 }).on('change',function() { 16224 newKind = $(this).val(); 16225 newClass = 'kind-' + newKind; 16226 $parentRow = $(this).closest('tr'); 16227 // replace the select field with the chosen value as text 16228 $(this).parent().text(newKind); 16229 // add a class to the parent row 16230 $parentRow.addClass(newClass); 16231 }); 16232 options = ['','captions','chapters','descriptions','subtitles']; 16233 for (i=0; i<options.length; i++) { 16234 $option = $('<option>',{ 16235 'value': options[i] 16236 }).text(options[i]); 16237 $select.append($option); 16238 } 16239 $td = $('<td>').append($select); 16240 $tr.append($td); 16241 16242 // Start 16243 $td = $('<td>',{ 16244 'contenteditable': 'true' 16245 }); // TODO; Intelligently assign a new start time (see getAdjustedTimes()) 16246 $tr.append($td); 16247 16248 // End 16249 $td = $('<td>',{ 16250 'contenteditable': 'true' 16251 }); // TODO; Intelligently assign a new end time (see getAdjustedTimes()) 16252 $tr.append($td); 16253 16254 // Content 16255 $td = $('<td>',{ 16256 'contenteditable': 'true' 16257 }); 16258 $tr.append($td); 16259 16260 // Actions 16261 $td = this.addVtsActionButtons(newRowNum,numRows); 16262 $tr.append($td); 16263 16264 // Now insert the new row 16265 $table.find('tr').eq(rowNum).after($tr); 16266 16267 // Update row.id, Row # cell, & action items for all rows after the inserted one 16268 for (i=newRowNum; i <= numRows; i++) { 16269 nextRowNum = i + 1; 16270 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 16271 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 16272 $buttons = $rows.eq(i).find('button'); 16273 this.updateVtsActionButtons($buttons,nextRowNum); 16274 } 16275 16276 // Auto-adjust times 16277 this.adjustTimes(newRowNum); 16278 16279 // Announce the insertion 16280 this.showVtsAlert('A new row ' + newRowNum + ' has been inserted'); // TODO: Localize this 16281 16282 // Place focus in new select field 16283 $select.focus(); 16284 16285 }; 16286 16287 AblePlayer.prototype.deleteRow = function(rowNum) { 16288 16289 var $table, $rows, numRows, i, nextRowNum, $buttons; 16290 16291 $table = $('#able-vts table'); 16292 $table[0].deleteRow(rowNum); 16293 $rows = $table.find('tr'); // this does not include the deleted row 16294 numRows = $rows.length - 1; // exclude header row 16295 16296 // Update row.id, Row # cell, & action buttons for all rows after the deleted one 16297 for (i=rowNum; i <= numRows; i++) { 16298 nextRowNum = i; 16299 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 16300 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 16301 $buttons = $rows.eq(i).find('button'); 16302 this.updateVtsActionButtons($buttons,nextRowNum); 16303 } 16304 16305 // Announce the deletion 16306 this.showVtsAlert('Row ' + rowNum + ' has been deleted'); // TODO: Localize this 16307 16308 }; 16309 16310 AblePlayer.prototype.moveRow = function(rowNum,direction) { 16311 16312 // swap two rows 16313 var $rows, $thisRow, otherRowNum, $otherRow, newTimes, msg; 16314 16315 $rows = $('#able-vts table').find('tr'); 16316 $thisRow = $('#able-vts table').find('tr').eq(rowNum); 16317 if (direction == 'up') { 16318 otherRowNum = parseInt(rowNum) - 1; 16319 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 16320 $otherRow.before($thisRow); 16321 } 16322 else if (direction == 'down') { 16323 otherRowNum = parseInt(rowNum) + 1; 16324 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 16325 $otherRow.after($thisRow); 16326 } 16327 // Update row.id, Row # cell, & action buttons for the two swapped rows 16328 $thisRow.attr('id','able-vts-row-' + otherRowNum); 16329 $thisRow.find('td').eq(0).text(otherRowNum); 16330 this.updateVtsActionButtons($thisRow.find('button'),otherRowNum); 16331 $otherRow.attr('id','able-vts-row-' + rowNum); 16332 $otherRow.find('td').eq(0).text(rowNum); 16333 this.updateVtsActionButtons($otherRow.find('button'),rowNum); 16334 16335 // auto-adjust times 16336 this.adjustTimes(otherRowNum); 16337 16338 // Announce the move (TODO: Localize this) 16339 msg = 'Row ' + rowNum + ' has been moved ' + direction; 16340 msg += ' and is now Row ' + otherRowNum; 16341 this.showVtsAlert(msg); 16342 }; 16343 16344 AblePlayer.prototype.adjustTimes = function(rowNum) { 16345 16346 // Adjusts start and end times of the current, previous, and next rows in VTS table 16347 // after a move or insert 16348 // NOTE: Fully automating this process would be extraordinarily complicated 16349 // The goal here is simply to make subtle tweaks to ensure rows appear 16350 // in the new order within the Able Player transcript 16351 // Additional tweaking will likely be required by the user 16352 16353 // HISTORY: Originally set minDuration to 2 seconds for captions and .500 for descriptions 16354 // However, this can results in significant changes to existing caption timing, 16355 // with not-so-positive results. 16356 // As of 3.1.15, setting minDuration to .001 for all track kinds 16357 // Users will have to make further adjustments manually if needed 16358 16359 // TODO: Add WebVTT validation on save, since tweaking times is risky 16360 16361 var minDuration, $rows, prevRowNum, nextRowNum, $row, $prevRow, $nextRow, 16362 kind, prevKind, nextKind, 16363 start, prevStart, nextStart, 16364 end, prevEnd, nextEnd; 16365 16366 // Define minimum duration (in seconds) for each kind of track 16367 minDuration = []; 16368 minDuration['captions'] = .001; 16369 minDuration['descriptions'] = .001; 16370 minDuration['chapters'] = .001; 16371 16372 // refresh rows object 16373 $rows = $('#able-vts table').find('tr'); 16374 16375 // Get kind, start, and end from current row 16376 $row = $rows.eq(rowNum); 16377 if ($row.is('[class^="kind-"]')) { 16378 // row has a class that starts with "kind-" 16379 // Extract kind from the class name 16380 kind = this.getKindFromClass($row.attr('class')); 16381 } 16382 else { 16383 // Kind has not been assigned (e.g., newly inserted row) 16384 // Set as captions row by default 16385 kind = 'captions'; 16386 } 16387 start = this.getSecondsFromColonTime($row.find('td').eq(2).text()); 16388 end = this.getSecondsFromColonTime($row.find('td').eq(3).text()); 16389 16390 // Get kind, start, and end from previous row 16391 if (rowNum > 1) { 16392 // this is not the first row. Include the previous row 16393 prevRowNum = rowNum - 1; 16394 $prevRow = $rows.eq(prevRowNum); 16395 if ($prevRow.is('[class^="kind-"]')) { 16396 // row has a class that starts with "kind-" 16397 // Extract kind from the class name 16398 prevKind = this.getKindFromClass($prevRow.attr('class')); 16399 } 16400 else { 16401 // Kind has not been assigned (e.g., newly inserted row) 16402 prevKind = null; 16403 } 16404 prevStart = this.getSecondsFromColonTime($prevRow.find('td').eq(2).text()); 16405 prevEnd = this.getSecondsFromColonTime($prevRow.find('td').eq(3).text()); 16406 } 16407 else { 16408 // this is the first row 16409 prevRowNum = null; 16410 $prevRow = null; 16411 prevKind = null; 16412 prevStart = null; 16413 prevEnd = null; 16414 } 16415 16416 // Get kind, start, and end from next row 16417 if (rowNum < ($rows.length - 1)) { 16418 // this is not the last row. Include the next row 16419 nextRowNum = rowNum + 1; 16420 $nextRow = $rows.eq(nextRowNum); 16421 if ($nextRow.is('[class^="kind-"]')) { 16422 // row has a class that starts with "kind-" 16423 // Extract kind from the class name 16424 nextKind = this.getKindFromClass($nextRow.attr('class')); 16425 } 16426 else { 16427 // Kind has not been assigned (e.g., newly inserted row) 16428 nextKind = null; 16429 } 16430 nextStart = this.getSecondsFromColonTime($nextRow.find('td').eq(2).text()); 16431 nextEnd = this.getSecondsFromColonTime($nextRow.find('td').eq(3).text()); 16432 } 16433 else { 16434 // this is the last row 16435 nextRowNum = null; 16436 $nextRow = null; 16437 nextKind = null; 16438 nextStart = null; 16439 nextEnd = null; 16440 } 16441 16442 if (isNaN(start)) { 16443 if (prevKind == null) { 16444 // The previous row was probably inserted, and user has not yet selected a kind 16445 // automatically set it to captions 16446 prevKind = 'captions'; 16447 $prevRow.attr('class','kind-captions'); 16448 $prevRow.find('td').eq(1).html('captions'); 16449 } 16450 // Current row has no start time (i.e., it's an inserted row) 16451 if (prevKind === 'captions') { 16452 // start the new row immediately after the captions end 16453 start = (parseFloat(prevEnd) + .001).toFixed(3); 16454 if (nextStart) { 16455 // end the new row immediately before the next row starts 16456 end = (parseFloat(nextStart) - .001).toFixed(3); 16457 } 16458 else { 16459 // this is the last row. Use minDuration to calculate end time. 16460 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 16461 } 16462 } 16463 else if (prevKind === 'chapters') { 16464 // start the new row immediately after the chapter start (not end) 16465 start = (parseFloat(prevStart) + .001).toFixed(3); 16466 if (nextStart) { 16467 // end the new row immediately before the next row starts 16468 end = (parseFloat(nextStart) - .001).toFixed(3); 16469 } 16470 else { 16471 // this is the last row. Use minDuration to calculate end time. 16472 end = (parseFloat(start) + minDurartion[kind]).toFixed(3); 16473 } 16474 } 16475 else if (prevKind === 'descriptions') { 16476 // start the new row minDuration['descriptions'] after the description starts 16477 // this will theoretically allow at least a small cushion for the description to be read 16478 start = (parseFloat(prevStart) + minDuration['descriptions']).toFixed(3); 16479 end = (parseFloat(start) + minDuration['descriptions']).toFixed(3); 16480 } 16481 } 16482 else { 16483 // current row has a start time (i.e., an existing row has been moved)) 16484 if (prevStart) { 16485 // this is not the first row. 16486 if (prevStart < start) { 16487 if (start < nextStart) { 16488 // No change is necessary 16489 } 16490 else { 16491 // nextStart needs to be incremented 16492 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 16493 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 16494 // TODO: Ensure nextEnd does not exceed the following start (nextNextStart) 16495 // Or... maybe this is getting too complicated and should be left up to the user 16496 } 16497 } 16498 else { 16499 // start needs to be incremented 16500 start = (parseFloat(prevStart) + minDuration[prevKind]).toFixed(3); 16501 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 16502 } 16503 } 16504 else { 16505 // this is the first row 16506 if (start < nextStart) { 16507 // No change is necessary 16508 } 16509 else { 16510 // nextStart needs to be incremented 16511 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 16512 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 16513 } 16514 } 16515 } 16516 16517 // check to be sure there is sufficient duration between new start & end times 16518 if (end - start < minDuration[kind]) { 16519 // duration is too short. Change end time 16520 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 16521 if (nextStart) { 16522 // this is not the last row 16523 // increase start time of next row 16524 nextStart = (parseFloat(end) + .001).toFixed(3); 16525 } 16526 } 16527 16528 // Update all affected start/end times 16529 $row.find('td').eq(2).text(this.formatSecondsAsColonTime(start,true)); 16530 $row.find('td').eq(3).text(this.formatSecondsAsColonTime(end,true)); 16531 if ($prevRow) { 16532 $prevRow.find('td').eq(2).text(this.formatSecondsAsColonTime(prevStart,true)); 16533 $prevRow.find('td').eq(3).text(this.formatSecondsAsColonTime(prevEnd,true)); 16534 } 16535 if ($nextRow) { 16536 $nextRow.find('td').eq(2).text(this.formatSecondsAsColonTime(nextStart,true)); 16537 $nextRow.find('td').eq(3).text(this.formatSecondsAsColonTime(nextEnd,true)); 16538 } 16539 }; 16540 16541 AblePlayer.prototype.getKindFromClass = function(myclass) { 16542 16543 // This function is called when a class with prefix "kind-" is found in the class attribute 16544 16545 var kindStart, kindEnd; 16546 16547 kindStart = myclass.indexOf('kind-')+5; 16548 kindEnd = myclass.indexOf(' ',kindStart); 16549 if (kindEnd == -1) { 16550 // no spaces found, "kind-" must be the only myclass 16551 return myclass.substring(kindStart); 16552 } 16553 else { 16554 // kind-* is one of multiple classes 16555 // the following will find it regardless of position of "kind-*" within the class string 16556 return myclass.substring(kindStart,kindEnd); 16557 } 16558 }; 16559 16560 AblePlayer.prototype.showVtsAlert = function(message) { 16561 16562 // this is distinct from greater Able Player showAlert() 16563 // because it's positioning needs are unique 16564 // For now, alertDiv is fixed at top left of screen 16565 // but could ultimately be modified to appear near the point of action in the VTS table 16566 this.$vtsAlert.text(message).show().delay(3000).fadeOut('slow'); 16567 }; 16568 16569 AblePlayer.prototype.parseVtsOutput = function($table) { 16570 16571 // parse table into arrays, then into WebVTT content, for each kind 16572 // Display the WebVTT content in textarea fields for users to copy and paste 16573 var lang, i, kinds, kind, vtt, $rows, start, end, content, $output; 16574 16575 lang = $table.attr('lang'); 16576 kinds = ['captions','chapters','descriptions','subtitles']; 16577 vtt = {}; 16578 for (i=0; i < kinds.length; i++) { 16579 kind = kinds[i]; 16580 vtt[kind] = 'WEBVTT' + "\n\n"; 16581 } 16582 $rows = $table.find('tr'); 16583 if ($rows.length > 0) { 16584 for (i=0; i < $rows.length; i++) { 16585 kind = $rows.eq(i).find('td').eq(1).text(); 16586 if ($.inArray(kind,kinds) !== -1) { 16587 start = $rows.eq(i).find('td').eq(2).text(); 16588 end = $rows.eq(i).find('td').eq(3).text(); 16589 content = $rows.eq(i).find('td').eq(4).text(); 16590 if (start !== undefined && end !== undefined) { 16591 vtt[kind] += start + ' --> ' + end + "\n"; 16592 if (content !== 'undefined') { 16593 vtt[kind] += content; 16594 } 16595 vtt[kind] += "\n\n"; 16596 } 16597 } 16598 } 16599 }
16600 $output = $('<div>',{ 16601 'id': 'able-vts-output' 16602 }) 16603 $('#able-vts').append($output); 16604 for (i=0; i < kinds.length; i++) { 16605 kind = kinds[i]; 16606 if (vtt[kind].length > 8) { 16607 // some content has been added 16608 this.showWebVttOutput(kind,vtt[kind],lang) 16609 } 16610 } 16611 }; 16612 16613 AblePlayer.prototype.showWebVttOutput = function(kind,vttString,lang) { 16614 16615 var $heading, filename, $p, pText, $textarea; 16616 16617 $heading = $('<h3>').text(kind.charAt(0).toUpperCase() + kind.slice(1)); 16618 filename = this.getFilenameFromTracks(kind,lang); 16619 pText = 'If you made changes, copy/paste the following content '; 16620 if (filename) { 16621 pText += 'to replace the original content of your ' + this.getLanguageName(lang) + ' '; 16622 pText += '<em>' + kind + '</em> WebVTT file (<strong>' + filename + '</strong>).'; 16623 } 16624 else { 16625 pText += 'into a new ' + this.getLanguageName(lang) + ' <em>' + kind + '</em> WebVTT file.'; 16626 } 16627 $p = $('<p>',{ 16628 'class': 'able-vts-output-instructions' 16629 }).html(pText); 16630 $textarea = $('<textarea>').text(vttString); 16631 $('#able-vts-output').append($heading,$p,$textarea); 16632 }; 16633 16634})(jQuery); 16635 16636 16637(function ($) { 16638 AblePlayer.prototype.initVimeoPlayer = function () { 16639 16640 var thisObj, deferred, promise, containerId, vimeoId, autoplay, videoDimensions, options; 16641 thisObj = this; 16642 16643 deferred = new $.Deferred(); 16644 promise = deferred.promise(); 16645 16646 containerId = this.mediaId + '_vimeo'; 16647 16648 // add container to which Vimeo player iframe will be appended 16649 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 16650 16651 // if a described version is available && user prefers desription 16652 // init player using the described version 16653 if (this.vimeoDescId && this.prefDesc) { 16654 vimeoId = this.vimeoDescId; 16655 } 16656 else { 16657 vimeoId = this.vimeoId; 16658 } 16659 this.activeVimeoId = vimeoId; 16660 16661 // Notes re. Vimeo Embed Options: 16662 // If a video is owned by a user with a paid Plus, PRO, or Business account, 16663 // setting the "controls" option to "false" will hide the default controls, without hiding captions. 16664 // This is a new option from Vimeo; previously used "background:true" to hide the controller, 16665 // but that had unwanted side effects: 16666 // - In addition to hiding the controls, it also hides captions 16667 // - It automatically autoplays (initializing the player with autoplay:false does not override this) 16668 // - It automatically loops (but this can be overridden by initializing the player with loop:false) 16669 // - It automatically sets volume to 0 (not sure if this can be overridden, since no longer using the background option) 16670 16671 if (this.okToPlay) { 16672 autoplay = 'true'; 16673 } 16674 else { 16675 autoplay = 'false'; 16676 } 16677 16678 if (this.playerWidth) { 16679 if (this.vimeoUrlHasParams) { 16680 // use url param, not id 16681 options = { 16682 url: vimeoId, 16683 width: this.playerWidth, 16684 controls: false 16685 } 16686 } 16687 else { 16688 options = { 16689 id: vimeoId, 16690 width: this.playerWidth, 16691 controls: false 16692 } 16693 } 16694 } 16695 else { 16696 // initialize without width & set width later 16697 if (this.vimeoUrlHasParams) { 16698 options = { 16699 url: vimeoId, 16700 controls: false 16701 } 16702 } 16703 else { 16704 options = { 16705 id: vimeoId, 16706 controls: false 16707 } 16708 } 16709 } 16710 16711 this.vimeoPlayer = new Vimeo.Player(containerId, options); 16712 16713 this.vimeoPlayer.ready().then(function() { 16714 // add tabindex -1 on iframe so vimeo frame cannot be focused on 16715 $('#'+containerId).children('iframe').attr({ 16716 'tabindex': '-1', 16717 'aria-hidden': true 16718 }); 16719 16720 // get video's intrinsic size and initiate player dimensions 16721 thisObj.vimeoPlayer.getVideoWidth().then(function(width) { 16722 if (width) { 16723 // also get height 16724 thisObj.vimeoPlayer.getVideoHeight().then(function(height) { 16725 if (height) { 16726 thisObj.resizePlayer(width,height); 16727 } 16728 }); 16729 } 16730 }).catch(function(error) { 16731 // an error occurred getting height or width 16732 // TODO: Test this to see how gracefully it organically recovers 16733 }); 16734 16735 if (!thisObj.hasPlaylist) { 16736 // remove the media element, since Vimeo replaces that with its own element in an iframe 16737 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 16738 thisObj.$media.remove(); 16739 16740 // define variables that will impact player setup 16741 16742 // vimeoSupportsPlaybackRateChange 16743 // changing playbackRate is only supported if the video is hosted on a Pro or Business account 16744 // unfortunately there is no direct way to query for that information. 16745 // this.vimeoPlayer.getPlaybackRate() returns a value, regardless of account type 16746 // This is a hack: 16747 // Attempt to change the playbackRate. If it results in an error, assume changing playbackRate is not supported.
vendor: 4,365 bytes, lines 16748-16908
16748 // Supported playbackRate values are 0.5 to 2. 16749 thisObj.vimeoPlaybackRate = 1; 16750 thisObj.vimeoPlayer.setPlaybackRate(thisObj.vimeoPlaybackRate).then(function(playbackRate) { 16751 // playback rate was set 16752 thisObj.vimeoSupportsPlaybackRateChange = true; 16753 }).catch(function(error) { 16754 thisObj.vimeoSupportsPlaybackRateChange = false; 16755 }); 16756 deferred.resolve(); 16757 } 16758 }); 16759 return promise; 16760 }; 16761 16762 AblePlayer.prototype.getVimeoPaused = function () { 16763 16764 var deferred, promise; 16765 deferred = new $.Deferred(); 16766 promise = deferred.promise(); 16767 16768 this.vimeoPlayer.getPaused().then(function (paused) { 16769 // paused is Boolean 16770 deferred.resolve(paused); 16771 }); 16772 16773 return promise; 16774 } 16775 16776 AblePlayer.prototype.getVimeoEnded = function () { 16777 16778 var deferred, promise; 16779 deferred = new $.Deferred(); 16780 promise = deferred.promise(); 16781 16782 this.vimeoPlayer.getEnded().then(function (ended) { 16783 // ended is Boolean 16784 deferred.resolve(ended); 16785 }); 16786 16787 return promise; 16788 } 16789 16790 AblePlayer.prototype.getVimeoState = function () { 16791 16792 var thisObj, deferred, promise, promises, gettingPausedPromise, gettingEndedPromise; 16793 16794 thisObj = this; 16795 16796 deferred = new $.Deferred(); 16797 promise = deferred.promise(); 16798 promises = []; 16799 16800 gettingPausedPromise = this.vimeoPlayer.getPaused(); 16801 gettingEndedPromise = this.vimeoPlayer.getEnded(); 16802 16803 promises.push(gettingPausedPromise); 16804 promises.push(gettingEndedPromise); 16805 16806 gettingPausedPromise.then(function (paused) { 16807 deferred.resolve(paused); 16808 }); 16809 gettingEndedPromise.then(function (ended) { 16810 deferred.resolve(ended); 16811 }); 16812 $.when.apply($, promises).then(function () { 16813 deferred.resolve(); 16814 }); 16815 return promise; 16816 } 16817 16818 AblePlayer.prototype.getVimeoCaptionTracks = function () { 16819 16820 // get data via Vimeo Player API, and push data to this.captions 16821 // Note: Vimeo doesn't expose the caption cues themselves 16822 // so this.captions will only include metadata about caption tracks; not cues 16823 var deferred = new $.Deferred(); 16824 var promise = deferred.promise(); 16825 16826 var thisObj, i, trackId, isDefaultTrack; 16827 16828 thisObj = this; 16829 16830 this.vimeoPlayer.getTextTracks().then(function(tracks) { 16831 16832 // each Vimeo track includes the following: 16833 // label (local name of the language) 16834 // language (2-character code) 16835 // kind (captions or subtitles, as declared by video owner) 16836 // mode ('disabled' or 'showing') 16837 16838 if (tracks.length) { 16839 16840 // create a new button for each caption track 16841 for (i=0; i<tracks.length; i++) { 16842 16843 thisObj.hasCaptions = true; 16844 if (thisObj.prefCaptions === 1) { 16845 thisObj.captionsOn = true; 16846 } 16847 else { 16848 thisObj.captionsOn = false; 16849 } 16850 // assign the default track based on language of the player 16851 if (tracks[i]['language'] === thisObj.lang) { 16852 isDefaultTrack = true; 16853 } 16854 else { 16855 isDefaultTrack = false; 16856 } 16857 thisObj.tracks.push({ 16858 'kind': tracks[i]['kind'], 16859 'language': tracks[i]['language'], 16860 'label': tracks[i]['label'], 16861 'def': isDefaultTrack 16862 }); 16863 } 16864 thisObj.captions = thisObj.tracks; 16865 thisObj.hasCaptions = true; 16866 16867 // setupPopups again with new captions array, replacing original 16868 thisObj.setupPopups('captions'); 16869 deferred.resolve(); 16870 } 16871 else { 16872 thisObj.hasCaptions = false; 16873 thisObj.usingVimeoCaptions = false; 16874 deferred.resolve(); 16875 } 16876 }); 16877 16878 return promise; 16879 }; 16880 16881 AblePlayer.prototype.getVimeoPosterUrl = function (vimeoId, width) { 16882 16883 // this is a placeholder, copied from getYouTubePosterUrl() 16884 // Vimeo doesn't seem to have anything similar, 16885 // nor does it seem to be possible to get the poster via the Vimeo API 16886 // Vimeo playlist support (with thumbnail images) may require use of data-poster 16887 16888 // return a URL for retrieving a YouTube poster image 16889 // supported values of width: 120, 320, 480, 640 16890 16891 var url = 'https://img.youtube.com/vi/' + youTubeId; 16892 if (width == '120') { 16893 // default (small) thumbnail, 120 x 90 16894 return url + '/default.jpg'; 16895 } 16896 else if (width == '320') { 16897 // medium quality thumbnail, 320 x 180 16898 return url + '/hqdefault.jpg'; 16899 } 16900 else if (width == '480') { 16901 // high quality thumbnail, 480 x 360 16902 return url + '/hqdefault.jpg'; 16903 } 16904 else if (width == '640') { 16905 // standard definition poster image, 640 x 480 16906 return url + '/sddefault.jpg'; 16907 } 16908 return false;
16909 }; 16910 16911 AblePlayer.prototype.getVimeoId = function (url) { 16912 16913 // return a Vimeo ID, extracted from a full Vimeo URL 16914 // Supported URL patterns are anything containing 'vimeo.com' 16915 // and ending with a '/' followed by the ID. 16916 // (Vimeo IDs do not have predicatable lengths) 16917 16918 // Update: If URL contains parameters, return the full url 16919 // This will need to be passed to the Vimeo Player API 16920 // as a url parameter, not as an id parameter 16921 this.vimeoUrlHasParams = false; 16922 16923 var idStartPos, id; 16924 16925 if (typeof url === 'number') { 16926 // this is likely already a vimeo ID 16927 return url; 16928 } 16929 else if (url.indexOf('vimeo.com') !== -1) { 16930 // this is a full Vimeo URL 16931 if (url.indexOf('?') !== -1) { 16932 // URL contains parameters 16933 this.vimeoUrlHasParams = true; 16934 return url; "use strict";var $,$,$,AblePlayerInstances=[];!function(t){t(function(){t("video, audio").each(function(e,i){void 0!==t(i).data("able-player")&&AblePlayerInstances.push(new AblePlayer(t(this),t(i)))})}),window.onYouTubeIframeAPIReady=function(){AblePlayer.youTubeIframeAPIReady=!0,t("body").trigger("youTubeIframeAPIReady",[])},t(window).on("keydown",function(t){1===AblePlayer.nextIndex&&AblePlayer.lastCreated.onPlayerKeyPress(t)}),window.AblePlayer=function(e){var i=this;if(AblePlayer.lastCreated=this,this.media=e,0===t(e).length){this.provideFallback();return}if(void 0!==t(e).attr("autoplay")?(this.autoplay=!0,this.okToPlay=!0):(this.autoplay=!1,this.okToPlay=!1),void 0!==t(e).attr("loop")?this.loop=!0:this.loop=!1,void 0!==t(e).attr("playsinline")?this.playsInline="1":this.playsInline="0",t(e).attr("poster")?this.hasPoster=!0:this.hasPoster=!1,t(e).attr("width")&&(this.width=t(e).attr("width")),t(e).attr("height")&&(this.height=t(e).attr("height")),void 0!==t(e).data("start-time")&&t.isNumeric(t(e).data("start-time"))?this.startTime=t(e).data("start-time"):this.startTime=0,void 0!==t(e).data("debug")&&!1!==t(e).data("debug")?this.debug=!0:this.debug=!1,void 0!==t(e).data("root-path")?this.rootPath=t(e).data("root-path").replace(/\/?$/,"/"):this.rootPath=this.getRootPath(),this.defaultVolume=7,void 0!==t(e).data("volume")&&""!==t(e).data("volume")){var s=t(e).data("volume");s>=0&&s<=10&&(this.defaultVolume=s)}if(this.volume=this.defaultVolume,void 0!==t(e).data("use-chapters-button")&&!1===t(e).data("use-chapters-button")?this.useChaptersButton=!1:this.useChaptersButton=!0,void 0!==t(e).data("descriptions-audible")&&!1===t(e).data("descriptions-audible")?this.readDescriptionsAloud=!1:void 0!==t(e).data("description-audible")&&!1===t(e).data("description-audible")?this.readDescriptionsAloud=!1:this.readDescriptionsAloud=!0,"screenreader"==t(e).data("desc-reader")?this.descReader="screenreader":this.descReader="browser","off"==t(e).data("state-captions")?this.defaultStateCaptions=0:this.defaultStateCaptions=1,"on"==t(e).data("state-descriptions")?this.defaultStateDescriptions=1:this.defaultStateDescriptions=0,"off"==t(e).data("desc-pause-default")?this.defaultDescPause=0:this.defaultDescPause=1,void 0!==t(e).data("heading-level")&&""!==t(e).data("heading-level")){var a=t(e).data("heading-level");/^[0-6]*$/.test(a)&&(this.playerHeadingLevel=a)}if(void 0!==t(e).data("transcript-div")&&""!==t(e).data("transcript-div")?this.transcriptDivLocation=t(e).data("transcript-div"):this.transcriptDivLocation=null,void 0!==t(e).data("include-transcript")&&!1===t(e).data("include-transcript")?this.hideTranscriptButton=!0:this.hideTranscriptButton=null,this.transcriptType=null,void 0!==t(e).data("transcript-src")?(this.transcriptSrc=t(e).data("transcript-src"),this.transcriptSrcHasRequiredParts()?this.transcriptType="manual":console.log("ERROR: Able Player transcript is missing required parts")):t(e).find('track[kind="captions"], track[kind="subtitles"]').length>0&&(this.transcriptDivLocation?this.transcriptType="external":this.transcriptType="popup"),void 0!==t(e).data("lyrics-mode")&&!1!==t(e).data("lyrics-mode")?this.lyricsMode=!0:this.lyricsMode=!1,void 0!==t(e).data("transcript-title")&&""!==t(e).data("transcript-title")&&(this.transcriptTitle=t
16934(e).data("transcript-title")),"overlay"===t(e).data("captions-position")?this.defaultCaptionsPosition="overlay":this.defaultCaptionsPosition="below",void 0!==t(e).data("chapters-div")&&""!==t(e).data("chapters-div")&&(this.chaptersDivLocation=t(e).data("chapters-div")),void 0!==t(e).data("chapters-title")&&(this.chaptersTitle=t(e).data("chapters-title")),void 0!==t(e).data("chapters-default")&&""!==t(e).data("chapters-default")?this.defaultChapter=t(e).data("chapters-default"):this.defaultChapter=null,"arrows"===t(e).data("speed-icons")?this.speedIcons="arrows":this.speedIcons="animals","chapter"===t(e).data("seekbar-scope")||"chapters"===t(e).data("seekbar-scope")?this.seekbarScope="chapter":this.seekbarScope="video",void 0!==t(e).data("youtube-id")&&""!==t(e).data("youtube-id")&&(this.youTubeId=this.getYouTubeId(t(e).data("youtube-id"))),void 0!==t(e).data("youtube-desc-id")&&""!==t(e).data("youtube-desc-id")&&(this.youTubeDescId=this.getYouTubeId(t(e).data("youtube-desc-id"))),void 0!==t(e).data("youtube-nocookie")&&t(e).data("youtube-nocookie")?this.youTubeNoCookie=!0:this.youTubeNoCookie=!1,void 0!==t(e).data("vimeo-id")&&""!==t(e).data("vimeo-id")&&(this.vimeoId=this.getVimeoId(t(e).data("vimeo-id"))),void 0!==t(e).data("vimeo-desc-id")&&""!==t(e).data("vimeo-desc-id")&&(this.vimeoDescId=this.getVimeoId(t(e).data("vimeo-desc-id"))),"2020"==t(e).data("skin")?this.skin="2020":this.skin="legacy",void 0!==t(e).data("width")?this.playerWidth=parseInt(t(e).data("width")):t(e)[0].getAttribute("width")?this.playerWidth=parseInt(t(e)[0].getAttribute("width")):this.playerWidth=null,this.iconType="font",this.forceIconType=!1,void 0!==t(e).data("icon-type")&&""!==t(e).data("icon-type")){var n=t(e).data("icon-type");("font"===n||"image"==n||"svg"==n)&&(this.iconType=n,this.forceIconType=!0)}if(void 0!==t(e).data("allow-fullscreen")&&!1===t(e).data("allow-fullscreen")?this.allowFullscreen=!1:this.allowFullscreen=!0,this.clickedFullscreenButton=!1,this.restoringAfterFullscreen=!1,this.defaultSeekInterval=10,this.useFixedSeekInterval=!1,void 0!==t(e).data("seek-interval")&&""!==t(e).data("seek-interval")){var r=t(e).data("seek-interval");/^[1-9][0-9]*$/.test(r)&&(this.seekInterval=r,this.useFixedSeekInterval=!0)}void 0!==t(e).data("show-now-playing")&&!1===t(e).data("show-now-playing")?this.showNowPlaying=!1:this.showNowPlaying=!0,void 0!==t(e).data("use-ttml")?(this.useTtml=!0,this.convert=require("xml-js")):this.useTtml=!1,void 0!==t(e).data("test-fallback")&&!1!==t(e).data("test-fallback")?"2"==t(e).data("test-fallback")?this.testFallback=2:this.testFallback=1:this.testFallback=!1,void 0!==t(e).data("lang")&&""!==t(e).data("lang")?this.lang=t(e).data("lang").toLowerCase():this.lang=null,void 0!==t(e).data("meta-type")&&""!==t(e).data("meta-type")&&(this.metaType=t(e).data("meta-type")),void 0!==t(e).data("meta-div")&&""!==t(e).data("meta-div")&&(this.metaDiv=t(e).data("meta-div")),void 0!==t(e).data("search-div")&&""!==t(e).data("search-div")&&(this.searchDiv=t(e).data("search-div"),void 0!==t(e).data("search")&&""!==t(e).data("search")&&(this.searchString=t(e).data("search")),void 0!==t(e).data("search-lang")&&""!==t(e).data("search-lang")?this.searchLang=t(e).data("search-lang"):this.searchLang=null,void 0!==t(e).data("search-ignore-caps")&&!1!==t(e).data("search-ignore-caps")?this.searchIgnoreCaps=!0:this.searchIgnoreCaps=!1,void 0!==t(e).data("search-div")&&""!==t(e).data("search-div")&&(this.searchString=t(e).data("search"),this.searchDiv=t(e).data("search-div"))),void 0!==t(e).data("hide-controls")&&!1!==t(e).data("hide-controls")?(this.hideControls=!0,this.hideControlsOriginal=!0):(this.hideControls=!1,this.hideControlsOriginal=!1),void 0!==t(e).data("steno-mode")&&!1!==t(e).data("steno-mode")?(this.stenoMode=!0,void 0!==t(e).data("steno-iframe-id")&&""!==t(e).data("steno-iframe-id")?(this.stenoFrameId=t(e).data("steno-iframe-id"),this.$stenoFrame=t("#"+this.stenoFrameId),this.$stenoFrame.length||(this.stenoFrameId=null,this.$stenoFrame=null)):(this.stenoFrameId=null,this.$stenoFrame=null)):(this.stenoMode=!1,this.stenoFrameId=null,this.$stenoFrame=null),this.setDefaults(),this.ableIndex=AblePlayer.nextIndex,AblePlayer.nextIndex+=1,this.title=t(e).attr("title"),this.tt
16934={};var i=this;t.when(this.getTranslationText()).then(function(){i.countProperties(i.tt)>50?i.setup():i.provideFallback()}).fail(function(){i.provideFallback()})},AblePlayer.nextIndex=0,AblePlayer.prototype.setup=function(){var t=this;this.initializing=!0,this.reinitialize().then(function(){t.player?t.setupInstance().then(function(){t.setupInstancePlaylist(),t.hasPlaylist||t.recreatePlayer().then(function(){t.initializing=!1,t.playerCreated=!0})}):t.provideFallback()})},AblePlayer.getActiveDOMElement=function(){for(var t=document.activeElement;t.shadowRoot&&t.shadowRoot.activeElement;)t=t.shadowRoot.activeElement;return t},AblePlayer.localGetElementById=function(e,i){return e.getRootNode?t(e.getRootNode().querySelector("#"+i)):t(document.getElementById(i))},AblePlayer.youTubeIframeAPIReady=!1,AblePlayer.loadingYouTubeIframeAPI=!1}(jQuery),function(t){AblePlayer.prototype.setDefaults=function(){this.playerCreated=!1,this.playing=!1,this.paused=!0,this.clickedPlay=!1,this.fullscreen=!1,this.swappingSrc=!1,this.initializing=!1,this.cueingPlaylistItems=!1,this.buttonWithFocus=null,this.speechEnabled=null,this.setIconColor(),this.setButtonImages()},AblePlayer.prototype.getRootPath=function(){var t,e,i,s,a,n,r,o;for(e=0,t=document.getElementsByTagName("script");e<t.length;e++)if(-1!==(s=(i=t[e].src).substring(i.lastIndexOf("/"))).indexOf("ableplayer")){a=i.split("?")[0];break}return r=(n=a.split("/").slice(0,-1).join("/")).lastIndexOf("/"),o=n.substring(0,r)+"/"},AblePlayer.prototype.setIconColor=function(){var e,i,s,a,n,r,o,h,l,p;for(i=0,e=["controller","toolbar"];i<e.length;i++)"controller"==e[i]?s=t("<div>",{class:"able-controller"}).hide():"toolbar"===e[i]&&(s=t("<div>",{class:"able-window-toolbar"}).hide()),t("body").append(s),r=(n=(a=s.css("background-color")).replace(/[^\d,]/g,"").split(","))[0],p=(l=.2126*r+.7152*(o=n[1])+.0722*(h=n[2]))<125?"white":"black","controller"===e[i]?this.iconColor=p:"toolbar"===e[i]&&(this.toolbarIconColor=p),s.remove()},AblePlayer.prototype.setButtonImages=function(){this.imgPath=this.rootPath+"button-icons/"+this.iconColor+"/",this.playButtonImg=this.imgPath+"play.png",this.pauseButtonImg=this.imgPath+"pause.png",this.restartButtonImg=this.imgPath+"restart.png",this.rewindButtonImg=this.imgPath+"rewind.png",this.forwardButtonImg=this.imgPath+"forward.png",this.previousButtonImg=this.imgPath+"previous.png",this.nextButtonImg=this.imgPath+"next.png","arrows"===this.speedIcons?(this.fasterButtonImg=this.imgPath+"slower.png",this.slowerButtonImg=this.imgPath+"faster.png"):"animals"===this.speedIcons&&(this.fasterButtonImg=this.imgPath+"rabbit.png",this.slowerButtonImg=this.imgPath+"turtle.png"),this.captionsButtonImg=this.imgPath+"captions.png",this.chaptersButtonImg=this.imgPath+"chapters.png",this.signButtonImg=this.imgPath+"sign.png",this.transcriptButtonImg=this.imgPath+"transcript.png",this.descriptionsButtonImg=this.imgPath+"descriptions.png",this.fullscreenExpandButtonImg=this.imgPath+"fullscreen-expand.png",this.fullscreenCollapseButtonImg=this.imgPath+"fullscreen-collapse.png",this.prefsButtonImg=this.imgPath+"preferences.png",this.helpButtonImg=this.imgPath+"help.png"},AblePlayer.prototype.getSvgData=function(t){var e=[];switch(t){case"play":e[0]="0 0 16 20",e[1]="M0 18.393v-16.429q0-0.29 0.184-0.402t0.441 0.033l14.821 8.237q0.257 0.145 0.257 0.346t-0.257 0.346l-14.821 8.237q-0.257 0.145-0.441 0.033t-0.184-0.402z";break;case"pause":e[0]="0 0 20 20",e[1]="M0 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502zM10 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502z";break;case"stop":e[0]="0 0 20 20",e[1]="M0 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h15.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-15.714q-0.29 0-0.502-0.212t-0.212-0.502z";break;case"restart":e[0]="0 0 20 20",e[1]="M18 8h-6l2.243-2.243c-1.133-1.133-2.64-1.757-4.243-1.757s-3.109 0.624-4.243 1.757c-1.133 1.133-1.757 2.64-1.757 4.243s0.624 3.109 1.757 4.243c1.133 1.133 2.64 1.757 4.243 1.757s3.109-0.624 4.243-1.757c0.095-0.095 0.185-0.192 0.273-0.292l1.505 1.317c-1.466 1.674-3.62 2.732-6.020 2.732-4.418 0-8-3.582-8-8s3.582-8 8-8c2.209 0 4.209 0.896 5.656 2.344l2.344-2.344v6z";break;case"rewind":e[0]="0 0 24 24",e[1]="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z";break;case"forward":e[0]="0 0 24 24",e[1]="M18 13c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6v4l5-5-5-5v4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8h-2z";break;case"previous":e[0]="0 0 20 20",e[1]="M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z";break;case"next":e[0]="0 0 20 20",e[1]="M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z";break;case"slower":e[0]="0 0 20 20",e[1]="M0 7.321q0-0.29 0.212-0.502t0.502-0.212h10q0.29 0 0.502 0.212t0.212 0.502-0.212 0.502l-5 5q-0.212 0.212-0.502 0.212t-0.502-0.212l-5-5q-0.212-0.212-0.212-0.502z";break;case"faster":e[0]="0 0 11 20",e[1]="M0 12.411q0-0.29 0.212-0.502l5-5q0.212-0.212 0.502-0.212t0.502 0.212l5 5q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-10q-0.29 0-0.502-0.212t-0.212-0.502z";break;case"turtle":e[0]="0 0 20 20",e[1]="M17.212 3.846c-0.281-0.014-0.549 0.025-0.817 0.144-1.218 0.542-1.662 2.708-2.163 3.942-1.207 2.972-7.090 4.619-11.755 5.216-0.887 0.114-1.749 0.74-2.428 1.466 0.82-0.284 2.126-0.297 2.74 0.144 0.007 0.488-0.376 1.062-0.625 1.37-0.404 0.5-0.398 0.793 0.12 0.793 0.473 0 0.752 0.007 1.635 0 0.393-0.003 0.618-0.16 1.49-1.49 3.592 0.718 5.986-0.264 5.986-0.264s0.407 1.755 1.418 1.755h1.49c0.633 0 0.667-0.331 0.625-0.433-0.448-1.082-0.68-1.873-0.769-2.5-0.263-1.857 0.657-3.836 2.524-5.457 0.585 0.986 2.253 0.845 2.909-0.096s0.446-2.268-0.192-3.221c-0.49-0.732-1.345-1.327-2.188-1.37zM8.221 4.663c-0.722-0.016-1.536 0.111-2.5 0.409-4.211 1.302-4.177 4.951-3.51 5.745 0 0-0.955 0.479-0.409 1.274 0.448 0.652 3.139 0.191 5.409-0.529s4.226-1.793 5.312-2.692c0.948-0.785 0.551-2.106-0.505-1.947-0.494-0.98-1.632-2.212-3.798-2.26zM18.846 5.962c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577z";break;case"rabbit":e[0]="0 0 20 20",e[1]="M10.817 0c-2.248 0-1.586 0.525-1.154 0.505 1.551-0.072 5.199 0.044 6.851 2.428 0 0-1.022-2.933-5.697-2.933zM10.529 0.769c-2.572 0-2.837 0.51-2.837 1.106 0 0.545 1.526 0.836 2.524 0.697 2.778-0.386 4.231-0.12 5.264 0.865-1.010 0.779-0.75 1.401-1.274 1.851-1.093 0.941-2.643-0.673-4.976-0.673-2.496 0-4.712 1.92-4.712 4.76-0.157-0.537-0.769-0.913-1.442-0.913-0.974 0-1.514 0.637-1.514 1.49 0 0.769 1.13 1.791 2.861 0.938 0.499 1.208 2.265 1.364 2.452 1.418 0.538 0.154 1.875 0.098 1.875 0.865 0 0.794-1.034 1.094-1.034 1.707 0 1.070 1.758 0.873 2.284 1.034 1.683 0.517 2.103 1.214 2.788 2.212 0.771 1.122 2.572 1.408 2.572 0.625 0-3.185-4.413-4.126-4.399-4.135 0.608-0.382 2.139-1.397 2.139-3.534 0-1.295-0.703-2.256-1.755-2.861 1.256 0.094 2.572 1.205 2.572 2.74 0 1.877-0.653 2.823-0.769 2.957 1.975-1.158 3.193-3.91 3.029-6.37 0.61 0.401 1.27 0.577 1.971 0.625 0.751 0.052 1.475-0.225 1.635-0.529 0.38-0.723 0.162-2.321-0.12-2.837-0.763-1.392-2.236-1.73-3.606-1.683-1.202-1.671-3.812-2.356-5.529-2.356zM1.37 3.077l-0.553 1.538h3.726c0.521-0.576 1.541-1.207 2.284-1.538h-5.457zM18.846 5.192c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577zM0.553 5.385l-0.553 1.538h3.197c0.26-0.824 0.586-1.328 0.769-1.538h-3.413z";break;case"ellipsis":e[0]="0 0 20 20",e[1]="M10.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2zM3.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.986 2.199-2.2s-0.984-2.2-2.199-2.2zM17.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2z";break;case"pipe":e[0]="0 0 20 20",e[1]="M10.15 0.179h0.623c0.069 0 0.127 0.114 0.127 0.253v19.494c0 0.139-0.057 0.253-0.127 0.253h-1.247c-0.069 0-0.126-0.114-0.126-0.253v-19.494c0-0.139 0.057-0.253 0.126-0.253h0.623z";break;case"captions":case"transcript":e[0]="0 0 20 20",e[1]="M0.033 3.624h19.933v12.956h-19.933v-12.956zM18.098 10.045c-0.025-2.264-0.124-3.251-0.743-3.948-0.112-0.151-0.322-0.236-0.496-0.344-0.606-0.386-3.465-0.526-6.782-0.526s-6.313 0.14-6.907 0.526c-0.185 0.108-0.396 0.193-0.519 0.344-0.607 0.697-0.693 1.684-0.731 3.948 0.037 2.265 0.124 3.252 0.731 3.949 0.124 0.161 0.335 0.236 0.519 0.344 0.594 0.396 3.59
169340.526 6.907 0.547 3.317-0.022 6.176-0.151 6.782-0.547 0.174-0.108 0.384-0.183 0.496-0.344 0.619-0.697 0.717-1.684 0.743-3.949v0 0zM9.689 9.281c-0.168-1.77-1.253-2.813-3.196-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.442-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.977zM16.607 9.281c-0.167-1.77-1.252-2.813-3.194-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.441-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.976z";break;case"descriptions":e[0]="0 0 20 20",e[1]="M17.623 3.57h-1.555c1.754 1.736 2.763 4.106 2.763 6.572 0 2.191-0.788 4.286-2.189 5.943h1.484c1.247-1.704 1.945-3.792 1.945-5.943-0-2.418-0.886-4.754-2.447-6.572v0zM14.449 3.57h-1.55c1.749 1.736 2.757 4.106 2.757 6.572 0 2.191-0.788 4.286-2.187 5.943h1.476c1.258-1.704 1.951-3.792 1.951-5.943-0-2.418-0.884-4.754-2.447-6.572v0zM11.269 3.57h-1.542c1.752 1.736 2.752 4.106 2.752 6.572 0 2.191-0.791 4.286-2.181 5.943h1.473c1.258-1.704 1.945-3.792 1.945-5.943 0-2.418-0.876-4.754-2.447-6.572v0zM10.24 9.857c0 3.459-2.826 6.265-6.303 6.265v0.011h-3.867v-12.555h3.896c3.477 0 6.274 2.806 6.274 6.279v0zM6.944 9.857c0-1.842-1.492-3.338-3.349-3.338h-0.876v6.686h0.876c1.858 0 3.349-1.498 3.349-3.348v0z";break;case"sign":e[0]="0 0 20 20",e[1]="M10.954 10.307c0.378 0.302 0.569 1.202 0.564 1.193 0.697 0.221 1.136 0.682 1.136 0.682 1.070-0.596 1.094-0.326 1.558-0.682 0.383-0.263 0.366-0.344 0.567-1.048 0.187-0.572-0.476-0.518-1.021-1.558-0.95 0.358-1.463 0.196-1.784 0.167-0.145-0.020-0.12 0.562-1.021 1.247zM14.409 17.196c-0.133 0.182-0.196 0.218-0.363 0.454-0.28 0.361 0.076 0.906 0.253 0.82 0.206-0.076 0.341-0.488 0.567-0.623 0.115-0.061 0.422-0.513 0.709-0.82 0.211-0.238 0.363-0.344 0.564-0.594 0.341-0.422 0.412-0.744 0.709-1.193 0.184-0.236 0.312-0.307 0.481-0.594 0.886-1.679 0.628-2.432 1.475-3.629 0.26-0.353 0.552-0.442 0.964-0.653 0.383-2.793-0.888-4.356-0.879-4.361-1.067 0.623-1.644 0.879-2.751 0.82-0.417-0.005-0.636-0.182-1.048-0.145-0.385 0.015-0.582 0.159-0.964 0.29-0.589 0.182-0.91 0.344-1.529 0.535-0.393 0.11-0.643 0.115-1.050 0.255-0.348 0.147-0.182 0.029-0.427 0.312-0.317 0.348-0.238 0.623-0.535 1.222-0.371 0.785-0.326 0.891-0.115 0.987-0.14 0.402-0.174 0.672-0.14 1.107 0.039 0.331-0.101 0.562 0.255 0.825 0.483 0.361 1.499 1.205 1.757 1.217 0.39-0.012 1.521 0.029 2.096-0.368 0.13-0.081 0.167-0.162 0.056 0.145-0.022 0.037-1.433 1.136-1.585 1.131-1.794 0.056-1.193 0.157-1.303 0.115-0.091 0-0.955-1.055-1.477-0.682-0.196 0.12-0.287 0.236-0.363 0.452 0.066 0.137 0.383 0.358 0.675 0.54 0.422 0.27 0.461 0.552 0.881 0.653 0.513 0.115 1.060 0.039 1.387 0.081 0.125 0.034 1.256-0.297 1.961-0.675 0.65-0.336-0.898 0.648-1.276 1.131-1.141 0.358-0.82 0.373-1.362 0.483-0.503 0.115-0.479 0.086-0.822 0.196-0.356 0.086-0.648 0.572-0.312 0.825 0.201 0.167 0.827-0.066 1.445-0.086 0.275-0.005 1.391-0.518 1.644-0.653 0.633-0.339 1.099-0.81 1.472-1.077 0.518-0.361-0.584 0.991-1.050 1.558zM8.855 9.799c-0.378-0.312-0.569-1.212-0.564-1.217-0.697-0.206-1.136-0.667-1.136-0.653-1.070 0.582-1.099 0.312-1.558 0.653-0.388 0.277-0.366 0.363-0.567 1.045-0.187 0.594 0.471 0.535 1.021 1.561 0.95-0.344 1.463-0.182 1.784-0.142 0.145 0.010 0.12-0.572 1.021-1.247zM5.4 2.911c0.133-0.191 0.196-0.228 0.368-0.454 0.27-0.371-0.081-0.915-0.253-0.849-0.211 0.096-0.346 0.508-0.599 0.653-0.093 0.052-0.4 0.503-0.682 0.82-0.211 0.228-0.363 0.334-0.564 0.599-0.346 0.407-0.412 0.729-0.709 1.161-0.184 0.258-0.317 0.324-0.481 0.621-0.886 1.669-0.631 2.422-1.475 3.6-0.26 0.38-0.552 0.461-0.964 0.682-0.383 2.788 0.883 4.346 0.879 4.336 1.068-0.609 1.639-0.861 2.751-0.825 0.417 0.025 0.636 0.201 1.048 0.174 0.385-0.025 0.582-0.169 0.964-0.285 0.589-0.196 0.91-0.358 1.499-0.54 0.422-0.12 0.672-0.125 1.080-0.285 0.348-0.128 0.182-0.010 0.427-0.282 0.312-0.358 0.238-0.633 0.508-1.217 0.398-0.8 0.353-0.906 0.142-0.991 0.135-0.412 0.174-0.677 0.14-1.107-0.044-0.336 0.101-0.572-0.255-0.82-0.483-0.375-1.499-1.22-1.752-1.222-0.395 0.002-1.526-0.039-2.101 0.339-0.13 0.101-0.167 0.182-0.056-0.11 0.022-0.052 1.433-1.148 1.585-1.163 1.794-0.039 1.193-0.14 1.303-0.088 0.091-0.007 0.955 1.045 1.477 0.682 0.191-0.13 0.287-0.245 0.368-0.452-0.071-0.147-0.388-0.368-0.68-0.537-0.422-0.282-0.464-0.564-0.881-0.655-0.513-0.125-1.065-0.049-1.387-0.11-0.125-0.015-1.256 0.317-1.956 0.68-0.66 0.351 0.893-0.631 1.276-1.136 1.136-0.339 0.81-0.353 1.36-0.479 0.501-0.101 0.476-0.071 0.82-0.172 0.351-0.096 0.648-0.577 0.312-0.849-0.206-0.152-0.827 0.081-1.44 0.086-0.28 0.020-1.396 0.533-1.649 0.677-0.633 0.329-1.099 0.8-1.472 1.048-0.523 0.38 0.584-0.967 1.050-1.529z";break;case"mute":case"volume-mute":e[0]="0 0 20 20",e[1]="M7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714zM18.7
169345 12.093v1.657h-1.657l-2.093-2.093-2.093 2.093h-1.657v-1.657l2.093-2.093-2.093-2.093v-1.657h1.657l2.093 2.093 2.093-2.093h1.657v1.657l-2.093 2.093z";break;case"volume-soft":e[0]="0 0 20 20",e[1]="M10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z";break;case"volume-medium":e[0]="0 0 20 20",e[1]="M14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z";break;case"volume-loud":e[0]="0 0 21 20",e[1]="M17.384 18.009c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.712-1.712 2.654-3.988 2.654-6.408s-0.943-4.696-2.654-6.408c-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.066 2.066 3.204 4.813 3.204 7.734s-1.138 5.668-3.204 7.734c-0.183 0.183-0.423 0.275-0.663 0.275zM14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z";break;case"chapters":e[0]="0 0 20 20",e[1]="M5 2.5v17.5l6.25-6.25 6.25 6.25v-1
169347.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z";break;case"preferences":e[0]="0 0 20 20",e[1]="M18.238 11.919c-1.049-1.817-0.418-4.147 1.409-5.205l-1.965-3.404c-0.562 0.329-1.214 0.518-1.911 0.518-2.1 0-3.803-1.714-3.803-3.828h-3.931c0.005 0.653-0.158 1.314-0.507 1.919-1.049 1.818-3.382 2.436-5.212 1.382l-1.965 3.404c0.566 0.322 1.056 0.793 1.404 1.396 1.048 1.815 0.42 4.139-1.401 5.2l1.965 3.404c0.56-0.326 1.209-0.513 1.902-0.513 2.094 0 3.792 1.703 3.803 3.808h3.931c-0.002-0.646 0.162-1.3 0.507-1.899 1.048-1.815 3.375-2.433 5.203-1.387l1.965-3.404c-0.562-0.322-1.049-0.791-1.395-1.391zM10 14.049c-2.236 0-4.050-1.813-4.050-4.049s1.813-4.049 4.050-4.049 4.049 1.813 4.049 4.049c-0 2.237-1.813 4.049-4.049 4.049z";break;case"close":e[0]="0 0 16 20",e[1]="M1.228 14.933q0-0.446 0.312-0.759l3.281-3.281-3.281-3.281q-0.313-0.313-0.313-0.759t0.313-0.759l1.518-1.518q0.313-0.313 0.759-0.313t0.759 0.313l3.281 3.281 3.281-3.281q0.313-0.313 0.759-0.313t0.759 0.313l1.518 1.518q0.313 0.313 0.313 0.759t-0.313 0.759l-3.281 3.281 3.281 3.281q0.313 0.313 0.313 0.759t-0.313 0.759l-1.518 1.518q-0.313 0.313-0.759 0.313t-0.759-0.313l-3.281-3.281-3.281 3.281q-0.313 0.313-0.759 0.313t-0.759-0.313l-1.518-1.518q-0.313-0.313-0.313-0.759z";break;case"fullscreen-expand":e[0]="0 0 20 20",e[1]="M0 18.036v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502zM8.717 8.393q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257z";break;case"fullscreen-collapse":e[0]="0 0 20 20",e[1]="M0.145 16.964q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257zM8.571 9.464v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502z";break;case"help":e[0]="0 0 11 20",e[1]="M0.577 6.317q-0.028-0.167 0.061-0.313 1.786-2.969 5.179-2.969 0.893 0 1.797 0.346t1.629 0.926 1.183 1.423 0.458 1.769q0 0.603-0.173 1.127t-0.391 0.854-0.614 0.664-0.642 0.485-0.681 0.396q-0.458 0.257-0.765 0.725t-0.307 0.748q0 0.19-0.134 0.363t-0.313 0.173h-2.679q-0.167 0-0.285-0.206t-0.117-0.419v-0.502q0-0.926 0.725-1.747t1.596-1.211q0.658-0.301 0.938-0.625t0.279-0.848q0-0.469-0.519-0.826t-1.2-0.357q-0.725 0-1.205 0.324-0.391 0.279-1.194 1.283-0.145 0.179-0.346 0.179-0.134 0-0.279-0.089l-1.83-1.395q-0.145-0.112-0.173-0.279zM3.786 16.875v-2.679q0-0.179 0.134-0.313t0.313-0.134h2.679q0.179 0 0.313 0.134t0.134 0.313v2.679q0 0.179-0.134 0.313t-0.313 0.134h-2.679q-0.179 0-0.313-0.134t-0.134-0.313z"}return e},AblePlayer.prototype.reinitialize=function(){var e,i,s;if(i=(e=new t.Deferred).promise(),s=this,window.console||(this.debug=!1),this.startedPlaying=!1,this.autoScrollTranscript=!0,this.$media=t(this.media).first(),this.media=this.$media[0],this.$media.is("audio"))this.mediaType="audio";else{if(!this.$media.is("video"))return this.provideFallback(),e.fail(),i;this.mediaType="video"}return this.$sources=this.$media.find("source"),this.player=this.getPlayer(),this.player||this.provideFallback(),this.setIconType(),e.resolve(),i},AblePlayer.prototype.setPlayerSize=function(t,e){this.$media.attr("id"),"audio"===this.mediaType?this.playerWidth&&this.$ableWrapper.css("width",this.playerWidth+"px"):t>0&&e>0&&(this.playerWidth=t,this.playerHeight=e,this.aspectRatio=e/t)},AblePlayer.prototype.setIconType=function(){var e,i,s;if(this.forceIconType)return!1;document.createElementNS&&document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect?this.iconType="svg":(window.getComputedStyle?(t("span.icon-play").length?i=t("span.icon-play"):(e=t("<span>",{class:"icon-play able-clipped"}),t("body").append(e),i=e),void 0!==(s=window.getComputedStyle(i.get(0),null).getPropertyValue("font-family"))&&-1!==s.indexOf("able")?this.iconType="font":this.iconType="image"):this.iconType="image",void 0!==e&&e.remove())},AblePlayer.prototype.setupInstance=function(){var e=new t.Deferred,i=e.promise();return this.$media.attr("id")?this.mediaId=this.$media.attr("id"):(this.mediaId="ableMediaId_"+this.ableIndex,this.$media.attr("id",this.mediaId)),e.resolve(),i},AblePlayer.prototype.setupInstancePlaylist=function(){var e=this;if(this.hasPlaylist=!1,t(".able-playlist").each(function(){if(t(this).data("player")===e.mediaId){e.hasPlaylist=!0,e.$playlist=t
16934(this).find("li"),t(this).find("li[data-youtube-id]").each(function(){var i=t(this).attr("data-youtube-id"),s=e.getYouTubePosterUrl(i,"120"),a=t("<img>",{src:s,alt:""});t(this).find("button").prepend(a)}),t(this).find("li[data-vimeo-id]").each(function(){var i=t(this).attr("data-youtube-id"),s=e.getVimeoPosterUrl(i,"120"),a=t("<img>",{src:s,alt:""});t(this).find("button").prepend(a)}),t(this).find("li span").attr("aria-hidden","true"),e.playlistIndex=0;var i=t(this).data("embedded");void 0!==i&&!1!==i?e.playlistEmbed=!0:e.playlistEmbed=!1}}),this.hasPlaylist&&this.loop&&this.media.removeAttribute("loop"),this.hasPlaylist&&this.playlistEmbed){var i=this.$playlist.parent();this.$playlistDom=i.clone(),i.remove()}this.hasPlaylist&&0===this.$sources.length&&(this.cuePlaylistItem(0),this.$sources=this.$media.find("source"))},AblePlayer.prototype.recreatePlayer=function(){var e,i,s,a,n;if(!this.player){console.log("Can't create player; no appropriate player type detected.");return}return i=(e=new t.Deferred).promise(),s=this,this.playerDeleted=!1,this.recreatingPlayer=!0,this.playerCreated||(this.loadCurrentPreferences(),this.injectPlayerCode(),this.resizePlayer(this.media.videoWidth,this.media.videoHeight)),this.get
16934SampleDescriptionText(),this.initSignLanguage(),this.initPlayer().then(function(){s.getTracks().then(function(){s.initDescription().then(function(){s.setupTracks().then(function(){s.hasClosedDesc&&(!s.$descDiv||s.$descDiv&&!t.contains(s.$ableDiv[0],s.$descDiv[0]))&&s.injectTextDescriptionArea(),s.initSpeech("init"),s.setupTranscript().then(function(){s.initStenoFrame().then(function(){s.stenoMode&&s.$stenoFrame&&(s.stenoFrameContents=s.$stenoFrame.contents()),s.getMediaTimes().then(function(t){for(s.duration=t.duration,s.elapsed=t.elapsed,s.setFullscreen(!1),void 0===s.volume&&(s.volume=s.defaultVolume),s.volume&&s.setVolume(s.volume),s.transcriptType&&(s.addTranscriptAreaEvents(),s.updateTranscript()),s.captions.length&&s.initDefaultCaption(),s.setMediaAttributes(),s.addControls(),s.addEventListeners(),a=s.getPreferencesGroups(),n=0;n<a.length;n++)s.injectPrefsForm(a[n]);s.setupPopups(),s.updateCaption(),s.injectVTS(),s.chaptersDivLocation&&s.populateChaptersDiv(),s.showSearchResults(),"html5"!==s.player||s.loadingMedia||(s.$media[0].load(),s.loadingMedia=!0),setTimeout(function(){s.refreshControls("init"),e.resolve()},100)})})})})})})},function(){s.provideFallback()}),i},AblePlayer.prototype.initPlayer=function(){var e,i=this;"html5"===this.player?e=this.initHtml5Player():"youtube"===this.player?e=this.initYouTubePlayer():"vimeo"===this.player&&(e=this.initVimeoPlayer());var s=new t.Deferred,a=s.promise();return e.done(function(){i.useFixedSeekInterval?(i.seekInterval||(i.seekInterval=i.defaultSeekInterval),i.seekIntervalCalculated=!0):i.setSeekInterval(),s.resolve()}).fail(function(){s.reject()}),a},AblePlayer.prototype.initStenoFrame=function(){var e,i,s;return e=this,s=(i=new t.Deferred).promise(),this.stenoMode&&this.$stenoFrame?(this.$stenoFrame[0].contentWindow,"complete"==document.readyState)?i.resolve():this.$stenoFrame.on("load",function(){i.resolve()}):i.resolve(),s},AblePlayer.prototype.setSeekInterval=function(){var t,e;if(t=this,this.seekInterval=this.defaultSeekInterval,void 0===(e=this.useChapterTimes?this.chapterDuration:this.duration)||e<1){this.seekIntervalCalculated=!1;return}e<=20?this.seekInterval=5:e<=30?this.seekInterval=6:e<=40?this.seekInterval=8:e<=100?this.seekInterval=10:this.seekInterval=e/10,this.seekIntervalCalculated=!0},AblePlayer.prototype.initDefaultCaption=function(){var t,e;if((t=this.captions).length>0){for(e=0;e<t.length;e++)!0===t[e].def&&(this.captionLang=t[e].language,this.selectedCaptions=t[e]);if(void 0===this.captionLang)for(e=0;e<t.length;e++)t[e].language===this.lang&&(this.captionLang=t[e].language,this.selectedCaptions=t[e]);void 0===this.captionLang&&(this.captionLang=t[0].language,this.selectedCaptions=t[0]),void 0!==this.captionLang&&(this.$transcriptLanguageSelect&&this.$transcriptLanguageSelect.find("option[lang="+this.captionLang+"]").prop("selected",!0),this.syncTrackLanguages("init",this.captionLang)),"vimeo"===this.player&&(this.usingVimeoCaptions&&1==this.prefCaptions?this.vimeoPlayer.enableTextTrack(this.captionLang).then(function(t){}).catch(function(t){switch(t.name){case"InvalidTrackLanguageError":case"InvalidTrackError":console.log("No "+track.kind+" track is available in the specified language ("+track.label+")");break;default:console.log("Error loading "+track.label+" "+track.kind+" track")}}):this.vimeoPlayer.disableTextTrack().then(function(){}).catch(function(t){console.log("Error disabling Vimeo text track: ",t)}))}},AblePlayer.prototype.initHtml5Player=function(){var e=new t.Deferred,i=e.promise();return e.resolve(),i},AblePlayer.prototype.setMediaAttributes=function(){this.$media.attr("tabindex",-1);var t=this.$media.get(0).textTracks;if(t)for(var e=0;e<t.length;)t[e].mode="disabled",e+=1},AblePlayer.prototype.getPlayer=function(){return this.testFallback?null:this.youTubeId?"video"!==this.mediaType?null:"youtube":this.vimeoId?"video"!==this.mediaType?null:"vimeo":this.media.canPlayType?"html5":null}}(jQuery),function(t){AblePlayer.prototype.setCookie=function(t){Cookies.set("Able-Player",JSON.stringify(t),{expires:90,sameSite:"strict"})},AblePlayer.prototype.getCookie=function(){var t,e={preferences:{},sign:{},transcript:{},voices:[]};
16934try{t=JSON.parse(Cookies.get("Able-Player"))}catch(i){this.setCookie(e),t=e}return t||e},AblePlayer.prototype.updateCookie=function(t){var e,i,s,a,n,r,o,h;if(e=this.getCookie(),"transcript"===t||"sign"===t)"transcript"===t?(s=(i=this.$transcriptArea).position(),void 0===e.transcript&&(e.transcript={}),e.transcript.position=i.css("position"),e.transcript.zindex=i.css("z-index"),e.transcript.top=s.top,e.transcript.left=s.left,e.transcript.width=i.width(),e.transcript.height=i.height()):"sign"===t&&(s=(i=this.$signWindow).position(),void 0===e.sign&&(e.sign={}),e.sign.position=i.css("position"),e.sign.zindex=i.css("z-index"),e.sign.top=s.top,e.sign.left=s.left,e.sign.width=i.width(),e.sign.height=i.height());else if("voice"===t){void 0===e.voices&&(e.voices=[]),o=!1;for(var l=0;l<e.voices.length;l++)e.voices[l].lang===this.prefDescVoiceLang&&(o=!0,e.voices[l].name=this.prefDescVoice);o||(h={name:this.prefDescVoice,lang:this.prefDescVoiceLang},e.voices.push(h))}else for(n=0,a=this.getAvailablePreferences();n<a.length;n++)(r=a[n].name)==t&&(e.preferences[r]=this[r]);this.setCookie(e)},AblePlayer.prototype.getPreferencesGroups=function(){return this.usingYouTubeCaptions?["captions","descriptions","keyboard"]:this.usingVimeoCaptions?["descriptions","keyboard"]:["captions","descriptions","keyboard","transcript"]},AblePlayer.prototype.getAvailablePreferences=function(){var t=[];return t.push({name:"prefAltKey",label:this.tt.prefAltKey,group:"keyboard",default:1}),t.push({name:"prefCtrlKey",label:this.tt.prefCtrlKey,group:"keyboard",default:1}),t.push({name:"prefShiftKey",label:this.tt.prefShiftKey,group:"keyboard",default:0}),t.push({name:"prefTranscript",label:null,group:"transcript",default:0}),t.push({name:"prefHighlight",label:this.tt.prefHighlight,group:"transcript",default:1}),t.push({name:"prefAutoScrollTranscript",label:null,group:"transcript",default:1}),t.push({name:"prefTabbable",label:this.tt.prefTabbable,group:"transcript",default:0}),t.push({name:"prefCaptions",label:null,group:"captions",default:this.defaultStateCaptions}),this.usingYouTubeCaptions||("video"===this.mediaType&&t.push({name:"prefCaptionsPosition",label:this.tt.prefCaptionsPosition,group:"captions",default:this.defaultCaptionsPosition}),t.push({name:"prefCaptionsFont",label:this.tt.prefCaptionsFont,group:"captions",default:"sans-serif"})),t.push({name:"prefCaptionsSize",label:this.tt.prefCaptionsSize,group:"captions",default:"100%"}),this.usingYouTubeCaptions||(t.push({name:"prefCaptionsColor",label:this.tt.prefCaptionsColor,group:"captions",default:"white"}),t.push({name:"prefCaptionsBGColor",label:this.tt.prefCaptionsBGColor,group:"captions",default:"black"}),t.push({name:"prefCaptionsOpacity",label:this.tt.prefCaptionsOpacity,group:"captions",default:"100%"})),"video"===this.mediaType&&(t.push({name:"prefDesc",label:null,group:"descriptions",default:this.defaultStateDescriptions}),t.push({name:"prefDescMethod",label:null,group:"descriptions",default:"video"}),t.push({name:"prefDescVoice",label:this.tt.prefDescVoice,group:"descriptions",default:null}),t.push({name:"prefDescPitch",label:this.tt.prefDescPitch,group:"descriptions",default:1}),t.push({name:"prefDescRate",label:this.tt.prefDescRate,group:"descriptions",default:1}),t.push({name:"prefDescVolume",label:this.tt.volume,group:"descriptions",default:1}),t.push({name:"prefDescPause",label:this.tt.prefDescPause,group:"descriptions",default:this.defaultDescPause}),t.push({name:"prefDescVisible",label:this.tt.prefDescVisible,group:"descriptions",default:0})),t.push({name:"prefSign",label:null,group:null,default:0}),t},AblePlayer.prototype.loadCurrentPreferences=function(){for(var t=this.getAvailablePreferences(),e=this.getCookie(),i=0;i<t.length;i++){var s=t[i].name,a=t[i].default;void 0!==e.preferences[s]?this[s]=e.preferences[s]:(e.preferences[s]=a,this[s]=a)}void 0!==e.voices&&(this.prefVoices=e.voices),this.setCookie(e)},AblePlayer.prototype.injectPrefsForm=function(e){var i,s,a,n,r,o,h,l,p,c,u,d,f,g,_,v,m,y,b,k,C,T,w,P,D,x,S,B,I,N,A,M,V,F,L,z,O,E,R,W,H;if(i=this,s=this.getAvailablePreferences(),(a=t("<div>",{class:"able-prefs-form "})).addClass("able-prefs-form-"+e),"captions"==e)n=this.tt.prefTitleCaptions;else if("descriptions"==e){n=this.tt.prefTitleDescriptions;var o=t("<p>",{text:this.tt.prefIntroDescription1}),q=t("<ul>"),K=t("<li>",{text:this.tt.prefDescFormatOption1}),j=t("<li>",{text:this.tt.prefDescFormatOption2});q.append(K,j),this.hasOpenDesc&&this.hasClosedDesc?(A=this.tt.prefIntroDescription2+" ",A+="<strong>"+this.tt.prefDescFormatOption1b+"</strong>",A+=" <em>"+this.tt.and+"</em> <strong>"+this.tt.prefDescFormatOption2b+"</strong>."):this.hasOpenDesc?(A=this.tt.prefIntroDescription2,A+=" <strong>"+this.tt.prefDescFormatOption1b+"</strong>."):this.hasClosedDesc?(A=this.tt.prefIntroDescription2,A+=" <strong>"+this.tt.prefDescFormatOption2b+"</strong>."):A=this.tt.prefIntroDescriptionNone,h=t("<p>",{html:A}),l=this.tt.prefIntroDescription3,(this.hasOpenDesc||this.hasClosedDesc)&&(l+=" "+this.tt.prefIntroDescription4),p=t("<p>",{text:l}),a.append(o,q,h,p)}else"keyboard"==e?(n=this.tt.prefTitleKeyboard,r=this.tt.prefIntroKeyboard1,r+=" "+this.tt.prefIntroKeyboard2,r+=" "+this.tt.prefIntroKeyboard3,o=t("<p>",{text:r}),a.append(o)):"transcript"==e&&(n=this.tt.prefTitleTranscript);for(d=t("<div>").attr("role","group"),f="able-prefs-"+e,v=(g=this.mediaId+"-prefs-"+e)+"-legend",d.addClass(f).attr("id",g),"keyboard"===e?((_=t("<h2>"+this.tt.prefHeadingKeyboard1+"</h2>")).attr("id",v),d.attr("aria-labelledby",v),d.append(_)):"descriptions"===e&&((_=t("<h2>"+this.tt.prefHeadingTextDescription+"</h2>")).attr("id",v),d.attr("aria-labelledby",v),d.append(_)),c=0;c<s.length;c++)if(s[c].group==e&&s[c].label){if(b="able-"+(m=s[c].name),k=this.mediaId+"_"+m,y=t("<div>").addClass(b),"captions"===e){for(C=t('<label for="'+k+'"> '+s[c].label+"</label>"),T=t("<select>",{name:m,id:k}),"prefCaptions"!==m&&"prefCaptionsStyle"!==m&&T.change(function(){B=t(this).attr("name"),i.stylizeCaptions(i.$sampleCapsDiv,B)}),w=this.getCaptionsOptions(m),u=0;u<w.length;u++)"prefCaptionsPosition"===m?"overlay"===(D=w[u])?S=this.tt.captionsPositionOverlay:"below"===D&&(D=w[u],S=this.tt.captionsPositionBelow):"prefCaptionsFont"===m||"prefCaptionsColor"===m||"prefCaptionsBGColor"===m?(D=w[u][0],S=w[u][1]):"prefCaptionsOpacity"===m?(D=w[u],S=w[u],"0%"===D?S+=" ("+this.tt.transparent+")":"100%"===D&&(S+=" ("+this.tt.s
16934olid+")")):(D=w[u],S=w[u]),P=t("<option>",{value:D,text:S}),this[m]===D&&P.prop("selected",!0),T.append(P);y.append(C,T)}else if("descriptions"===e){if(C=t('<label for="'+k+'"> '+s[c].label+"</label>"),"prefDescPause"===m||"prefDescVisible"===m)y.addClass("able-prefs-checkbox"),T=t("<input>",{type:"checkbox",name:m,id:k,value:"true"}),1===this[m]&&T.prop("checked",!0),y.append(T,C);else if(this.synth){if(y.addClass("able-prefs-select"),T=t("<select>",{name:m,id:k}),"prefDescVoice"===m&&this.descVoices){for(u=0,M=this.getPrefDescVoice();u<this.descVoices.length;u++)D=this.descVoices[u].name,x=this.descVoices[u].lang.substring(0,2).toLowerCase(),S=D+" ("+this.descVoices[u].lang+")",P=t("<option>",{value:D,"data-lang":x,text:S}),M===D&&P.prop("selected",!0),T.append(P);this.$voiceSelectField=T}else if("prefDescPitch"==m?w=[0,.5,1,1.5,2]:"prefDescRate"==m?w=[.7,.8,.9,1,1.1,1.2,1.5,2,2.5,3]:"prefDescVolume"==m&&(w=[.1,.2,.3,.4,.5,.6,.7,.8,.9,1]),void 0!==w)for(u=0;u<w.length;u++)D=w[u],S=this.makePrefsValueReadable(m,D),P=t("<option>",{value:D,text:S}),this[m]==D&&P.prop("selected",!0),T.append(P),y.append(C,T);T.on("change",function(){i.announceDescriptionText("sample",i.currentSampleText)}),y.append(C,T)}}else C=t('<label for="'+k+'"> '+s[c].label+"</label>"),T=t("<input>",{type:"checkbox",name:m,id:k,value:"true"}),1===this[m]&&T.prop("checked",!0),"keyboard"===e&&T.on("change",function(){"prefAltKey"===(B=t(this).attr("name"))?(I=".able-modkey-alt",N=i.tt.prefAltKey+" + "):"prefCtrlKey"===B?(I=".able-modkey-ctrl",N=i.tt.prefCtrlKey+" + "):"prefShiftKey"===B&&(I=".able-modkey-shift",N=i.tt.prefShiftKey+" + "),t(this).is(":checked")?t(I).text(N):t(I).text("")}),y.append(T,C);d.append(y)}if(a.append(d),"captions"===e)this.usingYouTubeCaptions||(this.$sampleCapsDiv=t("<div>",{class:"able-captions-sample"}).text(this.tt.sampleCaptionText),a.append(this.$sampleCapsDiv),this.stylizeCaptions(this.$sampleCapsDiv));else if("descriptions"===e)this.synth&&(this.$sampleDescDiv=t("<div>",{class:"able-desc-sample"}).text(this.tt.sampleDescriptionText),a.append(this.$sampleDescDiv),this.currentSampleText=this.tt.sampleDescriptionText);else if("keyboard"===e){for(c=0,V=t("<h2>",{text:this.tt.prefHeadingKeyboard2}),F=t("<ul>"),L=[],z=[];c<this.controls.length;c++)"play"===this.controls[c]?(L.push(this.tt.play+"/"+this.tt.pause),z.push("p</span> <em>"+this.tt.or+'</em> <span class="able-help-modifiers"> '+this.tt.spacebar)):"restart"===this.controls[c]?(L.push(this.tt.restart),z.push("s")):"previous"===this.controls[c]?(L.push(this.tt.prevTrack),z.push("b")):"next"===this.controls[c]?(L.push(this.tt.nextTrack),z.push("n")):"rewind"===this.controls[c]?(L.push(this.tt.rewind),z.push("r")):"forward"===this.controls[c]?(L.push(this.tt.forward),z.push("f")):"volume"===this.controls[c]?(L.push(this.tt.volume),z.push("v</span> <em>"+this.tt.or+'</em> <span class="able-modkey">1-9'),L.push(this.tt.mute+"/"+this.tt.unmute),z.push("m")):"captions"===this.controls[c]?(this.captions.length>1?L.push(this.tt.captions):this.captionsOn?L.push(this.tt.hideCaptions):L.push(this.tt.showCaptions),z.push("c")):"descriptions"===this.controls[c]?(this.descOn?L.push(this.tt.turnOffDescriptions):L.push(this.tt.turnOnDescriptions),z.push("d")):"prefs"===this.controls[c]?(L.push(this.tt.preferences),z.push("e")):"help"===this.controls[c]&&(L.push(this.tt.help),z.push("h"));for(c=0;c<z.length;c++)O='<span class="able-modkey-alt">',1===this.prefAltKey&&(O+=this.tt.prefAltKey+" + "),O+="</span>",O+='<span class="able-modkey-ctrl">',1===this.prefCtrlKey&&(O+=this.tt.prefCtrlKey+" + "),O+="</span>",O+='<span class="able-modkey-shift">',1===this.prefShiftKey&&(O+=this.tt.prefShiftKey+" + "),O+="</span>",O+='<span class="able-modkey">'+z[c]+"</span>",O+=" = "+L[c],E=t("<li>",{html:O}),F.append(E);O='<span class="able-modkey">'+this.tt.escapeKey+"</span>",O+=" = "+this.tt.escapeKeyFunction,E=t("<li>",{html:O}),F.append(E),a.append(V,F)}t("body").append(a),R=new AccessibleDialog(a,this.$prefsButton,"dialog",!0,n,o,i.tt.closeButtonLabel,"32em"),a.append("<hr>"),W=t('<button class="modal-button">'+this.tt.save+"</button>"),H=t('<button class="modal-button">'+this.tt.cancel+"</button>"),W.click(function(){R.hide(),i.savePrefsFromForm()}),H.click(function(){R.hide(),i.resetPrefsForm()}),a.append(W),a.append(H),("captions"===e||"transcript"===e)&&d.attr("aria-labelledby",R.titleH1.attr("id")),"captions"===e?this.captionPrefsDialog=R:"descriptions"===e?this.descPrefsDialog=R:"keyboard"===e?this.keyboardPrefsDialog=R:"transcript"===e&&(this.transcriptPrefsDialog=R),t("div.able-prefs-form button.modalCloseButton").click(function(){i.resetPrefsForm()}),t("div.able-prefs-form").keydown(function(t){27===t.which&&i.resetPrefsForm()})},AblePlayer.prototype.getPrefDescVoice=function(){var t,e,i;if(t=this.selectedDescriptions?this.selectedDescriptions.language:this.captionLang?this.captionLang:this.lang,(e=this.getCookie()).voices){for(i=0;i<e.voices.length;i++)if(e.voices[i].lang===t)return e.voices[i].name}return null},AblePlayer.prototype.rebuildDescPrefsForm=function(){var e,i,s,a;for(this.$voiceSelectField=t("#"+this.mediaId+"_prefDescVoice"),this.$voiceSelectField.empty(),e=0;e<this.descVoices.length;e++)s=(i=this.descVoices[e].name)+" ("+this.descVoices[e].lang+")",a=t("<option>",{value:i,"data-lang":this.descVoices[e].lang.substring(0,2).toLowerCase(),text:s}),this.prefDescVoice==i&&a.prop("selected",!0),this.$voiceSelectField.append(a)},AblePlayer.prototype.makePrefsValueReadable=function(t,e){if("prefDescPitch"===t){if(0===e)return this.tt.prefDescPitch1;
16934if(.5===e)return this.tt.prefDescPitch2;if(1===e)return this.tt.prefDescPitch3;else if(1.5===e)return this.tt.prefDescPitch4;else if(2===e)return this.tt.prefDescPitch5}else if("prefDescRate"===t){if(.7===e)return 1;if(.8===e)return 2;if(.9===e)return 3;else if(1===e)return 4;else if(1.1===e)return 5;else if(1.2===e)return 6;else if(1.5===e)return 7;else if(2===e)return 8;else if(2.5===e)return 9;else if(3===e)return 10}else if("prefDescVolume"===t)return 10*e;return e},AblePlayer.prototype.resetPrefsForm=function(){var e,i,s,a,n,r;for(a=0,e=this,i=this.getCookie(),s=this.getAvailablePreferences();a<s.length;a++)n=s[a].name,r=this.mediaId+"_"+n,-1!==n.indexOf("Captions")&&"prefCaptions"!==n?t('select[name="'+n+'"]').val(i.preferences[n]):1===this[n]?t('input[name="'+n+'"]').prop("checked",!0):t('input[name="'+n+'"]').prop("checked",!1);this.stylizeCaptions(this.$sampleCapsDiv)},AblePlayer.prototype.savePrefsFromForm=function(){var e,i,s,a,n,r,o,h,l,p,c,u;o=0,l=0,p=!1,e=this.getCookie(),i=this.getAvailablePreferences();for(var d=0;d<i.length;d++)if(i[d].label){if(s=i[d].name,a=this.mediaId+"_"+s,"prefDescVoice"===s){void 0===e.voices&&(e.voices=[]),n=this.mediaId+"_prefDescVoice",this.prefDescVoice=t("select#"+n).find(":selected").val(),this.prefDescVoiceLang=t("select#"+n).find(":selected").attr("data-lang"),h=!1;for(var f=0;f<e.voices.length;f++)e.voices[f].lang===this.prefDescVoiceLang&&(h=!0,e.voices[f].name=this.prefDescVoice);h||(r={name:this.prefDescVoice,lang:this.prefDescVoiceLang},e.voices.push(r)),o++}else"prefDescMethod"==s?(this.prefDescMethod="video",this.prefDescMethod!==e.preferences.prefDescMethod&&(e.preferences.prefDescMethod=this.prefDescMethod,o++)):-1!==s.indexOf("Captions")&&"prefCaptions"!==s?(u=t('select[id="'+a+'"]').val(),e.preferences[s]!==u&&(e.preferences[s]=u,this[s]=u,o++,l++),"prefCaptionsSize"===s&&(p=!0,c=u)):-1!==s.indexOf("Desc")&&"prefDescPause"!==s&&"prefDescVisible"!==s?(u=t('select[id="'+a+'"]').val(),e.preferences[s]!==u&&(e.preferences[s]=u,this[s]=u,o++)):t('input[id="'+a+'"]').is(":checked")?(e.preferences[s]=1,1===this[s]||(this[s]=1,o++)):(e.preferences[s]=0,1===this[s]&&(this[s]=0,o++))}if(o>0?(this.setCookie(e),this.showAlert(this.tt.prefSuccess)):this.showAlert(this.tt.prefNoChange),"youtube"===this.player&&void 0!==this.usingYouTubeCaptions&&this.usingYouTubeCaptions&&p&&this.youTubePlayer.setOption("captions","fontSize",this.translatePrefs("size",c,"youtube")),AblePlayerInstances.length>1)for(var d=0;d<AblePlayerInstances.length;d++)AblePlayerInstances[d].updatePrefs(),AblePlayerInstances[d].geteferences(),AblePlayerInstances[d].resetPrefsForm(),l>0&&(AblePlayerInstances[d].stylizeCaptions(AblePlayerInstances[d].$captionsDiv),void 0!==AblePlayerInstances[d].$descDiv&&AblePlayerInstances[d].stylizeCaptions(AblePlayerInstances[d].$descDiv));else this.updatePrefs(),l>0&&(this.stylizeCaptions(this.$captionsDiv),void 0!==this.$descDiv&&this.stylizeCaptions(this.$descDiv))},AblePlayer.prototype.updatePrefs=function(){this.$transcriptDiv&&(1===this.prefTabbable?this.$transcriptDiv.find("span.able-transcript-seekpoint").attr("tabindex","0"):this.$transcriptDiv.find("span.able-transcript-seekpoint").removeAttr("tabindex"),0===this.prefHighlight&&this.$transcriptDiv.find("span").removeClass("able-highlight")),this.updateCaption(),this.initDescription()},AblePlayer.prototype.usingModifierKeys=function(t){return(1!==this.prefAltKey||!!t.altKey)&&(1!==this.prefCtrlKey||!!t.ctrlKey)&&(1!==this.prefShiftKey||!!t.shiftKey)}}(jQuery),function(t){function e(t,e){for(var s=[],a=0;a<e.length;a++)s.push(i(t,e[a]));return s}function i(t,e){var i=e(t);if(null!==t.error)throw t.error;return i}function s(t,e){for(var i=0;i<e.length;i++)"\n"===e[i]?(t.column=1,t.line+=1):t.column+=1}function a(t,e){var i=t.text.substring(0,e);return s(t,i),t.text=t.text.substring(e),i}function n(t,e){var i,a=t.text.indexOf("\n");return -1===a?(i=t.text,s(t,i),t.text=""):(i=t.text.substring(0,a),s(t,i+"\n"),t.text=t.text.substring(a+1)),i}function r(t){var e=t.text.indexOf("\n");return -1===e?t.text:t.text.substring(0,e)}function o(t){e(t,[m,y]);var i=t.text[0];" "===i||" "===i||"\n"===i?e(t,[w,h,D,u]):t.error="WEBVTT signature not followed by whitespace."}function h(t){for(;;){var e=r(t);if(-1!==e.indexOf("-->"))return;if(0===e.length)return;var s=i(t,p);t.metadata[s[0]]=s[1],i(t,w)}}function l(t){var e=[],i=t.indexOf(" ");i>=0&&e.push(i);var s=t.indexOf(" ");s>=0&&e.push(s);var a=t.indexOf("\n");return a>=0&&e.push(a),Math.min.apply(null,e)}function p(t){var e,i=t.text.indexOf("\n"),s=a(t,i),n=s.indexOf(":");if(-1===n){t.error="Missing colon.";return}return[s.substring(0,n),s.substring(n+1)]}function c(t){var e,i=l(t.text),s=a(t,i),n=s.indexOf(":");if(-1===n){t.error="Missing colon.";return}return[s.substring(0,n),s.substring(n+1)]}function u(s){for(;;){var a=r(s);if(0===a.indexOf("NOTE")&&(4===a.length||" "===a[4]||" "===a[4]))e(s,[v,P]);else if(0===t.trim(a).length&&s.text.length>0)i(s,P);else{if(!(t.trim(a).length>0))return;i(s,d)}}}function d(t){var s,o,h=r(t);if(-1===h.indexOf("-->")&&(s=n(t),-1===(h=r(t)).indexOf("-->"))){o="Invalid WebVTT file: "+t.src+"\n",o+="Line: "+t.line+", ",o+="Column: "+t.column+"\n",o+="Expected cue timing for cueId '"+s+"' but found: "+h+"\n",console.warn?console.warn(o):console.log&&console.log(o);return}var l=e(t,[x,T,b,T,x]),p=l[0],c=l[4];if(p>=c){t.error="Start time is not sooner than end time.";return}i(t,C);var u=i(t,f);a(t,1);var d=i(t,g);void 0===s&&(s=t.cues.length+1),t.cues.push({id:s,start:p,end:c,settings:u,components:d})}function f(t){for(var e={};t.text.length>0&&"\n"!==t.text[0];){var s=i(t,c);e[s[0]]=s[1],i(t,C)}return e}function g(e){for(var s={type:"internal",tagName:"",value:"",classes:[],annotation:"",parent:null,children:[],language:""},n=s,o=[];e.text.length>0;){var h=r(e);
16934if(-1!==h.indexOf("-->")||/^\s+$/.test(h))break;if(e.text.length>=2&&"\n"===e.text[0]&&"\n"===e.text[1]){a(e,2);break}var l=_(e);if("string"===l.type)n.children.push(l);else if("startTag"===l.type)l.type=l.tagName,l.parent=n,-1!==t.inArray(l.tagName,["i","b","u","ruby"])?(o.length>0&&(n.language=o[o.length-1]),n.children.push(l),n=l):"rt"===l.tagName&&"ruby"===n.tagName?(o.length>0&&(n.language=o[o.length-1]),n.children.push(l),n=l):"c"===l.tagName?(l.value=l.annotation,o.length>0&&(n.language=o[o.length-1]),n.children.push(l),n=l):"v"===l.tagName?(l.value=l.annotation,o.length>0&&(n.language=o[o.length-1]),n.children.push(l),n=l):"lang"===l.tagName&&(o.push(l.annotation),o.length>0&&(n.language=o[o.length-1]),n.children.push(l),n=l);else if("endTag"===l.type)l.tagName===n.type&&-1!==t.inArray(l.tagName,["c","i","b","u","ruby","rt","v"])?n=n.parent:"lang"===l.tagName&&"lang"===n.type?(n=n.parent,o.pop()):"ruby"===l.tagName&&"rt"===n.type&&(n=n.parent.parent);else if("timestampTag"===l.type){var p={text:l.value,error:null,metadata:{},cues:[],line:1,column:1};try{var c=i(p,x);0===p.text.length&&(l.value=c,n.push(l))}catch(u){}}}return s}function _(e){for(var i,s="data",n=[],r="",o={type:"",tagName:"",value:"",classes:[],annotation:"",children:[]};;){if(i=e.text.length>=2&&"\n"===e.text[0]&&"\n"===e.text[1]?"\x04":e.text.length>0?e.text[0]:"\x04","data"===s){if("&"===i)r="&",s="escape";else if("<"===i){if(0!==n.length)return o.type="string",o.value=n.join(""),o;s="tag"}else{if("\x04"===i)return{type:"string",value:n.join("")};n.push(i)}}else if("escape"===s){if("&"===i)n.push(r),r="&";else if(i.match(/[0-9a-z]/))r+=i;else if(";"===i)"&"===r?n.push("&"):"<"===r?n.push("<"):">"===r?n.push(">"):"&lrm"===r?n.push("â"):"&rlm"===r?n.push("â"):" "===r?n.push("\xa0"):(n.push(r),n.push(";")),s="data";else{if("<"===i||"\x04"===i||" "===i||"\n"===i||"\f"===i||" "===i)return n.push(r),o.type="string",o.value=n.join(""),o;n.push(r),s="data"}}else if("tag"===s){if(" "===i||"\n"===i||"\f"===i||" "===i)s="startTagAnnotation";else if("."===i)s="startTagClass";else if("/"===i)s="endTag";else if(i.match("[0-9]"))s="timestampTag",n.push(i);else if(">"===i){a(e,1);break}else{if("\x04"===i)return o.tagName="",o.type="startTag",o;n.push(i),s="startTag"}}else if("startTag"===s){if(" "===i||"\f"===i||" "===i)s="startTagAnnotation";else if("\n"===i)r=i,s="startTagAnnotation";else if("."===i)s="startTagClass";else{if(">"===i)return a(e,1),o.tagName=n.join(""),o.type="startTag",o;if("\x04"===i)return o.tagName=n.join(""),o.type="startTag",o;n.push(i)}}else if("startTagClass"===s){if(" "===i||"\f"===i||" "===i)o.classes.push(r),r="",s="startTagAnnotation";else if("\n"===i)o.classes.push(r),r=i,s="startTagAnnotation";else if("."===i)o.classes.push(r),r="";else{if(">"===i)return a(e,1),o.classes.push(r),o.type="startTag",o.tagName=n.join(""),o;if("\x04"===i)return o.classes.push(r),o.type="startTag",o.tagName=n.join(""),o;r+="c"}}else if("startTagAnnotation"===s){if(">"===i)return a(e,1),r=t.trim(r).replace(/ +/," "),o.type="startTag",o.tagName=n.join(""),o.annotation=r,o;if("\x04"===i)return r=t.trim(r).replace(/ +/," "),o.type="startTag",o.tagName=n.join(""),o.annotation=r,o;r+=i}else if("endTag"===s){if(">"===i)return a(e,1),o.type="endTag",o.tagName=n.join(""),o;if("\x04"===i)return o.type="endTag",o.tagName=n.join(""),o;n.push(i)}else if("timestampTag"===s){if(">"===i)return a(e,1),o.type="timestampTag",o.name=n.join(""),o;if("\x04"===i)return o.type="timestampTag",o.name=n.join(""),o;n.push(i)}else throw"Unknown tokenState "+s;a(e,1)}}function v(e){if(-1!==n(e).indexOf("-->")){e.error="Invalid syntax: --> in NOTE line.";return}for(;;){var i=r(e);if(0===t.trim(i).length)return;if(-1!==i.indexOf("-->")){e.error="Invalid syntax: --> in comment.";return}n(e)}}function m(t){"\uFEFF"===t.text[0]&&a(t,1)}function y(t){"WEBVTT"===t.text.substring(0,6)?a(t,6):t.error="Invalid signature."}function b(t){t.text.length<3||"-->"!==t.text.substring(0,3)?t.error="Missing -->":a(t,3)}function k(t){" "===t.text[0]||" "===t.text[0]?a(t,1):t.error="Missing space."}function C(t){for(;" "===t.text[0]||" "===t.text[0];)a(t,1)}function T(t){for(var e=0;" "===t.text[0]||" "===t.text[0];)a(t,1),e+=1;0===e&&(t.error="Missing space.")}function w(t){var e=t.text.indexOf("\n");-1===e?t.error="Missing EOL.":a(t,e+1)}function P(e){for(;e.text.length>0;){var i=r(e);
16934if(0===t.trim(i).length)n(e);else break}}function D(e){for(var i=0;e.text.length>0;){var s=r(e);if(0===t.trim(s).length)n(e),i+=1;else break}0===i&&(e.error="Missing empty line.")}function x(t){var e=l(t.text);if(-1===e){t.error("Missing timing.");return}var i=a(t,e),s=/((\d\d):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(i);if(!s){t.error="Unable to parse timestamp";return}var n=0,r=s[2],o=s[4];if(o){if(parseInt(o,10)>59){t.error="Invalid minute range";return}r&&(n+=3600*parseInt(r,10)),n+=60*parseInt(o,10);var h=s[5];if(parseInt(h,10)>59){t.error="Invalid second range";return}n+=parseInt(h,10),n+=parseInt(s[6],10)/1e3}else n+=parseInt(s[7],10),n+=parseInt(s[8],10)/1e3;return n}AblePlayer.prototype.parseWebVTT=function(t,e){var s={src:t,text:e=e.replace(/(\r\n|\n|\r)/g,"\n"),error:null,metadata:{},cues:[],line:1,column:1};try{i(s,o)}catch(a){var n="Invalid WebVTT file: "+s.src+"\n";n+="Line: "+s.line+", ",n+="Column: "+s.column+"\n",n+=a,console.warn?console.warn(n):console.log&&console.log(n)}return s}}(jQuery),function(t){AblePlayer.prototype.injectPlayerCode=function(){var e,i;e=this,this.$mediaContainer=this.$media.wrap('<div class="able-media-container"></div>').parent(),this.$ableDiv=this.$mediaContainer.wrap('<div class="able"></div>').parent(),this.$ableWrapper=this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent(),this.$ableWrapper.addClass("able-skin-"+this.skin),this.$ableWrapper.css({width:this.playerWidth+"px"}),"video"===this.mediaType&&"image"!=this.iconType&&("youtube"!==this.player||this.hasPoster)&&this.injectBigPlayButton(),i=t("<div>"),"video"===this.mediaType?i.addClass("able-vidcap-container"):"audio"===this.mediaType&&(i.addClass("able-audcap-container"),i.addClass("captions-off")),this.injectPlayerControlArea(),this.$captionsContainer=this.$mediaContainer.wrap(i).parent(),this.injectAlert(),this.injectPlaylist(),this.injectOffscreenHeading()},AblePlayer.prototype.injectOffscreenHeading=function(){var e;"0"==this.playerHeadingLevel||(void 0===this.playerHeadingLevel&&(this.playerHeadingLevel=this.getNextHeadingLevel(this.$ableDiv)),e="h"+this.playerHeadingLevel.toString(),this.$headingDiv=t("<"+e+">"),this.$ableDiv.prepend(this.$headingDiv),this.$headingDiv.addClass("able-offscreen"),this.$headingDiv.text(this.tt.playerHeading))},AblePlayer.prototype.injectBigPlayButton=function(){var e,i,s,a;e=this,this.$bigPlayButton=t("<button>",{class:"able-big-play-button","aria-hidden":!1,"aria-label":this.tt.play,tabindex:0}),"svg"==this.iconType?(s=t("<svg>",{focusable:"false","aria-hidden":"true",viewBox:(i=this.getSvgData("play"))[0]}),a=t("<path>",{d:i[1]}),s.append(a),this.$bigPlayButton.html(s),this.$bigPlayButton.html(this.$bigPlayButton.html())):(this.$bigPlayIcon=t("<span>",{class:"icon-play"}),this.$bigPlayButton.append(this.$bigPlayIcon)),this.$bigPlayButton.click(function(t){t.preventDefault(),e.handlePlay()}),this.$mediaContainer.append(this.$bigPlayButton)},AblePlayer.prototype.injectPlayerControlArea=function(){this.$playerDiv=t("<div>",{class:"able-player",role:"region","aria-label":this.mediaType+" player"}),this.$playerDiv.addClass("able-"+this.mediaType),this.hasPlaylist&&this.showNowPlaying&&(this.$nowPlayingDiv=t("<div>",{class:"able-now-playing","aria-live":"assertive","aria-atomic":"true"})),this.$controllerDiv=t("<div>",{class:"able-controller"}),this.$controllerDiv.addClass("able-"+this.iconColor+"-controls"),this.$statusBarDiv=t("<div>",{class:"able-status-bar"}),this.$timer=t("<span>",{class:"able-timer"}),this.$elapsedTimeContainer=t("<span>",{class:"able-elapsedTime",text:"0:00"}),this.$durationContainer=t("<span>",{class:"able-duration"}),this.$timer.append(this.$elapsedTimeContainer).append(this.$durationContainer),this.$speed=t("<span>",{class:"able-speed","aria-live":"assertive"}).text(this.tt.speed+": 1x"),this.$status=t("<span>",{class:"able-status","aria-live":"polite"}),this.$statusBarDiv.append(this.$timer,this.$speed,this.$status),this.showNowPlaying?this.$playerDiv.append(this.$nowPlayingDiv,this.$controllerDiv,this.$statusBarDiv):this.$playerDiv.append(this.$controllerDiv,this.$statusBarDiv),"video"===this.mediaType?this.$ableDiv.append(this.$playerDiv):this.$ableDiv.prepend(this.$playerDiv)},AblePlayer.prototype.injectTextDescriptionArea=function(){this.$descDiv=t("<div>",{class:"able-descriptions"}),this.$descDiv.attr({"aria-live":"assertive","aria-atomic":"true"}),this.$descDiv.hide(),this.$ableDiv.append(this.$descDiv)},AblePlayer.prototype.getDefaultWidth=function(t){return"transcript"===t?450:"sign"===t?400:void 0},AblePlayer.prototype.positionDraggableWindow=function(e,i){var s,a,n,r;s=this.getCookie(),"transcript"===e?(n=this.$transcriptArea,void 0!==s.transcript&&(a=s.transcript)):"sign"===e&&(n=this.$signWindow,void 0!==s.transcript&&(a=s.sign)),void 0===a||t.isEmptyObject(a)?(r=this.getOptimumPosition(e,i),void 0===i&&(i=this.getDefaultWidth(e)),n.css({position:r[0],width:i,"z-index":r[3]}),"absolute"===r[0]&&n.css({top:r[1]+"px",left:r[2]+"px"})):(n.css({position:a.position,width:a.width,"z-index":a.zindex}
16934),"absolute"===a.position&&n.css({top:a.top,left:a.left}),this.updateZIndex(e))},AblePlayer.prototype.getOptimumPosition=function(e,i){var s,a,n,r,o,h,l,p,c;return void 0===i&&(i=this.getDefaultWidth(e)),s=5,a=[],n=this.$ableDiv.width(),r=this.$ableDiv.height(),h=(o=this.$ableDiv.offset()).top,l=o.left,p=t(window).width(),c=0,"transcript"===e?void 0!==this.$signWindow&&this.$signWindow.is(":visible")&&(c=this.$signWindow.width()+s):"sign"===e&&void 0!==this.$transcriptArea&&this.$transcriptArea.is(":visible")&&(c=this.$transcriptArea.width()+s),i<p-(l+n+s+c)?(a[0]="absolute",a[1]=0,a[2]=n+c+s):i+s<l?(a[0]="absolute",a[1]=0,a[2]=l-i-s):a[0]="relative",a},AblePlayer.prototype.injectAlert=function(){var e;this.$alertBox=t('<div role="alert"></div>'),this.$alertBox.addClass("able-alert"),this.$alertBox.hide(),this.$alertBox.appendTo(this.$ableDiv),e="audio"==this.mediaType?"-10":"10",this.$alertBox.css({top:e+"px"}),this.$srAlertBox=t('<div role="alert"></div>'),this.$srAlertBox.addClass("able-screenreader-alert"),this.$srAlertBox.appendTo(this.$ableDiv)},AblePlayer.prototype.injectPlaylist=function(){if(!0===this.playlistEmbed){var t=this.$playlistDom.clone();t.insertBefore(this.$statusBarDiv),this.$playlist=t.find("li")}},AblePlayer.prototype.createPopup=function(e,i){var s,a,n,r,o,h,l,p,c,u,d,f,g,_;if(s=this,a=t("<ul>",{id:this.mediaId+"-"+e+"-menu",class:"able-popup",role:"menu"}).hide(),"captions"===e&&a.addClass("able-popup-captions"),"prefs"===e){if(this.prefCats.length>1){for(r=0;r<this.prefCats.length;r++)o=t("<li></li>",{role:"menuitem",tabindex:"-1"}),"captions"===(h=this.prefCats[r])?o.text(this.tt.prefMenuCaptions):"descriptions"===h?o.text(this.tt.prefMenuDescriptions):"keyboard"===h?o.text(this.tt.prefMenuKeyboard):"transcript"===h&&o.text(this.tt.prefMenuTranscript),o.on("click",function(){l=t(this).text(),s.showingPrefsDialog=!0,s.setFullscreen(!1),l===s.tt.prefMenuCaptions?s.captionPrefsDialog.show():l===s.tt.prefMenuDescriptions?s.descPrefsDialog.show():l===s.tt.prefMenuKeyboard?s.keyboardPrefsDialog.show():l===s.tt.prefMenuTranscript&&s.transcriptPrefsDialog.show(),s.closePopups(),s.showingPrefsDialog=!1}),a.append(o);this.$prefsButton.attr("data-prefs-popup","menu")}else 1==this.prefCats.length&&this.$prefsButton.attr("data-prefs-popup",this.prefCats[0])}else if("captions"===e||"chapters"===e){for(r=0,p=!1;r<i.length;r++)c=i[r],(n="captions"!==e||"html5"!==this.player||void 0!==c.cues)&&(o=t("<li></li>",{role:"menuitemradio",tabindex:"-1",lang:c.language}),c.def&&1==this.prefCaptions?(o.attr("aria-checked","true"),p=!0):o.attr("aria-checked","false"),"captions"==e?(o.text(c.label),o.on("click",this.getCaptionClickFunction(c))):"chapters"==e&&(o.text(this.flattenCueForCaption(c)+" - "+this.formatSecondsAsColonTime(c.start)),o.on("click",this.getChapterClickFunction(c.start))),a.append(o));"captions"===e&&(o=t("<li></li>",{role:"menuitemradio",tabindex:"-1"}).text(this.tt.captionsOff),0===this.prefCaptions?(o.attr("aria-checked","true"),p=!0):o.attr("aria-checked","false"),o.on("click",this.getCaptionOffFunction()),a.append(o))}else if("transcript-window"===e||"sign-window"===e)for((u=[]).push({name:"move",label:this.tt.windowMove}),u.push({name:"resize",label:this.tt.windowResize}),u.push({name:"close",label:this.tt.windowClose}),r=0;r<u.length;r++)(o=t("<li></li>",{role:"menuitem",tabindex:"-1","data-choice":u[r].name})).text(u[r].label),o.on("click mousedown",function(i){if(i.stopPropagation(),void 0!==i.button&&0!==i.button)return!1;s.windowMenuClickRegistered||s.finishingDrag||(s.windowMenuClickRegistered=!0,s.handleMenuChoice(e.substring(0,e.indexOf("-")),t(this).attr("data-choice"),i))}),a.append(o);return"captions"!==e||p?"chapters"===e&&(a.find('li:contains("'+this.defaultChapter+'")')?a.find('li:contains("'+this.defaultChapter+'")').attr("aria-checked","true").addClass("able-focus"):a.find("li").first().attr("aria-checked","true").addClass("able-focus")):a.find("li[lang="+this.captionLang+"]")?a.find("li[lang="+this.captionLang+"]").attr("aria-checked","true"):a.find("li").last().attr("aria-checked","true"),a.on("keydown",function(e){d=t(this).attr("id").split("-")[1],(f=t(this).find("li:focus")).is(":first-child")?(g=t(this).find("li").last(),_=f.next()):f.is(":last-child")?(g=f.prev(),_=t(this).find("li").first()):(g=f.prev(),_=f.next()),9===e.which?e.shiftKey?(f.removeClass("able-focus"),g.focus().addClass("able-focus")):(f.removeClass("able-focus"),_.focus().addClass("able-focus")):40===e.which||39===e.which?(f.removeClass("able-focus"),_.focus().addClass("able-focus")):38==e.which||37===e.which?(f.removeClass("able-focus"),g.focus().addClass("able-focus")):32===e.which||13===e.which?f.click():27===e.which&&(f.removeClass("able-focus"),s.closePopups(),e.stopPropagation),e.preventDefault()}),this.$controllerDiv.append(a),a},AblePlayer.prototype.closePopups=function(){var t=this;this.chaptersPopup&&this.chaptersPopup.is(":visible")&&(this.chaptersPopup.hide(),this.$chaptersButton.attr("aria-expanded","fal
16934se").focus()),this.captionsPopup&&this.captionsPopup.is(":visible")&&(this.captionsPopup.hide(),this.$ccButton.attr("aria-expanded","false"),this.waitThenFocus(this.$ccButton)),this.prefsPopup&&this.prefsPopup.is(":visible")&&!this.hidingPopup&&(this.hidingPopup=!0,this.prefsPopup.hide(),this.prefsPopup.find("li").removeClass("able-focus").attr("tabindex","-1"),this.$prefsButton.attr("aria-expanded","false"),this.showingPrefsDialog||this.waitThenFocus(t.$prefsButton),setTimeout(function(){t.hidingPopup=!1},100)),this.$volumeSlider&&this.$volumeSlider.is(":visible")&&(this.$volumeSlider.hide().attr("aria-hidden","true"),this.$volumeButton.attr("aria-expanded","false").focus()),this.$transcriptPopup&&this.$transcriptPopup.is(":visible")&&(this.hidingPopup=!0,this.$transcriptPopup.hide(),this.$transcriptPopup.find("li").removeClass("able-focus").attr("tabindex","-1"),this.$transcriptPopupButton.attr("aria-expanded","false").focus(),setTimeout(function(){t.hidingPopup=!1},100)),this.$signPopup&&this.$signPopup.is(":visible")&&(this.$signPopup.hide(),this.$signPopup.find("li").removeClass("able-focus").attr("tabindex","-1"),this.$signPopupButton.attr("aria-expanded","false").focus())},AblePlayer.prototype.setupPopups=function(t){var e,i,s,a,n;if(e=[],void 0===t&&e.push("prefs"),("captions"===t||void 0===t)&&this.captions.length>0&&e.push("captions"),("chapters"===t||void 0===t)&&this.chapters.length>0&&this.useChaptersButton&&e.push("chapters"),"transcript-window"===t&&"popup"===this.transcriptType&&e.push("transcript-window"),"sign-window"===t&&this.hasSignLanguage&&e.push("sign-window"),e.length>0){i=this;for(var a=0;a<e.length;a++){var r=e[a];if(s=!1,"prefs"==r)this.prefsPopup=this.createPopup("prefs");else if("captions"==r)void 0!==this.captionsPopup&&this.captionsPopup||(this.captionsPopup=this.createPopup("captions",this.captions));else if("chapters"==r)n=this.selectedChapters?this.selectedChapters.cues:this.chapters.length>=1?this.chapters[0].cues:[],void 0!==this.chaptersPopup&&this.chaptersPopup||(this.chaptersPopup=this.createPopup("chapters",n));else if("transcript-window"==r)return this.createPopup("transcript-window");else if("sign-window"==r)return this.createPopup("sign-window")}}},AblePlayer.prototype.provideFallback=function(){var e,i;if(!this.usingFallback){if(this.usingFallback=!0,this.testFallback||(this.testFallback=1),void 0===this.$media&&(this.$media=t(this.media)),this.$media.attr("id")?this.mediaId=this.$media.attr("id"):this.mediaId="media"+Math.floor(1e9*Math.random()).toString(),this.hasFallback=!1,this.$media.children().length)for(e=0;e<this.$media.children().length&&!this.hasFallback;)"SOURCE"===this.$media.children()[e].tagName||"TRACK"===this.$media.children()[e].tagName||(this.hasFallback=!0),e++;if(this.hasFallback||(i=t("<p>").text("Media player unavailable."),this.$media.append(i)),this.$media.attr("width")&&this.$media.css("width",this.$media.attr("width")+"px"),this.$media.attr("height")&&this.$media.css("height",this.$media.attr("height")+"px"),this.$media.removeAttr("data-able-player"),this.$media.prop("controls",!0),2==this.testFallback){if(t(this.$media).replaceWith(t('<foobar id="foobar-'+this.mediaId+'">')),this.$newFallbackElement=t("#foobar-"+this.mediaId),this.$media.children().length)for(e=this.$media.children().length-1;e>=0;)this.$newFallbackElement.prepend(t(this.$media.children()[e])),e--;this.hasFallback||this.$newFallbackElement.append(i)}}},AblePlayer.prototype.calculateControlLayout=function(){var t,e,i,s;return(t=[])[0]=[],t[1]=[],"legacy"===this.skin&&(t[2]=[],t[3]=[]),t[0].push("play"),t[0].push("restart"),t[0].push("rewind"),t[0].push("forward"),"legacy"===this.skin&&t[1].push("seek"),this.hasPlaylist&&("legacy"===this.skin?(t[0].push("previous"),t[0].push("next")):"2020"==this.skin&&(t[0].push("previous"),t[0].push("next"))),this.isPlaybackRateSupported()?(i=!0,"legacy"===this.skin&&(t[2].push("slower"),t[2].push("faster"))):i=!1,s=0,this.hasCaptions&&(s++,"legacy"===this.skin?t[2].push("captions"):"2020"==this.skin&&t[1].push("captions")),this.hasSignLanguage&&(s++,"legacy"===this.skin?t[2].push("sign"):"2020"==this.skin&&t[1].push("sign")),"video"===this.mediaType&&(this.hasOpenDesc||this.hasClosedDesc)&&(s++,"legacy"===this.skin?t[2].push("descriptions"):"2020"==this.skin&&t[1].push("descriptions")),"popup"!==this.transcriptType||this.hideTranscriptButton||(s++,"legacy"===this.skin?t[2].push("transcript"):"2020"==this.skin&&t[1].push("transcript")),this.hasChapters&&this.useChaptersButton&&(s++,"legacy"===this.skin?t[2].push("chapters"):"2020"==this.skin&&t[1].push("chapters")),"2020"==this.skin&&s>0&&t[1].push("pipe"),i&&"2020"===this.skin&&(t[1].push("faster"),t[1].push("slower"),t[1].push("pipe")),"legacy"===this.skin?t[3].push("preferences"):"2020"==this.skin&&t[1].push("preferences"),"video"===this.mediaType&&this.allowFullscreen&&("legacy"===this.skin?t[3].push("fullscreen"):t[1].push("fullscreen")),this.browserSupportsVolume()?(e=!0,this.volumeButton="volume-"+this.getVolumeName(this.volume),"legacy"===this.skin?t[1].push("volume"):"2020"==this.skin&&t[1].push("volume")):(e=!1,this.volume=!1),t}
16934,AblePlayer.prototype.addControls=function(){for(e=this,i=100,a=(s=this.calculateControlLayout()).length,T=this.mediaId+"-tooltip",this.$tooltipDiv=t("<div>",{id:T,class:"able-tooltip"}).hide(),this.$controllerDiv.append(this.$tooltipDiv),"2020"==this.skin&&(l=t('<div class="able-seekbar"></div>'),p=this.mediaType+" "+this.tt.seekbarLabel,this.$controllerDiv.append(l),this.seekBar=new AccessibleSlider(this.mediaType,l,"horizontal",i,0,this.duration,this.seekInterval,p,"seekbar",!0,"visible")),n=0;n<a;n++){for(o=s[n],h=n%2==0?t("<div>",{class:"able-left-controls"}):t("<div>",{class:"able-right-controls"}),this.$controllerDiv.append(h),r=0;r<o.length;r++){if("seek"===(g=o[r]))l=t('<div class="able-seekbar"></div>'),p=this.mediaType+" "+this.tt.seekbarLabel,h.append(l),(void 0===this.duration||0===this.duration)&&(this.duration=60,this.elapsed=0),this.seekBar=new AccessibleSlider(this.mediaType,l,"horizontal",i,0,this.duration,this.seekInterval,p,"seekbar",!0,"visible");else if("pipe"===g)c=t("<span>",{tabindex:"-1","aria-hidden":"true"}),"font"===this.iconType?c.addClass("icon-pipe"):(u=t("<img>",{src:this.rootPath+"button-icons/"+this.iconColor+"/pipe.png",alt:"",role:"presentation"}),c.append(u)),h.append(c);else{m="volume"===g?this.rootPath+"button-icons/"+this.iconColor+"/"+this.volumeButton+".png":"fullscreen"===g?this.rootPath+"button-icons/"+this.iconColor+"/fullscreen-expand.png":"slower"===g?"animals"===this.speedIcons?this.rootPath+"button-icons/"+this.iconColor+"/turtle.png":this.rootPath+"button-icons/"+this.iconColor+"/slower.png":"faster"===g?"animals"===this.speedIcons?this.rootPath+"button-icons/"+this.iconColor+"/rabbit.png":this.rootPath+"button-icons/"+this.iconColor+"/faster.png":this.rootPath+"button-icons/"+this.iconColor+"/"+g+".png",b=t("<div>",{role:"button",tabindex:"0","aria-label":y=this.getButtonTitle(g),class:"able-button-handler-"+g}),("volume"===g||"preferences"===g||"captions"===g)&&("preferences"==g?(this.prefCats=this.getPreferencesGroups(),this.prefCats.length>1?(D=this.mediaId+"-prefs-menu",b.attr({"aria-controls":D,"aria-haspopup":"menu","aria-expanded":"false"})):1===this.prefCats.length&&b.attr({"aria-haspopup":"dialog"})):"volume"===g?(D=this.mediaId+"-volume-slider",b.attr({"aria-controls":D,"aria-expanded":"false"})):"captions"===g&&this.captions&&(this.captions.length>1?b.attr("aria-expanded","false"):b.attr("aria-pressed","false"))),"font"===this.iconType?(C=t("<span>",{class:k="volume"===g?"icon-"+this.volumeButton:"slower"===g?"animals"===this.speedIcons?"icon-turtle":"icon-slower":"faster"===g?"animals"===this.speedIcons?"icon-rabbit":"icon-faster":"icon-"+g,"aria-hidden":"true"}),b.append(C)):"svg"===this.iconType?(C=t("<svg>",{focusable:"false","aria-hidden":"true",viewBox:(d="volume"===g?this.get
16934SvgData(this.volumeButton):"fullscreen"===g?this.getSvgData("fullscreen-expand"):"slower"===g?"animals"===this.speedIcons?this.getSvgData("turtle"):this.getSvgData("slower"):"faster"===g?"animals"===this.speedIcons?this.getSvgData("rabbit"):this.getSvgData("faster"):this.getSvgData(g))[0]}),f=t("<path>",{d:d[1]}),C.append(f),b.html(C),b.html(b.html())):(v=t("<img>",{src:m,alt:"",role:"presentation"}),b.append(v));var e,i,s,a,n,r,o,h,l,p,c,u,d,f,g,_,v,m,y,b,k,C,T,w,P,D,_=t("<span>",{class:"able-clipped"}).text(y);b.append(_),b.on("mouseenter focus",function(i){clearTimeout(w);var s=t(this).attr("aria-label"),a=t(this).position(),n=t(this).height(),r=t(this).width(),o=e.$controllerDiv.width();a.right=o-a.left-r;var h=a.top+n+5;if(t(this).parent().hasClass("able-right-controls"))var l="right";else var l="left";var p=AblePlayer.localGetElementById(b[0],T).text(s).width();if("left"==l){var c=a.left-p/2;c<0&&(c=2);var u={left:c+"px",right:"",top:h+"px"}}else{var c=a.right-p/2;c<0&&(c=2);var u={left:"",right:c+"px",top:h+"px"}}var d=AblePlayer.localGetElementById(b[0],T).text(s).css(u);e.showTooltip(d),t(this).on("mouseleave blur",function(){clearTimeout(w),w=setTimeout(function(){AblePlayer.localGetElementById(b[0],T).text("").hide()},500),e.$tooltipDiv.on("mouseenter focus",function(){clearTimeout(w)}),e.$tooltipDiv.on("mouseleave blur",function(){AblePlayer.localGetElementById(b[0],T).text("").hide()})})}),"captions"===g?this.prefCaptions&&1===this.prefCaptions||(P=this.captions.length>1?this.tt.captions:this.tt.showCaptions,b.addClass("buttonOff").attr("title",P),b.attr("aria-pressed","false")):"descriptions"!==g||this.prefDesc&&1===this.prefDesc||b.addClass("buttonOff").attr("title",this.tt.turnOnDescriptions),h.append(b),"play"===g?this.$playpauseButton=b:"previous"==g?(this.$prevButton=b,"previous"==this.buttonWithFocus&&(this.$prevButton.focus(),this.buttonWithFocus=null)):"next"==g?(this.$nextButton=b,"next"==this.buttonWithFocus&&(this.$nextButton.focus(),this.buttonWithFocus=null)):"captions"===g?this.$ccButton=b:"sign"===g?(this.$signButton=b,this.$signWindow.is(":visible")||this.$signButton.addClass("buttonOff")):"descriptions"===g?this.$descButton=b:"mute"===g?this.$muteButton=b:"transcript"===g?(this.$transcriptButton=b,this.$transcriptDiv.is(":visible")||this.$transcriptButton.addClass("buttonOff").attr("title",this.tt.showTranscript)):"fullscreen"===g?this.$fullscreenButton=b:"chapters"===g?this.$chaptersButton=b:"preferences"===g?this.$prefsButton=b:"volume"===g&&(this.$volumeButton=b)}"volume"===g&&this.addVolumeSlider(h)}n%2==1&&this.$controllerDiv.append('<div style="clear:both;"></div>')}for(var x in void 0!==this.$captionsDiv&&this.stylizeCaptions(this.$captionsDiv),void 0!==this.$descDiv&&this.stylizeCaptions(this.$descDiv),this.controls=[],s)s.hasOwnProperty(x)&&(this.controls=this.controls.concat(s[x]));this.refreshControls("init")},AblePlayer.prototype.useSvg=function(){// @license Licensed under MIT license 16935var t,e,i=Object.create(null),s=function(){clearTimeout(e),e=setTimeout(t,100)},a=function(){},n=function(){var t;window.addEventListener("resize",s,!1),window.addEventListener("orientationchange",s,!1),window.MutationObserver?((t=new MutationObserver(s)).observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0}),a=function(){try{t.disconnect(),window.removeEventListener("resize",s,!1),window.removeEventListener("orientationchange",s,!1)}catch(e){}}):(document.documentElement.addEventListener("DOMSubtreeModified",s,!1),a=function(){document.documentElement.removeEventListener("DOMSubtreeModified",s,!1),window.removeEventListener("resize",s,!1),window.removeEventListener("orientationchange",s,!1)})},r="http://www.w3.org/1999/xlink";t=function(){var t,e,s,o,h,l,p,c,u,d=0;if(window.XMLHttpRequest&&(h=void 0!==(h=new XMLHttpRequest).withCredentials?XMLHttpRequest:XDomainRequest||void 0),void 0!==h){for(a(),c=document.getElementsByTagName("use"),o=0;o<c.length;o+=1){try{e=c[o].getBoundingClientRect()}catch(f){e=!1}t=(p=c[o].getAttributeNS(r,"href").split("#"))[0],s=p[1],l=e&&0===e.left&&0===e.right&&0===e.top&&0===e.bottom,e&&0===e.width&&0===e.height&&!l?t.length&&(!0!==(u=i[t])&&setTimeout(_({useEl:c[o],base:t,hash:s}),0),void 0===u&&(u=new h,i[t]=u,u.onload=v(u),u.onerror=m(u),u.ontimeout=m(u),u.open("GET",t),u.send(),d+=1)):!l&&(void 0===i[t]?i[t]=!0:i[t].onload&&(i[t].abort(),i[t].onload=void 0,i[t]=!0))}c="",d+=1,g()}function g(){0==(d-=1)&&n()}function _(t){return function(){!0!==i[t.base]&&t.useEl.setAttributeNS(r,"xlink:href","#"+t.hash)}}function v(t){return function(){var e,i=document.body,s=document.createElement("x");
16935t.onload=null,s.innerHTML=t.responseText,(e=s.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",i.insertBefore(e,i.firstChild)),g()}}function m(t){return function(){t.onerror=null,t.ontimeout=null,g()}}}},AblePlayer.prototype.cuePlaylistItem=function(e){var i,s,a,n,r,o,h=this;s=this.player,this.initializing||this.playerCreated&&this.deletePlayer("playlist"),this.swappingSrc=!0,this.startedPlaying?this.okToPlay=!0:this.okToPlay=!1,this.loadingMedia=!1,i=this.$playlist.eq(e),this.hasAttr(i,"data-youtube-id")?(this.youTubeId=this.getYouTubeId(i.attr("data-youtube-id")),this.hasAttr(i,"data-youtube-desc-id")&&(this.youTubeDescId=this.getYouTubeId(i.attr("data-youtube-desc-id"))),a="youtube"):this.hasAttr(i,"data-vimeo-id")?(this.vimeoId=this.getVimeoId(i.attr("data-vimeo-id")),this.hasAttr(i,"data-vimeo-desc-id")&&(this.vimeoDescId=this.getVimeoId(i.attr("data-vimeo-desc-id"))),a="vimeo"):a="html5","youtube"===a?"html5"===s&&(this.playing&&this.pauseMedia(),this.$media.hide()):(this.youTubeId=!1,"youtube"===s&&this.$media.show()),this.player=a,this.$media.empty(),this.hasAttr(i,"data-poster")&&this.$media.attr("poster",i.attr("data-poster")),this.hasAttr(i,"data-youtube-desc-id")&&this.$media.attr("data-youtube-desc-id",i.attr("data-youtube-desc-id")),this.youTubeId&&this.$media.attr("data-youtube-id",i.attr("data-youtube-id"));var l=i.children("span.able-source");l.length&&l.each(function(){if(h.hasAttr(t(this),"data-src")){var e=t("<source>",{src:t(this).attr("data-src")});h.hasAttr(t(this),"data-type")&&e.attr("type",t(this).attr("data-type")),h.hasAttr(t(this),"data-desc-src")&&e.attr("data-desc-src",t(this).attr("data-desc-src")),h.hasAttr(t(this),"data-sign-src")&&e.attr("data-sign-src",t(this).attr("data-sign-src")),h.$media.append(e)}});var p=i.children("span.able-track");p.length&&p.each(function(){if(h.hasAttr(t(this),"data-src")&&h.hasAttr(t(this),"data-kind")&&h.hasAttr(t(this),"data-srclang")){var e=t("<track>",{src:t(this).attr("data-src"),kind:t(this).attr("data-kind"),srclang:t(this).attr("data-srclang")});h.hasAttr(t(this),"data-label")&&e.attr("label",t(this).attr("data-label")),h.hasAttr(t(this),"data-desc")&&e.attr("data-desc",t(this).attr("data-desc")),h.$media.append(e)}}),n=i.text(),this.hasAttr(i,"lang")&&(r=i.attr("lang")),this.$sources=this.$media.find("source"),!this.recreatingPlayer&&this.recreatePlayer().then(function(){h.$playlist.removeClass("able-current").children("button").removeAttr("aria-current"),h.$playlist.eq(e).addClass("able-current").children("button").attr("aria-current","true"),!0===h.showNowPlaying&&void 0!==h.$nowPlayingDiv&&(o=t("<span>"),void 0!==r&&o.attr("lang",r),o.html("<span>"+h.tt.selectedTrack+":</span>"+n),h.$nowPlayingDiv.html(o)),h.initializing?h.swappingSrc=!1:"html5"===h.player?h.loadingMedia||(h.media.load(),h.loadingMedia=!0):"youtube"===h.player&&(h.okToPlay=!0),h.initializing=!1,h.playerCreated=!0})},AblePlayer.prototype.deletePlayer=function(e){"youtube"===this.player&&this.youTubePlayer&&this.youTubePlayer.destroy(),"vimeo"===this.player&&this.vimeoPlayer&&this.vimeoPlayer.destroy(),this.$controllerDiv.empty(),this.$elapsedTimeContainer.empty().text("0:00"),this.$durationContainer.empty(),this.$signWindow&&this.$signWindow.remove(),this.$transcriptArea&&this.$transcriptArea.remove(),t(".able-modal-dialog").remove(),this.$captionsWrapper&&this.$captionsWrapper.remove(),this.$descDiv&&this.$descDiv.remove(),this.hasCaptions=!1,this.hasChapters=!1,this.hasDescTracks=!1,this.hasOpenDesc=!1,this.hasClosedDesc=!1,this.captionsPopup=null,this.chaptersPopup=null,this.transcriptType=null,this.playerDeleted=!0},AblePlayer.prototype.getButtonTitle=function(t){if("playpause"===t||"play"===t)return this.tt.play;if("pause"===t)return this.tt.pause;if("restart"===t)return this.tt.restart;if("previous"===t)return this.tt.prevTrack;else if("next"===t)return this.tt.nextTrack;else if("rewind"===t)return this.tt.rewind;else if("forward"===t)return this.tt.forward;else if("captions"===t)return this.captions.length>1?this.tt.captions:this.captionsOn?this.tt.hideCaptions:this.tt.showCaptions;else if("descriptions"===t)return this.descOn?this.tt.turnOffDescriptions:this.tt.turnOnDescriptions;else if("transcript"===t)return this.$transcriptDiv.is(":visible")?this.tt.hideTranscript:this.tt.showTranscript;else if("chapters"===t)return this.tt.chapters;else if("sign"===t)return this.tt.sign;else if("volume"===t)return this.tt.volume;else if("faster"===t)return this.tt.faster;else if("slower"===t)return this.tt.slower;else if("preferences"===t)return this.tt.preferences;else if("help"===t);else if("fullscreen"!==t)return this.debug&&console.log("Found an untranslated label: "+t),t.charAt(0).toUpperCase()+t.slice(1);else return this.fullscreen?this.tt.exitFullscreen:this.tt.enterFullscreen}}(jQuery),function(t){AblePlayer.prototype.setupTracks=function(){var e,i,s,a,n,r,o,h,l;for(e=this,s=(i=new t.Deferred).promise(),a=[],t("#able-vts").length?(this.vtsTracks=[],this.hasVts=!0):this.hasVts=!1,o=this.hasDescTracks&&this.descOn?this.altTracks:this.tracks,r=0;r<o.length;r++){if(l=(h=o[r]).kind,!h.src){(e.usingYouTubeCaptions||e.usingVimeoCaptions)&&e.setupCaptions(h);continue}h.src,n=this.loadTextObject(h.src),a.push(n.catch(function(t){console.warn("Failed to load captions track from "+t)})),n.then(function(t,i){t.src;var s=t.language,a=t.label,n=t.desc;return function(r,o){var h=o,l=e.parseWebVTT(r,h).cues;e.hasVts&&e.setupVtsTracks(i,s,n,a,r,h),"captions"===i||"subtitles"===i?e.setupCaptions(t,l):"descriptions"===i?e.setupDescriptions(t,l):"chapters"===i?e.setupChapters(t,l):"metadata"===i&&e.setupMetadata(t,l)}}(h,l))}return e.usingYouTubeCaptions||e.usingVimeoCaptions?i.resolve():t.when.apply(t,a).then(function(){i.resolve()}),s},AblePlayer.prototype.getTracks=function(){var e,i,s,a,n,r,o,h,l,p,c;return e=this,h=!1,s=(i=new t.Deferred).promise(),this.$tracks=this.$media.find("track"),this.tracks=[],this.altTracks=[],this.captions=[],this.descriptions=[],this.chapters=[],this.meta=[],this.hasCaptionsTrack=!1,this.hasDescTracks=!1,this.$tracks.length&&(this.usingYouTubeCaptions=!1,this.$tracks.each(function(i,s){if("captions"===t(this).attr("kind")?e.hasCaptionsTrack=!0:"descriptions"===t(this).attr("kind")&&(e.hasClosedDesc=!0),a=t(this).attr("srclang")?t(this).attr("srclang"):e.lang,n=t(this).attr("label")?t(this).attr("label"):e.getLanguageName(a),void 0===t(this).attr("default")||h?(a===e.lang&&(l=!0),r=!1):(r=!0,h=!0),r&&(e.captionLang=a),void 0!==t(this).data("desc")?(o=!0,e.hasDescTracks=!0):o=!1,o?e.altTracks.push({kind:t(this).attr("kind"),src:t(this).attr("src"),language:a,label:n,def:r,desc:o}):e.tracks.push({kind:t(this).attr("kind"),src:t(this).attr("src"),language:a,label:n,def:r,desc:o}),i==e.$tracks.length-1){if(!h){if(l)for(e.captionLang=e.lang,p=!1,c=0;c<e.tracks.length&&!p;)e.tracks[c].language===e.lang&&(e.tracks[c].def=!0,p=!0),c++;else e.tracks[0].def=!0,e.captionLang=e.tracks[0].language}e.$media.find("track").removeAttr("default")}})),this.$tracks.length&&this.hasCaptionsTrack?i.resolve():"youtube"===this.player?this.getYouTubeCaptionTracks(this.youTubeId).then(function(){e.hasCaptions&&(e.usingYouTubeCaptions=!0,e.$captionsWrapper&&e.$captionsWrapper.remove()),i.resolve()}):"vimeo"===this.player?this.getVimeoCaptionTracks().then(function(){e.hasCaptions&&(e.usingVimeoCaptions=!0,e.$captionsWrapper&&e.$captionsWrapper.remove()),i.resolve()}):(this.hasCaptions=!1,e.$captionsWrapper&&e.$captionsWrapper.remove(),i.resolve()),s},AblePlayer.prototype.setupCaptions=function(e,i){var s,a,n;if(void 0===i&&(i=null),this.usingYouTubeCaptions||this.usingVimeoCaptions);else if(0===this.captions.length)this.captions.push({language:e.language,label:e.label,def:e.def,cues:i});else{for(a=0,s=!1;a<this.captions.length;a++)if((n=e.label).toLowerCase()<this.captions[a].label.toLowerCase()){this.captions.splice(a,0,{language:e.language,label:e.label,def:e.def,cues:i}),s=!0;break}s||this.captions.push({language:e.language,label:e.label,def:e.def,cues:i})}this.hasCaptions=!0,this.currentCaption=-1,1===this.prefCaptions?this.captionsOn=!0:0===this.prefCaptions?this.captionsOn=!1:1===this.defaultStateCaptions?this.captionsOn=!0:this.captionsOn=!1,"audio"===this.mediaType&&this.captionsOn&&this.$captionsContainer.removeClass("captions-off"),this.$captionsWrapper&&(!this.$captionsWrapper||t.contains(this.$ableDiv[0],this.$captionsWrapper[0]))||(this.$captionsDiv=t("<div>",{class:"able-captions"}),this.$captionsWrapper=t("<div>",{class:"able-captions-wrapper","aria-hidden":"true"}).hide(),"below"===this.prefCaptionsPosition?this.$captionsWrapper.addClass("able-captions-below"):this.$captionsWrapper.addClass("able-captions-overlay"),this.$captionsWrapper.append(this.$captionsDiv),this.$captionsContainer.append(this.$captionsWrapper))},AblePlayer.prototype.setupDescriptions=function(t,e){this.hasClosedDesc=!0,this.currentDescription=-1,this.descriptions.push({cues:e,language:t.language})},AblePlayer.prototype.setupChapters=function(t,e){this.hasChapters=!0,this.chapters.push({cues:e,language:t.language})},AblePlayer.prototype.setupMetadata=function(e,i,s){"text"===this.metaType?this.metaDiv&&t("#"+this.metaDiv)&&(this.$metaDiv=t("#"+this.metaDiv),this.hasMeta=!0,this.meta=i):"selector"===this.metaType&&(this.hasMeta=!0,this.visibleSelectors=[],this.meta=i)},AblePlayer.prototype.loadTextObject=function(e){var i,s,a,n;return s=(i=new t.Deferred).promise(),a=this,(n=t("<div>",{style:"display:none"})).load(e,function(t,s,r){"error"===s?(a.debug&&console.log("error reading file "+e+": "+s),i.reject(e)):i.resolve(e,t),n.remove()}),s}}(jQuery),function(t){AblePlayer.prototype.initYouTubePlayer=function(){var e,i,s,a;return e=this,s=(i=new t.Deferred).promise(),this.youTubePlayerReady=!1,a=this.youTubeDescId&&this.prefDesc?this.youTubeDescId:this.youTubeId,this.activeYouTubeId=a,AblePlayer.youTubeIframeAPIReady?e.finalizeYoutubeInit().then(function(){i.resolve()}):(AblePlayer.loadingYouTubeIframeAPI||t.getScript("https://www.youtube.com/iframe_api").fail(function(){i.fail()}),t("body").on("youTubeIframeAPIReady",function(){e.finalizeYoutubeInit().then(function(){i.resolve()})})),s},AblePlayer.prototype.finalizeYoutubeInit=function(){var e,i,s,a,n,r;return i=(e=new t.Deferred).promise(),s=this,a=this.mediaId+"_youtube",this.$mediaContainer.prepend(t("<div>").attr("id",a)),n=1,r=this.okToPlay?1:0,void 0===this.captionLang&&(this.captionLang=this.lang),this.youTubePlayer=new YT.Player(a,{videoId:this.activeYouTubeId,host:this.youTubeNoCookie?"https://www.youtube-nocookie.com":"https://www.youtube.com",playerVars:{autoplay:r,enablejsapi:1,disableKb:1,playsinline:this.playsInl
16935ine,start:this.startTime,controls:0,cc_load_policy:n,cc_lang_pref:this.captionLang,hl:this.lang,modestbranding:1,rel:0,iv_load_policy:3},events:{onReady:function(){s.youTubePlayerReady=!0,s.playerWidth&&s.playerHeight||s.getYouTubeDimensions(),s.playerWidth&&s.playerHeight&&(s.youTubePlayer.setSize(s.playerWidth,s.playerHeight),s.$ableWrapper.css({width:s.playerWidth+"px"})),s.swappingSrc&&(s.swappingSrc=!1,s.restoreFocus(),s.cueingPlaylistItem=!1,(s.playing||s.okToPlay)&&s.playMedia()),s.userClickedPlaylist&&(s.userClickedPlaylist=!1),s.recreatingPlayer&&(s.recreatingPlayer=!1),e.resolve()},onError:function(t){e.fail()},onStateChange:function(t){s.getPlayerState().then(function(t){"playing"===t?(s.playing=!0,s.startedPlaying=!0,s.paused=!1):"ended"==t?s.onMediaComplete():(s.playing=!1,s.paused=!0),s.stoppingYouTube&&"paused"===t&&(void 0!==s.$posterImg&&s.$posterImg.show(),s.stoppingYouTube=!1,s.seeking=!1,s.playing=!1,s.paused=!0)}),"youtube"===s.player&&!s.usingYouTubeCaptions&&s.youTubePlayer.getOptions("captions")&&s.youTubePlayer.unloadModule("captions")},onPlaybackQualityChange:function(){}}}),this.hasPlaylist||this.$media.remove(),i},AblePlayer.prototype.getYouTubeDimensions=function(t){var e,i,s;void 0!==(e=this.$ableWrapper.find("iframe"))&&e.prop("width")&&(i=e.prop("width"),e.prop("height")&&(s=e.prop("height"),this.resizePlayer(i,s)))},AblePlayer.prototype.getYouTubeCaptionTracks=function(e){var i,s,a,n,r,o,h=new t.Deferred,l=h.promise();return i=this,this.youTubePlayer.getOption("captions","tracklist")||(this.youTubePlayer.addEventListener("onApiChange",function(t){if(i.duration=i.youTubePlayer.getDuration(),i.loadingYouTubeCaptions){if(s=i.youTubePlayer.getOption("captions","tracklist"),i.okToPlay||i.youTubePlayer.pauseVideo(),s&&s.length){for(a=0;a<s.length;a++)n=s[a].languageCode,r=s[a].languageName,o=!1,void 0!==i.captionLang?n===i.captionLang&&(o=!0):void 0!==i.lang&&n===i.lang&&(o=!0),i.tracks.push({kind:"captions",language:n,label:r,def:o}),i.captions.push({language:n,label:r,def:o,cues:null});i.hasCaptions=!0,i.setupPopups("captions")}else i.usingYouTubeCaptions=!1,i.hasCaptions=!1;i.loadingYouTubeCaptions=!1,i.okToPlay&&i.youTubePlayer.playVideo()}i.captionLangPending&&(i.youTubePlayer.setOption("captions","track",{languageCode:i.captionLangPending}),i.captionLangPending=null),void 0!==i.prefCaptionsSize&&i.youTubePlayer.setOption("captions","fontSize",i.translatePrefs("size",i.prefCaptionsSize,"youtube")),h.resolve()}),this.loadingYouTubeCaptions=!0,this.youTubePlayer.playVideo()),l},AblePlayer.prototype.getYouTubeTimedTextUrl=function(t,e,i){var s="https://www.youtube.com/api/timedtext?fmt=vtt";return s+="&v="+t,s+="&lang="+i,""!==e&&(s+="&name="+e),s},AblePlayer.prototype.getYouTubePosterUrl=function(t,e){var i="https://img.youtube.com/vi/"+t;return"120"==e?i+"/default.jpg":"320"==e?i+"/hqdefault.jpg":"480"==e?i+"/hqdefault.jpg":"640"==e&&i+"/sddefault.jpg"},AblePlayer.prototype.getYouTubeId=function(t){var e,i;return -1!==t.indexOf("youtu")?(e=(t=t.trim()).length-11,i=t.substring(e)):t}}(jQuery),function(t){window.AccessibleSlider=function(e,i,s,a,n,r,o,h,l,p,c){var u,d;u=this,this.position=0,this.tracking=!1,this.trackDevice=null,this.keyTrackPosition=0,this.lastTrackPosition=0,this.nextStep=1,this.inertiaCount=0,this.bodyDiv=t(i),p&&(this.loadedDiv=t("<div></div>"),this.playedDiv=t("<div></div>")),this.seekHead=t("<div>",{"aria-orientation":s,class:"able-"+l+"-head"}),"visible"===c?this.seekHead.attr("tabindex","0"):this.seekHead.attr("tabindex","-1"),this.seekHead.attr({role:"slider","aria-label":h,"aria-valuemin":n,"aria-valuemax":r}),this.timeTooltipTimeoutId=null,this.overTooltip=!1,this.timeTooltip=t("<div>"),this.bodyDiv.append(this.timeTooltip),this.timeTooltip.attr("role","tooltip"),this.timeTooltip.addClass("able-tooltip"),this.timeTooltip.on("mouseenter focus",function(){u.overTooltip=!0,clearInterval(u.timeTooltipTimeoutId)}),this.timeTooltip.on("mouseleave blur",function(){u.overTooltip=!1,t(this).hide()}),this.timeTooltip.hide(),this.bodyDiv.append(this.loadedDiv),this.bodyDiv.append(this.playedDiv),this.bodyDiv.append(this.seekHead),this.bodyDiv.wrap("<div></div>"),this.wrapperDiv=this.bodyDiv.parent(),"legacy"===this.skin&&("horizontal"===s?(this.wrapperDiv.width(a),this.loadedDiv.width(0)):(this.wrapperDiv.height(a),this.loadedDiv.he
16935ight(0))),this.wrapperDiv.addClass("able-"+l+"-wrapper"),p&&(this.loadedDiv.addClass("able-"+l+"-loaded"),this.playedDiv.width(0),this.playedDiv.addClass("able-"+l+"-played"),this.setDuration(r)),this.seekHead.on("mouseenter mouseleave mousemove mousedown mouseup focus blur touchstart touchmove touchend",function(t){d=u.pointerEventToXY(t),"mouseenter"===t.type||"focus"===t.type?u.overHead=!0:"mouseleave"===t.type||"blur"===t.type?(u.overHead=!1,!u.overBody&&u.tracking&&"mouse"===u.trackDevice&&u.stopTracking(u.pageXToPosition(d.x))):"mousemove"===t.type||"touchmove"===t.type?u.tracking&&"mouse"===u.trackDevice&&u.trackHeadAtPageX(d.x):"mousedown"===t.type||"touchstart"===t.type?(u.startTracking("mouse",u.pageXToPosition(u.seekHead.offset()+u.seekHead.width()/2)),u.bodyDiv.is(":focus")||u.bodyDiv.focus(),t.preventDefault()):("mouseup"===t.type||"touchend"===t.type)&&u.tracking&&"mouse"===u.trackDevice&&u.stopTracking(u.pageXToPosition(d.x)),"mousemove"!==t.type&&"mousedown"!==t.type&&"mouseup"!==t.type&&"touchstart"!==t.type&&"touchend"!==t.type&&u.refreshTooltip()}),this.bodyDiv.on("mouseenter mouseleave mousemove mousedown mouseup keydown keyup touchstart touchmove touchend",function(t){if(d=u.pointerEventToXY(t),"mouseenter"===t.type)u.overBody=!0,u.overBodyMousePos={x:d.x,y:d.y};else if("mouseleave"===t.type)u.overBody=!1,u.overBodyMousePos=null,!u.overHead&&u.tracking&&"mouse"===u.trackDevice&&u.stopTracking(u.pageXToPosition(d.x));else if("mousemove"===t.type||"touchmove"===t.type)u.overBodyMousePos={x:d.x,y:d.y},u.tracking&&"mouse"===u.trackDevice&&u.trackHeadAtPageX(d.x);else if("mousedown"===t.type||"touchstart"===t.type)u.startTracking("mouse",u.pageXToPosition(d.x)),u.trackHeadAtPageX(d.x),u.seekHead.is(":focus")||u.seekHead.focus(),t.preventDefault();else if("mouseup"===t.type||"touchend"===t.type)u.tracking&&"mouse"===u.trackDevice&&u.stopTracking(u.pageXToPosition(d.x));else if("keydown"===t.type){if(36===t.which)u.trackImmediatelyTo(0);else if(35===t.which)u.trackImmediatelyTo(u.duration);else if(37===t.which||40===t.which)u.arrowKeyDown(-1);else if(39===t.which||38===t.which)u.arrowKeyDown(1);else if(33===t.which&&o>0)u.arrowKeyDown(o);else{if(34!==t.which||!(o>0))return;u.arrowKeyDown(-o)}t.preventDefault()}else"keyup"===t.type&&t.which>=33&&t.which<=40&&(u.tracking&&"keyboard"===u.trackDevice&&u.stopTracking(u.keyTrackPosition),t.preventDefault());u.overTooltip||"mouseup"===t.type||"keydown"===t.type||"keydown"===t.type||u.refreshTooltip()})},AccessibleSlider.prototype.arrowKeyDown=function(t){this.tracking&&"keyboard"===this.trackDevice?(this.keyTrackPosition=this.boundPos(this.keyTrackPosition+this.nextStep*t),this.inertiaCount+=1,20===this.inertiaCount&&(this.inertiaCount=0,this.nextStep*=2),this.trackHeadAtPosition(this.keyTrackPosition)):(this.nextStep=1,this.inertiaCount=0,this.keyTrackPosition=this.boundPos(this.position+this.nextStep*t),this.startTracking("keyboard",this.keyTrackPosition),this.trackHeadAtPosition(this.keyTrackPosition))},AccessibleSlider.prototype.pageXToPosition=function(t){var e=t-this.bodyDiv.offset().left,i=this.duration*(e/this.bodyDiv.width());return this.boundPos(i)},AccessibleSlider.prototype.boundPos=function(t){return Math.max(0,Math.min(t,this.duration))},AccessibleSlider.prototype.setDuration=function(t){t!==this.duration&&(this.duration=t,this.resetHeadLocation(),this.seekHead.attr("aria-valuemax",t))},AccessibleSlider.prototype.setWidth=function(t){this.wrapperDiv.width(t),this.resizeDivs(),this.resetHeadLocation()},AccessibleSlider.prototype.getWidth=function(){return this.wrapperDiv.width()},AccessibleSlider.prototype.resizeDivs=function(){this.playedDiv.width(this.bodyDiv.width()*(this.position/this.duration)),this.loadedDiv.width(this.bodyDiv.width()*this.buffered)},AccessibleSlider.prototype.resetHeadLocation=function(){var t=this.position/this.duration,e=this.bodyDiv.width()*t;this.seekHead.css("left",e-this.seekHead.width()/2),this.tracking&&this.stopTracking(this.position)},AccessibleSlider.prototype.setPosition=function(t,e){this.position=t,this.resetHeadLocation(),this.overHead&&this.refreshTooltip(),this.resizeDivs(),this.updateAriaValues(t,e)},AccessibleSlider.prototype.setBuffered=function(t){isNaN(t)||(this.buffered=t,this.redrawDivs)},AccessibleSlider.prototype.startTracking=function(t,e){this.tracking||(this.trackDevice=t,this.tracking=!0,this.bodyDiv.trigger("startTracking",[e]))},AccessibleSlider.prototype.stopTracking=function(t){this.trackDevice=null,this.tracking=!1,this.bodyDiv.trigger("stopTracking",[t]),this.setPosition(t,!0)},AccessibleSlider.prototype.trackHeadAtPageX=function(t){var e=this.pageXToPosition(t),i=t-this.bodyDiv.offset().left-this.seekHead.width()/2;i=Math.max(0,Math.min(i,this.bodyDiv.width()-this.seekHead.width())),this.lastTrackPosition=e,this.seekHead.css("left",i),this.reportTrackAtPosition(e)},AccessibleSlider.prototype.trackHeadAtPosition=function(t){var e=t/this.duration,i=this.bodyDiv.width()*e;this.lastTrackPosition=t,this.seekHead.css("left",i-this.seekHead.width()/2),this.reportTrackAtPosition(t)},AccessibleSlider.prototype.reportTrackAtPosition=function(t){this.bodyDiv.trigger("tracking",[t]),this.updateAriaValues(t,!0)},AccessibleSlider.prototype.updateAriaValues=function(e,i){var s,a=Math.floor(e/3600),n=Math.floor(e%3600/60),r=M
16935ath.floor(e%60),o=1===n?"minute":"minutes",h=1===r?"second":"seconds";s=a>0?a+" "+(1===a?"hour":"hours")+", "+n+" "+o+", "+r+" "+h:n>0?n+" "+o+", "+r+" "+h:r+" "+h,this.liveAriaRegion||(this.liveAriaRegion=t("<span>",{class:"able-offscreen","aria-live":"polite"}),this.wrapperDiv.append(this.liveAriaRegion)),i&&this.liveAriaRegion.text()!==s&&this.liveAriaRegion.text(s),this.seekHead.attr("aria-valuetext",s),this.seekHead.attr("aria-valuenow",Math.floor(e).toString())},AccessibleSlider.prototype.trackImmediatelyTo=function(t){this.startTracking("keyboard",t),this.trackHeadAtPosition(t),this.keyTrackPosition=t},AccessibleSlider.prototype.refreshTooltip=function(){if(this.overHead)this.timeTooltip.show(),this.tracking?this.timeTooltip.text(this.positionToStr(this.lastTrackPosition)):this.timeTooltip.text(this.positionToStr(this.position)),this.setTooltipPosition(this.seekHead.position().left+this.seekHead.width()/2);else if(this.overBody&&this.overBodyMousePos)this.timeTooltip.show(),this.timeTooltip.text(this.positionToStr(this.pageXToPosition(this.overBodyMousePos.x))),this.setTooltipPosition(this.overBodyMousePos.x-this.bodyDiv.offset().left);else{clearTimeout(this.timeTooltipTimeoutId);var t=this;this.timeTooltipTimeoutId=setTimeout(function(){t.timeTooltip.hide()},500)}},AccessibleSlider.prototype.hideSliderTooltips=function(){this.overHead=!1,this.overBody=!1,this.timeTooltip.hide()},AccessibleSlider.prototype.setTooltipPosition=function(t){this.timeTooltip.css({left:t-this.timeTooltip.width()/2-10,bottom:this.seekHead.height()+10})},AccessibleSlider.prototype.positionToStr=function(t){var e=Math.floor(t/3600),i=Math.floor(t/60)%60,s=Math.floor(t%60);return(s<10&&(s="0"+s),e>0)?(i<10&&(i="0"+i),e+":"+i+":"+s):i+":"+s},AccessibleSlider.prototype.pointerEventToXY=function(t){var e={x:0,y:0};if("touchstart"==t.type||"touchmove"==t.type||"touchend"==t.type||"touchcancel"==t.type){var i=t.originalEvent.touches[0]||t.originalEvent.changedTouches[0];e.x=i.pageX,e.y=i.pageY}else("mousedown"==t.type||"mouseup"==t.type||"mousemove"==t.type||"mouseover"==t.type||"mouseout"==t.type||"mouseenter"==t.type||"mouseleave"==t.type)&&(e.x=t.pageX,e.y=t.pageY);return e}}(jQuery),function(t){AblePlayer.prototype.addVolumeSlider=function(e){var i,s,a,n,r,o,h,l,p;for(i=this,s=this.mediaId+"-volume-slider",a=this.mediaId+"-volume-help",r=this.mediaId+"-volume-tick-labels",this.$volumeSlider=t("<div>",{id:s,class:"able-volume-slider","aria-hidden":"true"}).hide(),this.$volumeSliderTooltip=t("<div>",{class:"able-tooltip",role:"tooltip"}).hide(),this.$volumeRange=t("<input>",{type:"range",min:"0",max:"10",step:"1",orient:"vertical","aria-label":this.tt.volumeUpDown,value:this.volume}),n=parseInt(i.volume)/10*100,this.$volumeHelp=t("<div>",{id:a,class:"able-volume-help","aria-live":"polite"}).text(n+"%"),this.$volumeButton.attr({"aria-describedby":a}),o=t("<datalist>",{id:r}),h=0;h<=10;h++)p=0===h?this.tt.mute:10*h+"%",l=t("<option>",{value:h,label:p}),o.append(l);this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp),e.append(this.$volumeSlider),this.$volumeRange.on("change",function(e){i.handleVolumeChange(t(this).val())}),this.$volumeRange.on("input",function(e){i.handleVolumeChange(t(this).val())}),this.$volumeRange.on("keydown",function(t){(27===t.which||13===t.which||9===t.which)&&(i.$volumeSlider.is(":visible")?(i.closingVolume=!0,i.hideVolumePopup()):i.closingVolume||i.showVolumePopup())})},AblePlayer.prototype.refreshVolumeHelp=function(t){var e;e=t/10*100,this.$volumeHelp&&this.$volumeHelp.text(e+"%"),this.$volumeRange.attr("value",t)},AblePlayer.prototype.refreshVolumeButton=function(t){var e,i,s,a,n,r;e=this.getVolumeName(t),i=t/10*100,s=this.tt.volume+" "+i+"%","font"===this.iconType?(a="icon-volume-"+e,this.$volumeButton.find("span").first().removeClass().addClass(a),this.$volumeButton.find("span.able-clipped").text(s)):"image"===this.iconType?(n=this.imgPath+"volume-"+e+".png",this.$volumeButton.find("img").attr("src",n)):"svg"===this.iconType&&("mute"!==e&&(e="volume-"+e),r=this.getSvgData(e),this.$volumeButton.find("svg").attr("viewBox",r[0]),this.$volumeButton.find("path").attr("d",r[1]))},AblePlayer.prototype.handleVolumeButtonClick=function(){this.$volumeSlider.is(":visible")?this.hideVolumePopup():this.showVolumePopup()},AblePlayer.prototype.handleVolumeKeystroke=function(t){var e;if(!(t>=49)||!(t<=57))return!1;e=t-48,this.isMuted()&&e>0?this.setMute(!1):0===e?this.setMute(!0):(this.setVolume(e),this.refreshVolumeHelp(e),this.refreshVolumeButton(e))},AblePlayer.prototype.handleVolumeChange=function(t){this.isMuted()&&t>0?this.setMute(!1):0===t?this.setMute(!0):(this.setVolume(t),this.refreshVolumeHelp(t),this.refreshVolumeButton(t))},AblePlayer.prototype.handleMute=function(){this.isMuted()?this.setMute(!1):this.setMute(!0)},AblePlayer.prototype.showVolumePopup=function(){this.closePopups(),this.$tooltipDiv.hide(),this.$volumeSlider.show().attr("aria-hidden","false"),this.$volumeButton.attr("aria-expanded","true"),this.$volumeButton.focus(),this.waitThenFocus(this.$volumeRange)},AblePlayer.prototype.hideVolumePopup=function(){var t=this;this.$volumeSlider.hide().attr("aria-hidden","true"),this.$volumeButton.attr("aria-expanded","fal
16935se").focus(),setTimeout(function(){t.closingVolume=!1},1e3)},AblePlayer.prototype.isMuted=function(){return"html5"===this.player?this.media.muted:"youtube"===this.player?this.youTubePlayer.isMuted():void 0},AblePlayer.prototype.setMute=function(t){t?(this.lastVolume=this.volume,this.volume=0):void 0!==this.lastVolume&&(this.volume=this.lastVolume),"html5"===this.player?this.media.muted=t:"youtube"===this.player&&(t?this.youTubePlayer.mute():this.youTubePlayer.unMute()),this.setVolume(this.volume),this.refreshVolumeHelp(this.volume),this.refreshVolumeButton(this.volume)},AblePlayer.prototype.setVolume=function(t){var e;"html5"===this.player?(e=t/10,this.media.volume=e,this.hasSignLanguage&&this.signVideo&&(this.signVideo.volume=0)):"youtube"===this.player?(e=10*t,this.youTubePlayer.setVolume(e),this.volume=t):"vimeo"===this.player&&(e=t/10,this.vimeoPlayer.setVolume(e).then(function(){})),this.lastVolume=t},AblePlayer.prototype.getVolume=function(t){return"html5"===this.player?10*this.media.volume:"youtube"===this.player&&this.youTubePlayerReady?this.youTubePlayer.getVolume()/10:"vimeo"===this.player?this.volume:void 0},AblePlayer.prototype.getVolumeName=function(t){return 0==t?"mute":10==t?"loud":t<5?"soft":"medium"}}(jQuery),function(t){var e="a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";window.AccessibleDialog=function(i,s,a,n,r,o,h,l,p,c){this.title=r,this.closeButtonLabel=h,this.focusedElementBeforeModal=s,this.escapeHook=c,this.baseId=t(i).attr("id")||Math.floor(1e9*Math.random()).toString();var u=this,d=i;if(this.modal=d,d.css({width:l||"50%"}),d.addClass("able-modal-dialog"),!p){var f=t("<button>",{class:"modalCloseButton",title:u.closeButtonLabel,"aria-label":u.closeButtonLabel}).text("X");f.keydown(function(t){32===t.which&&u.hide()}).click(function(){u.hide()});var g=t("<h1></h1>");g.attr("id","modalTitle-"+this.baseId),g.css("text-align","center"),g.text(r),this.titleH1=g,d.attr({"aria-labelledby":"modalTitle-"+this.baseId}),d.prepend(g),d.prepend(f)}d.attr({"aria-hidden":"true",role:a}),n&&d.attr("aria-modal","true"),d.keydown(function(i){if(27===i.which)u.escapeHook?u.escapeHook(i,this):(u.hide(),i.preventDefault());else if(9===i.which){var s=d.find("*").filter(e).filter(":visible");if(0===s.length)return;var a=t(":focus"),n=s.index(a);
16935i.shiftKey?0===n&&(s.get(s.length-1).focus(),i.preventDefault()):n===s.length-1&&(s.get(0).focus(),i.preventDefault())}i.stopPropagation()}),t("body > *").not(".able-modal-overlay").not(".able-modal-dialog").removeAttr("aria-hidden")},AccessibleDialog.prototype.show=function(){if(!this.overlay){var i=t("<div></div>").attr({class:"able-modal-overlay",tabindex:"-1"});this.overlay=i,t("body").append(i),i.on("mousedown.accessibleModal",function(t){t.preventDefault()})}t("body > *").not(".able-modal-overlay").not(".able-modal-dialog").attr("aria-hidden","true"),this.overlay.css("display","block"),this.modal.css("display","block"),this.modal.attr({"aria-hidden":"false",tabindex:"-1"}),0===this.modal.find("*").filter(e).filter(":visible").length&&this.focusedElementBeforeModal.blur();var s=this;setTimeout(function(){s.modal.find("button.modalCloseButton").first().focus()},300)},AccessibleDialog.prototype.hide=function(){this.overlay&&this.overlay.css("display","none"),this.modal.css("display","none"),this.modal.attr("aria-hidden","true"),t("body > *").not(".able-modal-overlay").not(".able-modal-dialog").removeAttr("aria-hidden"),this.focusedElementBeforeModal.focus()},AccessibleDialog.prototype.getInputs=function(){return!!this.modal&&this.modal.find("input")}}(jQuery),$=jQuery,AblePlayer.prototype.getNextHeadingLevel=function(t){var e,i,s,a,n;return(e=t.parents()).each(function(){if(s=(i=$(this).children(":header")).length)return a=i.eq(s-1).prop("tagName"),!1}),void 0===a?n=1:(n=parseInt(a[1]),(n+=1)>6&&(n=6)),n},AblePlayer.prototype.countProperties=function(t){var e,i;for(i in e=0,t)t.hasOwnProperty(i)&&++e;return e},AblePlayer.prototype.formatSecondsAsColonTime=function(t,e){var i,s,a,n,r,o,h;if(e){if(2===(n=t.toString().split(".")).length){if((r=n[1]).length<3)for(h=1,o=3-r.length;h<=o;h++)r+="0"}else r="000"}return(i=Math.floor(t/3600),s=Math.floor(t/60)%60,(a=Math.floor(t%60))<10&&(a="0"+a),i>0)?(s<10&&(s="0"+s),e)?i+":"+s+":"+a+"."+r:i+":"+s+":"+a:e?(i<1?i="00":i<10&&(i="0"+i),s<1?s="00":s<10&&(s="0"+s),i+":"+s+":"+a+"."+r):s+":"+a},AblePlayer.prototype.getSecondsFromColonTime=function(t){var e,i,s,a;return 3===(e=t.split(":")).length?(i=parseInt(e[0]),3600*i+60*(s=parseInt(e[1]))+(a=parseFloat(e[2]))):2===e.length?60*(s=parseInt(e[0]))+(a=parseFloat(e[1])):1===e.length?parseFloat(e[0]):void 0},AblePlayer.prototype.capitalizeFirstLetter=function(t){return t.charAt(0).toUpperCase()+t.slice(1)},AblePlayer.prototype.roundDown=function(t,e){return Number(Math.floor(t+"e"+e)+"e-"+e)},AblePlayer.prototype.hasAttr=function(t,e){var i=t.attr(e);return void 0!==i&&!1!==i},Number.isInteger=Number.isInteger||function(t){return"number"==typeof t&&isFinite(t)&&Math.floor(t)===t},function(t){AblePlayer.prototype.initDescription=function(){var e,i,s;return i=(e=new t.Deferred).promise(),s=this,"audio"===this.mediaType&&e.resolve(),this.descFile=this.$sources.first().attr("data-desc-src"),void 0!==this.descFile?this.hasOpenDesc=!0:this.youTubeDescId||this.vimeoDescId?this.hasOpenDesc=!0:this.hasOpenDesc=!1,this.hasOpenDesc&&this.hasClosedDesc?this.prefDescMethod?this.descMethod=this.prefDescMethod:this.descMethod="video":this.hasOpenDesc?this.descMethod="video":this.hasClosedDesc?this.descMethod="text":this.descMethod=null,this.descMethod?1===this.prefDesc?this.descOn=!0:0===this.prefDesc?this.descOn=!1:1===this.defaultStateDescriptions?this.descOn=!0:this.descOn=!1:this.descOn=!1,void 0===this.$descDiv&&this.hasClosedDesc&&"text"===this.descMethod&&this.injectTextDescriptionArea(),this.descOn?("video"!==this.descMethod||this.usingDescribedVersion()||this.swapDescription(),this.hasClosedDesc&&(this.prefDescVisible?void 0!==this.$descDiv&&(this.$descDiv.show(),this.$descDiv.removeClass("able-clipped")):void 0!==this.$descDiv&&this.$descDiv.addClass("able-clipped"))):"video"===this.descMethod?this.usingDescribedVersion()&&this.swapDescription():"text"===this.descMethod&&void 0!==this.$descDiv&&(this.$descDiv.hide(),this.$descDiv.removeClass("able-clipped")),e.resolve(),i},AblePlayer.prototype.usingDescribedVersion=function(){return"youtube"===this.player?this.activeYouTubeId===this.youTubeDescId:"vimeo"===this.player?this.activeVimeoId===this.vimeoDescId:this.$sources.first().attr("data-desc-src")===this.$sources.first().attr("src")},AblePlayer.prototype.initSpeech=function(e){var i=this;if(null===this.speechEnabled){if(void 0!==this.synth&&this.synth.cancel(),window.speechSynthesis){if(this.synth=window.speechSynthesis,"init"===e)t(document).on("click",function(){var e=new SpeechSynthesisUtterance("Hi!");e.volume=0,e.rate=10,i.synth.speak(e),e.onstart=function(e){t(document).off("
16935click")},e.onend=function(t){i.getBrowserVoices(),i.descVoices.length&&(i.speechEnabled=!0)}}),this.getBrowserVoices(),this.descVoices.length&&(this.speechEnabled=!0);else{var s=new SpeechSynthesisUtterance("Hi!");s.volume=0,s.rate=10,i.synth.speak(s),s.onstart=function(e){t(document).off("click")},s.onend=function(t){i.getBrowserVoices(),i.descVoices.length&&(i.speechEnabled=!0)}}}else this.speechEnabled=!1}},AblePlayer.prototype.getBrowserVoices=function(){var t,e,i,s;if(s=this.captionLang?this.captionLang.substring(0,2).toLowerCase():this.lang.substring(0,2).toLowerCase(),this.descVoices=[],t=this.synth.getVoices(),e=this.getDescriptionLangs(),t.length>0){this.descVoices=[];for(var a=0;a<t.length;a++)(i=t[a].lang.substring(0,2).toLowerCase())===s&&-1!==e.indexOf(i)&&this.descVoices.push(t[a]);this.descVoices.length||(this.descVoices=t)}return!1},AblePlayer.prototype.getDescriptionLangs=function(){var t=[];if(this.tracks)for(var e=0;e<this.tracks.length;e++)"descriptions"===this.tracks[e].kind&&t.push(this.tracks[e].language.substring(0,2).toLowerCase());return t},AblePlayer.prototype.setDescriptionVoice=function(){var t,e,i,s,a,n;if(i=void 0!==(t=this.getCookie()).voices?this.getPrefDescVoice():null,this.getBrowserVoices(),this.rebuildDescPrefsForm(),a=this.selectedDescriptions?this.selectedDescriptions.language:this.captionLang?this.captionLang:this.lang,this.synth&&(e=this.synth.getVoices()).length>0){if(i){n=!1;for(var r=0;r<e.length;r++)if(e[r].lang.substring(0,2).toLowerCase()===a.substring(0,2).toLowerCase()&&e[r].name===i){s=e[r].name,n=!0;break}}if(!n){for(var r=0;r<e.length;r++)if(e[r].lang.substring(0,2).toLowerCase()===a.substring(0,2).toLowerCase()){s=e[r].name;break}}this.prefDescVoice=s,this.prefDescVoiceLang=a,this.$voiceSelectField&&(this.$voiceSelectField.find('option[value="'+this.prefDescVoice+'"]'),this.$voiceSelectField.val(this.prefDescVoice)),this.updateCookie("voice")}},AblePlayer.prototype.swapDescription=function(){var e,i,s,a,n;if(e=this,this.loadingMedia=!1,this.$focusedElement=t(":focus"),this.elapsed>0?this.swapTime=this.elapsed:this.swapTime=0,this.duration>0&&(this.prevDuration=this.duration),this.okToPlay||(this.okToPlay=this.playing),this.descOn?this.showAlert(this.tt.alertDescribedVersion):this.showAlert(this.tt.alertNonDescribedVersion),"html5"===this.player){if(this.swappingSrc=!0,this.paused=!0,this.usingDescribedVersion())for(i=0;i<this.$sources.length;i++)s=this.$sources[i].getAttribute("data-orig-src"),n=this.$sources[i].getAttribute("type"),s&&this.$sources[i].setAttribute("src",s);else for(i=0;i<this.$sources.length;i++)s=this.$sources[i].getAttribute("src"),a=this.$sources[i].getAttribute("data-desc-src"),n=this.$sources[i].getAttribute("type"),a&&(this.$sources[i].setAttribute("src",a),this.$sources[i].setAttribute("data-orig-src",s));if(this.recreatingPlayer)return;this.playerCreated&&(this.deletePlayer("swap-desc-html"),this.recreatePlayer().then(function(){e.loadingMedia||(e.media.load(),e.loadingMedia=!0)}))}else if("youtube"===this.player){if(this.usingDescribedVersion()?this.activeYouTubeId=this.youTubeId:this.activeYouTubeId=this.youTubeDescId,void 0!==this.youTubePlayer&&(e.swappingSrc=!0,e.playing?e.youTubePlayer.loadVideoById(e.activeYouTubeId,e.swapTime):e.youTubePlayer.cueVideoById(e.activeYouTubeId,e.swapTime)),this.playerCreated&&this.deletePlayer("swap-desc-youtube"),this.recreatingPlayer)return;this.recreatePlayer().then(function(){})}else if("vimeo"===this.player){if(this.usingDescribedVersion()?(this.activeVimeoId=this.vimeoId,this.showAlert(this.tt.alertNonDescribedVersion)):(this.activeVimeoId=this.vimeoDescId,this.showAlert(this.tt.alertDescribedVersion)),this.playerCreated&&this.deletePlayer("swap-desc-vimeo"),this.recreatingPlayer)return;this.recreatePlayer().then(function(){e.vimeoPlayer.loadVideo(e.activeVimeoId).then(function(){e.playing?e.vimeoPlayer.setCurrentTime(e.swapTime):e.vimeoPlayer.pause()})})}},AblePlayer.prototype.showDescription=function(t){if(this.hasClosedDesc&&!this.swappingSrc&&this.descOn&&"video"!==this.descMethod){e=this;var e,i,s,a,n,r=function(t){var e=[];
16935if("string"===t.type)e.push(t.value);else for(var i=0;i<t.children.length;i++)e.push(r(t.children[i]));return e.join("")};for(s=0,i=this.selectedDescriptions?this.selectedDescriptions.cues:this.descriptions.length>=1?this.descriptions[0].cues:[];s<i.length;s++)if(i[s].start<=t&&i[s].end>t){a=s;break}void 0!==a?this.currentDescription!==a&&(this.$status.removeAttr("aria-live"),n=r(i[a].components),"screenreader"===this.descReader?this.$descDiv.html(n):this.speechEnabled?(this.announceDescriptionText("description",n),this.prefDescVisible&&this.$descDiv.html(n).removeAttr("aria-live aria-atomic")):this.$descDiv.html(n),this.prefDescPause&&"text"===this.descMethod&&(this.pauseMedia(),this.pausedForDescription=!0),this.currentDescription=a):(this.$descDiv.html(""),this.currentDescription=-1,this.$status.attr("aria-live","polite"))}},AblePlayer.prototype.syncSpeechToPlaybackRate=function(t){var e;.5===t?e=.7:.75===t?e=.8:1===t?e=1:1.25===t?e=1.1:1.5===t?e=1.2:1.75===t?e=1.5:2===t?e=2:2.25===t?e=2.5:t>=2.5&&(e=3),this.prefDescRate=e},AblePlayer.prototype.announceDescriptionText=function(e,i){var s,a,n,r,o,h,l,p,c,u;if(s=this,this.speechEnabled||this.initSpeech("desc"),"sample"===e?(a=t("#"+this.mediaId+"_prefDescVoice").val(),o=t("#"+this.mediaId+"_prefDescPitch").val(),h=t("#"+this.mediaId+"_prefDescRate").val(),l=t("#"+this.mediaId+"_prefDescVolume").val()):(a=this.prefDescVoice,o=this.prefDescPitch,h=this.prefDescRate,l=this.prefDescVolume),this.descVoices&&this.descVoices.length>0){if(a){for(n=0;n<this.descVoices.length;n++)if(this.descVoices[n].name==a){r=this.descVoices[n];break}}void 0===r&&(r=this.descVoices[0]),(p=new SpeechSynthesisUtterance).voice=r,p.voiceURI="native",p.volume=l,p.rate=h,p.pitch=o,p.text=i,p.lang=this.lang,p.onstart=function(t){},p.onpause=function(t){},p.onend=function(t){this.speakingDescription=!1,u=(c=t.elapsedTime)>100?(t.elapsedTime/1e3).toFixed(2):t.elapsedTime.toFixed(2),this.debug&&console.log("Finished speaking. That took "+u+" seconds."),"description"===e&&s.prefDescPause&&s.pausedForDescription&&(s.playMedia(),this.pausedForDescription=!1)},p.onerror=function(t){console.log("Web Speech API error",t)},this.synth.paused&&this.synth.resume(),this.synth.speak(p),this.speakingDescription=!0}}}(jQuery),jQuery,AblePlayer.prototype.isIOS=function(t){var e,i;return e=navigator.userAgent.toLowerCase(),!!(i=/ipad|iphone|ipod/.exec(e))&&(void 0===t||-1!==e.indexOf("os "+t))},AblePlayer.prototype.browserSupportsVolume=function(){var t,e;return!this.isIOS()&&(e=.9,(t=new Audio).volume=e,t.volume===e)},AblePlayer.prototype.nativeFullscreenSupported=function(){return document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullscreenEnabled||document.msFullscreenEnabled},function(t){AblePlayer.prototype.seekTo=function(t){var e,i=this;this.seekFromTime=this.media.currentTime,this.seekToTime=t,this.seeking=!0,this.liveUpdatePending=!0,this.speakingDescription&&this.synth.cancel(),"html5"===this.player?(this.startTime=t,(e=this.media.seekable).length>0&&this.startTime>=e.start(0)&&this.startTime<=e.end(0)&&(this.media.currentTime=this.startTime,this.seekStatus="complete",this.hasSignLanguage&&this.signVideo&&(this.signVideo.currentTime=this.startTime))):"youtube"===this.player?(this.youTubePlayer.seekTo(t,!0),t>0&&void 0!==this.$posterImg&&this.$posterImg.hide()):"vimeo"===this.player&&this.vimeoPlayer.setCurrentTime(t).then(function(){i.elapsed=t,i.refreshControls("timeline")}),this.refreshControls("timeline")},AblePlayer.prototype.getMediaTimes=function(e,i){var s,a,n,r;return r={},a=(s=new t.Deferred).promise(),n=this,void 0!==e&&void 0!==i?(r.duration=e,r.elapsed=i,s.resolve(r)):this.getDuration().then(function(t){r.duration=n.roundDown(t,6),n.getElapsed().then(function(t){r.elapsed=n.roundDown(t,6),s.resolve(r)})}),a},AblePlayer.prototype.getDuration=function(){var e,i,s,a;return i=(e=new t.Deferred).promise(),s=this,"vimeo"===this.player?this.vimeoPlayer?this.vimeoPlayer.getDuration().then(function(t){void 0===t||isNaN(t)||-1===t?e.resolve(0):e.resolve(t)}):e.resolve(0):("html5"===this.player?a=this.media.duration:"youtube"===this.player&&(a=this.youTubePlayerReady?this.duration>0?this.duration:this.youTubePlayer.getDuration():0),void 0===a||isNaN(a)||-1===a?e.resolve(0):e.resolve(a)),i},AblePlayer.prototype.getElapsed=function(){var e,i,s,a;return i=(e=new t.Deferred).promise(),s=this,"vimeo"===this.player?this.vimeoPlayer?this.vimeoPlayer.getCurrentTime().then(function(t){void 0===t||isNaN(t)||-1===t?e.resolve(0):e.resolve(t)}):e.resolve(0):("html5"===this.player?a=this.media.currentTime:"youtube"===this.player&&(a=this.youTubePlayerReady?this.youTubePlayer.getCurrentTime():0),void 0===a||isNaN(a)||-1===a?e.resolve(0):e.resolve(a)),i},AblePlayer.prototype.getPlayerState=function(){var e,i,s;if(i=(e=new t.Deferred).promise(),s=this,"html5"===this.player)this.media.ended?e.resolve("ended"):this.media.paused?e.resolve("paused"):4!==this.media.readyState?e.resolve("buffering"):e.resolve("playing");else if("youtube"===this.player&&this.youTubePlayerReady){var a=this.youTubePlayer.getPlayerState();-1===a||5===a?e.resolve("stopped"):0===a?e.resolve("ended"):1===a?e.resolve("playing"):2===a?e.resolve("paused"):3===a&&e.resolve("buffering")}else"vimeo"===this.player&&this.vimeoPlayer&&this.vimeoPlayer.getPaused().then(function(t){t?e.resolve("paused"):s.vimeoPlayer.getEnded().then(function(t){t?e.resolve("ended"):e.resolve("playing")})});return i},AblePlayer.prototype.isPlaybackRateSupported=function(){return"html5"===this.player?!!this.media.playbackRate:"youtube"===this.player?!!this.youTubePlayerReady&&this.youTubePlayer.getAvailablePlaybackRates().length>1:"vimeo"===this.player?this.vimeoSupportsPlaybackRateChange:void 0},AblePlayer.prototype.setPlaybackRate=function(t){t=Math.max(.5,t),this.hasClosedDesc&&"text"===this.descMethod&&this.syncSpeechToPlaybackRate(t),"html5"===this.player?this.media.playbackRate=t:"youtube"===this.player?this.youTubePlayer.setPlaybackRate(t):"vimeo"===this.player&&this.vimeoPlayer.setPlaybackRate(t),this.hasSignLanguage&&this.signVideo&&(this.signVideo.playbackRate=t),this.playbackRate=t,this.$speed.text(this.tt.speed+": "+t.toFixed(2).toString()+"x")},AblePlayer.prototype.getPlaybackRate=function(){return"html5"===this.player?this.media.playbackRate:"youtube"===this.player&&this.youTubePlayerReady?this.youTubePlayer.getPlaybackRate():void 0},AblePlayer.prototype.isPaused=function(){if("vimeo"===this.player)return!this.playing;this.getPlayerState().then(function(t){return"paused"===t||"stopped"===t||"ended"===t})},AblePlayer.prototype.pauseMedia=function(){"html5"===this.player?(this.media.pause(!0),this.hasSignLanguage&&this.signVideo&&this.signVideo.pause(!0)):"youtube"===this.player?this.youTubePlayer.pauseVideo():"vimeo"===this.player&&this.vimeoPlayer.pause()},AblePlayer.prototype.playMedia=function(){"html5"===this.player?(this.media.play(!0),this.hasSignLanguage&&this.signVideo&&this.signVideo.play(!0)):"youtube"===this.player?(this.youTubePlayer.playVideo(),void 0!==this.$posterImg&&this.$posterImg.hide(),this.stoppingYouTube=!1):"vimeo"===this.player&&this.vimeoPlayer.play(),this.startedPlaying=!0,this.hideControls&&(this.hidingControls=!0,this.invokeHideControlsTimeout())},AblePlayer.prototype.fadeControls=function(t){var e,i,s;"out"==t?(s=(e=this.$mediaContainer.height())+(i=this.$playerDiv.height()),this.$playerDiv.fadeTo(2e3,0,function(){})):"in"==t&&this.$playerDiv.fadeTo(100,1)},AblePlayer.prototype.invokeHideControlsTimeout=function(){var t=this;this.hideControlsTimeout=window.setTimeout(function(){void 0!==t.playing&&!0===t.playing&&t.hideControls&&(t.fadeControls("out"),t.controlsHidden=!0)},5e3),this.hideControlsTimeoutStatus="active"}
16935,AblePlayer.prototype.refreshControls=function(e,i,s){var a,i,n,r,o,h,l,p,c,u,d,f,g,_;if(e="init",a=this,!this.swappingSrc||!this.playing){if("timeline"===e||"init"===e){if(void 0===this.duration)return;this.useChapterTimes&&(this.chapterDuration=this.getChapterDuration(),this.chapterElapsed=this.getChapterElapsed()),!1===this.useFixedSeekInterval&&!1===this.seekIntervalCalculated&&this.duration>0&&this.setSeekInterval(),this.seekBar&&(this.useChapterTimes?(n=this.selectedChapters.cues.length-1,this.selectedChapters.cues[n]==this.currentChapter&&this.currentChapter.end!==this.duration?this.seekBar.setDuration(this.duration-this.currentChapter.start):this.seekBar.setDuration(this.chapterDuration)):void 0===this.duration||isNaN(this.duration)||-1===this.duration||this.seekBar.setDuration(this.duration),this.seekBar.tracking||(o=this.liveUpdatePending||this.seekBar.seekHead.is(t(document.activeElement)),this.liveUpdatePending=!1,this.useChapterTimes?this.seekBar.setPosition(this.chapterElapsed,o):this.seekBar.setPosition(this.elapsed,o)),r=this.seekBar.tracking?this.seekBar.lastTrackPosition:this.useChapterTimes?this.chapterElapsed:this.elapsed),void 0!==this.$durationContainer&&(this.useChapterTimes?this.$durationContainer.text(" / "+this.formatSecondsAsColonTime(this.chapterDuration)):this.$durationContainer.text(" / "+this.formatSecondsAsColonTime(this.duration))),void 0!==this.$elapsedTimeContainer&&this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(r)),this.skin,"html5"===this.player?this.media.buffered.length>0&&(c=this.media.buffered.end(0),this.useChapterTimes?(c>this.chapterDuration&&(c=this.chapterDuration),this.seekBar&&this.seekBar.setBuffered(c/this.chapterDuration)):this.seekBar&&!isNaN(c)&&this.seekBar.setBuffered(c/i)):"youtube"===this.player?this.seekBar&&this.youTubePlayerReady&&this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction()):this.player}("descriptions"===e||"init"==e)&&this.$descButton&&(this.descOn?(this.$descButton.removeClass("buttonOff").attr("aria-label",this.tt.turnOffDescriptions),this.$descButton.find("span.able-clipped").text(this.tt.turnOffDescriptions)):(this.$descButton.addClass("buttonOff").attr("aria-label",this.tt.turnOnDescriptions),this.$descButton.find("span.able-clipped").text(this.tt.turnOnDescriptions))),("captions"===e||"init"==e)&&this.$ccButton&&(p=this.captions.length,this.captionsOn?(this.$ccButton.removeClass("buttonOff"),this.$ccButton.attr("aria-pressed","true"),1===p&&(this.$ccButton.attr("aria-label",this.tt.hideCaptions),this.$ccButton.find("span.able-clipped").text(this.tt.hideCaptions))):(this.$ccButton.addClass("buttonOff"),this.$ccButton.attr("aria-pressed","false"),1===p&&(this.$ccButton.attr("aria-label",this.tt.showCaptions),this.$ccButton.find("span.able-clipped").text(this.tt.showCaptions))),p>1&&(this.$ccButton.attr({"aria-label":this.tt.captions,"aria-haspopup":"true","aria-controls":this.mediaId+"-captions-menu"}),this.$ccButton.find("span.able-clipped").text(this.tt.captions))),("fullscreen"===e||"init"==e)&&this.$fullscreenButton&&(this.fullscreen?(this.$fullscreenButton.attr("aria-label",this.tt.exitFullscreen),"font"===this.iconType?(this.$fullscreenButton.find("span").first().removeClass("icon-fullscreen-expand").addClass("icon-fullscreen-collapse"),this.$fullscreenButton.find("span.able-clipped").text(this.tt.exitFullscreen)):"svg"===this.iconType?(_=this.getSvgData("fullscreen-collapse"),this.$fullscreenButton.find("svg").attr("viewBox",_[0]),this.$fullscreenButton.find("path").attr("d",_[1]),this.$fullscreenButton.find("span.able-clipped").text(this.tt.exitFullscreen)):this.$fullscreenButton.find("img").attr("src",this.fullscreenCollapseButtonImg)):(this.$fullscreenButton.attr("aria-label",this.tt.enterFullscreen),"font"===this.iconType?(this.$fullscreenButton.find("span").first().removeClass("icon-fullscreen-collapse").addClass("icon-fullscreen-expand"),this.$fullscreenButton.find("span.able-clipped").text(this.tt.enterFullscreen)):"svg"===this.iconType?(_=this.getSvgData("fullscreen-expand"),this.$fullscreenButton.find("svg").attr("viewBox",_[0]),this.$fullscreenButton.find("path").attr("d",_[1]),this.$fullscreenButton.find("span.able-clipped").text(this.tt.enterFullscreen)):this.$fullscreenButton.find("img").attr("src",this.fullscreenExpandButtonImg))),("playpause"===e||"init"==e)&&void 0!==this.$bigPlayButton&&void 0!==this.seekBar&&(this.paused&&!this.seekBar.tracking?(this.hideBigPlayButton||(this.$bigPlayButton.show(),this.$bigPlayButton.attr("aria-hidden","false")),this.fullscreen?(this.$bigPlayButton.width(t(window).width()),this.$bigPlayButton.height(t(window).height())):(this.$bigPlayButton.width(this.$mediaContainer.width()),this.$bigPlayButton.height(this.$mediaContainer.height()))):(this.$bigPlayButton.hide(),this.$bigPlayButton.attr("aria-hidden","true"))),("transcript"===e||"init"==e)&&this.transcriptType&&(1===this.prefAutoScrollTranscript?(this.autoScrollTranscript=!0,this.$autoScrollTranscriptCheckbox.prop("checked",!0)):(this.autoScrollTranscript=!1,this.$autoScrollTranscriptCheckbox.prop("checked",!1)),this.autoScrollTranscript&&this.currentHighlight&&(u=Math.floor(this.$transcriptDiv.scrollTop()+t(this.currentHighlight).position().top-this.$transcriptDiv.height()/2+t(this.currentHighlight).height()/2))!==Math.floor(this.$transcriptDiv.scrollTop())&&(this.scrollingTranscript=!0,this.movingHighlight&&(this.$transcriptDiv.scrollTop(u),this.movingHighlight=!1))),"init"===e&&this.$chaptersButton&&this.$chaptersButton.attr({"aria-label":this.tt.chapters,"aria-haspopup":"true","aria-controls":this.mediaId+"-chapter
16935s-menu"}),("timeline"===e||"playpause"===e||"init"===e)&&(h={stopped:this.tt.statusStopped,paused:this.tt.statusPaused,playing:this.tt.statusPlaying,buffering:this.tt.statusBuffering,ended:this.tt.statusEnd},this.stoppingYouTube?(this.$status.text()!==this.tt.statusStopped&&this.$status.text(this.tt.statusStopped),this.$playpauseButton.find("span").first().hasClass("icon-pause")&&("font"===this.iconType?(this.$playpauseButton.find("span").first().removeClass("icon-pause").addClass("icon-play"),this.$playpauseButton.find("span.able-clipped").text(this.tt.play)):"svg"===this.iconType?(_=this.getSvgData("play"),this.$playpauseButton.find("svg").attr("viewBox",_[0]),this.$playpauseButton.find("path").attr("d",_[1]),this.$playpauseButton.find("span.able-clipped").text(this.tt.play)):this.$playpauseButton.find("img").attr("src",this.playButtonImg))):void 0!==this.$status&&void 0!==this.seekBar&&this.getPlayerState().then(function(t){a.$status.text()===h[t]||a.seekBar.tracking?(a.statusDebounceStart=null,a.debouncingStatus=!1,clearTimeout(a.statusTimeout),a.statusTimeout=null):(a.swappingSrc?a.debouncingStatus||(a.statusMessageThreshold=2e3):a.debouncingStatus||(a.statusMessageThreshold=250),l=new Date().getTime(),a.statusDebounceStart?l-a.statusDebounceStart>a.statusMessageThreshold&&(a.$status.text(h[t]),a.statusDebounceStart=null,clearTimeout(a.statusTimeout),a.statusTimeout=null):(a.statusDebounceStart=l,a.debouncingStatus=!0,a.statusTimeout=setTimeout(function(){a.debouncingStatus=!1,a.refreshControls(e)},a.statusMessageThreshold))),a.seekBar.tracking||a.stoppingYouTube||("paused"===t||"stopped"===t||"ended"===t?(a.$playpauseButton.attr("aria-label",a.tt.play),"font"===a.iconType?(a.$playpauseButton.find("span").first().removeClass("icon-pause").addClass("icon-play"),a.$playpauseButton.find("span.able-clipped").text(a.tt.play)):"svg"===a.iconType?(_=a.getSvgData("play"),a.$playpauseButton.find("svg").attr("viewBox",_[0]),a.$playpauseButton.find("path").attr("d",_[1]),a.$playpauseButton.find("span.able-clipped").text(a.tt.play)):a.$playpauseButton.find("img").attr("src",a.playButtonImg)):(a.$playpauseButton.attr("aria-label",a.tt.pause),"font"===a.iconType?(a.$playpauseButton.find("span").first().removeClass("icon-play").addClass("icon-pause"),a.$playpauseButton.find("span.able-clipped").text(a.tt.pause)):"svg"===a.iconType?(_=a.getSvgData("pause"),a.$playpauseButton.find("svg").attr("viewBox",_[0]),a.$playpauseButton.find("path").attr("d",_[1]),a.$playpauseButton.find("span.able-clipped").text(a.tt.pause)):a.$playpauseButton.find("img").attr("src",a.pauseButtonImg)))})),!this.fullscreen&&(g=300,d=this.$statusBarDiv.height(),(f=this.$statusBarDiv.find("span.able-speed").height())>d+5?(this.$statusBarDiv.find("span.able-speed").hide(),this.hidingSpeed=!0):(this.hidingSpeed&&(this.$statusBarDiv.find("span.able-speed").show(),this.hidingSpeed=!1),this.$statusBarDiv.width()<g?(this.$statusBarDiv.find("span.able-speed").hide(),this.hidingSpeed=!0):this.hidingSpeed&&(this.$statusBarDiv.find("span.able-speed").show(),this.hidingSpeed=!1)))}},AblePlayer.prototype.getHiddenWidth=function(t){var e=t.clone().appendTo("body"),i=e.outerWidth();return e.remove(),i},AblePlayer.prototype.handlePlay=function(t){this.paused?(this.okToPlay=!0,this.playMedia(),this.synth.paused&&this.synth.resume()):(this.okToPlay=!1,this.pauseMedia(),this.speakingDescription&&this.synth.pause()),null===this.speechEnabled&&this.initSpeech("play")},AblePlayer.prototype.handleRestart=function(){this.speakingDescription&&this.synth.cancel(),this.seekTo(0)},AblePlayer.prototype.handlePrevTrack=function(){0===this.playlistIndex?this.playlistIndex=this.$playlist.length-1:this.playlistIndex--,this.cueingPlaylistItem=!0,this.cuePlaylistItem(this.playlistIndex)},AblePlayer.prototype.handleNextTrack=function(){this.playlistIndex===this.$playlist.length-1?this.playlistIndex=0:this.playlistIndex++,this.cueingPlaylistItem=!0,this.cuePlaylistItem(this.playlistIndex)},AblePlayer.prototype.handleRewind=function(){var t;t=this.elapsed-this.seekInterval,this.useChapterTimes?t<this.currentChapter.start&&(t=this.currentChapter.start):t<0&&(t=0),this.seekTo(t)},AblePlayer.prototype.handleFastForward=function(){var t,e;e=this.chapters.length-1,t=this.elapsed+this.seekInterval,this.useChapterTimes?this.chapters[e]==this.currentChapter?t>this.duration||t>
16935this.currentChapter.end?t=Math.min(this.duration,this.currentChapter.end):this.duration%t<this.seekInterval&&(t=Math.min(this.duration,this.currentChapter.end)):t>this.currentChapter.end&&(t=this.currentChapter.end):t>this.duration&&(t=this.duration),this.seekTo(t)},AblePlayer.prototype.handleRateIncrease=function(){this.changeRate(1)},AblePlayer.prototype.handleRateDecrease=function(){this.changeRate(-1)},AblePlayer.prototype.changeRate=function(t){var e,i,s,a,n,r;"html5"===this.player?this.setPlaybackRate(this.getPlaybackRate()+.25*t):"youtube"===this.player?this.youTubePlayerReady&&(e=this.youTubePlayer.getAvailablePlaybackRates(),i=this.getPlaybackRate(),-1===(s=e.indexOf(i))?console.log("ERROR: Youtube returning unknown playback rate "+i.toString()):(s+=t)<e.length&&s>=0&&this.setPlaybackRate(e[s])):"vimeo"===this.player&&(n=.5,r=2,1===t?a=this.vimeoPlaybackRate+.5<=r?this.vimeoPlaybackRate+.5:r:-1===t&&(a=this.vimeoPlaybackRate-.5>=n?this.vimeoPlaybackRate-.5:n),this.setPlaybackRate(a))},AblePlayer.prototype.handleCaptionToggle=function(){var t,e=this;if(this.hidingPopup)return this.hidingPopup=!1,!1;if(1===(t=this.captions.length?this.captions:[]).length){if(!0===this.captionsOn)this.captionsOn=!1,this.prefCaptions=0,this.$ccButton.attr("aria-pressed","false"),this.updateCookie("prefCaptions"),this.usingYouTubeCaptions?this.youTubePlayer.unloadModule("captions"):this.usingVimeoCaptions?this.vimeoPlayer.disableTextTrack():this.$captionsWrapper.hide();else{this.captionsOn=!0,this.prefCaptions=1,this.$ccButton.attr("aria-pressed","true"),this.updateCookie("prefCaptions"),this.usingYouTubeCaptions?this.youTubePlayer.loadModule("captions"):this.usingVimeoCaptions?this.vimeoPlayer.enableTextTrack(this.captionLang).then(function(t){}).catch(function(t){switch(t.name){case"InvalidTrackLanguageError":case"InvalidTrackError":console.log("No "+track.kind+" track is available in the specified language ("+track.label+")");break;default:console.log("Error loading "+track.label+" "+track.kind+" track")}}):this.$captionsWrapper.show();for(var i=0;i<t.length;i++)!0===t[i].def&&(this.selectedCaptions=t[i]);this.selectedCaptions=this.captions[0],this.descriptions.length>=0&&(this.selectedDescriptions=this.descriptions[0])}this.refreshControls("captions")}else this.captionsPopup&&this.captionsPopup.is(":visible")?(this.captionsPopup.hide(),this.hidingPopup=!1,this.$ccButton.attr("aria-expanded","false"),this.waitThenFocus(this.$ccButton)):(this.closePopups(),this.captionsPopup&&(this.captionsPopup.show(),this.$ccButton.attr("aria-expanded","true"),setTimeout(function(){e.captionsPopup.css("top",e.$ccButton.position().top-e.captionsPopup.outerHeight()),e.captionsPopup.css("left",e.$ccButton.position().left),e.captionsPopup.find("li").removeClass("able-focus"),e.captionsPopup.find("li").first().focus().addClass("able-focus")},50)))},AblePlayer.prototype.waitThenFocus=function(t,e){setTimeout(function(){t.focus()},null==e?50:e)},AblePlayer.prototype.handleChapters=function(){if(this.hidingPopup)return this.hidingPopup=!1,!1;this.chaptersPopup.is(":visible")?(this.chaptersPopup.hide(),this.hidingPopup=!1,this.$chaptersButton.attr("aria-expanded","false").focus()):(this.closePopups(),this.chaptersPopup.show(),this.$chaptersButton.attr("aria-expanded","true"),this.chaptersPopup.css("top",this.$chaptersButton.position().top-this.chaptersPopup.outerHeight()),this.chaptersPopup.css("left",this.$chaptersButton.position().left),this.chaptersPopup.find("li").removeClass("able-focus"),this.chaptersPopup.find('li[aria-checked="true"]').length?this.chaptersPopup.find('li[aria-checked="true"]').focus().addClass("able-focus"):this.chaptersPopup.find("li").first().addClass("able-focus").attr("aria-checked","true").focus())},AblePlayer.prototype.handleDescriptionToggle=function(){this.descOn=!this.descOn,this.prefDesc=+this.descOn,this.updateCookie("prefDesc"),void 0===this.$descDiv||this.$descDiv.is(":hidden")||this.$descDiv.hide(),this.initDescription(),this.refreshControls("descriptions")},AblePlayer.prototype.handlePrefsClick=function(t){var e,i,s,a;if(e=this,null===this.speechEnabled&&this.initSpeech("prefs"),this.hidingPopup)return this.hidingPopup=!1,!1;this.prefsPopup.is(":visible")?(this.prefsPopup.hide(),this.$prefsButton.attr("aria-expanded","fal
16935se"),this.prefsPopup.find("li").removeClass("able-focus").attr("tabindex","-1"),this.showingPrefsDialog||this.$prefsButton.focus(),setTimeout(function(){e.hidingPopup=!1},100)):(this.closePopups(),this.prefsPopup.show(),this.$prefsButton.attr("aria-expanded","true"),this.$prefsButton.focus(),setTimeout(function(){i=e.$prefsButton.position(),a=(s=e.$ableDiv.width()-5)-e.prefsPopup.width(),e.prefsPopup.css("top",i.top-e.prefsPopup.outerHeight()),e.prefsPopup.css("left",a),e.prefsPopup.find("li").removeClass("able-focus").attr("tabindex","0"),e.prefsPopup.find("li").first().focus().addClass("able-focus")},50))},AblePlayer.prototype.handleHelpClick=function(){this.setFullscreen(!1),this.helpDialog.show()},AblePlayer.prototype.handleTranscriptToggle=function(){var t=this;this.$transcriptDiv.is(":visible")?(this.$transcriptArea.hide(),this.$transcriptButton.addClass("buttonOff").attr("aria-label",this.tt.showTranscript),this.$transcriptButton.find("span.able-clipped").text(this.tt.showTranscript),this.prefTranscript=0,this.$transcriptButton.focus().addClass("able-focus"),setTimeout(function(){t.closingTranscript=!1},100)):(this.positionDraggableWindow("transcript"),this.$transcriptArea.show(),this.$transcriptPopup.hide(),this.$transcriptButton.removeClass("buttonOff").attr("aria-label",this.tt.hideTranscript),this.$transcriptButton.find("span.able-clipped").text(this.tt.hideTranscript),this.prefTranscript=1,this.focusNotClick=!0,this.$transcriptArea.find("button").first().focus(),setTimeout(function(){t.focusNotClick=!1},100)),this.updateCookie("prefTranscript")},AblePlayer.prototype.handleSignToggle=function(){var t=this;this.$signWindow.is(":visible")?(this.$signWindow.hide(),this.$signButton.addClass("buttonOff").attr("aria-label",this.tt.showSign),this.$signButton.find("span.able-clipped").text(this.tt.showSign),this.prefSign=0,this.$signButton.focus().addClass("able-focus"),setTimeout(function(){t.closingSign=!1},100)):(this.positionDraggableWindow("sign"),this.$signWindow.show(),this.$signPopup.hide(),this.$signButton.removeClass("buttonOff").attr("aria-label",this.tt.hideSign),this.$signButton.find("span.able-clipped").text(this.tt.hideSign),this.prefSign=1,this.focusNotClick=!0,this.$signWindow.find("button").first().focus(),setTimeout(function(){t.focusNotClick=!1},100)),this.updateCookie("prefSign")},AblePlayer.prototype.isFullscreen=function(){return this.nativeFullscreenSupported()?!!document.fullscreenElement||!!document.webkitFullscreenElement||!!document.webkitCurrentFullscreenElement||!!document.mozFullscreenElement||!!document.msFullscreenElement:!!this.modalFullscreenActive},AblePlayer.prototype.setFullscreen=function(e){if(this.fullscreen!=e){var i=this,s=this.$ableWrapper,a=s[0];if(this.nativeFullscreenSupported())e?(a.requestFullscreen?a.requestFullscreen():a.webkitRequestFullscreen?a.webkitRequestFullscreen():a.mozRequestFullscreen?a.mozRequestFullscreen():a.msRequestFullscreen&&a.msRequestFullscreen(),this.fullscreen=!0):(this.restoringAfterFullscreen=!0,document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.webkitCancelFullscreen?document.webkitCancelFullscreen():document.mozCancelFullscreen?document.mozCancelFullscreen():document.msExitFullscreen&&document.msExitFullscreen(),this.fullscreen=!1);else{if(!this.fullscreenDialog){var n=t("<div>"),r=t("<p>",{class:"able-screenreader-alert"}).text(this.tt.fullscreen);n.append(r),this.fullscreenDialog=new AccessibleDialog(n,this.$fullscreenButton,"dialog",!0,"Fullscreen video player",r,this.tt.exitFullscreen,"100%",!0,function(){i.handleFullscreenToggle()}),t("body").append(n)}var o=this.paused;if(e){this.modalFullscreenActive=!0,this.fullscreenDialog.show(),this.$modalFullscreenPlaceholder=t('<div class="placeholder">'),this.$modalFullscreenPlaceholder.insertAfter(s),s.appendTo(this.fullscreenDialog.modal),s===this.$ableColumnLeft&&s.width("100%");var h=t(window).height()-this.$playerDiv.height();void 0===this.$descDiv||this.$descDiv.is(":hidden")||(h-=this.$descDiv.height())}else this.modalFullscreenActive=!1,s===this.$ableColumnLeft&&s.width("50%"),s.insertAfter(this.$modalFullscreenPlaceholder),this.$modalFullscreenPlaceholder.remove(),this.fullscreenDialog.hide();!o&&this.paused&&this.playMedia()}t(document).on("webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",function(t){i.fullscreen?i.clickedFullscreenButton||(i.fullscreen=!1,i.restoringAfterFullscreen=!0):i.restoringAfterFullscreen=!0,i.resizePlayer(),i.refreshControl
16935s("fullscreen"),setTimeout(function(){i.clickedFullscreenButton=!1,i.restoringAfterFullscreen=!1},1e3)})}},AblePlayer.prototype.handleFullscreenToggle=function(){var t=this.paused;this.setFullscreen(!this.fullscreen),t?this.pauseMedia():t||this.playMedia(),this.fullscreen?(this.hideControls=!0,this.playing&&(this.fadeControls("out"),this.controlsHidden=!0)):(this.hideControls=this.hideControlsOriginal,this.hideControls||(this.controlsHidden&&(this.fadeControls("in"),this.controlsHidden=!1),"active"!==this.hideControlsTimeoutStatus||(window.clearTimeout(this.hideControlsTimeout),this.hideControlsTimeoutStatus="clear")))},AblePlayer.prototype.handleTranscriptLockToggle=function(t){this.autoScrollTranscript=t,this.prefAutoScrollTranscript=+t,this.updateCookie("prefAutoScrollTranscript"),this.refreshControls("transcript")},AblePlayer.prototype.showTooltip=function(t){t.is(":animated")?t.stop(!0,!0).show():t.stop().show()},AblePlayer.prototype.showAlert=function(t,e){var i,s,a,n,r;i=this,"transcript"===e?(s=this.$transcriptAlert,a=this.$transcriptArea):"sign"===e?(s=this.$signAlert,a=this.$signWindow):s="screenreader"===e?this.$srAlertBox:this.$alertBox,s.text(t).show(),"transcript"==e||"sign"===e?(n=a.width()>s.width()?a.width()/2-s.width()/2:10,"sign"===e?r=a.height()/3*2:"transcript"===e&&(r=this.$transcriptToolbar.height()+30),s.css({top:r+"px",left:n+"px"})):"screenreader"!==e&&s.css({left:this.$playerDiv.width()/2-s.width()/2}),"screenreader"!==e&&setTimeout(function(){s.fadeOut(300)},3e3)},AblePlayer.prototype.showedAlert=function(t){if("transcript"===t){if(this.showedTranscriptAlert)return!0}else if("sign"===t&&this.showedSignAlert)return!0;return!1},AblePlayer.prototype.resizePlayer=function(e,i){var s,a,n,r,o;"audio"!==this.mediaType&&(void 0!==e&&void 0!==i?(this.aspectRatio=i/e,this.playerWidth?(a=this.playerWidth,this.playerHeight?n=this.playerHeight:(n=Math.round(a*this.aspectRatio),this.playerHeight=n)):n=Math.round((a="html5"===this.player?t(window).width():this.$ableWrapper.width())*this.aspectRatio)):this.fullscreen?(this.$ableWrapper.addClass("fullscreen"),a=t(window).width(),n=t(window).height()-this.$playerDiv.outerHeight()-5,this.positionCaptions("overlay")):(this.$ableWrapper.removeClass("fullscreen"),n=Math.round((a="html5"===this.player?this.playerWidth?this.playerWidth:t(window).width():this.$ableWrapper.width())*this.aspectRatio),this.positionCaptions(this.prefCaptionsPosition)),this.debug&&console.log("resizePlayer to "+a+"x"+n),"youtube"===this.player||"vimeo"===this.player?(r=this.$ableWrapper.find("iframe"),"youtube"===this.player&&this.youTubePlayer?this.youTubePlayer.setSize(a,n):r.attr({width:a,height:n}),this.playerWidth&&this.playerHeight&&(this.fullscreen?r.css({"max-width":"","max-height":""}):r.css({"max-width":this.playerWidth+"px","max-height":this.playerHeight+"px"}))):"html5"===this.player&&(this.fullscreen?(this.$media.attr({width:a,height:n}),this.$ableWrapper.css({width:a,height:n})):(this.$media.removeAttr("width height"),this.$ableWrapper.css({width:a+"px",height:"auto"}))),void 0!==this.$captionsDiv&&(s=parseInt(this.prefCaptionsSize,10),this.fullscreen?s=s/100+"vw":s+="%",this.$captionsDiv.css({"font-size":s})),"video"===this.mediaType&&(o=2*Math.round(this.$mediaContainer.height()/3),this.$alertBox.css({top:o+"px"})),this.refreshControls())},AblePlayer.prototype.retrieveOffscreenWindow=function(t,e,i){var s,a,n,r,o,h,l,p;"transcript"==t?s=this.$transcriptArea:"sign"==t&&(s=this.$signWindow),h=s.width(),p=s.height(),n=(a=s.position()).top,o=(r=a.left)+h,l=n+p,n<0&&(n=10,s.css("top",n)),r<0&&(r=10,s.css("left",r)),o>e&&(r=e-20-h,s.css("left",r)),l>i&&(n=i-10-p,s.css("top",n))},AblePlayer.prototype.getHighestZIndex=function(){var e,i,s;return e=0,(i=t("body *").not(".able-modal-dialog,.able-modal-dialog *,.able-modal-overlay,.able-modal-overlay *,.able-sign-window,.able-transcript-area")).each(function(){Number.isInteger(+(s=t(this).css("z-index")))&&parseInt(s)>e&&(e=parseInt(s))}),e},AblePlayer.prototype.updateZIndex=function(t){var e,i,s,a,n,r,o;if(e=8e3,i=7e3,s=this.getHighestZIndex(),void 0===this.$transcriptArea||void 0===this.$signWindow)return void 0!==this.$transcriptArea?(a=parseInt(this.$transcriptArea.css("z-index")))>i&&this.$transcriptArea.css("z-index",i):void 0!==this.$signWindow&&(n=parseInt(this.$signWindow.css("z-index")))>e&&this.$signWindow.css("z-index",e),!1;if((a=parseInt(this.$transcriptArea.css("z-index")))===(n=parseInt(this.$signWindow.css("z-index"))))r=e,o=i;else if(a>n){if("transcript"===t)return!1;r=a,o=n}else{if("sign"===t)return!1;r=n,o=a}"transcript"===t?(this.$transcriptArea.css("z-index",r),this.$signWindow.css("z-index",o)):"sign"===t&&(this.$signWindow.css("z-index",r),this.$transcriptArea.css("z-index",o))},AblePlayer.prototype.syncTrackLanguages=function(t,e){var i,s,a,n,r;for(i=0;i<this.captions.length;i++)this.captions[i].language===e&&(s=this.captions[i]);for(i=0;i<this.chapters.length;i++)this.chapters[i].language===e&&(n=this.chapters[i]);for(i=0;i<this.descriptions.length;i++)this.descriptions[i].language===e&&(a=this.descriptions[i]);for(i=0;i<this.meta.length;i++)this.meta[i].language===e&&(r=this.meta[i]);if(this.transcriptLang=e,"init"===t||"captions"===t?(this.captionLang=e,this.selectedCaptions=s,this.selectedChapters=n,this.selectedDescriptions=a,this.selectedMeta=r,this.transcriptCaptions=s,this.transcriptChapters=n,this.transcriptDescriptions=a,this.updateChaptersList()):"transcript"===t&&(this.transcriptCaptions=s,this.transcriptChapters=n,this.transcriptDescriptions=a),this.selectedDescriptions&&(this.setDescriptionVoice(),this.$sampleDescDiv&&this.sampleText))for(i=0;i<this.sampleText.length;i++)this.sampleText[i].lang===this.selectedDescriptions.language&&(this.currentSampleText=this.sampleText[i].text,this.$sampleDescDiv.html(this.currentSampleText));this.updateTranscript()}}(jQuery),function(t){AblePlayer.prototype.updateCaption=function(t){this.usingYouTubeCaptions||this.usingVimeoCaptions||void 0===this.$captionsWrapper||(this.captionsOn?(this.$captionsWrapper.show(),void 0!==t&&this.showCaptions(t)):this.$captionsWrapper&&(this.$captionsWrapper.hide(),this.prefCaptions=0))},AblePlayer.prototype.updateCaptionsMenu=function(t){this.captionsPopup.find("li").attr("aria-checked","fal
16935se"),void 0===t?this.captionsPopup.find("li").last().attr("aria-checked","true"):this.captionsPopup.find("li[lang="+t+"]").attr("aria-checked","true")},AblePlayer.prototype.getCaptionClickFunction=function(t){var e=this;return function(){e.selectedCaptions=t,e.captionLang=t.language,e.currentCaption=-1,e.usingYouTubeCaptions?e.captionsOn?e.youTubePlayer.getOptions("captions")&&e.startedPlaying?e.youTubePlayer.setOption("captions","track",{languageCode:e.captionLang}):e.captionLangPending=e.captionLang:e.youTubePlayer.getOptions("captions")?e.youTubePlayer.setOption("captions","track",{languageCode:e.captionLang}):(e.youTubePlayer.loadModule("captions"),e.captionLangPending=e.captionLang):e.usingVimeoCaptions?e.vimeoPlayer.enableTextTrack(e.captionLang).then(function(t){}).catch(function(e){switch(e.name){case"InvalidTrackLanguageError":case"InvalidTrackError":console.log("No "+t.kind+" track is available in the specified language ("+t.label+")");break;default:console.log("Error loading "+t.label+" "+t.kind+" track")}}):(e.syncTrackLanguages("captions",e.captionLang),e.swappingSrc||(e.updateCaption(e.elapsed),e.showDescription(e.elapsed))),e.captionsOn=!0,e.hidingPopup=!0,e.captionsPopup.hide(),e.$ccButton.attr("aria-expanded","false"),"audio"===e.mediaType&&e.$captionsContainer.removeClass("captions-off"),setTimeout(function(){e.hidingPopup=!1},100),e.updateCaptionsMenu(e.captionLang),e.waitThenFocus(e.$ccButton),e.prefCaptions=1,e.updateCookie("prefCaptions"),e.refreshControls("captions")}},AblePlayer.prototype.getCaptionOffFunction=function(){var t=this;return function(){"youtube"==t.player?t.youTubePlayer.unloadModule("captions"):t.usingVimeoCaptions&&t.vimeoPlayer.disableTextTrack(),t.captionsOn=!1,t.currentCaption=-1,"audio"===t.mediaType&&t.$captionsContainer.addClass("captions-off"),t.hidingPopup=!0,t.captionsPopup.hide(),t.$ccButton.attr("aria-expanded","false"),setTimeout(function(){t.hidingPopup=!1},100),t.updateCaptionsMenu(),t.waitThenFocus(t.$ccButton),t.prefCaptions=0,t.updateCookie("prefCaptions"),this.swappingSrc||(t.refreshControls("captions"),t.updateCaption())}},AblePlayer.prototype.showCaptions=function(t){var e,i,s,a;for(e=0,a=this.selectedCaptions.cues.length?this.selectedCaptions.cues:this.captions.length>=1?this.captions[0].cues:[];e<a.length;e++)if(a[e].start<=t&&a[e].end>t){i=e;break}void 0!==i?this.currentCaption!==i&&(s=this.flattenCueForCaption(a[i]).replace("\n","<br>"),this.$captionsDiv.html(s),this.currentCaption=i,0===s.length?this.$captionsDiv.css("display","none"):this.$captionsDiv.css("display","inline-block")):(this.$captionsDiv.html("").css("display","none"),this.currentCaption=-1)},AblePlayer.prototype.flattenCueForCaption=function(t){var e=[],i=function(t){var e,s=[];if("string"===t.type)s.push(t.value);else if("v"===t.type)for(s.push("("+t.value+")"),e=0;e<t.children.length;e++)s.push(i(t.children[e]));else if("i"===t.type){for(s.push("<em>"),e=0;e<t.children.length;e++)s.push(i(t.children[e]));s.push("</em>")}else if("b"===t.type){for(s.push("<strong>"),e=0;e<t.children.length;e++)s.push(i(t.children[e]));s.push("</strong>")}else for(e=0;e<t.children.length;e++)s.push(i(t.children[e]));return s.join("")};if(void 0!==t.components)for(var s=0;s<t.components.children.length;s++)e.push(i(t.components.children[s]));return e.join("")},AblePlayer.prototype.getCaptionsOptions=function(t){var e=[];switch(t){case"prefCaptionsFont":e[0]=["serif",this.tt.serif],e[1]=["sans-serif",this.tt.sans],e[2]=["cursive",this.tt.cursive],e[3]=["fantasy",this.tt.fantasy],e[4]=["monospace",this.tt.monospace];break;case"prefCaptionsColor":case"prefCaptionsBGColor":e[0]=["white",this.tt.white],e[1]=["yellow",this.tt.yellow],e[2]=["green",this.tt.green],e[3]=["cyan",this.tt.cyan],e[4]=["blue",this.tt.blue],e[5]=["magenta",this.tt.magenta],e[6]=["red",this.tt.red],e[7]=["black",this.tt.black];break;case"prefCaptionsSize":e[0]="75%",e[1]="100%",e[2]="125%",e[3]="150%",e[4]="200%";break;case"prefCaptionsOpacity":e[0]="0%",e[1]="25%",e[2]="50%",e[3]="75%",e[4]="100%";break;case"prefCaptionsStyle":e[0]=this.tt.captionsStylePopOn,e[1]=this.tt.captionsStyleRollUp;break;case"prefCaptionsPosition":e[0]="overlay",e[1]="below"}return e}
vendor: 4,657 bytes, line 16935
16935,AblePlayer.prototype.translatePrefs=function(t,e,i){if("youtube"==i&&"size"===t)switch(e){case"75%":return -1;case"100%":return 0;case"125%":return 1;case"150%":return 2;case"200%":return 3}return!1},AblePlayer.prototype.stylizeCaptions=function(e,i){var s,a,n;void 0!==e&&("prefCaptionsPosition"==i?this.positionCaptions():void 0!==i?("prefCaptionsFont"===i?s="font-family":"prefCaptionsSize"===i?s="font-size":"prefCaptionsColor"===i?s="color":"prefCaptionsBGColor"===i?s="background-color":"prefCaptionsOpacity"===i&&(s="opacity"),a="prefCaptionsOpacity"===i?parseFloat(t("#"+this.mediaId+"_"+i).val())/100:t("#"+this.mediaId+"_"+i).val(),e.css(s,a)):(n=parseFloat(this.prefCaptionsOpacity)/100,e.css({"font-family":this.prefCaptionsFont,color:this.prefCaptionsColor,"background-color":this.prefCaptionsBGColor,opacity:n}),e===this.$captionsDiv&&void 0!==this.$captionsDiv&&this.$captionsDiv.css({"font-size":this.prefCaptionsSize}),"below"===this.prefCaptionsPosition?void 0!==this.$captionsWrapper&&this.$captionsWrapper.css({"background-color":this.prefCaptionsBGColor,opacity:"1"}):"overlay"===this.prefCaptionsPosition&&void 0!==this.$captionsWrapper&&this.$captionsWrapper.css({"background-color":"transparent",opacity:""}),this.positionCaptions()))},AblePlayer.prototype.positionCaptions=function(t){void 0===t&&(t=this.prefCaptionsPosition),void 0!==this.$captionsWrapper&&("below"==t?(this.$captionsWrapper.removeClass("able-captions-overlay").addClass("able-captions-below"),this.$captionsWrapper.css({"background-color":this.prefCaptionsBGColor,opacity:"1"})):(this.$captionsWrapper.removeClass("able-captions-below").addClass("able-captions-overlay"),this.$captionsWrapper.css({"background-color":"transparent",opacity:""})))}}(jQuery),function(t){AblePlayer.prototype.populateChaptersDiv=function(){var e,i,s,a;t("#"+this.chaptersDivLocation)&&(this.$chaptersDiv=t("#"+this.chaptersDivLocation),this.$chaptersDiv.addClass("able-chapters-div"),this.$chaptersDiv.empty(),this.chaptersTitle&&(a=t("<"+(i="h"+(e=this.getNextHeadingLevel(this.$chaptersDiv)).toString())+">",{class:"able-chapters-heading",id:s=this.mediaId+"-chapters-heading"}).text(this.chaptersTitle),this.$chaptersDiv.append(a)),this.$chaptersNav=t("<nav>"),this.chaptersTitle?this.$chaptersNav.attr("aria-labelledby",s):this.$chaptersNav.attr("aria-label",this.tt.chapters),this.$chaptersDiv.append(this.$chaptersNav),this.updateChaptersList())},AblePlayer.prototype.updateChaptersList=function(){var e,i,s,a,n,r,o,h,l,p,c;if(e=this,!this.$chaptersNav)return!1;if(void 0===this.useChapterTimes&&("chapter"===this.seekbarScope&&this.selectedChapters.cues.length?this.useChapterTimes=!0:this.useChapterTimes=!1),(i=this.useChapterTimes?this.selectedChapters.cues:this.chapters.length>=1?this.chapters[0].cues:[]).length>0){for(a=0,s=t("<ul>");a<i.length;a++)n=a,r=t("<li></li>"),o=t("<button>",{type:"button",val:n}).text(this.flattenCueForCaption(i[n])),l=function(i){return function(){e.seekTrigger="chapter",p=t(this).closest("li"),c=(s=t(this).closest("ul").find("li")).index(p),s.removeClass("able-current-chapter").children("button").removeAttr("aria-current"),p.addClass("able-current-chapter").children("button").attr("aria-current","true"),e.updateChapter(i),e.seekTo(i)}},o.on("click",l(i[n].start)),o.on("focus",function(){t(this).closest("ul").find("li").removeClass("able-focus"),t(this).closest("li").addClass("able-focus")}),r.on("hover",function(){t(this).closest("ul").find("li").removeClass("able-focus"),t(this).addClass("able-focus")}),r.on("mouseleave",function(){t(this).removeClass("able-focus")}),o.on("blur",function(){t(this).closest("li").removeClass("able-focus")}),r.append(o),s.append(r),this.defaultChapter===i[n].id&&(o.attr("aria-current","true").parent("li").addClass("able-current-chapter"),this.currentChapter=i[n],h=!0);h||(this.currentChapter=i[0],s.find("button").first().attr("aria-current","true").parent("li").addClass("able-current-chapter")),this.$chaptersNav.html(s)}return!1},AblePlayer.prototype.seekToChapter=function(t){for(var e=0;e<this.selectedChapters.cues.length;){if(this.selectedChapters.cues[e].id==t){this.seekTo(this.selectedChapters.cues[e].start),this.updateChapter(this.selectedChapters.cues[e].start);break}e++}},AblePlayer.prototype.updateChapter=function(t){var e,i,s;if(void 0!==this.selectedChapters){for(i=0,e=this.selectedChapters.cues;i<e.length;i++)if(e[i].start<=t&&e[i].end>t){s=i;break}void 0!==s&&this.currentChapter!==e[s]&&(this.currentChapter=e[s],this.useChapterTimes&&(this.chapterDuration=this.getChapterDuration(),this.seekIntervalCalculated=!1),void 0!==this.$chaptersDiv&&(this.$chaptersDiv.f
16935ind("ul").find("li").removeClass("able-current-chapter").children("button").removeAttr("aria-current"),this.$chaptersDiv.find("ul").find("li").eq(s).addClass("able-current-chapter").children("button").attr("aria-current","true")))}},AblePlayer.prototype.getChapterDuration=function(){var t,e;return void 0===this.currentChapter||void 0===this.duration?0:(t=this.selectedChapters.cues.length-1,this.selectedChapters.cues[t]==this.currentChapter&&this.currentChapter.end!==this.duration?(e=this.duration,this.currentChapter.end=this.duration):e=this.currentChapter.end,e-this.currentChapter.start)},AblePlayer.prototype.getChapterElapsed=function(){return void 0===this.currentChapter?0:this.elapsed>this.currentChapter.start?this.elapsed-this.currentChapter.start:0},AblePlayer.prototype.convertChapterTimeToVideoTime=function(t){if(void 0===this.currentChapter)return t;var e=this.currentChapter.start+t;return e>this.currentChapter.end?this.currentChapter.end:e},AblePlayer.prototype.getChapterClickFunction=function(t){var e=this;return function(){e.seekTrigger="chapter",e.seekTo(t),e.hidingPopup=!0,e.chaptersPopup.hide(),setTimeout(function(){e.hidingPopup=!1},100),e.$chaptersButton.focus()}}}(jQuery),function(t){AblePlayer.prototype.updateMeta=function(t){this.hasMeta&&("text"===this.metaType&&this.$metaDiv.show(),this.showMeta(t||this.elapsed))},AblePlayer.prototype.showMeta=function(e){var i,s,a,n,r,o,h,l,p,c;for(s=0,i=[],n=this.meta.length>=1?this.meta:[];s<n.length;s++)if(n[s].start<=e&&n[s].end>e){a=s;break}if(void 0!==a){if(this.currentMeta!==a){if("text"===this.metaType)this.$metaDiv.html(this.flattenCueForMeta(n[a]).replace("\n","<br>"));else if("selector"===this.metaType){for(h=0,o=(r=this.flattenCueForMeta(n[a])).split("\n");h<o.length;h++)"pause"===(l=t.trim(o[h])).toLowerCase().trim()?(this.hideBigPlayButton=!0,this.pauseMedia()):"focus:"==l.toLowerCase().substring(0,6)?t(c=l.substring(6).trim()).length&&t(c).focus():t(l).length&&(this.currentMeta=a,void 0===(p=parseInt(t(l).attr("data-duration")))||isNaN(p)?t(l).show():t(l).show().delay(p).fadeOut(),this.visibleSelectors.push(l),i.push(l));if(this.visibleSelectors&&this.visibleSelectors.length&&this.visibleSelectors.length!==i.length)for(h=this.visibleSelectors.length-1;h>=0;h--)-1==t.inArray(this.visibleSelectors[h],i)&&(t(this.visibleSelectors[h]).hide(),this.visibleSelectors.splice(h,1))}}}else{if(void 0!==this.$metaDiv&&this.$metaDiv.html(""),this.visibleSelectors&&this.visibleSelectors.length){for(h=0;h<this.visibleSelectors.length;h++)t(this.visibleSelectors[h]).hide();this.visibleSelectors=[]}this.currentMeta=-1}},AblePlayer.prototype.flattenCueForMeta=function(t){for(var e=[],i=function(t){var e,s=[];if("string"===t.type)s.push(t.value);else if("v"===t.type)for(s.push("["+t.value+"]"),e=0;e<t.children.length;e++)s.push(i(t.children[e]));else for(e=0;e<t.children.length;e++)s.push(i(t.children[e]));return s.join("")},s=0;s<t.components.children.length;s++)e.push(i(t.components.children[s]));return e.join("")}}(jQuery),function(t){AblePlayer.prototype.setupTranscript=function(){var e=new t.Deferred,i=e.promise();return this.usingYouTubeCaptions||this.usingVimeoCaptions?(this.transcriptType=null,e.resolve()):(!this.transcriptType&&this.captions.length&&(this.transcriptType="popup"),this.transcriptType?"popup"===this.transcriptType||"external"===this.transcriptType?(this.injectTranscriptArea(),e.resolve()):"manual"===this.transcriptType&&(this.setupManualTranscript(),e.resolve()):e.resolve()),i},AblePlayer.prototype.injectTranscriptArea=function(){var e,i,s,a,n,r;if(e=this,this.$transcriptArea=t("<div>",{class:"able-transcript-area",role:"dialog","aria-label":this.tt.transcriptTitle}),this.$transcriptToolbar=t("<div>",{class:"able-window-toolbar able-"+this.toolbarIconColor+"-controls"}),this.$transcriptDiv=t("<div>",{class:"able-transcript"}),this.$autoScrollTranscriptCheckbox=t("<input>",{id:"autoscroll-transcript-checkbox-"+this.mediaId,type:"checkbox"}),i=t("<label>",{for:"autoscroll-transcript-checkbox-"+this.mediaId}).text(this.tt.autoScroll),this.$transcriptToolbar.append(i,this.$autoScrollTranscriptCheckbox),this.captions.length>1)for(n=0,s=t("<div>",{class:"transcript-language-select-wrapper"}),a=t("<label>",{for:"transcript-language-select-"+this.mediaId}).text(this.tt.language),this.$transcriptLanguageSelect=t("<select>",{id:"transcript-language-select-"+this.mediaId});n<this.captions.length;n++)r=t("<option></option>",{value:this.captions[n].language,lang:this.captions[n].language}).text(this.captions[n].label),this.captions[n].def&&r.prop("selected",!0),this.$transcriptLanguageSelect.append(r);s&&(s.append(a,this.$transcriptLanguageSelect),this.$transcriptToolbar.append(s)),this.$transcriptArea.append(this.$transcriptToolbar,this.$transcriptDiv),this.transcriptDivLocation?t("#"+this.transcriptDivLocation).append(this.$transcriptArea):this.$ableWrapper.append(this.$transcriptArea),this.transcriptDivLocation||(this.initDragDrop("transcript"),1===this.prefTranscript&&this.positionDraggableWindow("transcript",this.getDefaultWidth("transcript"))),this.prefTranscript||this.transcriptDivLocation||this.$transcriptArea.hide()},AblePlayer.prototype.addTranscriptAreaEvents=function(){var t=this;this.$autoScrollTranscriptCheckbox.click(function(){t.handleTranscriptLockToggle(t.$autoScrollTranscriptCheckbox.prop("checked"))}),this.$transcriptDiv.on("mousewheel DOMMouseScroll click scroll",function(e){t.scrollingTranscript||(t.autoScrollTranscript=!1,t.refreshControl
vendor: 4,321 bytes, line 16935
16935s("transcript")),t.scrollingTranscript=!1}),void 0!==this.$transcriptLanguageSelect&&(this.$transcriptLanguageSelect.on("click mousedown",function(t){t.stopPropagation()}),this.$transcriptLanguageSelect.on("change",function(){var e=t.$transcriptLanguageSelect.val();t.syncTrackLanguages("transcript",e)}))},AblePlayer.prototype.transcriptSrcHasRequiredParts=function(){return!!t("#"+this.transcriptSrc).length&&(this.$transcriptArea=t("#"+this.transcriptSrc),!!this.$transcriptArea.find(".able-window-toolbar").length&&(this.$transcriptToolbar=this.$transcriptArea.find(".able-window-toolbar").eq(0),!!this.$transcriptArea.find(".able-transcript").length&&(this.$transcriptDiv=this.$transcriptArea.find(".able-transcript").eq(0),!!this.$transcriptArea.find(".able-transcript-seekpoint").length)))&&(this.$transcriptSeekpoints=this.$transcriptArea.find(".able-transcript-seekpoint"),!0)},AblePlayer.prototype.setupManualTranscript=function(){var e,i;e=t("<input>",{id:"autoscroll-transcript-checkbox-"+this.mediaId,type:"checkbox"}),i=t("<label>",{for:"autoscroll-transcript-checkbox-"+this.mediaId}).text(this.tt.autoScroll),this.$autoScrollTranscriptCheckbox=e,this.$transcriptToolbar.append(i,this.$autoScrollTranscriptCheckbox)},AblePlayer.prototype.updateTranscript=function(){if(this.transcriptType&&(!this.playerCreated||this.$transcriptArea)){if("external"===this.transcriptType||"popup"===this.transcriptType){if(this.transcriptLang?i=this.transcriptCaptions.cues:this.transcriptCaptions?(this.transcriptLang=this.transcriptCaptions.language,i=this.transcriptCaptions.cues):this.selectedCaptions&&(this.transcriptLang=this.captionLang,i=this.selectedCaptions.cues),this.transcriptChapters)e=this.transcriptChapters.cues;else if(this.chapters.length>0){if(this.transcriptLang)for(var e,i,s,a=0;a<this.chapters.length;a++)this.chapters[a].language===this.transcriptLang&&(e=this.chapters[a].cues);void 0===e&&(e=this.chapters[0].cues||[])}if(this.transcriptDescriptions)s=this.transcriptDescriptions.cues;else if(this.descriptions.length>0){if(this.transcriptLang)for(var a=0;a<this.descriptions.length;a++)this.descriptions[a].language===this.transcriptLang&&(s=this.descriptions[a].cues);s||(s=this.descriptions[0].cues||[])}var n=this.generateTranscript(e||[],i||[],s||[]);this.$transcriptDiv.html(n),this.$transcriptLanguageSelect&&(this.$transcriptLanguageSelect.find("option:selected").prop("selected",!1),this.$transcriptLanguageSelect.find("option[lang="+this.transcriptLang+"]").prop("selected",!0))}var r=this;1===this.prefTabbable&&this.$transcriptDiv.find("span.able-transcript-seekpoint").attr("tabindex","0"),this.$transcriptArea.length>0&&this.$transcriptArea.find("span.able-transcript-seekpoint").click(function(e){r.seekTrigger="transcript";var i=parseFloat(t(this).attr("data-start"));i+=.01,r.seekingFromTranscript?r.seekingFromTranscript=!1:(r.seekingFromTranscript=!0,r.seekTo(i))})}},AblePlayer.prototype.highlightTranscript=function(e){if(this.transcriptType){var i,s,a,n=this;e=parseFloat(e),this.$transcriptArea.find("span.able-transcript-seekpoint").each(function(){if(i=parseFloat(t(this).attr("data-start")),s=parseFloat(t(this).attr("data-end")),a=!!t(this).parent().hasClass("able-transcript-chapter-heading"),e>=i&&e<=s&&!a)return t(this).hasClass("able-highlight")||(n.$transcriptArea.find(".able-highlight").removeClass("able-highlight"),t(this).addClass("able-highlight"),n.movingHighlight=!0),!1}),n.currentHighlight=n.$transcriptArea.find(".able-highlight"),0===n.currentHighlight.length&&(n.currentHighlight=null)}},AblePlayer.prototype.generateTranscript=function(e,i,s){var a,n=this,r=t('<div class="able-transcript-container"></div>');if(r.attr("lang",this.transcriptLang),a=void 0!==this.transcriptTitle?this.transcriptTitle:this.lyricsMode?this.tt.lyricsTitle:this.tt.transcriptTitle,!this.transcriptDivLocation){var o=this.playerHeadingLevel,h=(o+=1)+1;if(o<=6)var l="h"+o.toString();else var l="div";var p=t("<"+l+">");p.addClass("able-transcript-heading"),o>6&&p.attr({role:"heading","aria-level":o}),p.text(a),p.attr("lang",this.lang),r.append(p)}for(var c=0,u=0,d=0,f=function(e,i){if(h<=6)var s="h"+h.toString();else var s="div";var a=t("<"+s+">",{class:"able-transcript-chapter-heading"});h>6&&a.attr({role:"heading","aria-level":h});for(var n=function(t){var e=[];
16935if("string"===t.type)e.push(t.value);else for(var i=0;i<t.children.length;i++)e=e.concat(n(t.children[i]));return e},r=t("<span>",{class:"able-transcript-seekpoint"}),o=0;o<i.components.children.length;o++)for(var l=n(i.components.children[o]),p=0;p<l.length;p++)r.append(l[p]);r.attr("data-start",i.start.toString()),r.attr("data-end",i.end.toString()),a.append(r),e.append(a)},g=function(e,i){var s=t("<div>",{class:"able-transcript-desc"}),a=t("<span>",{class:"able-hidden"});a.attr("lang",n.lang),a.text(n.tt.prefHeadingDescription+": "),s.append(a);for(var r=function(t){var e=[];if("string"===t.type)e.push(t.value);else for(var i=0;i<t.children.length;i++)e=e.concat(r(t.children[i]));return e},o=t("<span>",{class:"able-transcript-seekpoint"}),h=0;h<i.components.children.length;h++)for(var l=r(i.components.children[h]),p=0;p<l.length;p++)o.append(l[p]);o.attr("data-start",i.start.toString()),o.attr("data-end",i.end.toString()),s.append(o),e.append(s)},_=function(e,i){for(var s=t("<span>",{class:"able-transcript-seekpoint able-transcript-caption"}),a=function(e){var i=[],s=0,n=function(t){s++;var e,i=[];if(""===t)return i;var a=t.indexOf("["),r=t.indexOf("]"),o=t.indexOf("("),h=t.indexOf(")"),l=-1!==a&&-1!==r,p=-1!==o&&-1!==h;if(p||l){if(s>1)var c="<br/>";else var c="";var u=c+'<span class="able-unspoken">',d="</span>";p&&l&&(a<o?p=!1:l=!1)}return p?(e=t.substring(0,o),e+=u,e+=t.substring(o,h+1),e+=d,i.push(e+=n(t.substring(h+1)))):l?(e=t.substring(0,a),e+=u,e+=t.substring(a,r+1),e+=d,i.push(e+=n(t.substring(r+1)))):i.push(t),i};if("string"===e.type)i=i.concat(n(e.value));else if("v"===e.type){var r=t("<span>",{class:"able-unspoken"});r.text("("+e.value+")"),i.push(r);for(var o=0;o<e.children.length;o++)for(var h=a(e.children[o]),l=0;l<h.length;l++)i.push(h[l])}else if("b"===e.type||"i"===e.type){if("b"===e.type)var p=t("<strong>");else if("i"===e.type)var p=t("<em>");for(var o=0;o<e.children.length;o++)for(var h=a(e.children[o]),l=0;l<h.length;l++)p.append(h[l]);("b"===e.type||"i"==e.type)&&i.push(p," ")}else for(var o=0;o<e.children.length;o++)i=i.concat(a(e.children[o]));return i},r=0;r<i.components.children.length;r++)for(var o=a(i.components.children[r]),h=0;h<o.length;h++){var l=o[h];"string"==typeof l&&(n.lyricsMode?l=l.replace("\n","<br>")+"<br>":l+=" "),s.append(l)}s.attr("data-start",i.start.toString()),s.attr("data-end",i.end.toString()),e.append(s),e.append(" \n")};c<e.length||d<s.length||u<i.length;){if(c<e.length&&d<s.length&&u<i.length)var v=Math.min(e[c].start,s[d].start,i[u].start);else if(c<e.length&&d<s.length)var v=Math.min(e[c].start,s[d].start);else if(c<e.length&&u<i.length)var v=Math.min(e[c].start,i[u].start);else if(d<s.length&&u<i.length)var v=Math.min(s[d].start,i[u].start);else var v=null;null!==v?void 0!==e[c]&&e[c].start===v?(f(r,e[c]),c+=1):void 0!==s[d]&&s[d].start===v?(g(r,s[d]),d+=1):(_(r,i[u]),u+=1):c<e.length?(f(r,e[c]),c+=1):d<s.length?(g(r,s[d]),d+=1):u<i.length&&(_(r,i[u]),u+=1)}var m=r.children(),y=0;return m.each(function(){t(this).hasClass("able-transcript-caption")?((-1!==t(this).text().indexOf("[")||-1!==t(this).text().indexOf("("))&&y>0&&(r.find(".able-block-temp").removeClass("able-block-temp").wrapAll('<div class="able-transcript-block"></div>'),y=0),t(this).addClass("able-block-temp"),y++):y>0&&(r.find(".able-block-temp").removeClass("able-block-temp").wrapAll('<div class="able-transcript-block"></div>'),y=0)}),r}}(jQuery),function(t){AblePlayer.prototype.showSearchResults=function(){var e=this;if(this.searchDiv&&this.searchString&&t("#"+this.SearchDiv)){var i="<p>"+this.tt.resultsSummary1+" ";i+='<span id="able-search-term-echo">'+this.searchString+"</span>",i+="</p>";var s=this.searchFor(this.searchString,this.searchIgnoreCaps);if(s.length>0){var a=t("<p>",{class:"able-search-results-summary"}),n=this.tt.resultsSummary2;n+=" <strong>"+s.length+"</strong> ",n+=this.tt.resultsSummary3+" ",n+=this.tt.resultsSummary4,a.html(n);for(var r=t("<ul>"),o=0;o<s.length;o++){var h="aria-search-result-"+o,l=t("<li>",{}),p=this.secondsToTime(s[o].start),c=this.tt.searchButtonLabel+" "+p.title,u=t("<button>",{class:"able-search-results-time","data-start":s[o].start,title:c,"aria-label":c,"aria-describedby":h});u.text(p.value),u.on("click",function(i){e.seekTrigger="search";var s=parseFloat(t(this).attr("data-start"));s+=.01,e.seeking=!0,e.seekTo(s)});var d=t("<span>",{class:"able-search-result-text",id:h});d.html("..."+s[o].caption+"..."),l.append(u,d),r.append(l)}t("#"+this.searchDiv).html(i).append(a,r)}else{var f=t("<p>").text(this.tt.noResultsFound);t("#"+this.searchDiv).html(i).append(f)}}},AblePlayer.prototype.searchFor=function(e,i){n=[];var s,a,n,r,o,h,l,p=e.split(" ");if(this.captions.length>0){for(h=0;h<this.captions.length;h++)this.captions[h].language===this.searchLang&&(s=this.searchLang,a=this.captions[h].cues);if(a.length>0){for(h=0,o=0;h<a.length;h++)if(-1!==t.inArray(a[h].components.children[0].type,["string","i","b","u","v","c"])){r=this.flattenCueForCaption(a[h]);var c=i?r.toLowerCase():r;for(l=0;l<p.length;l++){var u=i?p[l].toLowerCase():p[l];if(-1!==c.indexOf(u)){n[o]=[],n[o].start=a[h].start,n[o].lang=s,n[o].caption=this.highlightSearchTerm(p,r),o++;break}}}}}return n},AblePlayer.prototype.highlightSearchTerm=function(t,e){return t.forEach(function(t){var i=RegExp(t,"gi");e=e.replace(i,'<span class="able-search-term">$&</span>')}),e},AblePlayer.prototype.secondsToTime=function(t){var t=Math.floor(t),e=parseInt(t/3600,10)%24,i=parseInt(t/60,10)%60,s=t%60,a="",n="";e>0&&(a+=e+":",1==e?n+="1 "+this.tt.hour+" ":n+=e+" "+this.tt.hours+" "),i<10?(a+="0"+i+":",i>0&&(1==i?n+="1 "+this.tt.minute+" ":n+=i+" "+this.tt.minutes+" ")):(a+=i+":",n+=i+" "+this.tt.minutes+" "),s<10?(a+="0"+s,s>0&&(1==s?n+="1 "+this.tt.second+" ":n+=s+" "+this.tt.seconds+" ")):(a+=s,n+=s+" "+this.tt.seconds+" ");var r=[];return r.value=a,r.title=n,r}}(jQuery),function(t){AblePlayer.prototype.onMediaUpdateTime=function(t,e){var i=this;this.getMediaTimes(t,e).then(function(t){i.duration=t.duration,i.elapsed=t.elapsed,i.duration>0&&(1===i.prefHighlight&&i.highlightTranscript(i.elapsed),i.updateCaption(i.elapsed),i.showDescription(i.elapsed),i.updateChapter(i.elapsed),i.updateMeta(i.elapsed),i.refreshControl
16935s("timeline",i.duration,i.elapsed))})},AblePlayer.prototype.onMediaPause=function(){this.controlsHidden&&(this.fadeControls("in"),this.controlsHidden=!1),"active"===this.hideControlsTimeoutStatus&&(window.clearTimeout(this.hideControlsTimeout),this.hideControlsTimeoutStatus="clear"),this.refreshControls("playpause")},AblePlayer.prototype.onMediaComplete=function(){this.hasPlaylist&&!this.cueingPlaylistItem&&(this.playlistIndex===this.$playlist.length-1?this.loop?(this.playlistIndex=0,this.cueingPlaylistItem=!0,this.cuePlaylistItem(0)):(this.playing=!1,this.paused=!0):(this.playlistIndex++,this.cueingPlaylistItem=!0,this.cuePlaylistItem(this.playlistIndex))),this.refreshControls("init")},AblePlayer.prototype.onMediaNewSourceLoad=function(){var t=!1;this.cueingPlaylistItem&&(this.cueingPlaylistItem=!1),this.recreatingPlayer&&(this.recreatingPlayer=!1),this.playbackRate&&this.setPlaybackRate(this.playbackRate),this.userClickedPlaylist?(!this.startedPlaying||this.okToPlay)&&(this.playMedia(),t=!0):"restart"==this.seekTrigger||"chapter"==this.seekTrigger||"transcript"==this.seekTrigger||"search"==this.seekTrigger?(this.playMedia(),t=!0):this.swappingSrc?this.hasPlaylist?(this.playlistIndex!==this.$playlist.length||this.loop)&&(this.playMedia(),t=!0):this.swapTime>0?"complete"===this.seekStatus?(this.okToPlay&&this.playMedia(),t=!0):"seeking"===this.seekStatus||(this.swapTime===this.elapsed?(this.seekStatus="complete",this.okToPlay&&this.playMedia(),t=!0):this.hasDescTracks?t=!0:this.durationsAreCloseEnough(this.duration,this.prevDuration)?(this.seekStatus="seeking",this.seekTo(this.swapTime)):t=!0):this.playing&&(this.playMedia(),t=!0):this.startedPlaying?this.hasPlaylist?(this.playlistIndex!==this.$playlist.length||this.loop)&&(this.playMedia(),t=!0):t=!0:this.startTime>0?this.seeking?(this.seeking=!1,this.okToPlay&&this.playMedia(),t=!0):this.seekTo(this.startTime):this.defaultChapter&&void 0!==this.selectedChapters?this.seekToChapter(this.defaultChapter):(this.okToPlay&&this.playMedia(),t=!0),t&&(this.swappingSrc=!1,this.seekStatus=null,this.swapTime=0,this.seekTrigger=null,this.seekingFromTranscript=!1,this.userClickedPlaylist=!1,this.okToPlay=!1),this.refreshControls("init"),this.$focusedElement&&(this.restoreFocus(),this.$focusedElement=null)},AblePlayer.prototype.durationsAreCloseEnough=function(t,e){var i,s;return i=1,(s=Math.abs(Math.round(t)-Math.round(e)))<=i},AblePlayer.prototype.restoreFocus=function(){var e;this.$focusedElement&&"button"===this.$focusedElement.attr("role")&&(e=this.$focusedElement.attr("class").split(/\s+/),t.each(e,function(e,i){"able-button-handler-"===i.substring(0,20)&&t("div.able-controller div."+i).focus()}))},AblePlayer.prototype.addSeekbarListeners=function(){var t=this;this.seekBar.bodyDiv.on("startTracking",function(e){t.pausedBeforeTracking=t.paused,t.pauseMedia()}).on("tracking",function(e,i){t.highlightTranscript(i),t.updateCaption(i),t.showDescription(i),t.updateChapter(t.convertChapterTimeToVideoTime(i)),t.updateMeta(i),t.refreshControls("init")}).on("stopTracking",function(e,i){t.useChapterTimes?t.seekTo(t.convertChapterTimeToVideoTime(i)):t.seekTo(i),t.pausedBeforeTracking||setTimeout(function(){t.playMedia()},200)})},AblePlayer.prototype.onClickPlayerButton=function(e){var i,s;"play"===(i=this.getButtonNameFromClass(t(e).attr("class")))?(this.clickedPlay=!0,this.handlePlay()):"restart"===i?(this.seekTrigger="restart",this.handleRestart()):"previous"===i?(this.userClickedPlaylist=!0,this.okToPlay=!0,this.seekTrigger="previous",this.buttonWithFocus="previous",this.handlePrevTrack()):"next"===i?(this.userClickedPlaylist=!0,this.okToPlay=!0,this.seekTrigger="next",this.buttonWithFocus="next",this.handleNextTrack()):"rewind"===i?(this.seekTrigger="rewind",this.handleRewind()):"forward"===i?(this.seekTrigger="forward",this.handleFastForward()):"mute"===i?this.handleMute():"volume"===i?this.handleVolumeButtonClick():"faster"===i?this.handleRateIncrease():"slower"===i?this.handleRateDecrease():"captions"===i?this.handleCaptionToggle():"chapters"===i?this.handleChapters():"descriptions"===i?this.handleDescriptionToggle(
16935):"sign"===i?this.closingSign||this.handleSignToggle():"preferences"===i?"menu"===t(e).attr("data-prefs-popup")?this.handlePrefsClick():(this.showingPrefsDialog=!0,this.closePopups(),"keyboard"===(s=t(e).attr("data-prefs-popup"))?this.keyboardPrefsDialog.show():"captions"===s?this.captionPrefsDialog.show():"descriptions"===s?this.descPrefsDialog.show():"transcript"===s&&this.transcriptPrefsDialog.show(),this.showingPrefsDialog=!1):"help"===i?this.handleHelpClick():"transcript"===i?this.closingTranscript||this.handleTranscriptToggle():"fullscreen"===i&&(this.clickedFullscreenButton=!0,this.handleFullscreenToggle())},AblePlayer.prototype.getButtonNameFromClass=function(t){var e,i;for(i=0,e=t.split(" ");i<e.length;i++)if("able-button-handler-"===e[i].substring(0,20))return e[i].substring(20);return t},AblePlayer.prototype.okToHandleKeyPress=function(){return"INPUT"!==t(AblePlayer.getActiveDOMElement()).prop("tagName")},AblePlayer.prototype.onPlayerKeyPress=function(e){var i,s;return((i=e.which)>=65&&i<=90&&(i+=32),s=t(document.activeElement),27===i&&this.$transcriptArea&&t.contains(this.$transcriptArea[0],s[0])&&!this.hidingPopup)?(this.handleTranscriptToggle(),!1):!!this.okToHandleKeyPress()&&void(!(t(":focus").is("[contenteditable]")||t(":focus").is("input")||t(":focus").is("textarea")&&!this.stenoMode||t(":focus").is("select")||e.target.hasAttribute("contenteditable")||"INPUT"===e.target.tagName||"TEXTAREA"===e.target.tagName&&!this.stenoMode||"SELECT"===e.target.tagName)&&(27===i?(this.closePopups(),this.$tooltipDiv.hide(),this.seekBar.hideSliderTooltips()):32===i?"button"===s.attr("role")&&(e.preventDefault(),s.click()):112===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handlePlay()):115===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleRestart()):109===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleMute()):118===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleVolumeButtonClick()):i>=49&&i<=57?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleVolumeKeystroke(i)):99===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleCaptionToggle()):100===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleDescriptionToggle()):102===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleFastForward()):114===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleRewind()):98===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handlePrevTrack()):110===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handleNextTrack()):101===i?this.usingModifierKeys(e)&&(e.preventDefault(),this.handlePrefsClick()):13===i&&("button"===s.attr("role")||"SPAN"===s.prop("tagName")?s.click():"LI"===s.prop("tagName")&&s.click())))},AblePlayer.prototype.addHtml5MediaListeners=function(){var t=this;this.$media.on("emptied",function(){}).on("loadedmetadata",function(){t.duration=t.media.duration}).on("canplay",function(){}).on("canplaythrough",function(){t.onMediaNewSourceLoad()}).on("play",function(){t.refreshControls("playpause")}).on("playing",function(){t.playing=!0,t.paused=!1,t.swappingSrc=!1,t.refreshControls("playpause")}).on("ended",function(){t.playing=!1,t.paused=!0,t.onMediaComplete()}).on("progress",function(){t.refreshControls("timeline")}).on("waiting",function(){}).on("durationchange",function(){t.refreshControls("timeline")}).on("timeupdate",function(){t.onMediaUpdateTime()}).on("pause",function(){t.clickedPlay?(t.playing=!1,t.paused=!0):t.hasPlaylist||t.swappingSrc||(t.playing=!1,t.paused=!0),t.clickedPlay=!1,t.onMediaPause()}).on("ratechange",function(){}).on("volumechange",function(){t.volume=t.getVolume()}).on("error",function(){if(t.debug)switch(t.media.error.code){case 1:console.log("HTML5 Media Error: MEDIA_ERR_ABORTED");break;case 2:console.log("HTML5 Media Error: MEDIA_ERR_NETWORK ");break;case 3:console.log("HTML5 Media Error: MEDIA_ERR_DECODE ");break;case 4:console.log("HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED ")}})},AblePlayer.prototype.addVimeoListeners=function(){var t=this;this.vimeoPlayer.on("loaded",function(e){t.onMediaNewSourceLoad()}),this.vimeoPlayer.on("play",function(e){t.playing=!0,t.startedPlaying=!0,t.paused=!1,t.refreshControl
16935s("playpause")}),this.vimeoPlayer.on("ended",function(e){t.playing=!1,t.paused=!0,t.onMediaComplete()}),this.vimeoPlayer.on("bufferstart",function(){}),this.vimeoPlayer.on("bufferend",function(){}),this.vimeoPlayer.on("progress",function(t){}),this.vimeoPlayer.on("seeking",function(t){}),this.vimeoPlayer.on("seeked",function(t){}),this.vimeoPlayer.on("timeupdate",function(e){t.onMediaUpdateTime(e.duration,e.seconds)}),this.vimeoPlayer.on("pause",function(e){t.clickedPlay?(t.playing=!1,t.paused=!0):t.hasPlaylist||t.swappingSrc||(t.playing=!1,t.paused=!0),t.clickedPlay=!1,t.onMediaPause(),t.refreshControls("playpause")}),this.vimeoPlayer.on("playbackratechange",function(e){t.vimeoPlaybackRate=e.playbackRate}),this.vimeoPlayer.on("texttrackchange",function(t){}),this.vimeoPlayer.on("volumechange",function(e){t.volume=10*e.volume}),this.vimeoPlayer.on("error",function(t){})},AblePlayer.prototype.addEventListeners=function(){var e;if(e=this,t(window).on("resize",function(){e.resizePlayer()}),window.MutationObserver){var i,s=this.$ableDiv[0];new MutationObserver(function(t){t.forEach(function(t){"attributes"===t.type&&"style"===t.attributeName&&e.$ableDiv.is(":visible")&&e.refreshControls("init")})}).observe(s,{attributes:!0,childList:!0,characterData:!0})}void 0!==this.seekBar?this.addSeekbarListeners():setTimeout(function(){void 0!==e.seekBar&&e.addSeekbarListeners()},2e3),this.$controllerDiv.find('div[role="button"]').on("click",function(t){t.stopPropagation(),e.onClickPlayerButton(this)}),t(document).on("click",function(i){if(0!==i.button)return!1;(t(".able-popup:visible").length||t(".able-volume-popup:visible"))&&e.closePopups(),"VIDEO"===i.target.tagName&&(e.clickedPlay=!0)}),this.$ableDiv.on("mousemove",function(){e.controlsHidden?(e.fadeControls("in"),e.controlsHidden=!1,"active"===e.hideControlsTimeoutStatus&&(window.clearTimeout(e.hideControlsTimeout),e.hideControlsTimeoutStatus="clear"),e.hideControls&&e.invokeHideControlsTimeout()):"active"===e.hideControlsTimeoutStatus&&(window.clearTimeout(e.hideControlsTimeout),e.hideControlsTimeoutStatus="clear",e.hideControls&&e.invokeHideControlsTimeout())}),t(document).keydown(function(t){e.controlsHidden?(e.fadeControls("in"),e.controlsHidden=!1,"active"===e.hideControlsTimeoutStatus&&(window.clearTimeout(e.hideControlsTimeout),e.hideControlsTimeoutStatus="clear"),e.hideControls&&e.invokeHideControlsTimeout()):"active"===e.hideControlsTimeoutStatus&&(window.clearTimeout(e.hideControlsTimeout),e.hideControlsTimeoutStatus="clear",e.hideControls&&e.invokeHideControlsTimeout())}),this.$ableDiv.keydown(function(t){AblePlayer.nextIndex>1&&e.onPlayerKeyPress(t)}),this.stenoMode&&void 0!==this.stenoFrameContents&&this.stenoFrameContents.on("keydown",function(t){e.onPlayerKeyPress(t)}),this.$transcriptArea&&this.$transcriptArea.on("keydown",function(t){AblePlayer.nextIndex>1&&e.onPlayerKeyPress(t)}),this.$playlist&&this.$playlist.click(function(i){e.userClickedPlaylist||(e.userClickedPlaylist=!0,e.playlistIndex=t(this).index(),e.cuePlaylistItem(e.playlistIndex))}),this.$media.click(function(){e.handlePlay()}),"html5"===this.player?this.addHtml5MediaListeners():"vimeo"===this.player?this.addVimeoListeners():"youtube"===this.player&&setInterval(function(){e.onMediaUpdateTime()},300)}}(jQuery),function(t){AblePlayer.prototype.initDragDrop=function(e){var i,s,a,n,r,o,h,l,p,c,u,d,f;for(i=this,"transcript"===e?(s=this.$transcriptArea,n="transcript-window",a=this.$transcriptToolbar):"sign"===e&&(s=this.$signWindow,n="sign-window",a=this.$signToolbar),a.addClass("able-draggable"),r=t("<div>",{class:"able-resizable"}),o=t("<svg>").attr({width:"100%",height:"100%",viewBox:"0 0 100 100",preserveAspectRatio:"none"}),h=1;h<=3;h++)1===h?(l="100",p="0",c="0",u="100"):2===h?(l="33",p="100",c="100",u="33"):3===h&&(l="67",p="100",c="100",u="67"),d=t("<line>").attr({x1:l,y1:p,x2:c,y2:u,"vector-effect":"non-scaling-stroke"}),o.append(d);r.html(o),f=parseInt(s.css("z-index"))+100,r.css("z-index",f),s.append(r),r.html(r.html()),a.on("mousedown mouseup touchstart touchend",function(t){return t.stopPropagation(),"mousedown"===t.type||"touchstart"===t.type?i.windowMenuClickRegistered||(i.windowMenuClickRegistered=!0,i.startMouseX=t.pageX,i.startMouseY=t.pageY,i.dragDevice="mouse",i.startDrag(e,s)):("mouseup"===t.type||"touchend"===t.type)&&i.dragging&&"mouse"===i.dragDevice&&i.endDrag(e),!1}),r.on("mousedown mouseup touchstart touchend",function(t){return t.stopPropagation(),"mousedown"===t.type||"touchstart"===t.type?i.windowMenuClickRegistered||(i.windowMenuClickRegistered=!0,i.startMouseX=t.pageX,i.startMouseY=t.pageY,i.startResize(e,s)):("mouseup"===t.type||"touchend"===t.type)&&i.resizing&&i.endResize(e),!1}),s.on("click",function(){i.windowMenuClickRegistered||i.finishingDrag||i.updateZIndex(e),i.finishingDrag=!1}),this.addWindowMenu(e,s,n)},AblePlayer.prototype.addWindowMenu=function(e,i,s){var a,n,r,o,h,l,p,c,u,d,f;a=this,this.windowMenuClickRegistered=!1,this.finishingDrag=!1,(n=t('<div role="alert"></div>')).addClass("able-alert"),n.hide(),n.appendTo(this.$activeWindow),n.css({top:i.offset().top}),r=this.mediaId+"-"+s+"-menu",o=t("<button>",{type:"button",tabindex:"0","aria-label":this.tt.windowButtonLabel,"aria-haspopup":"true","aria-controls":r,"aria-expanded":"false",class:"able-button-handler-preferences"}),"font"===this.iconType?(h=t("<span>",{class:"icon-preferences","aria-hidden":"true"}),o.append(h)):(p=t("<img>",{src:l=this.rootPath+"button-icons/"+this.toolbarIconColor+"/preferences.png",alt:"",role:"presentation"}),o.append(p)),c=t("<span>",{class:"able-clipped"}
16935).text(this.tt.windowButtonLabel),o.append(c),d=t("<div>",{class:"able-tooltip",id:u=this.mediaId+"-"+s+"-tooltip"}).hide(),o.on("mouseenter focus",function(e){var i=t(this).attr("aria-label"),s=t(this).position(),n=t(this).height();t(this).width();var r=s.top-n-5,h=AblePlayer.localGetElementById(o[0],u).text(i).css({left:"",right:"0px",top:r+"px"});a.showTooltip(h),t(this).on("mouseleave blur",function(){AblePlayer.localGetElementById(o[0],u).text("").hide()})}),f=this.setupPopups(s),"transcript"===e?(this.$transcriptAlert=n,this.$transcriptPopupButton=o,this.$transcriptPopup=f,this.$transcriptToolbar.prepend(n,o,d,f)):"sign"===e&&(this.$signAlert=n,this.$signPopupButton=o,this.$signPopup=f,this.$signToolbar.append(n,o,d,f)),o.on("click mousedown keydown",function(t){return!a.focusNotClick&&(a.dragging?(a.dragKeys(e,t),!1):void(t.stopPropagation(),a.windowMenuClickRegistered||a.finishingDrag||a.handleWindowButtonClick(e,t),a.finishingDrag=!1))}),this.addResizeDialog(e,i)},AblePlayer.prototype.addResizeDialog=function(e,i){var s,a,n,r,o,h,l,p,c,u,d,f,g,_,v,m,y,b,k,C,T,w,P;s=this,"transcript"===e?(a=this.$transcriptPopup,n=this.$transcriptPopupButton):"sign"===e&&(a=this.$signPopup,n=this.$signPopupButton),r=this.mediaId+"-resize-"+e+"-width",o=this.mediaId+"-resize-"+e+"-height",p=(h=i.width())/(l=i.height()),c=t("<div></div>",{class:"able-resize-form"}),u=t("<div></div>"),d=t("<div></div>"),f=t("<input>",{type:"text",id:r,value:h}),g=t("<label>",{for:r}).text(this.tt.width),_=t("<div></div>"),v=t("<input>",{type:"text",id:o,value:l}),m=t("<label>",{for:o}).text(this.tt.height),"sign"===e&&(v.prop("readonly",!0),f.on("input",function(){b=Math.round((y=t(this).val())/p,0),v.val(b)})),k=t('<button class="modal-button">'+this.tt.save+"</button>"),C=t('<button class="modal-button">'+this.tt.cancel+"</button>"),k.on("click",function(){T=t("#"+r).val(),w=t("#"+o).val(),(T!==h||w!==l)&&(s.resizeObject(e,T,w),s.updateCookie(e)),P.hide(),a.hide(),n.focus()}),C.on("click",function(){P.hide(),a.hide(),n.focus()}),d.append(g,f),_.append(m,v),u.append(d,_),c.append(u,"<hr>",k,C),t("body").append(c),P=new AccessibleDialog(c,n,"dialog",!0,this.tt.windowResizeHeading,u,this.tt.closeButtonLabel,"20em"),"transcript"===e?this.transcriptResizeDialog=P:"sign"===e&&(this.signResizeDialog=P)},AblePlayer.prototype.handleWindowButtonClick=function(e,i){var s,a,n,r,o;if(s=this,this.focusNotClick)return!1;if("transcript"===e?(a=this.$transcriptPopup,n=this.$transcriptPopupButton,r=this.$transcriptToolbar):"sign"===e&&(a=this.$signPopup,n=this.$signPopupButton,r=this.$signToolbar),"keydown"===i.type){if(32===i.which||13===i.which)this.windowMenuClickRegistered=!0;else{if(27!==i.which)return!1;a.is(":visible")?a.hide("fast",function(){s.windowMenuClickRegistered=!1,a.find("li").removeClass("able-focus").attr("tabindex","-1"),n.focus()}):"sign"===e?this.handleSignToggle():"transcript"===e&&this.handleTranscriptToggle()}}else this.windowMenuClickRegistered=!0;a.is(":visible")?(a.hide(200,"",function(){s.windowMenuClickRegistered=!1}),a.find("li").removeClass("able-focus"),n.attr("aria-expanded","false").focus()):(this.updateZIndex(e),o=n.position().top+n.outerHeight(),a.css("top",o),a.show(200,"",function(){n.attr("aria-expanded","true"),t(this).find("li").first().focus().addClass("able-focus"),s.windowMenuClickRegistered=!1}))},AblePlayer.prototype.handleMenuChoice=function(t,e,i){var s,a,n,r,o;if(s=this,"transcript"===t?(a=this.$transcriptArea,n=this.$transcriptPopup,r=this.$transcriptPopupButton,o=this.transcriptResizeDialog):"sign"===t&&(a=this.$signWindow,n=this.$signPopup,r=this.$signPopupButton,o=this.signResizeDialog),this.$activeWindow=a,"keydown"===i.type)return 27===i.which?(n.hide("fast",function(){s.windowMenuClickRegistered=!1,n.find("li").removeClass("able-focus").attr("tabindex","-1"),r.attr("aria-expanded","false"),r.focus()}),!1):("close"!==e&&(this.$activeWindow=a),!1);if(n.hide("fast",function(){s.windowMenuClickRegistered=!1,n.find("li").removeClass("able-focus").attr("tabindex","-1"),r.attr("aria-expanded","false")}),"close"!==e&&r.focus(),"move"===e)this.$activeWindow.attr("role","application"),this.showedAlert(t)||(this.showAlert(this.tt.windowMoveAlert,t),"transcript"===t?this.showedTranscriptAlert=!0:"sign"!==t||(this.showedSignAlert=!0)),"keydown"===i.t
16935ype?this.dragDevice="keyboard":this.dragDevice="mouse",this.startDrag(t,a),n.hide().parent().focus();else if("resize"==e){var h=o.getInputs();h&&(h[0].value=a.width(),h[1].value=a.height()),o.show()}else"close"==e&&("transcript"===t?(this.closingTranscript=!0,this.handleTranscriptToggle()):"sign"===t&&(this.closingSign=!0,this.handleSignToggle()))},AblePlayer.prototype.startDrag=function(e,i){var s,a,n,r,o;return s=this,this.$activeWindow||(this.$activeWindow=i),this.dragging=!0,"transcript"===e?a=this.$transcriptPopup:"sign"===e&&(a=this.$signPopup),this.showedAlert(e)||(this.showAlert(this.tt.windowMoveAlert,e),"transcript"===e?this.showedTranscriptAlert=!0:"sign"!==e||(this.showedSignAlert=!0)),a.is(":visible")&&a.hide(),this.updateZIndex(e),n=this.$activeWindow.position(),this.dragStartX=n.left,this.dragStartY=n.top,void 0===this.startMouseX?(this.dragDevice="keyboard",this.dragKeyX=this.dragStartX,this.dragKeyY=this.dragStartY,this.startingDrag=!0):(this.dragDevice="mouse",this.dragOffsetX=this.startMouseX-this.dragStartX,this.dragOffsetY=this.startMouseY-this.dragStartY),this.$activeWindow.addClass("able-drag").css({position:"absolute",top:this.dragStartY+"px",left:this.dragStartX+"px"}).focus(),"mouse"===this.dragDevice?t(document).on("mousemove touchmove",function(t){s.dragging&&(r=t.pageX-s.dragOffsetX,o=t.pageY-s.dragOffsetY,s.resetDraggedObject(r,o))}):"keyboard"===this.dragDevice&&this.$activeWindow.on("keydown",function(t){s.dragging&&s.dragKeys(e,t)}),!1},AblePlayer.prototype.dragKeys=function(t,e){var i,s;if(this.startingDrag)return this.startingDrag=!1,!1;switch(i=e.which,s=10,i){case 37:case 63234:this.dragKeyX-=s;break;case 38:case 63232:this.dragKeyY-=s;break;case 39:case 63235:this.dragKeyX+=s;break;case 40:case 63233:this.dragKeyY+=s;break;case 13:case 27:return this.endDrag(t),!1;default:return!1}return this.resetDraggedObject(this.dragKeyX,this.dragKeyY),e.preventDefault&&e.preventDefault(),!1},AblePlayer.prototype.resetDraggedObject=function(t,e){this.$activeWindow.css({left:t+"px",top:e+"px"})},AblePlayer.prototype.resizeObject=function(t,e,i){var s;this.$activeWindow.css({width:e+"px",height:i+"px"}),"transcript"===t&&(s=i-50,this.$transcriptDiv.css("height",s+"px"))},AblePlayer.prototype.endDrag=function(e){var i,s,a;i=this,"transcript"===e?(s=this.$transcriptPopup,a=this.$transcriptPopupButton):"sign"===e&&(s=this.$signPopup,a=this.$signPopupButton),t(document).off("mousemove mouseup touchmove touchup"),this.$activeWindow.off("keydown").removeClass("able-drag"),this.$activeWindow.attr("role","dialog"),this.$activeWindow=null,"keyboard"===this.dragDevice&&a.focus(),this.dragging=!1,this.updateCookie(e),this.startMouseX=void 0,this.startMouseY=void 0,this.windowMenuClickRegistered=!1,this.finishingDrag=!0,setTimeout(function(){i.finishingDrag=!1},100)},AblePlayer.prototype.isCloseToCorner=function(t,e,i){var s,a,n,r,o,h,l,p;return s=10,n=(a=t.offset()).top,r=a.left,o=t.width(),l=n+(h=t.height()),p=r+o,!!(Math.abs(l-i)<=s&&Math.abs(p-e)<=s)},AblePlayer.prototype.startResize=function(e,i){var s,a,n,r,o;return s=this,this.$activeWindow=i,this.resizing=!0,"transcript"===e?a=this.$transcriptPopup:"sign"===e&&(a=this.$signPopup),a.is(":visible")&&a.hide().parent().focus(),n=this.$activeWindow.position(),this.dragKeyX=this.dragStartX,this.dragKeyY=this.dragStartY,this.dragStartWidth=this.$activeWindow.width(),this.dragStartHeight=this.$activeWindow.height(),t(document).on("mousemove touchmove",function(t){s.resizing&&(r=s.dragStartWidth+(t.pageX-s.startMouseX),o=s.dragStartHeight+(t.pageY-s.startMouseY),s.resizeObject(e,r,o))}),!1},AblePlayer.prototype.endResize=function(e){var i,s;"transcript"===e?(i=this.$transcriptPopup,s=this.$transcriptPopupButton):"sign"===e&&(i=this.$signPopup,s=this.$signPopupButton),t(document).off("mousemove mouseup touchmove touchup"),this.$activeWindow.off("keydown"),s.show().focus(),this.resizing=!1,this.$activeWindow.removeClass("able-resize"),this.updateCookie(e),this.windowMenuClickRegistered=!1,this.finishingDrag=!0,setTimeout(function(){this.finishingDrag=!1},100)}}(jQuery),function(t){AblePlayer.prototype.initSignLanguage=function(){"html5"===this.player?(this.signFile=this.$sources.first().attr("data-sign-src"),this.signFile?this.isIOS()?(this.hasSignLanguage=!1,this.debug&&console.log("Sign language has been disabled due to IOS restrictions")):(this.debug&&console.log("This video has an accompanying sign language video: "+this.signFile),this.hasSignLanguage=!0,this.injectSignPlayerCode()):this.hasSignLanguage=!1):this.hasSignLanguage=!1},AblePlayer.prototype.injectSignPlayerCode=function(){var e,i,s,a,n,r,o;for(a=0,e=this,s=this.getDefaultWidth("sign"),i=this.mediaId+"-sign",this.$signVideo=t("<video>",{id:i,tabindex:"-1"}),this.signVideo=this.$signVideo[0];a<this.$sources.length;a++)if(n=this.$sources[a].getAttribute("data-sign-src"),r=this.$sources[a].getAttribute("type"),n)o=t("<source>",{src:n,type:r}),this.$signVideo.append(o);else{this.hasSignLanguage=!1;break}this.$signWindow=t("<div>",{class:"able-sign-window",role:"dialog","aria-label":this.tt.sign}),this.$signToolbar=t("<div>",{class:"able-window-toolbar able-"+this.toolbarIconColor+"-control
16935s"}),this.$signWindow.append(this.$signToolbar,this.$signVideo),this.$ableWrapper.append(this.$signWindow),this.initDragDrop("sign"),1===this.prefSign?this.positionDraggableWindow("sign",this.getDefaultWidth("sign")):this.$signWindow.hide()}}(jQuery),function(t){var e={ab:{name:"Abkhaz",nativeName:"аҧÑÑа"},aa:{name:"Afar",nativeName:"Afaraf"},af:{name:"Afrikaans",nativeName:"Afrikaans"},ak:{name:"Akan",nativeName:"Akan"},sq:{name:"Albanian",nativeName:"Shqip"},am:{name:"Amharic",nativeName:"á ááá"},ar:{name:"Arabic",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ©"},an:{name:"Aragonese",nativeName:"Aragon\xe9s"},hy:{name:"Armenian",nativeName:"ÕÕ¡ÕµÕ¥ÖÕ¥Õ¶"},as:{name:"Assamese",nativeName:"à¦
সমà§à¦¯à¦¼à¦¾"},av:{name:"Avaric",nativeName:"Ð°Ð²Ð°Ñ Ð¼Ð°ÑÓ, магÓаÑÑл маÑÓ"},ae:{name:"Avestan",nativeName:"avesta"},ay:{name:"Aymara",nativeName:"aymar aru"},az:{name:"Azerbaijani",nativeName:"azÉrbaycan dili"},bm:{name:"Bambara",nativeName:"bamanankan"},ba:{name:"Bashkir",nativeName:"баÑҡоÑÑ Ñеле"},eu:{name:"Basque",nativeName:"euskara, euskera"},be:{name:"Belarusian",nativeName:"ÐелаÑÑÑкаÑ"},bn:{name:"Bengali",nativeName:"বাà¦à¦²à¦¾"},bh:{name:"Bihari",nativeName:"à¤à¥à¤à¤ªà¥à¤°à¥"},bi:{name:"Bislama",nativeName:"Bislama"},bs:{name:"Bosnian",nativeName:"bosanski jezik"},br:{name:"Breton",nativeName:"brezhoneg"},bg:{name:"Bulgarian",nativeName:"бÑлгаÑÑки език"},my:{name:"Burmese",nativeName:"ááá¬á
á¬"},ca:{name:"Catalan",nativeName:"Catal\xe0"},ch:{name:"Chamorro",nativeName:"Chamoru"},ce:{name:"Chechen",nativeName:"ноÑ
Ñийн моÑÑ"},ny:{name:"Chichewa",nativeName:"chiCheŵa, chinyanja"},zh:{name:"Chinese",nativeName:"䏿 (ZhÅngw\xe9n), æ±è¯, æ¼¢èª"},cv:{name:"Chuvash",nativeName:"ÑÓÐ²Ð°Ñ ÑÓлÑ
и"},kw:{name:"Cornish",nativeName:"Kernewek"},co:{name:"Corsican",nativeName:"corsu, lingua corsa"},cr:{name:"Cree",nativeName:"áá¦ááááá£"},hr:{name:"Croatian",nativeName:"hrvatski"},cs:{name:"Czech",nativeName:"Äesky, ÄeÅ¡tina"},da:{name:"Danish",nativeName:"dansk"},dv:{name:"Divehi",nativeName:"ÞÞ¨ÞÞ¬ÞÞ¨"},nl:{name:"Dutch",nativeName:"Nederlands, Vlaams"},en:{name:"English",nativeName:"English"},eo:{name:"Esperanto",nativeName:"Esperanto"},et:{name:"Estonian",nativeName:"eesti, eesti keel"},ee:{name:"Ewe",nativeName:"EÊegbe"},fo:{name:"Faroese",nativeName:"f\xf8royskt"},fj:{name:"Fijian",nativeName:"vosa Vakaviti"},fi:{name:"Finnish",nativeName:"suomi, suomen kieli"},fr:{name:"French",nativeName:"fran\xe7ais, langue fran\xe7aise"},ff:{name:"Fula",nativeName:"Fulfulde, Pulaar, Pular"},gl:{name:"Galician",nativeName:"Galego"},ka:{name:"Georgian",nativeName:"á¥áá áá£áá"},de:{name:"German",nativeName:"Deutsch"},el:{name:"Greek",nativeName:"Îλληνικά"},gn:{name:"Guaran\xed",nativeName:"Ava\xf1eẽ"},gu:{name:"Gujarati",nativeName:"àªà«àªàª°àª¾àª¤à«"},ht:{name:"Haitian",nativeName:"Krey\xf2l ayisyen"},ha:{name:"Hausa",nativeName:"Hausa, ÙÙÙÙØ³Ù"},he:{name:"Hebrew",nativeName:"×¢×ר×ת"},hz:{name:"Herero",nativeName:"Otjiherero"},hi:{name:"Hindi",nativeName:"हिनà¥à¤¦à¥, हिà¤à¤¦à¥"},ho:{name:"Hiri Motu",nativeName:"Hiri Motu"},hu:{name:"Hungarian",nativeName:"Magyar"},ia:{name:"Interlingua",nativeName:"Interlingua"},id:{name:"Indonesian",nativeName:"Bahasa Indonesia"},ie:{name:"Interlingue",nativeName:"Originally called Occidental; then Interlingue after WWII"},ga:{name:"Irish",nativeName:"Gaeilge"},ig:{name:"Igbo",nativeName:"Asụsụ Igbo"},ik:{name:"Inupiaq",nativeName:"I\xf1upiaq, I\xf1upiatun"},io:{name:"Ido",nativeName:"Ido"},is:{name:"Icelandic",nativeName:"\xcdslenska"},it:{name:"Italian",nativeName:"Italiano"},iu:{name:"Inuktitut",nativeName:"áááááá¦"},ja:{name:"Japanese",nativeName:"æ¥æ¬èª (ã«ã»ããï¼ã«ã£ã½ãã)"},jv:{name:"Javanese",nativeName:"basa Jawa"},kl:{name:"Kalaallisut",nativeName:"kalaallisut, kalaallit oqaasii"},kn:{name:"Kannada",nativeName:"à²à²¨à³à²¨à²¡"},kr:{name:"Kanuri",nativeName:"Kanuri"},ks:{name:"Kashmiri",nativeName:"à¤à¤¶à¥à¤®à¥à¤°à¥, ÙØ´Ù
ÙØ±Ùâ"},kk:{name:"Kazakh",nativeName:"ÒÐ°Ð·Ð°Ò ÑÑлÑ"},km:{name:"Khmer",nativeName:"áá¶áá¶ááááá"},ki:{name:"Kikuyu",nativeName:"GÄ©kÅ©yÅ©"},rw:{name:"Kinyarwanda",nativeName:"Ikinyarwanda"},ky:{name:"Kyrgyz",nativeName:"кÑÑгÑз Ñили"},kv:{name:"Komi",nativeName:"коми кÑв"}
16935,kg:{name:"Kongo",nativeName:"KiKongo"},ko:{name:"Korean",nativeName:"íêµì´ (éåèª), ì¡°ì ë§ (æé®®èª)"},ku:{name:"Kurdish",nativeName:"Kurd\xee, ÙÙØ±Ø¯Ûâ"},kj:{name:"Kuanyama",nativeName:"Kuanyama"},la:{name:"Latin",nativeName:"latine, lingua latina"},lb:{name:"Luxembourgish",nativeName:"L\xebtzebuergesch"},lg:{name:"Luganda",nativeName:"Luganda"},li:{name:"Limburgish",nativeName:"Limburgs"},ln:{name:"Lingala",nativeName:"Ling\xe1la"},lo:{name:"Lao",nativeName:"àºàº²àºªàº²àº¥àº²àº§"},lt:{name:"Lithuanian",nativeName:"lietuvių kalba"},lu:{name:"Luba-Katanga",nativeName:""},lv:{name:"Latvian",nativeName:"latvieÅ¡u valoda"},gv:{name:"Manx",nativeName:"Gaelg, Gailck"},mk:{name:"Macedonian",nativeName:"македонÑки Ñазик"},mg:{name:"Malagasy",nativeName:"Malagasy fiteny"},ms:{name:"Malay",nativeName:"bahasa Melayu, Ø¨ÙØ§Ø³ Ù
ÙØ§ÙÙâ"},ml:{name:"Malayalam",nativeName:"മലയാളà´"},mt:{name:"Maltese",nativeName:"Malti"},mi:{name:"MÄori",nativeName:"te reo MÄori"},mr:{name:"Marathi",nativeName:"मराठà¥"},mh:{name:"Marshallese",nativeName:"Kajin M̧ajeļ"},mn:{name:"Mongolian",nativeName:"монгол"},na:{name:"Nauru",nativeName:"EkakairÅ© Naoero"},nv:{name:"Navajo",nativeName:"Din\xe9 bizaad, Din\xe9kʼehǰ\xed"},nb:{name:"Norwegian Bokm\xe5l",nativeName:"Norsk bokm\xe5l"},nd:{name:"North Ndebele",nativeName:"isiNdebele"},ne:{name:"Nepali",nativeName:"नà¥à¤ªà¤¾à¤²à¥"},ng:{name:"Ndonga",nativeName:"Owambo"},nn:{name:"Norwegian Nynorsk",nativeName:"Norsk nynorsk"},no:{name:"Norwegian",nativeName:"Norsk"},ii:{name:"Nuosu",nativeName:"êê ê¿ Nuosuhxop"},nr:{name:"South Ndebele",nativeName:"isiNdebele"},oc:{name:"Occitan",nativeName:"Occitan"},oj:{name:"Ojibwe",nativeName:"ááááá¯á§áá"},cu:{name:"Church Slavonic",nativeName:"ѩзÑÐºÑ ÑловѣнÑÑкÑ"},om:{name:"Oromo",nativeName:"Afaan Oromoo"},or:{name:"Oriya",nativeName:"à¬à¬¡à¬¼à¬¿à¬"},os:{name:"Ossetian",nativeName:"иÑон \xe6взаг"},pa:{name:"Punjabi",nativeName:"ਪੰà¨à¨¾à¨¬à©, Ù¾ÙØ¬Ø§Ø¨Ûâ"},pi:{name:"PÄli",nativeName:"पाऴि"},fa:{name:"Persian",nativeName:"ÙØ§Ø±Ø³Û"},pl:{name:"Polish",nativeName:"polski"},ps:{name:"Pashto",nativeName:"Ù¾ÚØªÙ"},pt:{name:"Portuguese",nativeName:"Portugu\xeas"},qu:{name:"Quechua",nativeName:"Runa Simi, Kichwa"},rm:{name:"Romansh",nativeName:"rumantsch grischun"},rn:{name:"Kirundi",nativeName:"kiRundi"},ro:{name:"Romanian",nativeName:"rom\xe2nÄ"},ru:{name:"Russian",nativeName:"ÑÑÑÑкий ÑзÑк"},sa:{name:"Sanskrit",nativeName:"सà¤à¤¸à¥à¤à¥à¤¤à¤®à¥"}
16935,sc:{name:"Sardinian",nativeName:"sardu"},sd:{name:"Sindhi",nativeName:"सिनà¥à¤§à¥, سÙÚÙØ Ø³ÙØ¯Ú¾Ûâ"},se:{name:"Northern Sami",nativeName:"Davvis\xe1megiella"},sm:{name:"Samoan",nativeName:"gagana faa Samoa"},sg:{name:"Sango",nativeName:"y\xe2ng\xe2 t\xee s\xe4ng\xf6"},sr:{name:"Serbian",nativeName:"ÑÑпÑки Ñезик"},gd:{name:"Gaelic",nativeName:"G\xe0idhlig"},sn:{name:"Shona",nativeName:"chiShona"},si:{name:"Sinhalese",nativeName:"à·à·à¶à·à¶½"},sk:{name:"Slovak",nativeName:"slovenÄina"},sl:{name:"Slovene",nativeName:"slovenÅ¡Äina"},so:{name:"Somali",nativeName:"Soomaaliga, af Soomaali"},st:{name:"Southern Sotho",nativeName:"Sesotho"},es:{name:"Spanish",nativeName:"espa\xf1ol, castellano"},su:{name:"Sundanese",nativeName:"Basa Sunda"},sw:{name:"Swahili",nativeName:"Kiswahili"},ss:{name:"Swati",nativeName:"SiSwati"},sv:{name:"Swedish",nativeName:"svenska"},ta:{name:"Tamil",nativeName:"தமிழà¯"},te:{name:"Telugu",nativeName:"à°¤à±à°²à±à°à±"},tg:{name:"Tajik",nativeName:"Ñоҷикӣ, toÄikÄ«, تاجÛÚ©Ûâ"},th:{name:"Thai",nativeName:"à¹à¸à¸¢"},ti:{name:"Tigrinya",nativeName:"áµááá"},bo:{name:"Tibetan",nativeName:"à½à½¼à½à¼à½¡à½²à½"},tk:{name:"Turkmen",nativeName:"T\xfcrkmen, ТүÑкмен"},tl:{name:"Tagalog",nativeName:"Wikang Tagalog, áááá
á áááááá"},tn:{name:"Tswana",nativeName:"Setswana"},to:{name:"Tonga",nativeName:"faka Tonga"},tr:{name:"Turkish",nativeName:"T\xfcrk\xe7e"},ts:{name:"Tsonga",nativeName:"Xitsonga"},tt:{name:"Tatar",nativeName:"ÑаÑаÑÑа, tatar\xe7a, ØªØ§ØªØ§Ø±ÚØ§â"},tw:{name:"Twi",nativeName:"Twi"},ty:{name:"Tahitian",nativeName:"Reo Tahiti"},ug:{name:"Uyghur",nativeName:"UyÆ£urqÉ, ئÛÙØºÛرÚÛâ"},uk:{name:"Ukrainian",nativeName:"ÑкÑаÑнÑÑка"},ur:{name:"Urdu",nativeName:"اردÙ"},uz:{name:"Uzbek",nativeName:"zbek, Ðзбек, Ø£ÛØ²Ø¨ÛÙâ"},ve:{name:"Venda",nativeName:"Tshivená¸a"},vi:{name:"Vietnamese",nativeName:"Tiếng Viá»t"},vo:{name:"Volap\xfck",nativeName:"Volap\xfck"},wa:{name:"Walloon",nativeName:"Walon"},cy:{name:"Welsh",nativeName:"Cymraeg"},wo:{name:"Wolof",nativeName:"Wollof"},fy:{name:"Western Frisian",nativeName:"Frysk"},xh:{name:"Xhosa",nativeName:"isiXhosa"},yi:{name:"Yiddish",nativeName:"××Ö´××ש"},yo:{name:"Yoruba",nativeName:"Yor\xf9b\xe1"},za:{name:"Zhuang",nativeName:"Saɯ cueÅÆ
, Saw cuengh"},"ar-dz":{name:"Arabic (Algeria)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¬Ø²Ø§Ø¦Ø±)"},"ar-bh":{name:"Arabic (Bahrain)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¨ØØ±ÙÙ)"},"ar-eg":{name:"Arabic (Egypt)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ù
صر)"},"ar-iq":{name:"Arabic (Iraq)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¹Ø±Ø§Ù)"},"ar-jo":{name:"Arabic (Jordan)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ£Ø±Ø¯Ù)"},"ar-kw":{name:"Arabic (Kuwait)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙÙØª)"},"ar-lb":{name:"Arabic (Lebanon)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ¨ÙاÙ)"},"ar-ly":{name:"Arabic (Libya)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙÙØ¨Ùا)"},"ar-ma":{name:"Arabic (Morocco)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ
Ù
ÙÙØ© اÙÙ
ØºØ±Ø¨ÙØ©)"},"ar-om":{name:"Arabic (Oman)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (عÙ
اÙ)"},"ar-qa":{name:"Arabic (Qatar)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ·Ø±)"},"ar-sa":{name:"Arabic (Saudi Arabia)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ
Ù
ÙÙØ© Ø§ÙØ¹Ø±Ø¨ÙØ© Ø§ÙØ³Ø¹ÙØ¯ÙØ©)"},"ar-sy":{name:"Arabic (Syria)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø³ÙØ±Ùا)"},"ar-tn":{name:"Arabic (Tunisia)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (تÙÙØ³)"},"ar-ae":{name:"Arabic (U.A.E.)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¥Ù
ارات Ø§ÙØ¹Ø±Ø¨ÙØ© اÙÙ
ØªØØ¯Ø©)"},"ar-ye":{name:"Arabic (Yemen)",nativeName:"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙ
Ù)"},"de-at":{name:"German (Austria)",nativeName:"Deutsch (\xd6sterreich)"},"de-li":{name:"German (Liechtenstein)",nativeName:"Deutsch (Liechtenstein)"},"de-lu":{name:"German (Luxembourg)",nativeName:"Deutsch (Luxemburg)"},"de-ch":{name:"German (Switzerland)",nativeName:"Deutsch (Schweiz)"},"en-au":{name:"English (Australia)",nativeName:"English (Australia)"},"en-bz":{name:"English (Belize)",nativeName:"English (Belize)"}
16935,"en-ca":{name:"English (Canada)",nativeName:"English (Canada)"},"en-ie":{name:"English (Ireland)",nativeName:"English (Ireland)"},"en-jm":{name:"English (Jamaica)",nativeName:"English (Jamaica)"},"en-nz":{name:"English (New Zealand)",nativeName:""},"en-za":{name:"English (South Africa)",nativeName:"English (South Africa)"},"en-tt":{name:"English (Trinidad)",nativeName:"English (Trinidad y Tobago)"},"en-gb":{name:"English (United Kingdom)",nativeName:"English (United Kingdom)"},"en-us":{name:"English (United States)",nativeName:"English (United States)"},"es-ar":{name:"Spanish (Argentina)",nativeName:"Espa\xf1ol (Argentina)"},"es-bo":{name:"Spanish (Bolivia)",nativeName:"Espa\xf1ol (Bolivia)"},"es-cl":{name:"Spanish (Chile)",nativeName:"Espa\xf1ol (Chile)"},"es-co":{name:"Spanish (Colombia)",nativeName:"Espa\xf1ol (Colombia)"},"es-cr":{name:"Spanish (Costa Rica)",nativeName:"Espa\xf1ol (Costa Rica)"},"es-do":{name:"Spanish (Dominican Republic)",nativeName:"Espa\xf1ol (Rep\xfablica Dominicana)"},"es-ec":{name:"Spanish (Ecuador)",nativeName:"Espa\xf1ol (Ecuador)"},"es-sv":{name:"Spanish (El Salvador)",nativeName:"Espa\xf1ol (El Salvador)"},"es-gt":{name:"Spanish (Guatemala)",nativeName:"Espa\xf1ol (Guatemala)"},"es-hn":{name:"Spanish (Honduras)",nativeName:"Espa\xf1ol (Honduras)"},"es-mx":{name:"Spanish (Mexico)",nativeName:"Espa\xf1ol (M\xe9xico)"},"es-ni":{name:"Spanish (Nicaragua)",nativeName:"Espa\xf1ol (Nicaragua)"},"es-pa":{name:"Spanish (Panama)",nativeName:"Espa\xf1ol (Panam\xe1)"},"es-py":{name:"Spanish (Paraguay)",nativeName:"Espa\xf1ol (Paraguay)"},"es-pe":{name:"Spanish (Peru)",nativeName:"Espa\xf1ol (Per\xfa)"},"es-pr":{name:"Spanish (Puerto Rico)",nativeName:"Espa\xf1ol (Puerto Rico)"},"es-uy":{name:"Spanish (Uruguay)",nativeName:"Espa\xf1ol (Uruguay)"},"es-ve":{name:"Spanish (Venezuela)",nativeName:"Espa\xf1ol (Venezuela)"},"fr-be":{name:"French (Belgium)",nativeName:"fran\xe7ais (Belgique)"},"fr-ca":{name:"French (Canada)",nativeName:"fran\xe7ais (Canada)"},"fr-lu":{name:"French (Luxembourg)",nativeName:"fran\xe7ais (Luxembourg)"},"fr-ch":{name:"French (Switzerland)",nativeName:"fran\xe7ais (Suisse)"},"it-ch":{name:"Italian (Switzerland)",nativeName:"italiano (Svizzera)"},"nl-be":{name:"Dutch (Belgium)",nativeName:"Nederlands (Belgi\xeb)"},"pt-br":{name:"Portuguese (Brazil)",nativeName:"Portugu\xeas (Brasil)"},"sv-fi":{name:"Swedish (Finland)",nativeName:"svenska (Finland)"},"zh-hk":{name:"Chinese (Hong Kong)",nativeName:"䏿(馿¸¯ç¹å«è¡æ¿å)"},"zh-cn":{name:"Chinese (PRC)",nativeName:"䏿(ä¸å人æ°å
±åå½)"},"zh-sg":{name:"Chinese (Singapore)",nativeName:"䏿(æ°å å¡)"},"zh-tw":{name:"Chinese Traditional (Taiwan)",nativeName:"䏿ï¼å°ç£ï¼"}};AblePlayer.prototype.getLanguageName=function(t,i){var s,a,n;return(s=e[t.toLowerCase()])?"local"===i?s.nativeName:s.name:t.includes("-")&&(a=t.substring(0,2),n=t.substring(3),s=e[a.toLowerCase()])?"local"===i?s.nativeName+" ("+n+")":s.name+" ("+n+")":t}}(jQuery),function(t){AblePlayer.prototype.getSupportedLangs=function(){return["ca","cs","da","de","en","es","fr","he","id","it","ja","nb","nl","pt","pt-br","sv","tr","zh-tw"]},AblePlayer.prototype.getTranslationText=function(){var e,i,s,a,n,r,o;if(e=t.Deferred(),i=this,s=this.getSupportedLangs(),this.lang&&-1===t.inArray(this.lang,s)){if(2==this.lang.indexOf("-"))-1!==t.inArray(this.lang.substring(0,2),s)?this.lang=this.lang.substring(0,2):this.lang=null;else{for(o=!1,r=0;r<s.length;)s[r].substring(0,2)==this.lang&&(this.lang=s[r],o=!0),r++;o||(this.lang=null)}}return!this.lang&&(a=t("body").attr("lang")?t("body").attr("lang").toLowerCase():t("html").attr("lang")?t("html").attr("lang").toLowerCase():null)&&(-1!==t.inArray(a,s)?this.lang=a:2==a.indexOf("-")&&-1!==t.inArray(a.substring(0,2),s)&&(this.lang=a.substring(0,2))),this.lang||(this.lang="en"),this.searchLang||(this.searchLang=this.lang),n=this.rootPath+"translations/"+this.lang+".js",t.getJSON(n,function(t){i.tt=t,e.resolve()}).fail(function(){console.log("Critical Error: Unable to load translation file:",n),i.provideFallback(),e.fail()}),e.promise()},AblePlayer.prototype.getSampleDescriptionText=function(){var e,i,s,a,n,r,o;for(s=0,i=this.getSupportedLangs(),e=this,this.sampleText=[];s<i.length;s++)n=this.rootPath+"translations/"+i[s]+".js",t.getJSON(n,a,function(t){return function(i){o={lang:t,text:r=i.sampleDescriptionText},e.sampleText.push(o)}}(i[s]))}}(jQuery),jQuery,AblePlayer.prototype.computeEndTime=function(t,e){var i=t.split(":").reverse().map(function(t){return parseFloat(t)}),s=e.split(":").reverse().map(function(t){return parseFloat(t)});
16935return i.reduce(function(t,e,i){var a=e+s[i];return 0===i&&(a>60&&(s[i+1]+=1,a-=60),a=a.toFixed(3)),1===i&&a>60&&(s[i+1]+=1,a-=60),a<10&&(a="0"+a),t.push(a),t},[]).reverse().join(":")},AblePlayer.prototype.ttml2webvtt=function(t){var e,i=this;return JSON.parse(i.convert.xml2json(t,{ignoreComment:!0,alwaysChildren:!0,compact:!0,spaces:2})).tt.body.div.p.reduce(function(t,e,s){var a=e._text,n=Array.isArray(a),r=e._attributes;return i.computeEndTime(r.begin,r.dur),t+(i.computeEndTime(r.begin,"00:00:0")+" --> "+i.computeEndTime(r.begin,r.dur)+"\n"+(n?a.join("\n"):a))+"\n\n"},"WEBVTT\n\n\n")},function(t){t.doWhen=function(e){return t.Deferred(function(i){var s=t.extend({},{when:null,exec:function(){},interval:100,attempts:100,delayed:0},e,{checkId:null}),a=function(){if(!0===s.when()){s.exec.call(i.promise()),i.resolve();return}s.checkId=setInterval(function(){0===s.attempts?(clearInterval(s.checkId),i.reject()):(--s.attempts,!0===s.when()&&(s.attempts=0,clearInterval(s.checkId),s.exec.call(i.promise()),i.resolve()))},s.interval)};s.delayed>0?setTimeout(a,Number(s.delayed)):a()}).promise()}}(jQuery),function(t){AblePlayer.prototype.injectVTS=function(){var e,i,s,a,n,r,o,h,l,p,c,u,d,f,g,_,v,m,y,b,k,C;if(e=this,t("#able-vts").length&&this.vtsTracks.length){for(u in this.langs=[],this.getAllLangs(this.vtsTracks),this.vtsLang=this.lang,i=t("<h2>").text("Video Transcript Sorter"),t("#able-vts").append(i),this.$vtsAlert=t("<div>",{id:"able-vts-alert","aria-live":"polite","aria-atomic":"true"}),t("#able-vts").append(this.$vtsAlert),s=t("<div>",{id:"able-vts-instructions"}),a=t("<p>").text("Use the Video Transcript Sorter to perform any of the following tasks:"),r=t("<ul>"),o=t("<li>").text("Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."),h=t("<li>").text("Modify content or start/end times (all are directly editable within the table)."),l=t("<li>").text("Insert new content, such as chapters or descriptions."),n=t("<p>").text('When finished editing, click the "Save Changes" button. This will auto-generate new content for all relevant timed text files (chapters, descriptions, captions, and/or subtitles), which can be copied and pasted into separate WebVTT files for use by Able Player.'),r.append(o,h,l),s.append(a,r,n),t("#able-vts").append(s),p=t("<fieldset>"),c=t("<legend>").text("Select a language"),p.append(c),this.langs)f="vts-lang-radio-"+this.langs[u],d=t("<div>",{}),_=t("<input>",{type:"radio",name:"vts-lang",id:f,value:this.langs[u]}).on("click",function(){e.vtsLang=t(this).val(),e.showVtsAlert("Loading "+e.getLanguageName(e.vtsLang)+" tracks"),e.injectVtsTable("update",e.vtsLang)}),this.langs[u]==this.lang&&_.prop("checked",!0),g=t("<label>",{for:f}).text(this.getLanguageName(this.langs[u])),d.append(_,g),p.append(d);t("#able-vts").append(p),v=t("<button>",{type:"button",id:"able-vts-save",value:"save"}).text("Save Changes"),t("#able-vts").append(v),this.injectVtsTable("add",this.vtsLang),y=["captions","chapters","descriptions","subtitles"],t('td[contenteditable="true"]').on("focus",function(){b=t(this).text()}).on("blur",function(){b!=t(this).text()&&(k=t(this).index(),C=t(this).text(),1===k?-1===t.inArray(C,y)&&("s"===C.substring(0,1)?t(this).text("subtitles"):"d"===C.substring(0,1)?t(this).text("descriptions"):"ch"===C.substring(0,2)?t(this).text("chapters"):t(this).text("captions")):(2===k||3===k)&&t(this).text(e.formatTimestamp(C)))}).on("keydown",function(t){t.stopPropagation()}),t("#able-vts-save").on("click",function(i){i.stopPropagation(),"save"==t(this).attr("value")?(t(this).attr("value","cancel").text("Return to Editor"),m=t("#able-vts table"),t("#able-vts-instructions").hide(),t("#able-vts > fieldset").hide(),t("#able-vts table").remove(),t("#able-vts-icon-credit").remove(),e.parseVtsOutput(m)):(t(this).attr("value","save").text("Save Changes"),t("#able-vts-output").remove(),t("#able-vts-instructions").show(),t("#able-vts > fieldset").show(),t("#able-vts").append(m),t("#able-vts").append(e.getIconCredit()),e.showVtsAlert("Cancelling saving. Any edits you made have been restored in the VTS table."))})}}
vendor: 5,561 bytes, line 16935
16935,AblePlayer.prototype.setupVtsTracks=function(t,e,i,s,a,n){var r,o;r=this.getFilenameFromPath(a),o=this.parseVtsTracks(n),this.vtsTracks.push({kind:t,language:e,label:s,srcFile:r,cues:o})},AblePlayer.prototype.getFilenameFromPath=function(t){var e;return -1===(e=t.lastIndexOf("/"))?t:t.substring(e+1)},AblePlayer.prototype.getFilenameFromTracks=function(t,e){for(var i=0;i<this.vtsTracks.length;i++)if(this.vtsTracks[i].kind===t&&this.vtsTracks[i].language===e)return this.vtsTracks[i].srcFile;return!1},AblePlayer.prototype.parseVtsTracks=function(t){var e,i,s,a,n,r,o,h,l;for(e=t.split("\n"),s=[],a=0;a<e.length;)if(-1!==(r=e[a]).indexOf(" --> ")){if(i=r.trim().split(" "),this.isValidTimestamp(i[0])&&this.isValidTimestamp(i[2])){for(h="",n=a+1,l=!1;n<e.length&&!l;)(o=e[n].trim()).length>0?h.length>0?h+="\n"+o:h+=o:l=!0,n++;s.push({start:i[0],end:i[2],content:h}),a=n}}else a++;return s},AblePlayer.prototype.isValidTimestamp=function(t){return!!/^[0-9:,.]*$/.test(t)},AblePlayer.prototype.formatTimestamp=function(t){var e,i;if(e=t.substring(0,t.lastIndexOf(".")+1),(i=t.substring(t.lastIndexOf(".")+1)).length>3)i=i.substring(0,3);else if(i.length<3)for(;i.length<3;)i+="0";return e+i},AblePlayer.prototype.injectVtsTable=function(e,i){var s,a,n,r,o,h,l,p,c;for("update"===e&&(t("#able-vts table").remove(),t("#able-vts-icon-credit").remove()),s=t("<table>",{lang:i}),r=t("<tr>",{lang:"en"}),a=["Row #","Kind","Start","End","Content","Actions"],n=0;n<a.length;n++)o=t("<th>",{scope:"col"}).text(a[n]),"Actions"===a[n]&&o.addClass("actions"),r.append(o);for(s.append(r),l=this.getAllRows(i),n=0;n<l.length;n++)r=t("<tr>",{id:c="able-vts-row-"+(p=n+1),class:"kind-"+l[n].kind}),h=t("<td>").text(p),r.append(h),h=t("<td>",{contenteditable:"true"}).text(l[n].kind),r.append(h),h=t("<td>",{contenteditable:"true"}).text(l[n].start),r.append(h),h=t("<td>",{contenteditable:"true"}).text(l[n].end),r.append(h),h=t("<td>",{contenteditable:"true"}).text(l[n].content),r.append(h),h=this.addVtsActionButtons(p,l.length),r.append(h),s.append(r);t("#able-vts").append(s),t("#able-vts").append(this.getIconCredit())},AblePlayer.prototype.addVtsActionButtons=function(e,i){var s,a,n,r,o,h,l,p,c,u,d,f;for(r=0,s=this,a=t("<td>"),n=["up","down","insert","delete"];r<n.length;r++)"up"===(o=n[r])?e>1&&(h=t("<button>",{id:"able-vts-button-up-"+e,title:"Move up","aria-label":"Move Row "+e+" up"}).on("click",function(t){s.onClickVtsActionButton(t.currentTarget)}),l=t("<svg>",{focusable:"false","aria-hidden":"true",x:"0px",y:"0px",width:"254.296px",height:"254.296px",viewBox:"0 0 254.296 254.296",style:"enable-background:new 0 0 254.296 254.296"}),d=t("<path>",{d:c="M249.628,176.101L138.421,52.88c-6.198-6.929-16.241-6.929-22.407,0l-0.381,0.636L4.648,176.101c-6.198,6.897-6.198,18.052,0,24.981l0.191,0.159c2.892,3.305,6.865,5.371,11.346,5.371h221.937c4.577,0,8.613-2.161,11.41-5.594l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z"}),p=t("<g>").append(d),l.append(p),h.append(l),h.html(h.html()),a.append(h)):"down"===o?e<i&&(h=t("<button>",{id:"able-vts-button-down-"+e,title:"Move down","aria-label":"Move Row "+e+" down"}).on("click",function(t){s.onClickVtsActionButton(t.currentTarget)}),l=t("<svg>",{focusable:"false","aria-hidden":"true",x:"0px",y:"0px",width:"292.362px",height:"292.362px",viewBox:"0 0 292.362 292.362",style:"enable-background:new 0 0 292.362 292.362"}),d=t("<path>",{d:c="M286.935,69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952,0-9.233,1.807-12.85,5.424C1.807,72.998,0,77.279,0,82.228c0,4.948,1.807,9.229,5.424,12.847l127.907,127.907c3.621,3.617,7.902,5.428,12.85,5.428s9.233-1.811,12.847-5.428L286.935,95.074c3.613-3.617,5.427-7.898,5.427-12.847C292.362,77.279,290.548,72.998,286.935,69.377z"}),p=t("<g>").append(d),l.append(p),h.append(l),h.html(h.html()),a.append(h)):"insert"===o?(h=t("<button>",{id:"able-vts-button-insert-"+e,title:"Insert row below","aria-label":"Insert row before Row "+e}).on("click",function(t){s.onClickVtsActionButton(t.currentTarget)}),l=t("<svg>",{focusable:"false","aria-hidden":"true",x:"0px",y:"0px",width:"401.994px",height:"401.994px",viewBox:"0 0 401.994 401.994",style:"enable-background:new 0 0 401.994 401.994"}),d=t("<path>",{d:c="M394,154.175c-5.331-5.33-11.806-7.994-19.417-7.994H255.811V27.406c0-7.611-2.666-14.084-7.994-19.414C242.488,2.666,236.02,0,228.398,0h-54.812c-7.612,0-14.084,2.663-19.414,7.993c-5.33,5.33-7.994,11.803-7.994,19.414v118.775H27.407c-7.611,0-14.084,2.664-19.414,7.994S0,165.973,0,173.589v54.819c0,7.618,2.662,14.086,7.992,19.411c5.33,5.332,11.803,7.994,19.414,7.994h118.771V374.59c0,7.611,2.664,14.089,7.994,19.417c5.33,5.325,11.802,7.987,19.414,7.987h54.816c7.617,0,14.086-2.662,19.417-7.987c5.332-5.331,7.994-11.806,7.994-19.417V255.813h118.77c7.618,0,14.089-2.662,19.417-7.994c5.329-5.325,7.994-11.793,7.994-19.411v-54.819C401.991,165.973,399.332,159.502,394,154.175z"}),p=t("<g>").append(d),l.append(p),h.append(l),h.html(h.html()),a.append(h)):"delete"===o&&(h=t("<button>",{id:"able-vts-button-delete-"+e,title:"Delete row ","aria-label":"Delete Row "+e}).on("click",function(t){s.onClickVtsActionButton(t.currentTarget)}),l=t("<svg>",{focusable:"false","aria-hidden":"true",x:"0px",y:"0px",width:"508.52px",height:"508.52px",viewBox:"0 0 508.52 508.52",style:"enable-background:new 0 0 508.52 508.52"}),d=t("<path>",{d:c="M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347c-17.544,0-31.782,14.239-31.782,31.782h-63.565c-17.544,0-31.782,14.239-31.782,31.782h349.607C429.063,46.021,414.825,31.782,397.281,31.782z"}),f=t("<path>
16935",{d:u="M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042c17.544,0,31.782-14.239,31.782-31.782V95.347H79.456V476.737z M333.716,174.804c0-8.772,7.151-15.891,15.891-15.891c8.74,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.151,15.891-15.891,15.891c-8.74,0-15.891-7.151-15.891-15.891V174.804zM238.369,174.804c0-8.772,7.119-15.891,15.891-15.891c8.74,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.151,15.891-15.891,15.891c-8.772,0-15.891-7.151-15.891-15.891V174.804z M143.021,174.804c0-8.772,7.119-15.891,15.891-15.891c8.772,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.119,15.891-15.891,15.891c-8.772,0-15.891-7.151-15.891-15.891V174.804z"}),p=t("<g>").append(d,f),l.append(p),h.append(l),h.html(h.html()),a.append(h));return a},AblePlayer.prototype.updateVtsActionButtons=function(t,e){var i,s,a,n,r,o;for(i=0;i<t.length;i++)a=(s=t.eq(i)).attr("id"),n=s.attr("aria-label"),r=a.replace(/[0-9]+/g,e),o=n.replace(/[0-9]+/g,e),s.attr("id",r),s.attr("aria-label",o)},AblePlayer.prototype.getIconCredit=function(){var t;return'<div id="able-vts-icon-credit">Action buttons made by <a href="https://www.flaticon.com/authors/elegant-themes">Elegant Themes</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> are licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>'},AblePlayer.prototype.getAllLangs=function(e){var i;for(i in e)e[i].hasOwnProperty("language")&&-1===t.inArray(e[i].language,this.langs)&&(this.langs[this.langs.length]=e[i].language)},AblePlayer.prototype.getAllRows=function(t){var e,i,s,a;for(e=0,a=[];e<this.vtsTracks.length;e++)if((i=this.vtsTracks[e]).language==t)for(s in i.cues)a.push({kind:i.kind,lang:t,id:i.cues[s].id,start:i.cues[s].start,end:i.cues[s].end,content:i.cues[s].content});return a.sort(function(t,e){return t.start>e.start?1:-1}),a},AblePlayer.prototype.onClickVtsActionButton=function(e){var i,s,a;s=(i=t(e).attr("id").split("-"))[3],a=i[4],"up"==s?this.moveRow(a,"up"):"down"==s?this.moveRow(a,"down"):"insert"==s?this.insertRow(a):"delete"==s&&this.deleteRow(a)},AblePlayer.prototype.insertRow=function(e){var i,s,a,n,r,o,h,l,p,c,u,d,f,g,_,v;for(a=(s=(i=t("#able-vts table")).find("tr")).length-1,o=t("<tr>",{id:r="able-vts-row-"+(n=parseInt(e)+1)}),h=t("<td>").text(n),o.append(h),d=null,l=t("<select>",{id:"able-vts-kind-"+n,"aria-label":"What kind of track is this?",placeholder:"Select a kind"}).on("change",function(){f="kind-"+(d=t(this).val()),g=t(this).closest("tr"),t(this).parent().text(d),g.addClass(f)}),p=["","captions","chapters","descriptions","subtitles"],c=0;c<p.length;c++)u=t("<option>",{value:p[c]}).text(p[c]),l.append(u);for(h=t("<td>").append(l),o.append(h),h=t("<td>",{contenteditable:"true"}),o.append(h),h=t("<td>",{contenteditable:"true"}),o.append(h),h=t("<td>",{contenteditable:"true"}),o.append(h),h=this.addVtsActionButtons(n,a),o.append(h),i.find("tr").eq(e).after(o),c=n;c<=a;c++)_=c+1,s.eq(c).attr("id","able-vts-row-"+_),s.eq(c).find("td").eq(0).text(_),v=s.eq(c).find("button"),this.updateVtsActionButtons(v,_);this.adjustTimes(n),this.showVtsAlert("A new row "+n+" has been inserted"),l.focus()},AblePlayer.prototype.deleteRow=function(e){var i,s,a,n,r,o;for((i=t("#able-vts table"))[0].deleteRow(e),a=(s=i.find("tr")).length-1,n=e;n<=a;n++)r=n,s.eq(n).attr("id","able-vts-row-"+r),s.eq(n).find("td").eq(0).text(r),o=s.eq(n).find("button"),this.updateVtsActionButtons(o,r);this.showVtsAlert("Row "+e+" has been deleted")},AblePlayer.prototype.moveRow=function(e,i){var s,a,n,r,o;s=t("#able-vts table").find("tr"),a=t("#able-vts table").find("tr").eq(e),"up"==i?(n=parseInt(e)-1,(r=t("#able-vts table").find("tr").eq(n)).before(a)):"down"==i&&(n=parseInt(e)+1,(r=t("#able-vts table").find("tr").eq(n)).after(a)),a.attr("id","able-vts-row-"+n),a.find("td").eq(0).text(n),this.updateVtsActionButtons(a.find("button"),n),r.attr("id","able-vts-row-"+e),r.find("td").eq(0).text(e),this.updateVtsActionButtons(r.find("button"),e),this.adjustTimes(n),o="Row "+e+" has been moved "+i,o+=" and is now Row "+n,this.showVtsAlert(o)},AblePlayer.prototype.adjustTimes=function(e){var i,s,a,n,r,o,h,l,p,c,u,d,f,g,_,v;(i=[]).captions=.001,i.descriptions=.001,i.chapters=.001,l=(r=(s=t("#able-vts table").find("tr")).eq(e)).is('[class^="kind-"]')?this.getKindFromClass(r.attr("class")):"captions",u=this.getSecondsFromColonTime(r.find("td").eq(2).text()),g=this.getSecondsFromColonTime(r.find("td").eq(3).text()),e>1?(a=e-1,p=(o=s.eq(a)).is('[class^="kind-"]')?this.getKindFromClass(o.attr("class")):null,d=this.getSecondsFromColonTime(o.find("td").eq(2).text()),_=this.getSecondsFromColonTime(o.find("td").eq(3).text())):(a=null,o=null,p=null,d=null,_=null),e<s.length-1?(n=e+1,c=(h=s.eq(n)).is('[class^="kind-"]')?this.getKindFromClass(h.attr("class")):null,f=this.getSecondsFromColonTime(h.find("td").eq(2).text()),v=this.getSecondsFromColonTime(h.find("td").eq(3).text())):(n=null,h=null,c=null,f=null,v=null),isNaN(u)?(null==p&&(p="captions",o.attr("class","kind-captions"),o.find("td").eq(1).html("captions")),"captions"===p?(u=(parseFloat(_)+.001).toFixed(3),g=f?(parseFloat(f)-.001).toFixed(3):(parseFloat(u)+i[l]).toFixed(3)):"chapters"===p?(u=(parseFloat(d)+.001).toFixed(3),g=f?(parseFloat(f)-.001).toFixed(3):(parseFloat(u)+minDurartion[l]).toFixed(3)):"descriptions"===p&&(u=(parseFloat(d)+i.descriptions).toFixed(3),g=(parseFloat(u)+i.descriptions).toFixed(3))):d?d<u?u<f||(f=(parseFloat(u)+i[l]).toFixed(3),v=(parseFloat(f)+i[c]).toFixed(3)):(u=(parseFloat(d)+i[p]).toFixed(3),g=(parseFloat(u)+i[l]).toFixed(3)):u<f||(f=(parseFloat(u)+i[l]).toFixed(3),v=(parseFloat(f)+i[c]).toFixed(3)),g-u<i[l]&&(g=(parseFloat(u)+i[l]).toFixed(3),f&&(f=(parseFloat(g)+.001).toFixed(3))),r.find("td").eq(2).text(this.formatSecondsAsColonTime(u,!0)),r.find("td").eq(3).text(this.formatSecondsAsColonTime(g,!0)),o&&(o.find("td").eq(2).text(this.formatSecondsAsColonTime(d,!0)),o.find("td").eq(3).text(this.formatSecondsAsColonTime(_,!0))),h&&(h.find("td").eq(2).text(this.formatSecondsAsColonTime(f,!0)),h.find("td").eq(3).text(this.formatSecondsAsColonTime(v,!0)))},AblePlayer.prototype.getKindFromClass=function(t){var e,i;return(e=t.indexOf("kind-")+5,-1==(i=t.indexOf(" ",e)))?t.substring(e):t.substring(e,i)},AblePlayer.prototype.showVtsAlert=function(t){this.$vtsAlert.text(t).show().delay(3e3).fadeOut("slow")},AblePlayer.prototype.parseVtsOutput=function(e){var i,s,a,n,r,o,h,l,p,c;for(s=0,i=e.attr("lang"),a=["captions","chapters","descriptions","subtitles"],r={};s<a.length;s++)r[n=a[s]]="WEBVTT\n\n";if((o=e.find("tr")).length>0)for(s=0;s<o.length;s++)n=o.eq(s).find("td").eq(1).text(),-1!==t.inArray(n,a)&&(h=o.eq(s).find("td").eq(2).text(),l=o.eq(s).find("td").eq(3).text(),p=o.eq(s).find("td").eq(4).text(),void 0!==h&&void 0!==l&&(r[n]+=h+" --> "+l+"\n","undefined"!==p&&(r[n]+=p),r[n]+="\n\n"));for(c=t("<div>",{id:"able-vts-output"}),t("#able-vts").append(c),s=0;s<a.length;s++)r[n=a[s]].length>8&&this.showWebVttOutput(n,r[n],i)},AblePlayer.prototype.showWebVttOutput=function(e,i,s){var a,n,r,o,h;a=t("<h3>").text(e.charAt(0).toUpperCase()+e.slice(1)),n=this.getFilenameFromTracks(e,s),o="If you made changes, copy/paste the following content ",n?(o+="to replace the original content of your "+this.getLanguageName(s)+" ",o+="<em>"+e+"</em> WebVTT file (<strong>"+n+"</strong>
16935)."):o+="into a new "+this.getLanguageName(s)+" <em>"+e+"</em> WebVTT file.",r=t("<p>",{class:"able-vts-output-instructions"}).html(o),h=t("<textarea>").text(i),t("#able-vts-output").append(a,r,h)}}(jQuery),function(t){AblePlayer.prototype.initVimeoPlayer=function(){var e,i,s,a,n,r,o;return e=this,s=(i=new t.Deferred).promise(),a=this.mediaId+"_vimeo",this.$mediaContainer.prepend(t("<div>").attr("id",a)),n=this.vimeoDescId&&this.prefDesc?this.vimeoDescId:this.vimeoId,this.activeVimeoId=n,r=this.okToPlay?"true":"false",o=this.playerWidth?this.vimeoUrlHasParams?{url:n,width:this.playerWidth,controls:!1}:{id:n,width:this.playerWidth,controls:!1}:this.vimeoUrlHasParams?{url:n,controls:!1}:{id:n,controls:!1},this.vimeoPlayer=new Vimeo.Player(a,o),this.vimeoPlayer.ready().then(function(){t("#"+a).children("iframe").attr({tabindex:"-1","aria-hidden":!0}),e.vimeoPlayer.getVideoWidth().then(function(t){t&&e.vimeoPlayer.getVideoHeight().then(function(i){i&&e.resizePlayer(t,i)})}).catch(function(t){}),e.hasPlaylist||(e.$media.remove(),e.vimeoPlaybackRate=1,e.vimeoPlayer.setPlaybackRate(e.vimeoPlaybackRate).then(function(t){e.vimeoSupportsPlaybackRateChange=!0}).catch(function(t){e.vimeoSupportsPlaybackRateChange=!1}),i.resolve())}),s},AblePlayer.prototype.getVimeoPaused=function(){var e,i;return i=(e=new t.Deferred).promise(),this.vimeoPlayer.getPaused().then(function(t){e.resolve(t)}),i},AblePlayer.prototype.getVimeoEnded=function(){var e,i;return i=(e=new t.Deferred).promise(),this.vimeoPlayer.getEnded().then(function(t){e.resolve(t)}),i},AblePlayer.prototype.getVimeoState=function(){var e,i,s,a,n,r;return e=this,s=(i=new t.Deferred).promise(),a=[],n=this.vimeoPlayer.getPaused(),r=this.vimeoPlayer.getEnded(),a.push(n),a.push(r),n.then(function(t){i.resolve(t)}),r.then(function(t){i.resolve(t)}),t.when.apply(t,a).then(function(){i.resolve()}),s},AblePlayer.prototype.getVimeoCaptionTracks=function(){var e,i,s,a=new t.Deferred,n=a.promise();return e=this,this.vimeoPlayer.getTextTracks().then(function(t){if(t.length){for(i=0;i<t.length;i++)e.hasCaptions=!0,1===e.prefCaptions?e.captionsOn=!0:e.captionsOn=!1,s=t[i].language===e.lang,e.tracks.push({kind:t[i].kind,language:t[i].language,label:t[i].label,def:s});e.captions=e.tracks,e.hasCaptions=!0,e.setupPopups("captions"),a.resolve()}else e.hasCaptions=!1,e.usingVimeoCaptions=!1,a.resolve()}),n},AblePlayer.prototype.getVimeoPosterUrl=function(t,e){var i="https://img.youtube.com/vi/"+youTubeId;return"120"==e?i+"/default.jpg":"320"==e?i+"/hqdefault.jpg":"480"==e?i+"/hqdefault.jpg":"640"==e&&i+"/sddefault.jpg"},AblePlayer.prototype.getVimeoId=function(t){var e,i;return(this.vimeoUrlHasParams=!1,"number"==typeof t||-1===t.indexOf("vimeo.com"))?t:-1!==t.indexOf("?")?(this.vimeoUrlHasParams=!0,t):(e=(t=t.trim()).lastIndexOf("/")+1,i=t.substring(e))}}(jQuery); 16936 } 16937 else { 16938 url = url.trim(); 16939 idStartPos = url.lastIndexOf('/') + 1; 16940 id = url.substring(idStartPos); 16941 return id; 16942 } 16943 } 16944 else { 16945 return url; 16946 } 16947}; 16948 16949})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.