(function ($) {
// Set default variable values. AblePlayer.prototype.setDefaults = function () { this.playerCreated = false; // will set to true after recreatePlayer() is complete the first time this.playing = false; // will change to true after 'playing' event is triggered this.paused = true; // will always be the opposite of this.playing (available for convenience) this.clickedPlay = false; // will change to true temporarily if user clicks 'play' (or pause) this.fullscreen = false; // will change to true if player is in full screen mode this.swappingSrc = false; // will change to true temporarily while media source is being swapped this.initializing = false; // will change to true temporarily while initPlayer() is processing this.cueingPlaylistItems = false; // will change to true temporarily while cueing next playlist item this.okToPlay = false; // will change to true if conditions are acceptible for automatic playback after media loads this.getUserAgent(); this.setIconColor(); this.setButtonImages(); }; AblePlayer.prototype.getRootPath = function() { // returns Able Player root path (assumes ableplayer.js is in /build, one directory removed from root) var scripts, i, scriptSrc, scriptFile, fullPath, ablePath, parentFolderIndex, rootPath; scripts= document.getElementsByTagName('script'); for (i=0; i < scripts.length; i++) { scriptSrc = scripts[i].src; scriptFile = scriptSrc.substr(scriptSrc.lastIndexOf('/')); if (scriptFile.indexOf('ableplayer') !== -1) { // this is the ableplayerscript fullPath = scriptSrc.split('?')[0]; // remove any ? params break; } } ablePath= fullPath.split('/').slice(0, -1).join('/'); // remove last filename part of path parentFolderIndex = ablePath.lastIndexOf('/'); rootPath = ablePath.substring(0, parentFolderIndex) + '/'; return rootPath; } AblePlayer.prototype.setIconColor = function() { // determine the best color choice (white or black) for icons, // given the background-color of their container elements // Source for relative luminance formula: // https://en.wikipedia.org/wiki/Relative_luminance // We need to know the color *before* creating the element // so the element doesn't exist yet when this function is called // therefore, need to create a temporary element then remove it after color is determined // Temp element must be added to the DOM or WebKit can't retrieve its CSS properties var $elements, i, $el, bgColor, rgb, red, green, blue, luminance, iconColor; $elements = ['controller', 'toolbar']; for (i=0; i<$elements.length; i++) { if ($elements[i] == 'controller') { $el = $('<div>', { 'class': 'able-controller' }).hide(); } else if ($elements[i] === 'toolbar') { $el = $('<div>', { 'class': 'able-window-toolbar' }).hide(); } $('body').append($el); bgColor = $el.css('background-color'); // bgColor is a string in the form 'rgb(R, G, B)', perhaps with a 4th item for alpha; // split the 3 or 4 channels into an array rgb = bgColor.replace(/[^\d,]/g, '').split(','); red = rgb[0]; green = rgb[1]; blue = rgb[2]; luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); // range is 1 - 255; therefore 125 is the tipping point if (luminance < 125) { // background is dark iconColor = 'white'; } else { // background is light iconColor = 'black'; } if ($elements[i] === 'controller') { this.iconColor = iconColor; } else if ($elements[i] === 'toolbar') { this.toolbarIconColor = iconColor; } $el.remove(); } }; AblePlayer.prototype.setButtonImages = function() { // NOTE: volume button images are now set dynamically within volume.js 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'; if (this.speedIcons === 'arrows') { this.fasterButtonImg = this.imgPath + 'slower.png'; this.slowerButtonImg = this.imgPath + 'faster.png'; } else if (this.speedIcons === 'animals') { 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(button) { // returns array of values for creating <svg> tag for specified button // 0 = <svg> viewBox attribute // 1 = <path> d (description) attribute var svg = Array(); switch (button) { case 'play': svg[0] = '0 0 16 20'; 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'; break; case 'pause': svg[0] = '0 0 20 20'; 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'; break; case 'stop': svg[0] = '0 0 20 20'; 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'; break; case 'restart': svg[0] = '0 0 20 20'; 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'; break; case 'rewind': svg[0] = '0 0 20 20'; svg[1] = 'M11.25 3.125v6.25l6.25-6.25v13.75l-6.25-6.25v6.25l-6.875-6.875z'; break; case 'forward': svg[0] = '0 0 20 20'; svg[1] = 'M10 16.875v-6.25l-6.25 6.25v-13.75l6.25 6.25v-6.25l6.875 6.875z'; break; case 'previous': svg[0] = '0 0 20 20'; svg[1] = 'M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z'; break; case 'next': svg[0] = '0 0 20 20'; svg[1] = 'M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z'; break; case 'slower': svg[0] = '0 0 20 20'; 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'; break; case 'faster': svg[0] = '0 0 11 20'; 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'; break; case 'turtle': svg[0] = '0 0 20 20'; 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'; break; case 'rabbit': svg[0] = '0 0 20 20'; 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'; break; case 'ellipsis': svg[0] = '0 0 20 20'; 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'; break; case 'pipe': svg[0] = '0 0 20 20'; 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'; break; case 'captions': svg[0] = '0 0 20 20'; 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'; break; case 'descriptions': svg[0] = '0 0 20 20'; 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'; break; case 'sign': svg[0] = '0 0 20 20'; 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'; break; case 'mute': svg[0] = '0 0 20 20'; 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'; break; case 'volume-mute': svg[0] = '0 0 20 20'; 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'; break; case 'volume-medium': svg[0] = '0 0 20 20'; 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'; break; case 'volume-loud': svg[0] = '0 0 21 20'; 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'; break; case 'chapters': svg[0] = '0 0 20 20'; svg[1] = 'M5 2.5v17.5l6.25-6.25 6.25 6.25v-17.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z'; break; case 'transcript': svg[0] = '0 0 20 20'; svg[1] = 'M0 19.107v-17.857q0-0.446 0.313-0.759t0.759-0.313h8.929v6.071q0 0.446 0.313 0.759t0.759 0.313h6.071v11.786q0 0.446-0.313 0.759t-0.759 0.312h-15q-0.446 0-0.759-0.313t-0.313-0.759zM4.286 15.536q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM4.286 12.679q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM4.286 9.821q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM11.429 5.893v-5.268q0.246 0.156 0.402 0.313l4.554 4.554q0.156 0.156 0.313 0.402h-5.268z'; break; case 'preferences': svg[0] = '0 0 20 20'; 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'; break; case 'close': svg[0] = '0 0 16 20'; 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'; break; case 'fullscreen-expand': svg[0] = '0 0 20 20'; 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'; break; case 'fullscreen-collapse': svg[0] = '0 0 20 20'; 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'; break; case 'help': svg[0] = '0 0 11 20'; 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'; break; } return svg; }; // Initialize player based on data on page. // This sets some variables, but does not modify anything. Safe to call multiple times. // Can call again after updating this.media so long as new media element has the same ID. AblePlayer.prototype.reinitialize = function () { var deferred, promise, thisObj, errorMsg, srcFile; deferred = new $.Deferred(); promise = deferred.promise(); thisObj = this; // if F12 Developer Tools aren't open in IE (through 9, no longer a problen in IE10) // console.log causes an error - can't use debug without a console to log messages to if (! window.console) { this.debug = false; } this.startedPlaying = false; // TODO: Move this setting to cookie. this.autoScrollTranscript = true; //this.autoScrollTranscript = this.getCookie(autoScrollTranscript); // (doesn't work) // Bootstrap from this.media possibly being an ID or other selector. this.$media = $(this.media).first(); this.media = this.$media[0]; // Set media type to 'audio' or 'video'; this determines some of the behavior of player creation. if (this.$media.is('audio')) { this.mediaType = 'audio'; } else if (this.$media.is('video')) { this.mediaType = 'video'; } else { // Able Player was initialized with some element other than <video> or <audio> this.provideFallback(); deferred.fail(); return promise; } this.$sources = this.$media.find('source'); this.player = this.getPlayer(); if (!this.player) { // an error was generated in getPlayer() this.provideFallback(); } this.setIconType(); this.setDimensions(); deferred.resolve(); return promise; }; AblePlayer.prototype.setDimensions = function() { // if media element includes width and height attributes, // use these to set the max-width and max-height of the player if (this.$media.attr('width') && this.$media.attr('height')) { this.playerMaxWidth = parseInt(this.$media.attr('width'), 10); this.playerMaxHeight = parseInt(this.$media.attr('height'), 10); } else if (this.$media.attr('width')) { // media element includes a width attribute, but not height this.playerMaxWidth = parseInt(this.$media.attr('width'), 10); } else { // set width to width of #player // don't set height though; YouTube will automatically set that to match width this.playerMaxWidth = this.$media.parent().width(); this.playerMaxHeight = this.getMatchingHeight(this.playerMaxWidth); } // override width and height attributes with in-line CSS to make video responsive this.$media.css({ 'width': '100%', 'height': 'auto' }); }; AblePlayer.prototype.getMatchingHeight = function(width) { // returns likely height for a video, given width // These calculations assume 16:9 aspect ratio (the YouTube standard) // Videos recorded in other resolutions will be sized to fit, with black bars on each side // This function is only called if the <video> element does not have width and height attributes var widths, heights, closestWidth, closestIndex, closestHeight, height; widths = [ 3840, 2560, 1920, 1280, 854, 640, 426 ]; heights = [ 2160, 1440, 1080, 720, 480, 360, 240 ]; closestWidth = null; closestIndex = null; $.each(widths, function(index){ if (closestWidth == null || Math.abs(this - width) < Math.abs(closestWidth - width)) { closestWidth = this; closestIndex = index; } }); closestHeight = heights[closestIndex]; this.aspectRatio = closestWidth / closestHeight; height = Math.round(width / this.aspectRatio); return height; }; AblePlayer.prototype.setIconType = function() { // returns either "svg", "font" or "image" (in descending order of preference) // Test for support of each type. If not supported, test the next type. // last resort is image icons var $tempButton, $testButton, controllerFont; if (this.forceIconType) { // use value specified in data-icon-type return false; } // test for SVG support // Test this method widely; failed as expected on IE8 and below // https://stackoverflow.com/a/27568129/744281 if (!!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect)) { // browser supports SVG this.iconType = 'svg'; } else { // browser does NOT support SVG // test whether browser can support icon fonts, and whether user has overriding the default style sheet // which could cause problems with proper display of the icon fonts if (window.getComputedStyle) { // webkit doesn't return calculated styles unless element has been added to the DOM // and is visible (note: visibly clipped is considered "visible") // use playpauseButton for font-family test if it exists; otherwise must create a new temp button if ($('span.icon-play').length) { $testButton = $('span.icon-play'); } else { $tempButton = $('<span>',{ 'class': 'icon-play able-clipped' }); $('body').append($tempButton); $testButton = $tempButton; } // the following retrieves the computed value of font-family // tested in Firefox 45.x with "Allow pages to choose their own fonts" unchecked - works! // tested in Chrome 49.x with Font Changer plugin - works! // tested in IE with user-defined style sheet enables - works! // It does NOT account for users who have "ignore font styles on web pages" checked in IE // There is no known way to check for that ??? controllerFont = window.getComputedStyle($testButton.get(0), null).getPropertyValue('font-family'); if (typeof controllerFont !== 'undefined') { if (controllerFont.indexOf('able') !== -1) { this.iconType = 'font'; } else { this.iconType = 'image'; } } else { // couldn't get computed font-family; use images to be safe this.iconType = 'image'; } } else { // window.getComputedStyle is not supported (IE 8 and earlier) // No known way to detect computed font // The following retrieves the value from the style sheet, not the computed font // controllerFont = $tempButton.get(0).currentStyle.fontFamily; // It will therefore return "able", even if the user is overriding that with a custom style sheet // To be safe, use images this.iconType = 'image'; } if (this.debug) { console.log('Using ' + this.iconType + 's for player controls'); } if (typeof $tempButton !== 'undefined') { $tempButton.remove(); } } }; // Perform one-time setup for this instance of player; called after player is first initialized. AblePlayer.prototype.setupInstance = function () { var deferred = new $.Deferred(); var promise = deferred.promise(); if (this.$media.attr('id')) { this.mediaId = this.$media.attr('id'); } else { // Ensure the base media element always has an ID. this.mediaId = "ableMediaId_" + this.ableIndex; this.$media.attr('id', this.mediaId); } deferred.resolve(); return promise; }; AblePlayer.prototype.setupInstancePlaylist = function() { // find a matching playlist and set this.hasPlaylist // if there is one, also set this.$playlist, this.playlistIndex, & this.playlistEmbed var thisObj = this; this.hasPlaylist = false; // will change to true if a matching playlist is found $('.able-playlist').each(function() { if ($(this).data('player') === thisObj.mediaId) { // this is the playlist for the current player thisObj.hasPlaylist = true; // If using an embedded player, we'll replace $playlist with the clone later. thisObj.$playlist = $(this).find('li'); // check to see if list item has YouTube as its source // if it does, inject a thumbnail from YouTube var $youTubeVideos = $(this).find('li[data-youtube-id]'); $youTubeVideos.each(function() { var youTubeId = $(this).attr('data-youtube-id'); var youTubePoster = thisObj.getYouTubePosterUrl(youTubeId,'120'); var $youTubeImg = $('<img>',{ 'src': youTubePoster, 'alt': '' }); $(this).find('button').prepend($youTubeImg); }); // add accessibility to the list markup $(this).find('li span').attr('aria-hidden','true'); thisObj.playlistIndex = 0; var dataEmbedded = $(this).data('embedded'); if (typeof dataEmbedded !== 'undefined' && dataEmbedded !== false) { // embed playlist within player thisObj.playlistEmbed = true; } else { thisObj.playlistEmbed = false; } } }); if (this.hasPlaylist && this.loop) { // browser will loop the current track in the playlist, rather than the playlist // therefore, need to remove loop attribute from media element // but keep this.loop as true and handle the playlist looping ourselves this.media.removeAttribute('loop'); } if (this.hasPlaylist && this.playlistEmbed) { // Copy the playlist out of the dom, so we can reinject when we build the player. var parent = this.$playlist.parent(); this.$playlistDom = parent.clone(); parent.remove(); } if (this.hasPlaylist && this.$sources.length === 0) { // no source elements were provided. Construct them from the first playlist item this.cuePlaylistItem(0); // redefine this.$sources now that media contains one or more <source> elements this.$sources = this.$media.find('source'); } }; AblePlayer.prototype.recreatePlayer = function () { // Creates the appropriate player for the current source. var thisObj, prefsGroups, i; thisObj = this; // TODO: Ensure when recreating player that we carry over the mediaId if (!this.player) { console.log("Can't create player; no appropriate player type detected."); return; } if (!this.playerCreated) { // only call these functions once this.loadCurrentPreferences(); this.injectPlayerCode(); } // call all remaining functions each time a new media instance is loaded this.initSignLanguage();
// thisObj.initializing = true;
this.initPlayer().then(function() { // initPlayer success
// thisObj.initializing = false;
thisObj.setupTracks().then(function() { thisObj.setupAltCaptions().then(function() { thisObj.setupTranscript().then(function() { if (thisObj.Volume) { thisObj.setMute(false); } thisObj.setFullscreen(false); thisObj.setVolume(thisObj.defaultVolume); if (thisObj.transcriptType) { thisObj.addTranscriptAreaEvents(); thisObj.updateTranscript(); } if (thisObj.mediaType === 'video') { thisObj.initDescription(); } if (thisObj.captions.length) { thisObj.initDefaultCaption(); } // setMediaAttributes() sets textTrack.mode to 'disabled' for all tracks // This tells browsers to ignore the text tracks so Able Player can handle them // However, timing is critical as browsers - especially Safari - tend to ignore this request // unless it's sent late in the intialization process. // If browsers ignore the request, the result is redundant captions thisObj.setMediaAttributes(); thisObj.addControls(); thisObj.addEventListeners(); // inject each of the hidden forms that will be accessed from the Preferences popup menu prefsGroups = thisObj.getPreferencesGroups(); for (i = 0; i < prefsGroups.length; i++) { thisObj.injectPrefsForm(prefsGroups[i]); } thisObj.setupPopups(); thisObj.updateCaption(); thisObj.injectVTS(); if (thisObj.chaptersDivLocation) { thisObj.populateChaptersDiv(); } thisObj.showSearchResults(); // Go ahead and load media, without user requesting it // Normally, we wait until user clicks play, rather than unnecessarily consume their bandwidth // Exceptions are if the video is intended to autostart or if running on iOS (a workaround for iOS issues) // TODO: Confirm that this is still necessary with iOS (this would added early, & I don't remember what the issues were) if (thisObj.player === 'html5' && (thisObj.isIOS() || thisObj.startTime > 0 || thisObj.autoplay || thisObj.okToPlay)) { thisObj.$media[0].load(); } // refreshControls is called twice building/initializing the player // this is the second. Best to pause a bit before executing, to be sure all prior steps are complete setTimeout(function() { thisObj.refreshControls('init'); },100); }, function() { // initPlayer fail thisObj.provideFallback(); }); }); }); }); }; AblePlayer.prototype.initPlayer = function () { var thisObj = this; var playerPromise; // First run player specific initialization. if (this.player === 'html5') { playerPromise = this.initHtml5Player(); } else if (this.player === 'youtube') { playerPromise = this.initYouTubePlayer(); } else if (this.player === 'vimeo') { playerPromise = this.initVimeoPlayer(); } // After player specific initialization is done, run remaining general initialization. var deferred = new $.Deferred(); var promise = deferred.promise(); playerPromise.done( function () { // done/resolved if (thisObj.useFixedSeekInterval === false) { thisObj.setSeekInterval(); } deferred.resolve(); } ).fail(function () { // failed deferred.reject(); } ); return promise; }; AblePlayer.prototype.setSeekInterval = function () { // this function is only called if this.useFixedSeekInterval is false // if this.useChapterTimes, this is called as each new chapter is loaded // otherwise, it's called once, as the player is initialized var thisObj, duration; thisObj = this; this.seekInterval = this.defaultSeekInterval; if (this.useChapterTimes) { duration = this.chapterDuration; } else { duration = this.duration; } if (typeof duration === 'undefined' || duration < 1) { // no duration; just use default for now but keep trying until duration is available this.seekIntervalCalculated = false; return; } else { if (duration <= 20) { this.seekInterval = 5; // 4 steps max } else if (duration <= 30) { this.seekInterval = 6; // 5 steps max } else if (duration <= 40) { this.seekInterval = 8; // 5 steps max } else if (duration <= 100) { this.seekInterval = 10; // 10 steps max } else { // never more than 10 steps from start to end this.seekInterval = (duration / 10); } this.seekIntervalCalculated = true; } }; AblePlayer.prototype.initDefaultCaption = function () { var captions, i; captions = this.captions; if (captions.length > 0) { for (i=0; i<captions.length; i++) { if (captions[i].def === true) { this.captionLang = captions[i].language; this.selectedCaptions = captions[i]; } } if (typeof this.captionLang === 'undefined') { // No caption track was flagged as default // find and use a caption language that matches the player language for (i=0; i<captions.length; i++) { if (captions[i].language === this.lang) { this.captionLang = captions[i].language; this.selectedCaptions = captions[i]; } } } if (typeof this.captionLang === 'undefined') { // Still no matching caption track // just use the first track this.captionLang = captions[0].language; this.selectedCaptions = captions[0]; } if (typeof this.captionLang !== 'undefined') { // reset transcript selected <option> to this.captionLang if (this.$transcriptLanguageSelect) { this.$transcriptLanguageSelect.find('option[lang=' + this.captionLang + ']').prop('selected',true); } // sync all other tracks to this same languge this.syncTrackLanguages('init',this.captionLang); } } }; AblePlayer.prototype.initHtml5Player = function () { // Nothing special to do! var deferred = new $.Deferred(); var promise = deferred.promise(); deferred.resolve(); return promise; }; // Sets media/track/source attributes; is called whenever player is recreated since $media may have changed. AblePlayer.prototype.setMediaAttributes = function () { // Firefox puts videos in tab order; remove. this.$media.attr('tabindex', -1); // Keep native player from displaying captions/subtitles by setting textTrack.mode='disabled' // https://dev.w3.org/html5/spec-author-view/video.html#text-track-mode // This *should* work but historically hasn't been supported in all browsers // Workaround for non-supporting browsers is to remove default attribute // We're doing that too in track.js > setupCaptions() var textTracks = this.$media.get(0).textTracks; if (textTracks) { var i = 0; while (i < textTracks.length) { textTracks[i].mode = 'disabled'; i += 1; } } }; AblePlayer.prototype.getPlayer = function() { // Determine which player to use, if any // return 'html5', 'youtube', 'vimeo', or null var i, sourceType, $newItem; if (this.youTubeId) { if (this.mediaType !== 'video') { // attempting to play a YouTube video using an element other than <video> return null; } else { return 'youtube'; } } else if (this.vimeoId) { if (this.mediaType !== 'video') { // attempting to play a Vimeo video using an element other than <video> return null; } else { return 'vimeo'; } } else if (this.testFallback || ((this.isUserAgent('msie 7') || this.isUserAgent('msie 8') || this.isUserAgent('msie 9')) && this.mediaType === 'video') || (this.isIOS() && (this.isIOS(4) || this.isIOS(5) || this.isIOS(6))) ) { // the user wants to test the fallback player, or // the user is using an older version of IE or IOS, // both of which had buggy implementation of HTML5 video return null; } else if (this.media.canPlayType) { return 'html5'; } else { // Browser does not support the available media file return null; } };
})(jQuery);